---
url: https://docs.dataflair.ai/dataflair/toplist-api/toplists-endpoint.md
description: >-
  List toplists by template or geo, fetch one toplist, and read the response
  shape.
---

# Toplists endpoints

## `GET /api/v1/toplists`

Returns every toplist visible to your authenticated site, paginated (default 15 per page, max 100,
ordered newest-updated-first). Two optional, independent filters:

* **`template_id`**: the filter you'll use most. Every toplist sharing one `template_id` is one
  **geo family**: one row per market, per country, and/or a global/rest-of-world variant, all built
  from the same template. This is how you fetch "everything I might need to show for this toplist
  concept" ahead of any specific visitor. If a family has more than 15 members, pass a higher
  `per_page` (up to 100). **`per_page` caps at 100 per request**, so a family larger than that is
  never returned in one call regardless of what you pass. Follow the `links`/`meta` pagination
  fields (see the response-wrapping note below) across successive requests, incrementing `page`,
  until `links.next` is `null` or `meta.current_page == meta.last_page`. Stopping after the first
  page silently drops the rest of the family, which can make a visitor whose country is only on a
  later page fall through to a global/no-match render.

  ```bash
  curl "https://{your-tenant-domain}/api/v1/toplists?template_id=55" \
    -H "Authorization: Bearer <token>" \
    -H "Accept: application/json"
  ```

* **`geo_type` + `geo_code`**: filter to toplists matching a specific market or country directly,
  if you already know exactly which geo variant you want and don't need the whole family.

  ```bash
  curl "https://{your-tenant-domain}/api/v1/toplists?geo_type=country&geo_code=IN" \
    -H "Authorization: Bearer <token>" \
    -H "Accept: application/json"
  ```

## `GET /api/v1/toplists/by-slug/{slug}` and `GET /api/v1/toplists/{id}`

Fetch exactly one toplist, by its slug or numeric ID respectively. Both return the identical
per-toplist JSON shape documented below, or a `404 not_found` if it doesn't exist or isn't currently
available to your site.

## Response shape

Every toplist, from any of the three endpoints above, has this shape:

```jsonc
{
  "type": "toplist",
  "id": 482,
  "name": "Best Casinos India",
  "status": "published",
  "locked": false,
  "version": "20260214100300",
  "owner": { "id": 12, "name": "Jane Doe" },
  "createdAt": "2026-02-14T10:03:00+00:00",
  "updatedAt": "2026-07-01T09:12:44+00:00",
  "template": {
    "type": "listTemplate",
    "id": 55,
    "name": "Best Casinos",
    "productTypeId": 2,
    "productType": "Casino",
    "listClassificationTypeId": 1,
    "listClassificationType": "Best Of"
  },
  "site": { "id": 7, "domain": "example-operator.com" },
  "geo": {
    "geo_type": "country",
    "name": "India",
    "code": "IN",
    "coveredCountries": null
  },
  "slug": "best-casinos-india",
  "currentPeriod": "July 2026",
  "publishedAt": "2026-07-01T09:12:44+00:00",
  "shortcode": null,
  "items": [
    {
      "type": "topListItem",
      "id": 9931,
      "position": 1,
      "isLocked": false,
      "dealId": 204,
      "pros": ["Fast payouts", "24/7 live chat"],
      "cons": [],
      "brand": {
        "type": "brand",
        "id": 341,
        "externalId": "brand-341",
        "name": "Lucky Spin Casino",
        "slug": "lucky-spin-casino",
        "rating": 4.6,
        "logo": {
          "rectangular": "https://cdn.dataflair.ai/brands/341/logo-rect.png",
          "square": "https://cdn.dataflair.ai/brands/341/logo-square.png",
          "backgroundColor": "#101820"
        },
        "licenses": ["Curacao"],
        "paymentMethods": ["UPI", "Visa", "Mastercard"],
        "restrictedCountries": [
          { "name": "United States", "code": "US" },
          { "name": "France", "code": "FR" }
        ],
        "allowedCountries": [
          { "name": "India", "code": "IN" }
        ],
        "classificationTypes": ["Casino"],
        "languages": { "website": ["en", "hi"], "support": ["en"], "livechat": ["en"] }
      },
      "offer": {
        "type": "offer",
        "id": 5820,
        "offerTypeId": 1,
        "offerTypeName": "Welcome Bonus",
        "offerText": "100% up to ₹20,000 + 100 Free Spins",
        "currencies": ["INR"],
        "has_free_spins": true,
        "bonus_wagering_requirement": 35,
        "bonus_expiry_date": "2026-12-31",
        "bonus_code": "LUCKY100",
        "minimum_deposit": 500,
        "max_payout": null,
        "max_bonus_amount": "20000.00",
        "is_sticky_bonus": false,
        "minimum_odds": null,
        "free_bet_value": null,
        "stake_returned": null,
        "bet_type": null,
        "tournament_ticket_value": null,
        "rakeback_percentage": null,
        "free_tickets": null,
        "geos": { "countries": ["India"], "markets": [] },
        "trackers": [
          {
            "id": 88,
            "campaignName": "India Launch",
            "trackerLink": "https://track.example.com/click?c=88",
            "tcLink": "https://example-operator.com/terms",
            "pageType": "LP",
            "geos": { "countries": ["India"], "markets": [] }
          }
        ]
      }
    }
  ]
}
```

Notes worth calling out explicitly:

* **`status` is always `"published"`.** The API only ever serves toplists it considers live.
  There's no draft/paused status value you need to branch on.
* **`items` is `[]`, not omitted or null, when a toplist's live edition currently has zero items in
  it**, a valid, safe-to-iterate response. This is different from a toplist having **no** live
  edition at all: the single-item endpoints (`by-slug` / `{id}`) 404 in that case instead of
  returning `200` with `items: []`, and such a toplist never appears in `GET /api/v1/toplists`
  either.
* **`locked`** (toplist-level) and **`isLocked`** (per-item) both reflect editorial state, not geo,
  unrelated to the geo-targeting resolution algorithm.
* **`pros` and `cons`** (per item, taken from the item's brand) are always JSON arrays of plain-text
  strings: `[]` when nothing has been entered, never `null` and never an object. At most 8 lines per list
  and 160 characters per line. The app rejects `<` and `>` when the text is entered, so a line never
  contains them, but still treat the values as untrusted text and escape them when you render. They are an
  additive field on v1 and v2 (same values in both), not part of the nested `brand` object. The additive
  revision is announced by `/api/v1/meta` `contract_rev` 1.2.0 and `/api/v2/meta` 2.2.0.
  When a brand's pros or cons change, each site with an active webhook receives a `toplist.published`
  webhook for each of its own live toplists that lists the brand, so re-fetch the toplist on it as usual.
  These share the site's normal webhook delivery queue, so a brand on many toplists can take a few minutes
  to reach the site.
* **`pageType`** (inside each tracker) is normally one of a small set of internal short codes
  (`LP`, `NDP`, `GP`, `RMP`, `RP`, `DEFAULT`), but isn't a closed/validated enum. Treat it as an opaque
  internal code you pass through or ignore, never show it to a user directly.
* **`shortcode` is currently always `null`.** The field exists on the resource but isn't backed by
  any stored value or generation logic yet. Don't build against it until your DataFlair contact
  confirms it's populated.
* **Money/precision offer fields are quoted strings, not JSON numbers.** `max_bonus_amount`,
  `max_payout`, `minimum_odds`, `free_bet_value`, `tournament_ticket_value`, and
  `rakeback_percentage` (when non-null) all render like `"20000.00"`: parse them with a
  decimal-safe type, not a native float. Plain counts such as `minimum_deposit` and `free_tickets`
  are real JSON integers and don't have this quirk.
* **This endpoint wraps its payload in Laravel's `{"data": ...}` envelope**, with `links`/`meta`
  siblings around the `data` array: `{"data": [...], "links": {...}, "meta": {"current_page",
  "per_page", "total", "last_page", ...}}`. The two single-item endpoints wrap the same
  per-toplist shape in `{"data": { ... }}` too, just a single object, no `links`/`meta` alongside
  it. Always read from `response.data`, never the response root. **This wrapping is specific to
  these resource endpoints**: the auth exchange and every error response return their fields
  directly at the response root, with no `data` envelope.

See [Geo-targeting & compliance](/dataflair/toplist-api/geo-targeting) for what the `geo` object means and
how to use it before rendering anything.
