Appearance
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 onetemplate_idis 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 higherper_page(up to 100).per_pagecaps at 100 per request, so a family larger than that is never returned in one call regardless of what you pass. Follow thelinks/metapagination fields (see the response-wrapping note below) across successive requests, incrementingpage, untillinks.nextisnullormeta.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.bashcurl "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.bashcurl "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,
"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": ["United States", "France"],
"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:
statusis always"published". The API only ever serves toplists it considers live. There's no draft/paused status value you need to branch on.itemsis[], 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 returning200withitems: [], and such a toplist never appears inGET /api/v1/toplistseither.locked(toplist-level) andisLocked(per-item) both reflect editorial state, not geo, unrelated to the geo-targeting resolution algorithm.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.shortcodeis currently alwaysnull. 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, andrakeback_percentage(when non-null) all render like"20000.00": parse them with a decimal-safe type, not a native float. Plain counts such asminimum_depositandfree_ticketsare real JSON integers and don't have this quirk. - This endpoint wraps its payload in Laravel's
{"data": ...}envelope, withlinks/metasiblings around thedataarray:{"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, nolinks/metaalongside it. Always read fromresponse.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 nodataenvelope.
See Geo-targeting & compliance for what the geo object means and how to use it before rendering anything.