{"openapi": "3.0.3", "info": {"title": "iGaming Tools API", "version": "1.0.0", "description": "Public REST API for iGaming data: slot providers, slots, news, jobs, sources.\n\n## Authentication\n\nEvery endpoint documented in this schema requires an `Authorization: Token <your-token>` header. Create a free account and issue your own key at /account/.\n\n## Response Codes\n\n- **200** OK. Quota is decremented, unless this specific operation is documented as exempt.\n- **304** Not Modified — ETag match (If-None-Match). Quota is not decremented.\n- **400** Bad Request — invalid filter or parameter. Quota is not decremented.\n- **401** Unauthorized — missing or invalid token. Quota is not decremented.\n- **402** Payment Required — billing quota exhausted (monthly free tier or paid balance depleted). Retry after quota reset or top-up. Distinct from rate limiting. Quota is not decremented.\n- **404** Not Found — resource does not exist or is not yet public. Quota is not decremented.\n- **429** Too Many Requests — technical per-user rate limit exceeded (requests/min). Back off and retry. Distinct from quota (402). Quota is not decremented.\n\n## Sync Protocol\n\nUse `?updated_since=<ISO-8601>` for incremental sync. Store `X-Sync-Timestamp` response header and pass as `updated_since` next call. Use `/ids/` endpoints for tombstone reconcile (unpublished items do not appear in deltas).", "termsOfService": "https://i-gaming.tools/terms/", "contact": {"name": "iGaming Tools support", "email": "support@i-gaming.tools", "url": "https://i-gaming.tools/docs/"}, "license": {"name": "Terms of Service", "url": "https://i-gaming.tools/terms/"}}, "paths": {"/api/v1/demand/": {"get": {"operationId": "demand_list", "description": "Single parametrized endpoint over the market-demand cut (slot × country search-volume aggregate). The volume_12m figure is a SUM over a rolling 12-month window shared by every row in the response (not the latest month) — see meta.window and meta.coverage.pairs_at_frontier_pct for how complete the current month's collection is. coverage=our own catalogue, not the wider market (meta.coverage.basis). The response schema is a oneOf over four envelopes, one per axis family, and meta.by tells you which one you received: by='slot' carries per-market detail, by='slot_global' aggregates a slot across markets, by='country' is one market per row, and the remaining axes share a keyed shape. See the examples for each.", "summary": "Search-demand cut", "parameters": [{"in": "query", "name": "by", "schema": {"type": "string", "enum": ["country", "feature", "game_category", "mechanic", "provider", "slot", "slot_global", "theme"], "default": "slot"}, "description": "Cut axis; the shape of each result item is decided by this parameter alone. 'slot' (default) is one slot in ONE market and requires ?country=; 'slot_global' is one slot aggregated across every tracked market and does not require one; passing ?country= there simply narrows the aggregate. 'country' is one market; 'provider', 'theme', 'feature', 'mechanic' and 'game_category' share one shape keyed by the axis value. Any other value → 400. 'theme'/'feature' are overlapping axes (a slot with multiple themes counts in each) — see response meta.overlapping."}, {"in": "query", "name": "country", "schema": {"type": "string"}, "description": "2-letter ISO country code (case-insensitive, normalized to upper-case). REQUIRED when by='slot', because that axis reports one slot in one market and its per-market fields are undefined without one — use by='slot_global' for a worldwide ranking instead. Optional for every other axis, where omitting it aggregates across all tracked markets. Invalid format → 400."}, {"in": "query", "name": "coverage", "schema": {"type": "boolean", "default": true}, "description": "false suppresses meta.coverage entirely — the coverage aggregates are not computed at all, rather than computed and discarded. Omitted or true follows the default rule: meta.coverage is populated on the first page of a cursor traversal (previous == null) and null on subsequent pages. Any other value returns 400."}, {"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous response's next/previous link."}, {"in": "query", "name": "feature", "schema": {"type": "string"}, "description": "Feature slug (exact match). Non-existent slug → empty results, not 400."}, {"in": "query", "name": "game_category", "schema": {"type": "string", "enum": ["crash", "instant_win", "live", "other", "scratch", "table", "unknown", "video_slot"]}, "description": "Game category filter (catalogue). One of: unknown, video_slot, crash, instant_win, scratch, live, table, other. Not to be confused with ?by=game_category (cut axis) — both can be combined."}, {"in": "query", "name": "has_bonus_buy", "schema": {"type": "string"}, "description": "'true' or 'false'. Any other value returns 400."}, {"in": "query", "name": "jackpot_type", "schema": {"type": "string", "enum": ["fixed", "none", "pooled", "progressive", "unknown"]}, "description": "Jackpot type (none/fixed/progressive/pooled/unknown)"}, {"in": "query", "name": "max_win_max", "schema": {"type": "integer"}, "description": "max_win <= this value (multiplier of stake, positive integer). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "max_win_min", "schema": {"type": "integer"}, "description": "max_win >= this value (multiplier of stake, positive integer). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "mechanic", "schema": {"type": "string", "enum": ["cluster", "lines", "megaways", "scatter_pays", "variable_ways", "ways"]}, "description": "Bet mechanic (catalogue filter). One of: lines, ways, cluster, megaways, scatter_pays. Not to be confused with ?by=mechanic (cut axis) — both can be combined."}, {"in": "query", "name": "ordering", "schema": {"type": "string", "enum": ["-volume_12m", "volume_12m"], "default": "-volume_12m"}, "description": "Whitelist: '-volume_12m' (default, highest demand first) or 'volume_12m' (lowest first). Any other value → 400."}, {"in": "query", "name": "page_size", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20}, "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum."}, {"in": "query", "name": "provider", "schema": {"type": "string"}, "description": "Provider slug (exact match). Non-existent slug → empty results, not 400. Not to be confused with ?by=provider (cut axis) — both can be combined."}, {"in": "query", "name": "released_after", "schema": {"type": "string", "format": "date"}, "description": "ISO date (YYYY-MM-DD). release_date >= this value. Slots with release_date IS NULL are excluded. 400 on invalid format."}, {"in": "query", "name": "released_before", "schema": {"type": "string", "format": "date"}, "description": "ISO date (YYYY-MM-DD). release_date <= this value — INCLUSIVE, the named day is part of the range. Note that ?published_before, ?posted_before and ?established_before on other endpoints are EXCLUSIVE. Slots with release_date IS NULL are excluded. 400 on invalid format."}, {"in": "query", "name": "rtp_max", "schema": {"type": "number", "format": "double"}, "description": "rtp_default <= this value (percent, decimal, e.g. 96.5). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "rtp_min", "schema": {"type": "number", "format": "double"}, "description": "rtp_default >= this value (percent, decimal, e.g. 96.5). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "search", "schema": {"type": "string"}, "description": "Case-insensitive substring search over slot name + aliases. Empty/whitespace-only value is a no-op (does not narrow results). Soft-clipped to 100 characters (not a 400)."}, {"in": "query", "name": "series", "schema": {"type": "string"}, "description": "Series slug (exact match). A slot belongs to at most one series."}, {"in": "query", "name": "theme", "schema": {"type": "string"}, "description": "Theme slug (exact match). Non-existent slug → empty results, not 400."}, {"in": "query", "name": "volatility", "schema": {"type": "string", "enum": ["high", "low", "med_high", "med_low", "medium", "very_high"]}, "description": "Volatility (low/med_low/medium/med_high/high/very_high)"}], "tags": ["demand"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/DemandCutEnvelope"}, "examples": {"CutBySlot(default,Requires?country=)": {"value": {"meta": {"by": "slot", "country": "BR", "window": {"start": "2025-07", "end": "2026-06"}, "window_mixed": false, "metric": "volume_12m", "total_volume_12m": 4820100, "overlapping": false, "coverage": {"basis": "our_catalogue", "markets_tracked": 81, "slots_total": 509, "providers": [{"slug": "pragmatic-play", "name": "Pragmatic Play", "slots": 509}], "pairs_at_frontier_pct": 70, "computed_at": "2026-07-29T03:30:00Z"}}, "next": null, "previous": null, "results": [{"slot": {"slug": "example-slot-title", "name": "Example Slot Title", "provider": {"slug": "pragmatic-play", "name": "Pragmatic Play"}, "demo_url": "https://demo.i-gaming.tools/iframe/example-slot-title/"}, "volume_12m": 12000, "months_covered": 12, "last_seen": {"month": "2026-06", "volume": 1100}, "prev_volume": 950}]}, "summary": "Cut by slot (default, requires ?country=)"}, "CutBySlotGlobal(worldwideRanking,No?country=Needed)": {"value": {"meta": {"by": "slot_global", "country": null, "window": {"start": "2025-08", "end": "2026-07"}, "window_mixed": false, "metric": "volume_12m", "total_volume_12m": 1200000, "overlapping": false, "coverage": null}, "next": "https://i-gaming.tools/api/v1/demand/?by=slot_global&cursor=abc", "previous": null, "results": [{"slot": {"slug": "example-slot-title", "name": "Example Slot Title", "provider": {"slug": "example-provider", "name": "Example Provider"}, "demo_url": null}, "volume_12m": 48000, "markets_with_data": 17}]}, "summary": "Cut by slot_global (worldwide ranking, no ?country= needed)"}, "CutByCountry(marketMap,OneCall—No?country=Filter)": {"value": {"meta": {"by": "country", "country": null, "window": {"start": "2025-07", "end": "2026-06"}, "window_mixed": false, "metric": "volume_12m", "total_volume_12m": 4820100, "overlapping": false, "coverage": null}, "next": "https://i-gaming.tools/api/v1/demand/?by=country&cursor=abc", "previous": null, "results": [{"country": {"iso": "BR", "name": "Brazil"}, "volume_12m": 812000, "slots_with_data": 509, "pairs_at_frontier_pct": 70}]}, "summary": "Cut by country (market map, one call — no ?country= filter)"}, "CutByProvider|theme|feature|mechanic|gameCategory": {"value": {"meta": {"by": "game_category", "country": null, "window": {"start": "2025-07", "end": "2026-06"}, "window_mixed": false, "metric": "volume_12m", "total_volume_12m": 4820100, "overlapping": false, "coverage": null}, "next": null, "previous": null, "results": [{"key": {"slug": "video_slot", "name": "Video Slot"}, "volume_12m": 4400000, "slots_with_data": 480}]}, "summary": "Cut by provider|theme|feature|mechanic|game_category"}}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/features/": {"get": {"operationId": "features_list", "description": "Cursor-paginated directory of slot features (game mechanics) that have at least one public slot. Use ?feature=<slug> on GET /api/v1/slots/ to filter by feature. aliases are alternative spellings for matching a user's query to this slug (e.g. 'egypt' -> 'egyptian'). Filter by slug only — aliases are not a second filter key. If next is not null, the directory does not fit in one response — keep paging until next is null before treating the set as complete.", "summary": "List slot features", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous response's next/previous link."}, {"in": "query", "name": "page_size", "schema": {"type": "integer", "minimum": 1, "maximum": 500, "default": 200}, "description": "Number of results per page. Default 200, maximum 500. The default is large enough to return the whole directory in one response; if next is not null, keep paginating with cursor until it is."}], "tags": ["features"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TaxonomyDirectoryPage"}, "examples": {"BonusGameDirectoryEntry": {"value": {"count": 64, "next": null, "previous": null, "results": [{"slug": "bonus-game", "name": "Bonus Game", "slots_count": 27, "aliases": ["bonus round", "mini game"]}]}, "summary": "Bonus Game directory entry"}}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/jobs/": {"get": {"operationId": "jobs_list", "description": "Public iGaming vacancy catalog. Cursor-pagination. No count. Field source_url — original vacancy page. Field url is absent: use source_url instead. With ?updated_since the order switches to (updated_at, id) ASC for forward sync, and only vacancies CHANGED since that moment are returned. Combine it with ?include_closed=true when replicating: without that, a vacancy that closed since your last run is simply absent from the delta, and your copy keeps showing it as open. Store the X-Sync-Timestamp header and pass it back on the next run.", "summary": "List vacancies", "parameters": [{"in": "query", "name": "brand_slug", "schema": {"type": "string"}, "description": "Brand slug (exact). The provider or operator this vacancy is attributed to."}, {"in": "query", "name": "city", "schema": {"type": "string"}, "description": "City slug"}, {"in": "query", "name": "company", "schema": {"type": "string"}, "description": "Company slug"}, {"in": "query", "name": "country", "schema": {"type": "string"}, "description": "Location country ISO-2 (→ location_country)"}, {"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"in": "query", "name": "employment_type", "schema": {"type": "string", "enum": ["contract", "freelance", "full_time", "internship", "part_time"]}, "description": "Employment type."}, {"in": "query", "name": "igaming_segment", "schema": {"type": "string", "enum": ["affiliate", "casino", "esports", "game_studio", "lottery", "payment_provider", "platform_provider", "poker", "regulator", "sportsbook"]}, "description": "iGaming segment."}, {"in": "query", "name": "include_closed", "schema": {"type": "boolean"}, "description": "false (default) = ACTIVE only; true = + CLOSED within window"}, {"in": "query", "name": "ordering", "schema": {"type": "string", "enum": ["-posted_at", "posted_at"]}, "description": "Ordering (whitelist: posted_at, -posted_at; default -posted_at)"}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}, {"in": "query", "name": "posted_after", "schema": {"type": "string", "format": "date-time"}, "description": "Posted at >= this value (ISO 8601, inclusive)."}, {"in": "query", "name": "posted_before", "schema": {"type": "string", "format": "date-time"}, "description": "Posted at < this value (ISO 8601, EXCLUSIVE — the named moment is not included)."}, {"in": "query", "name": "q", "schema": {"type": "string"}, "description": "Text search by title (case-insensitive substring)"}, {"in": "query", "name": "remote_mode", "schema": {"type": "string", "enum": ["hybrid", "on_site", "remote"]}, "description": "Remote mode."}, {"in": "query", "name": "seniority", "schema": {"type": "string", "enum": ["c_level", "head", "junior", "lead", "mid", "senior"]}, "description": "Seniority level."}, {"in": "query", "name": "skills", "schema": {"type": "array", "items": {"type": "string"}}, "description": "Skill slugs (OR semantics; repeatable parameter)"}, {"in": "query", "name": "source", "schema": {"type": "string", "enum": ["careers", "linkedin"]}, "description": "Source."}, {"in": "query", "name": "updated_since", "schema": {"type": "string", "format": "date-time"}, "description": "ISO-8601 datetime. Returns only records updated at or after this moment."}], "tags": ["jobs"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedJobsListList"}}}, "description": "Success."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/jobs/{id}/": {"get": {"operationId": "jobs_retrieve", "description": "Full vacancy card by UUID. CLOSED older than window or hidden → 404.", "summary": "Get vacancy detail", "parameters": [{"in": "path", "name": "id", "schema": {"type": "string", "format": "uuid"}, "description": "Stable identifier of the vacancy, safe to store and reuse.", "required": true}], "tags": ["jobs"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/JobsDetail"}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/jobs/stats/": {"get": {"operationId": "jobs_stats_retrieve", "description": "Approximate counts for landing total-badge. total_approx — approximate row count (±5%; not an exact count). Free — this endpoint does not count against your quota.", "summary": "Jobs statistics (approximate)", "tags": ["jobs"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "object", "properties": {"total_approx": {"type": "integer", "description": "Approximate count (±5%; not an exact count)."}, "by_status_public": {"type": "object", "additionalProperties": {"type": "integer"}, "description": "Count per public status code."}, "by_segment": {"type": "object", "additionalProperties": {"type": "integer"}, "description": "Count per iGaming segment code."}, "last_posted_at": {"type": "string", "format": "date-time", "nullable": true}, "computed_at": {"type": "string", "format": "date-time"}}}, "examples": {"ExampleResponse": {"value": {"total_approx": 4210, "by_status_public": {"live": 3900, "closed_recent": 310}, "by_segment": {"casino": 1200, "sportsbook": 800}, "last_posted_at": "2026-06-08T10:00:00Z", "computed_at": "2026-06-09T12:00:00Z"}, "summary": "Example response"}}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429WithQuota"}}}}, "/api/v1/news/": {"get": {"operationId": "news_list", "description": "Cursor-paginated list of iGaming news articles ordered by published_at DESC. Does not include body_en — body is only available in the detail endpoint. Filterable by brand, language, host, and date range. With ?updated_since the order switches to (updated_at, id) ASC for forward sync, and only articles CHANGED since that moment are returned — including ones published long ago but ingested or corrected recently. Store the X-Sync-Timestamp header and pass it back on the next run.", "summary": "List news articles", "parameters": [{"in": "query", "name": "brand_kind", "schema": {"type": "string"}, "description": "Brand kind code. Currently one of slot_provider, casino_brand, regulator, other. The set is data-driven, so treat it as open."}, {"in": "query", "name": "brand_slug", "schema": {"type": "string"}, "description": "Brand slug"}, {"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"in": "query", "name": "host", "schema": {"type": "string"}, "description": "Host FQDN"}, {"in": "query", "name": "language", "schema": {"type": "string"}, "description": "Language code (ISO 639-1)"}, {"name": "ordering", "required": false, "in": "query", "description": "Which field to use when ordering the results.", "schema": {"type": "string"}}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}, {"in": "query", "name": "published_after", "schema": {"type": "string", "format": "date-time"}, "description": "Published at >= this value (ISO 8601, inclusive)."}, {"in": "query", "name": "published_before", "schema": {"type": "string", "format": "date-time"}, "description": "Published at < this value (ISO 8601, EXCLUSIVE — the named moment is not included)."}, {"in": "query", "name": "updated_since", "schema": {"type": "string", "format": "date-time"}, "description": "ISO-8601 datetime. Returns only records updated at or after this moment."}], "tags": ["news"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedNewsArticleShortList"}}}, "description": "Success."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/news/{id}/": {"get": {"operationId": "news_retrieve", "description": "Full news article by UUID including body_en from content storage. If content storage is unavailable: body_en=\"\", body_unavailable_reason=\"content_storage_unavailable\". If body was not extracted: body_unavailable_reason=\"extraction_failed\".", "summary": "Get news article detail", "parameters": [{"in": "path", "name": "id", "schema": {"type": "string", "format": "uuid"}, "description": "Stable UUID identifier of the article.", "required": true}], "tags": ["news"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/NewsArticleFull"}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/news/stats/": {"get": {"operationId": "news_stats_retrieve", "description": "Approximate counts for total-badge display. Returns total_approx, by_status_done, by_language, last_published_at. Free — this endpoint does not count against your quota. Same as /jobs/stats/ and /stats/.", "summary": "News statistics (approximate)", "tags": ["news"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "object", "properties": {"total_approx": {"type": "integer", "description": "±5% approximate total."}, "by_status_done": {"type": "integer"}, "by_language": {"type": "object", "additionalProperties": {"type": "integer"}, "description": "Count per language code."}, "last_published_at": {"type": "string", "format": "date-time", "nullable": true}, "computed_at": {"type": "string", "format": "date-time"}}}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429WithQuota"}}}}, "/api/v1/providers/": {"get": {"operationId": "providers_list", "description": "Cursor-paginated list of public slot providers. Supports ?updated_since for incremental sync and ETag/304. X-Sync-Timestamp response header for next-sync cursor. Use /providers/ids/ for tombstone reconcile.", "summary": "List slot providers", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"in": "query", "name": "country", "schema": {"type": "string"}, "description": "Country (exact, free-text)"}, {"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"in": "query", "name": "established_after", "schema": {"type": "integer"}, "description": "Established in this year or later (inclusive)."}, {"in": "query", "name": "established_before", "schema": {"type": "integer"}, "description": "Established strictly before this year (EXCLUSIVE — the named year is not included)."}, {"in": "query", "name": "has_news", "schema": {"type": "boolean"}, "description": "Brands with news only"}, {"name": "ordering", "required": false, "in": "query", "description": "Which field to use when ordering the results.", "schema": {"type": "string"}}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}, {"in": "query", "name": "search", "schema": {"type": "string"}, "description": "Search by name and aliases"}, {"in": "query", "name": "updated_since", "schema": {"type": "string", "format": "date-time"}, "description": "ISO-8601 datetime. Returns only records updated at or after this moment."}], "tags": ["providers"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedBrandShortList"}, "examples": {"ListProviders": {"value": {"next": "https://i-gaming.tools/api/v1/providers/?cursor=cD00ODY%3D", "previous": "https://i-gaming.tools/api/v1/providers/?cursor=cj0xJnA9NDg3", "results": [{"slug": "example-provider", "kind": "slot_provider", "name": "Example Provider", "aliases": ["Example"], "hosts_count": 3, "news_count": 42}]}, "summary": "List providers"}}}}, "description": "Success."}, "304": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/providers/{slug}/": {"get": {"operationId": "providers_retrieve", "description": "Full provider profile: firmographics, assets (logo), company block (hq/offices/licenses/contacts), counts (slots/jobs/news/team/has_demand), HATEOAS links to sub-resources. ETag/304 via If-None-Match.", "summary": "Get provider detail", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"in": "path", "name": "slug", "schema": {"type": "string"}, "required": true}], "tags": ["providers"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProviderDetail"}, "examples": {"ProviderDetail": {"value": {"slug": "example-provider", "kind": "slot_provider", "name": "Example Provider", "aliases": ["Example"], "about_html": "<p>Example Provider builds video slots.</p>", "firmographics": {"country": "MT", "jurisdiction": "Malta", "established": 2015, "authority_for": "", "statutory_authority": ""}, "assets": {"logo": {"url": "https://cdn.i-gaming.tools/brands/example-provider.png", "key": "brands/example-provider.png", "version": "brands/example-provider.png"}}, "company": {"hq": {"label": "Head office", "city": "Sliema", "country": "MT"}, "offices": [{"label": "Studio", "city": "Kyiv", "country": "UA", "sort_order": 1}], "licenses": [{"authority": "MGA", "license_ref": "MGA/B2B/000/0000", "jurisdiction": "Malta", "status": "active", "sort_order": 0}], "contacts": [{"kind": "email", "label": "Sales", "value": "mailto:sales@example.com"}]}, "counts": {"slots": 128, "jobs": 4, "news": 42, "team": 11, "has_demand": true}, "links": {"slots": "/api/v1/providers/example-provider/slots/", "demand": "/api/v1/providers/example-provider/demand/", "jobs": "/api/v1/providers/example-provider/jobs/", "news": "/api/v1/providers/example-provider/news/", "team": "/api/v1/providers/example-provider/team/"}, "hosts": [{"host": "example-provider.com", "display_name": "Example Provider"}], "verified_at": "2026-07-01T10:00:00Z", "updated_at": "2026-08-01T09:30:00Z"}, "summary": "Provider detail"}}}}, "description": "Success."}, "304": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/providers/{slug}/demand/": {"get": {"operationId": "providers_demand_retrieve", "description": "Provider demand snapshot: computed_at, provider-level metrics, top_slots. Empty snapshot → 200 with nulls/empty (not 404). PUBLISHED-ONLY for top_slots. The monthly series is returned as `provider.series_24m` and is fixed at 24 trailing months — this endpoint takes no `?months=` parameter, unlike GET /api/v1/slots/{slug}/demand/ which accepts 1..60 or \"all\". The window is part of the field name, so widening it would require a new field rather than a new parameter.", "summary": "Search demand for this provider", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"in": "path", "name": "slug", "schema": {"type": "string"}, "required": true}], "tags": ["providers"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProviderDemand"}}}, "description": "Success."}, "304": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/providers/{slug}/jobs/": {"get": {"operationId": "providers_jobs_list", "description": "Brand-scoped cursor-paginated job listings, attributed to this brand. The filters of GET /api/v1/jobs/, including ?include_closed, work here too. Supports ?updated_since and ETag/304.", "summary": "Job vacancies for this provider", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"in": "query", "name": "city", "schema": {"type": "string"}, "description": "City slug"}, {"in": "query", "name": "company", "schema": {"type": "string"}, "description": "Company slug"}, {"in": "query", "name": "country", "schema": {"type": "string"}, "description": "Location country ISO-2 (→ location_country)"}, {"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"in": "query", "name": "employment_type", "schema": {"type": "string", "enum": ["contract", "freelance", "full_time", "internship", "part_time"]}, "description": "Employment type."}, {"in": "query", "name": "igaming_segment", "schema": {"type": "string", "enum": ["affiliate", "casino", "esports", "game_studio", "lottery", "payment_provider", "platform_provider", "poker", "regulator", "sportsbook"]}, "description": "iGaming segment."}, {"in": "query", "name": "include_closed", "schema": {"type": "boolean"}, "description": "false (default) = ACTIVE only; true = + CLOSED within window"}, {"in": "query", "name": "ordering", "schema": {"type": "string", "enum": ["-posted_at", "posted_at"]}, "description": "Ordering (whitelist: posted_at, -posted_at; default -posted_at)"}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}, {"in": "query", "name": "posted_after", "schema": {"type": "string", "format": "date-time"}, "description": "Posted at >= this value (ISO 8601, inclusive)."}, {"in": "query", "name": "posted_before", "schema": {"type": "string", "format": "date-time"}, "description": "Posted at < this value (ISO 8601, EXCLUSIVE — the named moment is not included)."}, {"in": "query", "name": "q", "schema": {"type": "string"}, "description": "Text search by title (case-insensitive substring)"}, {"in": "query", "name": "remote_mode", "schema": {"type": "string", "enum": ["hybrid", "on_site", "remote"]}, "description": "Remote mode."}, {"in": "query", "name": "seniority", "schema": {"type": "string", "enum": ["c_level", "head", "junior", "lead", "mid", "senior"]}, "description": "Seniority level."}, {"in": "query", "name": "skills", "schema": {"type": "array", "items": {"type": "string"}}, "description": "Skill slugs (OR semantics; repeatable parameter)"}, {"in": "path", "name": "slug", "schema": {"type": "string"}, "required": true}, {"in": "query", "name": "source", "schema": {"type": "string", "enum": ["careers", "linkedin"]}, "description": "Source."}, {"in": "query", "name": "updated_since", "schema": {"type": "string", "format": "date-time"}, "description": "DEPRECATED on brand-scoped endpoints. Here it filters by publication date, not by when the record last changed, so a delta silently omits items edited after publication — an article corrected long after it was published, or a vacancy that has since closed. Use the root listing with ?brand_slug= instead: GET /api/v1/news/?brand_slug=<slug>&updated_since=… or GET /api/v1/jobs/?brand_slug=<slug>&updated_since=…&include_closed=true, both of which filter and order by the last change. This parameter keeps its current behaviour so that traversals already in progress are not invalidated.", "deprecated": true}], "tags": ["providers"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedJobsListList"}}}, "description": "Success."}, "304": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/providers/{slug}/news/": {"get": {"operationId": "providers_news_list", "description": "Cursor-paginated news articles attributed to this brand. Supports ?updated_since for incremental sync and ETag/304. X-Sync-Timestamp response header for next-sync cursor.", "summary": "News for this brand", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"in": "query", "name": "host", "schema": {"type": "string"}, "description": "Host FQDN"}, {"in": "query", "name": "language", "schema": {"type": "string"}, "description": "Language code (ISO 639-1)"}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}, {"in": "query", "name": "published_after", "schema": {"type": "string", "format": "date-time"}, "description": "Published at >= this value (ISO 8601, inclusive)."}, {"in": "query", "name": "published_before", "schema": {"type": "string", "format": "date-time"}, "description": "Published at < this value (ISO 8601, EXCLUSIVE — the named moment is not included)."}, {"in": "path", "name": "slug", "schema": {"type": "string"}, "required": true}, {"in": "query", "name": "updated_since", "schema": {"type": "string", "format": "date-time"}, "description": "DEPRECATED on brand-scoped endpoints. Here it filters by publication date, not by when the record last changed, so a delta silently omits items edited after publication — an article corrected long after it was published, or a vacancy that has since closed. Use the root listing with ?brand_slug= instead: GET /api/v1/news/?brand_slug=<slug>&updated_since=… or GET /api/v1/jobs/?brand_slug=<slug>&updated_since=…&include_closed=true, both of which filter and order by the last change. This parameter keeps its current behaviour so that traversals already in progress are not invalidated.", "deprecated": true}], "tags": ["providers"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedNewsArticleShortList"}}}, "description": "Success."}, "304": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/providers/{slug}/slots/": {"get": {"operationId": "providers_slots_list", "description": "Brand-scoped cursor-paginated slot listing. ?provider= is pinned to this brand; the catalogue filters and ?ordering= of GET /api/v1/slots/ work here as they do there. Supports ?updated_since and ETag/304. Items carry volume_12m/trend (per-slot search-demand aggregate).", "summary": "Slots for this provider", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"in": "query", "name": "feature", "schema": {"type": "string"}, "description": "Feature slug (exact match). A slot may carry several features."}, {"in": "query", "name": "game_category", "schema": {"type": "string", "enum": ["crash", "instant_win", "live", "other", "scratch", "table", "unknown", "video_slot"]}, "description": "Game category, exact match. 400 on any value outside the enum. Also filters GET /api/v1/demand/, where it doubles as a cut axis (?by=game_category)."}, {"in": "query", "name": "has_bonus_buy", "schema": {"type": "string"}, "description": "'true' or 'false'. Any other value returns 400."}, {"in": "query", "name": "jackpot_type", "schema": {"type": "string", "enum": ["fixed", "none", "pooled", "progressive", "unknown"]}, "description": "Jackpot type (none/fixed/progressive/pooled/unknown)"}, {"in": "query", "name": "max_win_max", "schema": {"type": "integer"}, "description": "max_win <= this value (multiplier of stake, positive integer). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "max_win_min", "schema": {"type": "integer"}, "description": "max_win >= this value (multiplier of stake, positive integer). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "mechanic", "schema": {"type": "string", "enum": ["cluster", "lines", "megaways", "scatter_pays", "variable_ways", "ways"]}, "description": "Bet mechanic (lines/ways/cluster/megaways/scatter_pays)"}, {"in": "query", "name": "ordering", "schema": {"type": "string", "enum": ["-captured_at", "-release_date", "captured_at", "release_date"]}, "description": "Whitelist: '-release_date' (newest first) or 'release_date' (oldest first). Slots with release_date IS NULL sort to the end for '-release_date' and to the start for 'release_date'. '-captured_at' (most recently captured from a live demo first) or 'captured_at' (oldest/never-captured first) — matches the public listing's default order. Any other value → 400. Ignored when ?updated_since is present (sync order wins). Note on backward paging: cursor position is derived from the sort field alone, so walking back with `previous` through the trailing block of items that share the same sort value (those with no release date, or never captured) can skip entries. Forward paging with `next` is always complete, and neither ?updated_since sync nor the /ids/ reconcile is affected. Walk forward when you need the full set."}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}, {"in": "query", "name": "provider", "schema": {"type": "string"}, "description": "Provider slug (exact match)."}, {"in": "query", "name": "released_after", "schema": {"type": "string", "format": "date"}, "description": "ISO date (YYYY-MM-DD). release_date >= this value. Slots with release_date IS NULL are excluded. 400 on invalid format."}, {"in": "query", "name": "released_before", "schema": {"type": "string", "format": "date"}, "description": "ISO date (YYYY-MM-DD). release_date <= this value — INCLUSIVE, the named day is part of the range. Note that ?published_before, ?posted_before and ?established_before on other endpoints are EXCLUSIVE. Slots with release_date IS NULL are excluded. 400 on invalid format."}, {"in": "query", "name": "rtp_max", "schema": {"type": "number", "format": "double"}, "description": "rtp_default <= this value (percent, decimal, e.g. 96.5). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "rtp_min", "schema": {"type": "number", "format": "double"}, "description": "rtp_default >= this value (percent, decimal, e.g. 96.5). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "search", "schema": {"type": "string"}, "description": "Case-insensitive substring search over slot name + aliases. Empty/whitespace-only value is a no-op (does not narrow results). Soft-clipped to 100 characters (not a 400)."}, {"in": "query", "name": "series", "schema": {"type": "string"}, "description": "Series slug (exact match). A slot belongs to at most one series."}, {"in": "path", "name": "slug", "schema": {"type": "string"}, "required": true}, {"in": "query", "name": "theme", "schema": {"type": "string"}, "description": "Theme slug (exact match). A slot may carry several themes."}, {"in": "query", "name": "updated_since", "schema": {"type": "string", "format": "date-time"}, "description": "ISO-8601 datetime. Returns only records updated at or after this moment."}, {"in": "query", "name": "volatility", "schema": {"type": "string", "enum": ["high", "low", "med_high", "med_low", "medium", "very_high"]}, "description": "Volatility (low/med_low/medium/med_high/high/very_high)"}], "tags": ["providers"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedSlotListItemList"}}}, "description": "Success."}, "304": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/providers/{slug}/team/": {"get": {"operationId": "providers_team_list", "description": "Key people we publish for this provider. NOT paginated (bounded ≤60). profile_url NOT included (PII). Returns {count, results[]} envelope. ETag/304 supported.", "summary": "Key team members for this provider", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"in": "path", "name": "slug", "schema": {"type": "string"}, "required": true}], "tags": ["providers"], "security": [{"TokenAuth": []}], "responses": {"304": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "200": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"type": "object", "properties": {"count": {"type": "integer", "description": "Total visible team members (≤60)."}, "results": {"type": "array", "items": {"type": "object", "properties": {"full_name": {"type": "string"}, "headline": {"type": "string"}, "seniority": {"type": "string"}, "is_key": {"type": "boolean"}, "location_text": {"type": "string"}, "photo": {"type": "object", "nullable": true, "properties": {"url": {"type": "string", "format": "uri"}, "key": {"type": "string"}, "version": {"type": "string"}}}}}}}}}}, "description": "Success."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/providers/ids/": {"get": {"operationId": "providers_ids_list", "description": "Cursor-paginated list of ALL public provider slugs for tombstone diff. Results are plain strings, not objects. Decrements quota once per request. X-Sync-Timestamp included in response header.", "summary": "Reconcile — all public provider slugs", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous response's next/previous link."}, {"in": "query", "name": "page_size", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20}, "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum."}], "tags": ["providers"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"type": "object", "properties": {"next": {"type": "string", "format": "uri", "nullable": true}, "previous": {"type": "string", "format": "uri", "nullable": true}, "results": {"type": "array", "items": {"type": "string", "example": "example-provider"}}}, "required": ["next", "previous", "results"]}}}, "description": "Success."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/regulators/": {"get": {"operationId": "regulators_list", "description": "Cursor-paginated list of iGaming regulators (UKGC, MGA, GGL, ANJ, etc.). All seeded regulators are public.", "summary": "List regulators", "parameters": [{"in": "query", "name": "country", "schema": {"type": "string"}, "description": "Country (exact, free-text)"}, {"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"in": "query", "name": "established_after", "schema": {"type": "integer"}, "description": "Established in this year or later (inclusive)."}, {"in": "query", "name": "established_before", "schema": {"type": "integer"}, "description": "Established strictly before this year (EXCLUSIVE — the named year is not included)."}, {"in": "query", "name": "has_news", "schema": {"type": "boolean"}, "description": "Brands with news only"}, {"name": "ordering", "required": false, "in": "query", "description": "Which field to use when ordering the results.", "schema": {"type": "string"}}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}, {"in": "query", "name": "search", "schema": {"type": "string"}, "description": "Search by name and aliases"}], "tags": ["regulators"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedBrandShortList"}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/regulators/{slug}/": {"get": {"operationId": "regulators_retrieve", "description": "Full regulator profile with licensing authority information (jurisdiction, authority_for, statutory_authority, hosts).", "summary": "Get regulator detail", "parameters": [{"in": "path", "name": "slug", "schema": {"type": "string"}, "description": "Regulator slug.", "required": true}], "tags": ["regulators"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/BrandFull"}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/regulators/{slug}/news/": {"get": {"operationId": "regulators_news_list", "description": "Cursor-paginated news articles attributed to this brand. Supports ?updated_since for incremental sync and ETag/304. X-Sync-Timestamp response header for next-sync cursor.", "summary": "News for this brand", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"in": "query", "name": "host", "schema": {"type": "string"}, "description": "Host FQDN"}, {"in": "query", "name": "language", "schema": {"type": "string"}, "description": "Language code (ISO 639-1)"}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}, {"in": "query", "name": "published_after", "schema": {"type": "string", "format": "date-time"}, "description": "Published at >= this value (ISO 8601, inclusive)."}, {"in": "query", "name": "published_before", "schema": {"type": "string", "format": "date-time"}, "description": "Published at < this value (ISO 8601, EXCLUSIVE — the named moment is not included)."}, {"in": "path", "name": "slug", "schema": {"type": "string"}, "required": true}, {"in": "query", "name": "updated_since", "schema": {"type": "string", "format": "date-time"}, "description": "DEPRECATED on brand-scoped endpoints. Here it filters by publication date, not by when the record last changed, so a delta silently omits items edited after publication — an article corrected long after it was published, or a vacancy that has since closed. Use the root listing with ?brand_slug= instead: GET /api/v1/news/?brand_slug=<slug>&updated_since=… or GET /api/v1/jobs/?brand_slug=<slug>&updated_since=…&include_closed=true, both of which filter and order by the last change. This parameter keeps its current behaviour so that traversals already in progress are not invalidated.", "deprecated": true}], "tags": ["regulators"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedNewsArticleShortList"}}}, "description": "Success."}, "304": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/series/": {"get": {"operationId": "series_list", "description": "Cursor-paginated directory of slot series (franchises) that have at least one public slot. Use ?series=<slug> on GET /api/v1/slots/ to list the games in a series. aliases are alternative spellings for matching a user's query to a slug — not a second filter key; filter by slug only. An unknown ?provider= slug returns an empty page, not a 400. If next is not null, the directory does not fit in one response — keep paging until next is null before treating the set as complete.", "summary": "List slot series (franchises)", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous response's next/previous link."}, {"in": "query", "name": "page_size", "schema": {"type": "integer", "minimum": 1, "maximum": 500, "default": 200}, "description": "Number of results per page. Default 200, maximum 500. The default is large enough to return the whole directory in one response; if next is not null, keep paginating with cursor until it is."}, {"in": "query", "name": "provider", "schema": {"type": "string"}, "description": "Provider slug (exact match)."}], "tags": ["series"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SeriesDirectoryPage"}, "examples": {"SeriesDirectoryPage": {"value": {"count": 61, "next": null, "previous": null, "results": [{"slug": "big-bass", "name": "Big Bass", "aliases": [], "provider": {"slug": "pragmatic-play", "name": "Pragmatic Play"}, "slots_count": 33, "years": {"from": 2020, "to": 2026}, "rtp": {"min": "96.07", "max": "96.52"}, "max_win": {"min": 2100, "max": 25000}}]}, "summary": "Series directory page"}}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/series/{slug}/": {"get": {"operationId": "series_retrieve", "description": "Family-wide summary for one series: catalogue aggregates, distributions (volatility / has_bonus_buy / game_category) and a short roster ordered newest release first. For the full roster with every slot field and catalogue filter, use GET /api/v1/slots/?series=<slug> — slots_url points there directly. Distribution keys include 'unknown' (has_bonus_buy / game_category, not evaluated) and 'null' (volatility, not set); values in each distribution always sum to slots_count. 404 both when the slug does not exist and when the series has no public slot.", "summary": "Get series (franchise) family summary", "parameters": [{"in": "path", "name": "slug", "schema": {"type": "string"}, "description": "URL-friendly unique identifier of the series.", "required": true}], "tags": ["series"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SeriesDetail"}, "examples": {"SeriesFamilySummary": {"value": {"slug": "big-bass", "name": "Big Bass", "provider": {"slug": "pragmatic-play", "name": "Pragmatic Play"}, "slots_count": 33, "years": {"from": 2020, "to": 2026}, "rtp": {"min": "96.07", "max": "96.52", "spread": "0.45"}, "max_win": {"min": 2100, "max": 25000}, "volatility": {"high": 31, "medium": 1, "low": 1}, "has_bonus_buy": {"yes": 32, "no": 1}, "game_category": {"video_slot": 33}, "slots_url": "https://i-gaming.tools/api/v1/slots/?series=big-bass", "slots_truncated": false, "slots": [{"slug": "big-bass-blast-b7", "name": "Big Bass Blast", "released": "2026-07-06", "rtp": "96.50", "max_win": 5000, "demo_url": "https://demo.i-gaming.tools/iframe/big-bass-blast-b7/"}]}, "summary": "Series family summary"}}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/slots/": {"get": {"operationId": "slots_list", "description": "Cursor-paginated list of published slots. Default order: -created_at. With ?updated_since: order switches to (updated_at, id) ASC for forward-sync. With ?ordering=-release_date|release_date: order switches to release-date mode. With ?ordering=-captured_at|captured_at: order switches to capture-date mode (matches the public listing default order). Use /slots/ids/ for tombstone reconcile (removed slots do not appear in deltas). ETag/304 support via If-None-Match.", "summary": "List public slots", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"in": "query", "name": "feature", "schema": {"type": "string"}, "description": "Feature slug (exact match). A slot may carry several features."}, {"in": "query", "name": "game_category", "schema": {"type": "string", "enum": ["crash", "instant_win", "live", "other", "scratch", "table", "unknown", "video_slot"]}, "description": "Game category, exact match. 400 on any value outside the enum. Also filters GET /api/v1/demand/, where it doubles as a cut axis (?by=game_category)."}, {"in": "query", "name": "has_bonus_buy", "schema": {"type": "string"}, "description": "'true' or 'false'. Any other value returns 400."}, {"in": "query", "name": "jackpot_type", "schema": {"type": "string", "enum": ["fixed", "none", "pooled", "progressive", "unknown"]}, "description": "Jackpot type (none/fixed/progressive/pooled/unknown)"}, {"in": "query", "name": "max_win_max", "schema": {"type": "integer"}, "description": "max_win <= this value (multiplier of stake, positive integer). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "max_win_min", "schema": {"type": "integer"}, "description": "max_win >= this value (multiplier of stake, positive integer). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "mechanic", "schema": {"type": "string", "enum": ["cluster", "lines", "megaways", "scatter_pays", "variable_ways", "ways"]}, "description": "Bet mechanic (lines/ways/cluster/megaways/scatter_pays)"}, {"in": "query", "name": "ordering", "schema": {"type": "string", "enum": ["-captured_at", "-release_date", "captured_at", "release_date"]}, "description": "Whitelist: '-release_date' (newest first) or 'release_date' (oldest first). Slots with release_date IS NULL sort to the end for '-release_date' and to the start for 'release_date'. '-captured_at' (most recently captured from a live demo first) or 'captured_at' (oldest/never-captured first) — matches the public listing's default order. Any other value → 400. Ignored when ?updated_since is present (sync order wins). Note on backward paging: cursor position is derived from the sort field alone, so walking back with `previous` through the trailing block of items that share the same sort value (those with no release date, or never captured) can skip entries. Forward paging with `next` is always complete, and neither ?updated_since sync nor the /ids/ reconcile is affected. Walk forward when you need the full set."}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}, {"in": "query", "name": "provider", "schema": {"type": "string"}, "description": "Provider slug (exact match)."}, {"in": "query", "name": "released_after", "schema": {"type": "string", "format": "date"}, "description": "ISO date (YYYY-MM-DD). release_date >= this value. Slots with release_date IS NULL are excluded. 400 on invalid format."}, {"in": "query", "name": "released_before", "schema": {"type": "string", "format": "date"}, "description": "ISO date (YYYY-MM-DD). release_date <= this value — INCLUSIVE, the named day is part of the range. Note that ?published_before, ?posted_before and ?established_before on other endpoints are EXCLUSIVE. Slots with release_date IS NULL are excluded. 400 on invalid format."}, {"in": "query", "name": "rtp_max", "schema": {"type": "number", "format": "double"}, "description": "rtp_default <= this value (percent, decimal, e.g. 96.5). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "rtp_min", "schema": {"type": "number", "format": "double"}, "description": "rtp_default >= this value (percent, decimal, e.g. 96.5). Also filters GET /api/v1/demand/ — same catalogue filter."}, {"in": "query", "name": "search", "schema": {"type": "string"}, "description": "Case-insensitive substring search over slot name + aliases. Empty/whitespace-only value is a no-op (does not narrow results). Soft-clipped to 100 characters (not a 400)."}, {"in": "query", "name": "series", "schema": {"type": "string"}, "description": "Series slug (exact match). A slot belongs to at most one series."}, {"in": "query", "name": "theme", "schema": {"type": "string"}, "description": "Theme slug (exact match). A slot may carry several themes."}, {"in": "query", "name": "updated_since", "schema": {"type": "string", "format": "date-time"}, "description": "ISO-8601 datetime. Returns only slots whose content changed at or after this moment. When present, results are ordered by (updated_at, id) ASC — forward through time. Without this param the default order (-created_at, -id) is preserved. 400 on invalid format. Known limitation: cross-object renames (provider/studio/series name changes) do NOT appear in this delta — use /slots/ids/ for tombstone reconcile."}, {"in": "query", "name": "volatility", "schema": {"type": "string", "enum": ["high", "low", "med_high", "med_low", "medium", "very_high"]}, "description": "Volatility (low/med_low/medium/med_high/high/very_high)"}], "tags": ["slots"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedSlotListItemList"}, "examples": {"IncrementalSyncWithUpdatedSince": {"value": {"next": "https://i-gaming.tools/api/v1/slots/?cursor=cD00ODY%3D", "previous": "https://i-gaming.tools/api/v1/slots/?cursor=cj0xJnA9NDg3", "results": [{"slug": "example-slot-title", "name": "Example Slot Title", "provider": {"slug": "example-provider", "name": "Example Provider"}, "game_category": "video_slot", "game_subtype": null, "rtp_default": "96.50", "volatility": "high", "max_win": 5000, "mechanic": "ways", "mechanic_value": 243, "release_date": "2026-01-15", "release_date_precision": "day", "thumbnail_url": "https://cdn.i-gaming.tools/slots/example-slot-title.webp", "thumbnail": {"url": "https://cdn.i-gaming.tools/slots/example-slot-title.webp", "key": "slots/example-slot-title.webp", "version": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, "volume_12m": 12000, "trend": "growing", "demo_url": null, "updated_at": "2026-08-01T09:30:00Z"}]}, "summary": "Incremental sync with updated_since", "description": "Pass X-Sync-Timestamp from a previous response as ?updated_since= to receive only slots modified since that moment."}}}}, "description": "Success."}, "304": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/slots/{slug}/": {"get": {"operationId": "slots_retrieve", "description": "Full slot profile: data (RTP/volatility/layout/mechanics), spec_sheet (symbols/modes/palette/accessibility), assets (screenshots/icon). ETag/304: pass If-None-Match to skip the heavy response on unchanged content.", "summary": "Get slot detail", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"in": "path", "name": "slug", "schema": {"type": "string"}, "description": "URL-friendly unique identifier of the slot.", "required": true}], "tags": ["slots"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SlotDetail"}, "examples": {"SlotDetail": {"value": {"data": {"slug": "example-slot-title", "name": "Example Slot Title", "provider": {"slug": "example-provider", "name": "Example Provider"}, "game_category": "video_slot", "rtp_default": "96.50", "volatility": "high", "max_win": 5000, "mechanic": "ways", "mechanic_value": 243, "reels": 5, "rows": 3, "release_date": "2026-01-15", "release_date_precision": "day", "status": "active", "studio": null, "series": null, "order_in_series": null, "licensed_brand": null, "rtp_variants": [{"rtp": "96.50", "variant": "default", "condition_note": "", "is_default": true}], "min_bet": "0.20", "max_bet": "100.00", "currency": "EUR", "payline_direction": "ltr", "jackpot_type": "none", "jackpot_tiers": [], "has_bonus_buy": "yes", "bonus_buy_cost": "100.00", "bonus_buys": [{"label": "Free Spins", "cost": "100.00", "effect": "Awards 10 free spins", "is_default": true}], "has_autoplay": "yes", "mobile_adapted": "yes", "ante_bet_cost": null, "themes": [{"slug": "fishing", "name": "Fishing"}], "features": [{"slug": "free-spins", "name": "Free spins"}], "aliases": [], "provider_ref": "", "symbols": [], "modes": [], "accessibility_notes": [], "coverage_notes": [], "coverage_gap_count": 0, "captured_at": "2026-02-01T10:00:00Z", "capture_method": "Live demo capture (headless browser) & OCR verification", "counts": {"symbols": 0, "screenshots": 1, "modes": 0, "palette": 0}}, "spec_sheet": {"source": "extracted, unverified", "raw": "RTP 96.5% · Max win 5000x · 5 reels", "updated_at": "2026-02-02T08:15:00Z"}, "assets": {"screenshots": [{"type": "preview", "device": "desktop", "page": "base_game", "order": 0, "width": 1280, "height": 720, "note": "", "url": "https://cdn.i-gaming.tools/slots/example-slot-title/preview.webp", "srcset": "", "key": "slots/example-slot-title/preview.webp", "version": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}], "logo_url": "https://cdn.i-gaming.tools/slots/example-slot-title/logo.webp", "logo": {"url": "https://cdn.i-gaming.tools/slots/example-slot-title/logo.webp", "key": "slots/example-slot-title/logo.webp", "version": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, "palette": [], "demo": {"available": true, "page_url": "https://i-gaming.tools/all-slots/example-slot-title/", "embed_url": "https://demo.i-gaming.tools/iframe/example-slot-title/"}}}, "summary": "Slot detail"}}}}, "description": "Success."}, "304": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/slots/{slug}/demand/": {"get": {"operationId": "slots_demand_retrieve", "description": "Per-slot search-demand aggregate: volume_12m/prev_12m/yoy_pct/trend + 24-point sparkline, derived from the provider demand snapshot. Graceful nulls when the provider has no demand snapshot yet. ETag/304 via If-None-Match (seed=computed_at, mirrors provider /demand/). See the `months` parameter for the trailing-window override.", "summary": "Slot demand snapshot", "parameters": [{"in": "header", "name": "If-None-Match", "schema": {"type": "string"}, "description": "Opaque ETag value from a previous response to this same endpoint. When it matches the current ETag, the server returns 304 Not Modified with no body instead of re-sending the full payload."}, {"in": "query", "name": "months", "schema": {"type": "string"}, "description": "Number of trailing months to include in months/totals/markets[].values. Integer values are clamped to [1, 60] (0/negative/huge values do not error, they clamp). 'all' returns the full stored history (60 months). Omitting the parameter defaults to 12 months (pre-existing behaviour, byte-identical response shape). A non-numeric, non-\"all\" value returns 400. Note: per-market `volume_12m` always sums exactly the requested window — with `?months=all` it is NOT a 12-month sum despite the field name (the name is historical and kept for backward compatibility)."}, {"in": "path", "name": "slug", "schema": {"type": "string"}, "description": "URL-friendly unique identifier of the slot.", "required": true}], "tags": ["slots"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SlotDemand"}, "examples": {"SlotDemand": {"value": {"computed_at": "2026-08-01T03:15:00Z", "volume_12m": 12000, "prev_12m": 9000, "yoy_pct": 33.3, "trend": "growing", "sparkline": [700, 720, 740, 760, 780, 800, 820, 840, 860, 880, 900, 920, 940, 960, 980, 1000, 1020, 1040, 1060, 1080, 1100, 1120, 1140, 1160], "months": ["2026-07", "2026-06", "2026-05"], "totals": [1160, 1140, 1120], "markets": [{"country": "Brazil", "country_iso": "BR", "flag": "🇧🇷", "values": [640, 620, 600], "mom_delta": 20, "volume_12m": 7000, "trend": "growing"}, {"country": "Canada", "country_iso": "CA", "flag": "🇨🇦", "values": [520, 520, 520], "mom_delta": 0, "volume_12m": 5000, "trend": "flat"}]}, "summary": "Slot demand"}}}}, "description": "Success."}, "304": {"headers": {"ETag": {"$ref": "#/components/headers/ETag"}}, "description": "Not Modified — the ETag in If-None-Match matched the current value. No body."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/slots/ids/": {"get": {"operationId": "slots_ids_list", "description": "Cursor-paginated list of ALL public slot slugs for tombstone diff. Results are plain strings, not objects. Decrements quota once per request. REQUIRED for unpublish/compliance detection: ?updated_since delta does NOT include removed slots. Compare this set with your own copy and tombstone the slugs that are gone. X-Sync-Timestamp is included in the response header.", "summary": "Reconcile — all public slot slugs", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous response's next/previous link."}, {"in": "query", "name": "page_size", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20}, "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum."}], "tags": ["slots"], "security": [{"TokenAuth": []}], "responses": {"200": {"headers": {"X-Sync-Timestamp": {"$ref": "#/components/headers/XSyncTimestamp"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}, "content": {"application/json": {"schema": {"type": "object", "properties": {"next": {"type": "string", "format": "uri", "nullable": true}, "previous": {"type": "string", "format": "uri", "nullable": true}, "results": {"type": "array", "items": {"type": "string", "example": "example-slot-title"}}}, "required": ["next", "previous", "results"]}}}, "description": "Success."}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/sources/": {"get": {"operationId": "sources_list", "description": "Cursor-paginated list of active parsing hosts with brand attribution. Ordered by host (FQDN). Includes articles_count_30d (last 30 days) and last_extracted_at.", "summary": "List news sources", "parameters": [{"name": "cursor", "required": false, "in": "query", "description": "Opaque pagination cursor from a previous response's next/previous link.", "schema": {"type": "string"}}, {"name": "ordering", "required": false, "in": "query", "description": "Which field to use when ordering the results.", "schema": {"type": "string"}}, {"name": "page_size", "required": false, "in": "query", "description": "Number of results per page. Default 20, maximum 100 — same limits as the rest of the public API. An operator-configured limit may lower the maximum.", "schema": {"type": "integer"}}], "tags": ["sources"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PaginatedSourceList"}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}, "/api/v1/sources/{host}/": {"get": {"operationId": "sources_retrieve", "description": "Source detail by host FQDN. Same fields as the list endpoint. The host is the full domain, dots included.", "summary": "Get source detail", "parameters": [{"in": "path", "name": "host", "schema": {"type": "string", "pattern": "^[^/]+$"}, "required": true}], "tags": ["sources"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Source"}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "404": {"$ref": "#/components/responses/Err404"}}}}, "/api/v1/stats/": {"get": {"operationId": "stats_retrieve", "description": "Aggregate stats for landing/dashboard display. Free — this endpoint does not count against your quota. Cached server-side; values can be up to 60 minutes old.", "summary": "Public aggregate statistics", "tags": ["stats"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"type": "object", "properties": {"sources_count": {"type": "integer", "description": "Enabled parsing sources."}, "brands_count": {"type": "integer", "description": "Public brands."}, "articles_count_approx": {"type": "integer", "description": "±5% approximate."}, "computed_at": {"type": "string", "format": "date-time"}}}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429WithQuota"}}}}, "/api/v1/themes/": {"get": {"operationId": "themes_list", "description": "Cursor-paginated directory of slot themes that have at least one public slot. Use ?theme=<slug> on GET /api/v1/slots/ to filter by theme. aliases are alternative spellings for matching a user's query to this slug (e.g. 'egypt' -> 'egyptian'). Filter by slug only — aliases are not a second filter key. If next is not null, the directory does not fit in one response — keep paging until next is null before treating the set as complete.", "summary": "List slot themes", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Opaque pagination cursor from a previous response's next/previous link."}, {"in": "query", "name": "page_size", "schema": {"type": "integer", "minimum": 1, "maximum": 500, "default": 200}, "description": "Number of results per page. Default 200, maximum 500. The default is large enough to return the whole directory in one response; if next is not null, keep paginating with cursor until it is."}], "tags": ["themes"], "security": [{"TokenAuth": []}], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TaxonomyDirectoryPage"}, "examples": {"EgyptianDirectoryEntry": {"value": {"count": 113, "next": null, "previous": null, "results": [{"slug": "egyptian", "name": "Egyptian", "slots_count": 41, "aliases": ["egypt", "ancient egypt", "pharaoh", "pyramids", "book of"]}]}, "summary": "Egyptian directory entry"}}}}, "description": "Success.", "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "401": {"$ref": "#/components/responses/Err401"}, "429": {"$ref": "#/components/responses/Err429"}, "402": {"$ref": "#/components/responses/Err402"}, "400": {"$ref": "#/components/responses/Err400"}}}}}, "components": {"schemas": {"BlankEnum": {"enum": [""]}, "BrandFull": {"type": "object", "description": "Full brand profile: everything from the listing entry plus the structured fields — hosts, country, jurisdiction, established, and the regulator-specific authority_for / statutory_authority.", "properties": {"slug": {"type": "string"}, "kind": {"type": "string", "nullable": true, "readOnly": true}, "name": {"type": "string"}, "aliases": {"type": "array", "items": {"type": "string"}}, "hosts_count": {"type": "integer", "default": 0}, "news_count": {"type": "integer", "description": "Attributed news article count.", "readOnly": true}, "hosts": {"type": "array", "items": {"type": "object", "properties": {"host": {"type": "string"}, "display_name": {"type": "string"}}}, "readOnly": true}, "created_at": {"type": "string", "format": "date-time"}, "country": {"type": "string", "default": ""}, "jurisdiction": {"type": "string", "default": ""}, "established": {"type": "integer", "nullable": true}, "authority_for": {"type": "string", "default": ""}, "statutory_authority": {"type": "string", "default": ""}}, "required": ["aliases", "created_at", "hosts", "kind", "name", "news_count", "slug"]}, "BrandShort": {"type": "object", "description": "Compact brand entry for the provider and regulator listings: slug, kind, name, aliases, hosts_count, news_count. Structured profile fields are in the detail endpoint, not here.", "properties": {"slug": {"type": "string"}, "kind": {"type": "string", "nullable": true, "readOnly": true}, "name": {"type": "string"}, "aliases": {"type": "array", "items": {"type": "string"}}, "hosts_count": {"type": "integer", "default": 0}, "news_count": {"type": "integer", "description": "Attributed news article count.", "readOnly": true}}, "required": ["aliases", "kind", "name", "news_count", "slug"]}, "ClosedReasonEnum": {"type": "string", "enum": ["filled", "closed", "expired"]}, "CountryCut": {"type": "object", "description": "Result item for ?by=country: one market, with its rolling 12-month volume, how many slots carry data there, and how complete the current collection is.", "properties": {"country": {"type": "object", "description": "The market: {iso, name}.", "properties": {"iso": {"type": "string"}, "name": {"type": "string"}}, "required": ["iso", "name"], "readOnly": true}, "volume_12m": {"type": "integer"}, "slots_with_data": {"type": "integer"}, "pairs_at_frontier_pct": {"type": "integer", "description": "Share of this country's pairs collected at the frontier, rounded to whole percent.", "readOnly": true}}, "required": ["country", "pairs_at_frontier_pct", "slots_with_data", "volume_12m"]}, "CountryCutEnvelope": {"type": "object", "description": "Response for ?by=country: each result is one market.", "properties": {"meta": {"$ref": "#/components/schemas/DemandMetaCountry"}, "next": {"type": "string", "nullable": true, "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/CountryCut"}}}, "required": ["meta", "next", "previous", "results"]}, "DemandCoverage": {"type": "object", "description": "`meta.coverage` — what the number is made of. `null` on pages after the\nfirst one and when `?coverage=false` is passed.", "properties": {"basis": {"type": "string"}, "markets_tracked": {"type": "integer"}, "slots_total": {"type": "integer"}, "providers": {"type": "array", "items": {"$ref": "#/components/schemas/DemandProviderCoverage"}}, "pairs_at_frontier_pct": {"type": "integer"}, "computed_at": {"type": "string", "nullable": true}}, "required": ["basis", "computed_at", "markets_tracked", "pairs_at_frontier_pct", "providers", "slots_total"]}, "DemandCutEnvelope": {"oneOf": [{"$ref": "#/components/schemas/SlotCutEnvelope"}, {"$ref": "#/components/schemas/SlotGlobalCutEnvelope"}, {"$ref": "#/components/schemas/CountryCutEnvelope"}, {"$ref": "#/components/schemas/KeyCutEnvelope"}]}, "DemandMetaCountry": {"type": "object", "description": "Response metadata for this envelope. by is pinned to \"country\"; every other field is identical across axes.", "properties": {"by": {"$ref": "#/components/schemas/DemandMetaCountryByEnum"}, "country": {"type": "string", "nullable": true}, "window": {"$ref": "#/components/schemas/DemandWindow"}, "window_mixed": {"type": "boolean"}, "metric": {"type": "string"}, "total_volume_12m": {"type": "integer"}, "overlapping": {"type": "boolean"}, "coverage": {"allOf": [{"$ref": "#/components/schemas/DemandCoverage"}], "nullable": true}}, "required": ["by", "country", "coverage", "metric", "overlapping", "total_volume_12m", "window", "window_mixed"]}, "DemandMetaCountryByEnum": {"enum": ["country"], "type": "string", "description": "* `country` - country"}, "DemandMetaProviderThemeFeatureMechanicGameCategory": {"type": "object", "description": "Response metadata for this envelope. by is one of \"provider\", \"theme\", \"feature\", \"mechanic\", \"game_category\"; every other field is identical across axes.", "properties": {"by": {"$ref": "#/components/schemas/DemandMetaProviderThemeFeatureMechanicGameCategoryByEnum"}, "country": {"type": "string", "nullable": true}, "window": {"$ref": "#/components/schemas/DemandWindow"}, "window_mixed": {"type": "boolean"}, "metric": {"type": "string"}, "total_volume_12m": {"type": "integer"}, "overlapping": {"type": "boolean"}, "coverage": {"allOf": [{"$ref": "#/components/schemas/DemandCoverage"}], "nullable": true}}, "required": ["by", "country", "coverage", "metric", "overlapping", "total_volume_12m", "window", "window_mixed"]}, "DemandMetaProviderThemeFeatureMechanicGameCategoryByEnum": {"enum": ["provider", "theme", "feature", "mechanic", "game_category"], "type": "string", "description": "* `provider` - provider\n* `theme` - theme\n* `feature` - feature\n* `mechanic` - mechanic\n* `game_category` - game_category"}, "DemandMetaSlot": {"type": "object", "description": "Response metadata for this envelope. by is pinned to \"slot\"; every other field is identical across axes.", "properties": {"by": {"$ref": "#/components/schemas/DemandMetaSlotByEnum"}, "country": {"type": "string", "nullable": true}, "window": {"$ref": "#/components/schemas/DemandWindow"}, "window_mixed": {"type": "boolean"}, "metric": {"type": "string"}, "total_volume_12m": {"type": "integer"}, "overlapping": {"type": "boolean"}, "coverage": {"allOf": [{"$ref": "#/components/schemas/DemandCoverage"}], "nullable": true}}, "required": ["by", "country", "coverage", "metric", "overlapping", "total_volume_12m", "window", "window_mixed"]}, "DemandMetaSlotByEnum": {"enum": ["slot"], "type": "string", "description": "* `slot` - slot"}, "DemandMetaSlotGlobal": {"type": "object", "description": "Response metadata for this envelope. by is pinned to \"slot_global\"; every other field is identical across axes.", "properties": {"by": {"$ref": "#/components/schemas/DemandMetaSlotGlobalByEnum"}, "country": {"type": "string", "nullable": true}, "window": {"$ref": "#/components/schemas/DemandWindow"}, "window_mixed": {"type": "boolean"}, "metric": {"type": "string"}, "total_volume_12m": {"type": "integer"}, "overlapping": {"type": "boolean"}, "coverage": {"allOf": [{"$ref": "#/components/schemas/DemandCoverage"}], "nullable": true}}, "required": ["by", "country", "coverage", "metric", "overlapping", "total_volume_12m", "window", "window_mixed"]}, "DemandMetaSlotGlobalByEnum": {"enum": ["slot_global"], "type": "string", "description": "* `slot_global` - slot_global"}, "DemandProviderCoverage": {"type": "object", "description": "Element of `meta.coverage.providers[]`.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "slots": {"type": "integer"}}, "required": ["name", "slots", "slug"]}, "DemandWindow": {"type": "object", "description": "`meta.window` — bounds of the shared rolling 12-month window. `null,null`\non an empty (not yet materialized) table.", "properties": {"start": {"type": "string", "nullable": true}, "end": {"type": "string", "nullable": true}}, "required": ["end", "start"]}, "EmploymentTypeEnum": {"enum": ["full_time", "part_time", "contract", "freelance", "internship"], "type": "string", "description": "* `full_time` - Full-time\n* `part_time` - Part-time\n* `contract` - Contract\n* `freelance` - Freelance\n* `internship` - Internship\n* `` - Unknown"}, "IgamingSegmentEnum": {"enum": ["sportsbook", "casino", "poker", "lottery", "esports", "affiliate", "platform_provider", "game_studio", "regulator", "payment_provider"], "type": "string", "description": "* `sportsbook` - Sportsbook\n* `casino` - Casino\n* `poker` - Poker\n* `lottery` - Lottery\n* `esports` - Esports\n* `affiliate` - Affiliate\n* `platform_provider` - Platform Provider\n* `game_studio` - Game Studio\n* `regulator` - Regulator\n* `payment_provider` - Payment Provider\n* `` - Unknown"}, "JobsDetail": {"type": "object", "description": "Full iGaming job vacancy detail. Extends list fields with description, application URLs, and company info. secondary_apply_url: LinkedIn mirror link when available.", "properties": {"id": {"type": "string", "format": "uuid"}, "source": {"$ref": "#/components/schemas/SourceEnum"}, "title": {"type": "string"}, "company_name": {"type": "string"}, "company": {"type": "object", "nullable": true, "properties": {"slug": {"type": "string"}, "canonical_name": {"type": "string"}, "country": {"type": "string"}}, "readOnly": true}, "location_country": {"type": "string"}, "location_city": {"type": "string"}, "city": {"type": "object", "nullable": true, "properties": {"slug": {"type": "string"}, "canonical_name": {"type": "string"}, "country": {"type": "string"}}, "readOnly": true}, "remote_mode": {"oneOf": [{"$ref": "#/components/schemas/RemoteModeEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "employment_type": {"oneOf": [{"$ref": "#/components/schemas/EmploymentTypeEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "seniority": {"oneOf": [{"$ref": "#/components/schemas/SeniorityEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "igaming_segment": {"oneOf": [{"$ref": "#/components/schemas/IgamingSegmentEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "salary_min": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "nullable": true}, "salary_max": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "nullable": true}, "salary_currency": {"type": "string"}, "salary_period": {"oneOf": [{"$ref": "#/components/schemas/SalaryPeriodEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "salary_is_disclosed": {"type": "boolean"}, "posted_at": {"type": "string", "format": "date-time", "nullable": true}, "last_seen_at": {"type": "string", "format": "date-time", "nullable": true}, "status_public": {"allOf": [{"$ref": "#/components/schemas/StatusPublicEnum"}], "readOnly": true}, "closed_at": {"type": "string", "format": "date-time", "nullable": true, "readOnly": true}, "closed_reason": {"allOf": [{"$ref": "#/components/schemas/ClosedReasonEnum"}], "nullable": true, "readOnly": true}, "apply_url": {"type": "string", "nullable": true, "format": "uri", "readOnly": true}, "source_url": {"type": "string", "nullable": true, "format": "uri", "readOnly": true}, "secondary_apply_url": {"type": "string", "nullable": true, "format": "uri", "readOnly": true}, "summary": {"type": "string"}, "responsibilities": {"type": "array", "items": {"type": "string"}}, "requirements_must": {"type": "array", "items": {"type": "string"}}, "requirements_nice": {"type": "array", "items": {"type": "string"}}, "benefits": {"type": "array", "items": {"type": "string"}}, "skills": {"type": "array", "items": {"type": "string"}}, "skills_resolved": {"type": "array", "items": {"type": "object", "properties": {"slug": {"type": "string"}, "canonical_name": {"type": "string"}}}, "readOnly": true}, "languages": {"type": "array", "items": {"$ref": "#/components/schemas/VacancyLanguage"}, "description": "Language requirements stated by the vacancy."}, "licenses_relevant": {"type": "array", "items": {"type": "string"}}, "product_areas": {"type": "array", "items": {"type": "string"}}, "regulatory_role": {"type": "boolean"}, "company_website": {"type": "string", "nullable": true, "format": "uri", "readOnly": true}, "company_logo_url": {"type": "string", "nullable": true, "format": "uri", "readOnly": true}, "company_size_bucket": {"type": "string"}, "timezone_constraints": {"type": "string"}, "salary_gross_or_net": {"type": "string"}, "visa_sponsorship": {"$ref": "#/components/schemas/TriStateEnum"}, "relocation_offered": {"$ref": "#/components/schemas/TriStateEnum"}, "application_deadline": {"type": "string", "format": "date", "nullable": true}, "apply_email": {"type": "string", "nullable": true, "format": "email", "readOnly": true}, "discovered_at": {"type": "string", "format": "date-time"}}, "required": ["application_deadline", "apply_email", "apply_url", "benefits", "city", "closed_at", "closed_reason", "company", "company_logo_url", "company_name", "company_size_bucket", "company_website", "discovered_at", "employment_type", "id", "igaming_segment", "languages", "last_seen_at", "licenses_relevant", "location_city", "location_country", "posted_at", "product_areas", "regulatory_role", "relocation_offered", "remote_mode", "requirements_must", "requirements_nice", "responsibilities", "salary_currency", "salary_gross_or_net", "salary_is_disclosed", "salary_max", "salary_min", "salary_period", "secondary_apply_url", "seniority", "skills", "skills_resolved", "source", "source_url", "status_public", "summary", "timezone_constraints", "title", "visa_sponsorship"]}, "JobsList": {"type": "object", "description": "iGaming job vacancy card (list view). JSON key \"url\" is absent: use source_url instead.", "properties": {"id": {"type": "string", "format": "uuid"}, "source": {"$ref": "#/components/schemas/SourceEnum"}, "title": {"type": "string"}, "company_name": {"type": "string"}, "company": {"type": "object", "nullable": true, "properties": {"slug": {"type": "string"}, "canonical_name": {"type": "string"}, "country": {"type": "string"}}, "readOnly": true}, "location_country": {"type": "string"}, "location_city": {"type": "string"}, "city": {"type": "object", "nullable": true, "properties": {"slug": {"type": "string"}, "canonical_name": {"type": "string"}, "country": {"type": "string"}}, "readOnly": true}, "remote_mode": {"oneOf": [{"$ref": "#/components/schemas/RemoteModeEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "employment_type": {"oneOf": [{"$ref": "#/components/schemas/EmploymentTypeEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "seniority": {"oneOf": [{"$ref": "#/components/schemas/SeniorityEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "igaming_segment": {"oneOf": [{"$ref": "#/components/schemas/IgamingSegmentEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "salary_min": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "nullable": true}, "salary_max": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "nullable": true}, "salary_currency": {"type": "string"}, "salary_period": {"oneOf": [{"$ref": "#/components/schemas/SalaryPeriodEnum"}, {"$ref": "#/components/schemas/BlankEnum"}]}, "salary_is_disclosed": {"type": "boolean"}, "posted_at": {"type": "string", "format": "date-time", "nullable": true}, "last_seen_at": {"type": "string", "format": "date-time", "nullable": true}, "status_public": {"allOf": [{"$ref": "#/components/schemas/StatusPublicEnum"}], "readOnly": true}, "closed_at": {"type": "string", "format": "date-time", "nullable": true, "readOnly": true}, "closed_reason": {"allOf": [{"$ref": "#/components/schemas/ClosedReasonEnum"}], "nullable": true, "readOnly": true}, "apply_url": {"type": "string", "nullable": true, "format": "uri", "readOnly": true}, "source_url": {"type": "string", "nullable": true, "format": "uri", "readOnly": true}}, "required": ["apply_url", "city", "closed_at", "closed_reason", "company", "company_name", "employment_type", "id", "igaming_segment", "last_seen_at", "location_city", "location_country", "posted_at", "remote_mode", "salary_currency", "salary_is_disclosed", "salary_max", "salary_min", "salary_period", "seniority", "source", "source_url", "status_public", "title"]}, "KeyCut": {"type": "object", "description": "Result item for ?by=provider, theme, feature, mechanic or game_category — one shape shared by all five axes: the axis value, its rolling 12-month volume, and how many slots carry data for it.", "properties": {"key": {"type": "object", "description": "The value of the chosen axis: {slug, name}.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["slug", "name"], "readOnly": true}, "volume_12m": {"type": "integer"}, "slots_with_data": {"type": "integer"}}, "required": ["key", "slots_with_data", "volume_12m"]}, "KeyCutEnvelope": {"type": "object", "description": "Response for ?by=provider, theme, feature, mechanic or game_category: each result is one value of the chosen axis.", "properties": {"meta": {"$ref": "#/components/schemas/DemandMetaProviderThemeFeatureMechanicGameCategory"}, "next": {"type": "string", "nullable": true, "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/KeyCut"}}}, "required": ["meta", "next", "previous", "results"]}, "NewsArticleFull": {"type": "object", "description": "A news article with its full text. body_en is empty when the text could not be extracted or the content store is temporarily unavailable; body_unavailable_reason says which.", "properties": {"id": {"type": "string", "format": "uuid"}, "title_en": {"type": "string"}, "published_at": {"type": "string", "format": "date-time"}, "discovered_at": {"type": "string", "format": "date-time"}, "extraction_completed_at": {"type": "string", "format": "date-time", "nullable": true}, "language": {"type": "string"}, "brand": {"type": "object", "nullable": true, "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "kind": {"type": "string", "nullable": true}}, "readOnly": true}, "host": {"type": "string", "nullable": true, "readOnly": true}, "url": {"type": "string"}, "body_en": {"type": "string", "default": ""}, "body_unavailable_reason": {"type": "string", "default": ""}}, "required": ["brand", "discovered_at", "extraction_completed_at", "host", "id", "language", "published_at", "title_en", "url"]}, "NewsArticleShort": {"type": "object", "description": "A news article as it appears in listings. The body is not included here — fetch the detail endpoint for it.", "properties": {"id": {"type": "string", "format": "uuid"}, "title_en": {"type": "string"}, "published_at": {"type": "string", "format": "date-time"}, "discovered_at": {"type": "string", "format": "date-time"}, "extraction_completed_at": {"type": "string", "format": "date-time", "nullable": true}, "language": {"type": "string"}, "brand": {"type": "object", "nullable": true, "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "kind": {"type": "string", "nullable": true}}, "readOnly": true}, "host": {"type": "string", "nullable": true, "readOnly": true}, "url": {"type": "string"}}, "required": ["brand", "discovered_at", "extraction_completed_at", "host", "id", "language", "published_at", "title_en", "url"]}, "PaginatedBrandShortList": {"type": "object", "required": ["results"], "properties": {"next": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/BrandShort"}}}}, "PaginatedJobsListList": {"type": "object", "required": ["results"], "properties": {"next": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/JobsList"}}}}, "PaginatedNewsArticleShortList": {"type": "object", "required": ["results"], "properties": {"next": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/NewsArticleShort"}}}}, "PaginatedSlotListItemList": {"type": "object", "required": ["results"], "properties": {"next": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/SlotListItem"}}}}, "PaginatedSourceList": {"type": "object", "required": ["results"], "properties": {"next": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "format": "uri", "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/Source"}}}}, "ProviderDemand": {"type": "object", "description": "Provider search demand snapshot. computed_at: when the snapshot was computed. provider: volume/trend metrics. top_slots: demand-ranked public slots. Empty snapshot → 200 with nulls/empty (not 404).", "properties": {"computed_at": {"type": "string", "format": "date-time", "nullable": true}, "provider": {"$ref": "#/components/schemas/ProviderDemandMetrics"}, "top_slots": {"type": "array", "items": {"$ref": "#/components/schemas/ProviderDemandTopSlot"}}}, "required": ["computed_at", "provider", "top_slots"]}, "ProviderDemandMarket": {"type": "object", "description": "One market inside the provider demand block.", "properties": {"country": {"type": "string"}, "country_iso": {"type": "string"}, "flag": {"type": "string"}, "volume": {"type": "integer"}, "series": {"type": "array", "items": {"type": "integer"}}}, "required": ["country", "country_iso", "flag", "series", "volume"]}, "ProviderDemandMetrics": {"type": "object", "description": "Provider-level demand metrics. Empty object when no snapshot exists yet.", "properties": {"total_volume": {"type": "integer"}, "peak_volume": {"type": "integer"}, "yoy_pct": {"type": "number", "format": "double", "nullable": true}, "top_query": {"type": "string", "nullable": true}, "series_24m": {"type": "array", "items": {"type": "integer"}, "description": "24 monthly volumes, oldest first."}, "markets": {"type": "array", "items": {"$ref": "#/components/schemas/ProviderDemandMarket"}}}}, "ProviderDemandTopSlot": {"type": "object", "description": "One slot in the provider's demand-ranked top list.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "latest_volume": {"type": "integer"}, "sparkline": {"type": "array", "items": {"type": "integer"}}, "demo_url": {"type": "string", "format": "uri", "nullable": true}}, "required": ["demo_url", "latest_volume", "name", "slug", "sparkline"]}, "ProviderDetail": {"type": "object", "description": "Full slot provider profile. Includes firmographics, assets (logo), company block (hq/offices/licenses/contacts), counts (slots/jobs/news/team/has_demand), and HATEOAS sub-resource links. about is sanitized HTML, never raw text.", "properties": {"slug": {"type": "string"}, "kind": {"type": "string", "nullable": true, "description": "Brand kind code string.", "readOnly": true}, "name": {"type": "string"}, "aliases": {"type": "array", "items": {"type": "string"}}, "tagline": {"type": "string", "default": ""}, "about_html": {"type": "string", "description": "Sanitized provider bio (HTML). Never raw text.", "readOnly": true}, "firmographics": {"type": "object", "description": "Identity attributes of the provider. Empty strings when unknown.", "properties": {"country": {"type": "string"}, "jurisdiction": {"type": "string"}, "established": {"type": "integer", "nullable": true}, "authority_for": {"type": "string"}, "statutory_authority": {"type": "string"}}, "required": ["authority_for", "country", "established", "jurisdiction", "statutory_authority"], "readOnly": true}, "verified_at": {"type": "string", "format": "date-time", "nullable": true}, "updated_at": {"type": "string", "format": "date-time"}, "assets": {"type": "object", "description": "Provider assets. logo is null when none is on file or storage is unavailable.", "properties": {"logo": {"type": "object", "nullable": true, "properties": {"url": {"type": "string", "format": "uri"}, "key": {"type": "string"}, "version": {"type": "string"}}, "required": ["key", "url", "version"]}}, "required": ["logo"], "readOnly": true}, "company": {"type": "object", "description": "Company block: headquarters, offices, licences and public contacts.", "properties": {"hq": {"type": "object", "nullable": true, "properties": {"label": {"type": "string"}, "city": {"type": "string"}, "country": {"type": "string"}}}, "offices": {"type": "array", "items": {"type": "object", "properties": {"label": {"type": "string"}, "city": {"type": "string"}, "country": {"type": "string"}, "sort_order": {"type": "integer"}}}}, "licenses": {"type": "array", "items": {"type": "object", "properties": {"authority": {"type": "string"}, "license_ref": {"type": "string"}, "jurisdiction": {"type": "string"}, "status": {"type": "string"}, "sort_order": {"type": "integer"}}}}, "contacts": {"type": "array", "items": {"type": "object", "properties": {"kind": {"type": "string"}, "label": {"type": "string"}, "value": {"type": "string"}}, "required": ["kind", "label", "value"]}}}, "required": ["contacts", "hq", "licenses", "offices"], "readOnly": true}, "counts": {"type": "object", "description": "How much of each sub-resource this provider has.", "properties": {"slots": {"type": "integer"}, "jobs": {"type": "integer"}, "news": {"type": "integer"}, "team": {"type": "integer"}, "has_demand": {"type": "boolean", "description": "Whether a demand snapshot exists — if false, the demand link returns empty values."}}, "required": ["slots", "jobs", "news", "team", "has_demand"], "readOnly": true}, "hosts": {"type": "array", "description": "Public web hosts operated by this provider.", "items": {"type": "object", "properties": {"host": {"type": "string"}, "display_name": {"type": "string"}}, "required": ["host", "display_name"]}, "readOnly": true}, "links": {"type": "object", "description": "Relative URLs of this provider's sub-resources. Follow these rather than building paths by hand.", "properties": {"slots": {"type": "string", "example": "/api/v1/providers/example-provider/slots/"}, "demand": {"type": "string", "example": "/api/v1/providers/example-provider/demand/"}, "jobs": {"type": "string", "example": "/api/v1/providers/example-provider/jobs/"}, "news": {"type": "string", "example": "/api/v1/providers/example-provider/news/"}, "team": {"type": "string", "example": "/api/v1/providers/example-provider/team/"}}, "required": ["slots", "demand", "jobs", "news", "team"], "readOnly": true}}, "required": ["about_html", "aliases", "assets", "company", "counts", "firmographics", "hosts", "kind", "links", "name", "slug", "updated_at", "verified_at"]}, "RemoteModeEnum": {"enum": ["on_site", "hybrid", "remote"], "type": "string", "description": "* `on_site` - On-site\n* `hybrid` - Hybrid\n* `remote` - Remote\n* `` - Unknown"}, "SalaryPeriodEnum": {"enum": ["year", "month", "day", "hour"], "type": "string", "description": "* `year` - Per year\n* `month` - Per month\n* `day` - Per day\n* `hour` - Per hour\n* `` - Unknown"}, "SeniorityEnum": {"enum": ["junior", "mid", "senior", "lead", "head", "c_level"], "type": "string", "description": "* `junior` - Junior\n* `mid` - Mid\n* `senior` - Senior\n* `lead` - Lead\n* `head` - Head\n* `c_level` - C-level\n* `` - Unknown"}, "SeriesDetail": {"type": "object", "description": "Family-wide summary for one series: catalogue aggregates, distributions and a short roster.", "properties": {"slug": {"type": "string", "readOnly": true}, "name": {"type": "string", "readOnly": true}, "provider": {"type": "object", "description": "The provider that owns this series.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["slug", "name"], "readOnly": true}, "slots_count": {"type": "integer", "readOnly": true}, "years": {"type": "object", "description": "Release-year span of the public slots in this series.", "properties": {"from": {"type": "integer", "nullable": true}, "to": {"type": "integer", "nullable": true}}, "required": ["from", "to"], "readOnly": true}, "rtp": {"type": "object", "description": "RTP range across every public game in this series, plus spread (max - min, as decimal string; null when either bound is null). The range covers games of every category, not only video slots — a few series mix a scratch card in, and one such game can widen the range by tens of points. Read game_category alongside this field before treating the range as a slot figure.", "properties": {"min": {"type": "string", "nullable": true}, "max": {"type": "string", "nullable": true}, "spread": {"type": "string", "nullable": true}}, "required": ["min", "max", "spread"], "readOnly": true}, "max_win": {"type": "object", "description": "Max-win range (multiplier of stake) across the public slots in this series. null when no slot in the series has a value.", "properties": {"min": {"type": "integer", "nullable": true}, "max": {"type": "integer", "nullable": true}}, "required": ["min", "max"], "readOnly": true}, "volatility": {"type": "object", "additionalProperties": {"type": "integer"}, "description": "Count of public slots in this series by volatility. Keys are the volatility enum values, plus 'null' for slots where volatility is not set. Values always sum to slots_count.", "readOnly": true}, "has_bonus_buy": {"type": "object", "additionalProperties": {"type": "integer"}, "description": "Count of public slots in this series by has_bonus_buy. Keys are 'yes', 'no' and 'unknown' (not evaluated). Values always sum to slots_count.", "readOnly": true}, "game_category": {"type": "object", "additionalProperties": {"type": "integer"}, "description": "Count of public slots in this series by game_category. 'unknown' means the category was not classified yet. A series mixing categories (e.g. a video slot spin-off with a scratch-card entry) shows more than one key here. Values always sum to slots_count.", "readOnly": true}, "slots_url": {"type": "string", "format": "uri", "description": "GET /api/v1/slots/?series=<slug> — the full roster with all slot fields and every catalogue filter. slots[] below is a short preview, not the full roster.", "readOnly": true}, "slots_truncated": {"type": "boolean", "readOnly": true}, "slots": {"type": "array", "items": {"$ref": "#/components/schemas/SeriesSlotItem"}, "readOnly": true, "description": "Short roster, ordered newest release first (slots with no release date last), then by name. Truncated at 100 entries — see slots_truncated and slots_url for the full set."}}, "required": ["game_category", "has_bonus_buy", "max_win", "name", "provider", "rtp", "slots", "slots_count", "slots_truncated", "slots_url", "slug", "volatility", "years"]}, "SeriesDirectoryPage": {"type": "object", "description": "Cursor-paginated page of the series directory. count is the total number of series across all pages.", "properties": {"count": {"type": "integer"}, "next": {"type": "string", "nullable": true, "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/SeriesListItem"}}}, "required": ["count", "next", "previous", "results"]}, "SeriesListItem": {"type": "object", "description": "Slot series (franchise) directory entry with public-catalogue aggregates.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "aliases": {"type": "array", "items": {"type": "string"}, "description": "Alternative spellings for matching a user's query to this series' slug. Filter by slug: an alias is not accepted as a filter value.", "readOnly": true}, "provider": {"type": "object", "description": "The provider that owns this series.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["slug", "name"], "readOnly": true}, "slots_count": {"type": "integer"}, "years": {"type": "object", "description": "Release-year span of the public slots in this series.", "properties": {"from": {"type": "integer", "nullable": true}, "to": {"type": "integer", "nullable": true}}, "required": ["from", "to"], "readOnly": true}, "rtp": {"type": "object", "description": "RTP range (percent, as decimal string) across every public game in this series; null when no game in the series has a value. The range covers games of every category, not only video slots — a few series mix a scratch card in, and one such game can widen it a lot. Read game_category alongside this field before treating the range as a slot figure.", "properties": {"min": {"type": "string", "nullable": true}, "max": {"type": "string", "nullable": true}}, "required": ["min", "max"], "readOnly": true}, "max_win": {"type": "object", "description": "Max-win range (multiplier of stake) across the public slots in this series. null when no slot in the series has a value.", "properties": {"min": {"type": "integer", "nullable": true}, "max": {"type": "integer", "nullable": true}}, "required": ["min", "max"], "readOnly": true}}, "required": ["aliases", "max_win", "name", "provider", "rtp", "slots_count", "slug", "years"]}, "SeriesSlotItem": {"type": "object", "description": "Short roster entry inside a series family summary.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "released": {"type": "string", "nullable": true, "readOnly": true}, "rtp": {"type": "string", "nullable": true, "readOnly": true}, "max_win": {"type": "integer", "nullable": true}, "demo_url": {"type": "string", "format": "uri", "nullable": true, "description": "Demo-embed URL (same format as detail assets.demo.embed_url) or null.", "readOnly": true}}, "required": ["demo_url", "max_win", "name", "released", "rtp", "slug"]}, "SlotCut": {"type": "object", "description": "Result item for `by=slot`: `{slot: {slug, name, provider: {slug, name}},\nvolume_12m, months_covered, last_seen: {month, volume}, prev_volume}`.", "properties": {"slot": {"type": "object", "description": "The slot this row aggregates.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "provider": {"type": "object", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["slug", "name"]}, "demo_url": {"type": "string", "format": "uri", "nullable": true, "description": "Demo-embed URL (same format as detail assets.demo.embed_url) or null."}}, "required": ["slug", "name", "provider", "demo_url"], "readOnly": true}, "volume_12m": {"type": "integer"}, "months_covered": {"type": "integer"}, "last_seen": {"type": "object", "description": "The most recent month with data for this pair. volume may legitimately be 0 — that is a measurement, not a missing value.", "properties": {"month": {"type": "string", "example": "2026-06"}, "volume": {"type": "integer"}}, "required": ["month", "volume"], "readOnly": true}, "prev_volume": {"type": "integer", "nullable": true}}, "required": ["last_seen", "months_covered", "prev_volume", "slot", "volume_12m"]}, "SlotCutEnvelope": {"type": "object", "description": "Response for ?by=slot (the default axis): each result is one slot in one market.", "properties": {"meta": {"$ref": "#/components/schemas/DemandMetaSlot"}, "next": {"type": "string", "nullable": true, "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/SlotCut"}}}, "required": ["meta", "next", "previous", "results"]}, "SlotDemand": {"type": "object", "description": "Per-slot search demand snapshot. computed_at: when the underlying provider snapshot was computed. volume_12m/prev_12m/yoy_pct: trailing-12-month aggregate and YoY change; these are null when there is no snapshot yet. trend: always one of growing/declining/flat — it falls back to 'flat' rather than null when there is nothing to compare. sparkline: a fixed 24-point monthly series, OLDEST first, not affected by ?months=. months: \"YYYY-MM\" labels, NEWEST first — note the opposite direction to sparkline. Their count follows ?months= (default 12). totals: total demand per month, aligned to months (chart line). markets: per-country {country, country_iso, flag, values[] (aligned to months), mom_delta, volume_12m, trend}, sorted by month-over-month growth desc. No snapshot yet → 200 with nulls/empty (not 404).", "properties": {"computed_at": {"type": "string", "format": "date-time", "nullable": true}, "volume_12m": {"type": "integer", "nullable": true}, "prev_12m": {"type": "integer", "nullable": true}, "yoy_pct": {"type": "number", "format": "double", "nullable": true}, "trend": {"$ref": "#/components/schemas/TrendEnum"}, "sparkline": {"type": "array", "items": {"type": "integer"}}, "months": {"type": "array", "items": {"type": "string"}, "readOnly": true}, "totals": {"type": "array", "items": {"type": "integer"}, "readOnly": true}, "markets": {"type": "array", "items": {"$ref": "#/components/schemas/SlotDemandMarket"}, "readOnly": true}}, "required": ["computed_at", "markets", "months", "prev_12m", "sparkline", "totals", "trend", "volume_12m", "yoy_pct"]}, "SlotDemandMarket": {"type": "object", "description": "One country row of the slot demand matrix.", "properties": {"country": {"type": "string"}, "country_iso": {"type": "string"}, "flag": {"type": "string"}, "values": {"type": "array", "items": {"type": "integer"}}, "mom_delta": {"type": "integer"}, "volume_12m": {"type": "integer"}, "trend": {"type": "string"}}, "required": ["country", "country_iso", "flag", "mom_delta", "trend", "values", "volume_12m"]}, "SlotDetail": {"type": "object", "description": "Full slot profile in exactly three top-level blocks: data (verified, editorially curated), spec_sheet (unverified, as extracted), assets (screenshots, logo, palette, demo). Identity fields (slug, name, provider) live inside data, not at the top level.", "properties": {"data": {"type": "object", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "status": {"type": "string"}, "provider": {"type": "object", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["name", "slug"]}, "studio": {"type": "object", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["name", "slug"], "nullable": true}, "series": {"type": "object", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["name", "slug"], "nullable": true}, "order_in_series": {"type": "integer", "nullable": true}, "licensed_brand": {"type": "object", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "license_type": {"type": "string"}}, "required": ["license_type", "name", "slug"], "nullable": true}, "game_category": {"type": "string"}, "rtp_default": {"type": "string", "nullable": true}, "rtp_variants": {"type": "array", "items": {"type": "object", "properties": {"rtp": {"type": "string"}, "variant": {"type": "string"}, "condition_note": {"type": "string"}, "is_default": {"type": "boolean"}}, "required": ["condition_note", "is_default", "rtp", "variant"]}}, "volatility": {"type": "string", "nullable": true}, "max_win": {"type": "integer", "nullable": true}, "min_bet": {"type": "string", "nullable": true}, "max_bet": {"type": "string", "nullable": true}, "currency": {"type": "string"}, "release_date": {"type": "string", "format": "date", "nullable": true}, "release_date_precision": {"type": "string"}, "mechanic": {"type": "string", "nullable": true}, "mechanic_value": {"type": "integer", "nullable": true}, "reels": {"type": "integer", "nullable": true}, "rows": {"type": "integer", "nullable": true}, "payline_direction": {"type": "string"}, "jackpot_type": {"type": "string"}, "jackpot_tiers": {"type": "array", "items": {"type": "object", "properties": {"label": {"type": "string"}, "value": {"type": "string", "nullable": true}, "value_kind": {"type": "string"}, "order": {"type": "integer"}}, "required": ["label", "order", "value", "value_kind"]}}, "has_bonus_buy": {"type": "string"}, "bonus_buy_cost": {"type": "string", "nullable": true}, "bonus_buys": {"type": "array", "items": {"type": "object", "properties": {"label": {"type": "string"}, "cost": {"type": "string"}, "effect": {"type": "string"}, "is_default": {"type": "boolean"}}, "required": ["cost", "effect", "is_default", "label"]}}, "has_autoplay": {"type": "string"}, "mobile_adapted": {"type": "string"}, "ante_bet_cost": {"type": "string", "nullable": true}, "themes": {"type": "array", "items": {"type": "object", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["name", "slug"]}}, "features": {"type": "array", "items": {"type": "object", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["name", "slug"]}}, "aliases": {"type": "array", "items": {"type": "string"}}, "provider_ref": {"type": "string"}, "symbols": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string"}, "role": {"type": "string"}, "role_label": {"type": "string"}, "color_hex": {"type": "string"}, "note": {"type": "string"}, "order": {"type": "integer"}, "icon_url": {"type": "string", "nullable": true}, "key": {"type": "string", "nullable": true}, "version": {"type": "string", "nullable": true}, "payouts": {"type": "array", "items": {"type": "object", "properties": {"match_count": {"type": "integer"}, "value": {"type": "string"}, "unit": {"type": "string"}}, "required": ["match_count", "unit", "value"]}}}, "required": ["color_hex", "icon_url", "key", "name", "note", "order", "payouts", "role", "role_label", "version"]}}, "modes": {"type": "array", "items": {"type": "object", "properties": {"code": {"type": "string"}, "name": {"type": "string"}, "kind": {"type": "string"}, "description": {"type": "string"}, "order": {"type": "integer"}}, "required": ["code", "description", "kind", "name", "order"]}}, "accessibility_notes": {"type": "array", "items": {"type": "object", "properties": {"group": {"type": "string"}, "text": {"type": "string"}, "order": {"type": "integer"}}, "required": ["group", "order", "text"]}}, "coverage_notes": {"type": "array", "items": {"type": "object", "properties": {"status": {"type": "string"}, "text": {"type": "string"}, "order": {"type": "integer"}}, "required": ["order", "status", "text"]}}, "coverage_gap_count": {"type": "integer"}, "captured_at": {"type": "string", "format": "date-time", "nullable": true}, "capture_method": {"type": "string", "description": "How the data was captured, as a short normalised phrase. Not a free-form field: the value comes from a fixed set."}, "counts": {"type": "object", "properties": {"symbols": {"type": "integer"}, "screenshots": {"type": "integer"}, "modes": {"type": "integer"}, "palette": {"type": "integer"}}, "required": ["modes", "palette", "screenshots", "symbols"]}, "provably_fair": {"type": "string"}, "bets_per_round": {"type": "integer", "nullable": true}, "has_auto_cashout": {"type": "string"}, "game_subtype": {"type": "string", "nullable": true}, "blackjack_payout": {"type": "string", "nullable": true}, "side_bets": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string"}, "payout_note": {"type": "string"}, "order": {"type": "integer"}}, "required": ["name", "order", "payout_note"]}}}, "required": ["accessibility_notes", "aliases", "ante_bet_cost", "bonus_buy_cost", "bonus_buys", "capture_method", "captured_at", "counts", "coverage_gap_count", "coverage_notes", "currency", "features", "game_category", "has_autoplay", "has_bonus_buy", "jackpot_tiers", "jackpot_type", "licensed_brand", "max_bet", "max_win", "mechanic", "mechanic_value", "min_bet", "mobile_adapted", "modes", "name", "order_in_series", "payline_direction", "provider", "provider_ref", "reels", "release_date", "release_date_precision", "rows", "rtp_default", "rtp_variants", "series", "slug", "status", "studio", "symbols", "themes", "volatility"], "description": "Verified, editorially curated structured data for this slot. Decimal values (rtp, bets, payouts) are strings. provably_fair, bets_per_round and has_auto_cashout appear only when game_category is \"crash\"; game_subtype, blackjack_payout and side_bets only when it is \"table\".", "readOnly": true}, "spec_sheet": {"type": "object", "properties": {"source": {"type": "string"}, "raw": {"type": "string", "nullable": true}, "updated_at": {"type": "string", "format": "date-time", "nullable": true}}, "required": ["raw", "source", "updated_at"], "description": "Unverified specification block, as extracted from the source. source is the constant \"extracted, unverified\"; raw is the extracted text or null; updated_at is when the extraction was last written.", "readOnly": true}, "assets": {"type": "object", "properties": {"screenshots": {"type": "array", "items": {"type": "object", "properties": {"type": {"type": "string"}, "device": {"type": "string"}, "page": {"type": "string"}, "order": {"type": "integer"}, "width": {"type": "integer", "nullable": true}, "height": {"type": "integer", "nullable": true}, "note": {"type": "string"}, "url": {"type": "string", "nullable": true}, "srcset": {"type": "string", "nullable": true}, "key": {"type": "string", "nullable": true}, "version": {"type": "string", "nullable": true}}, "required": ["device", "height", "key", "note", "order", "page", "srcset", "type", "url", "version", "width"]}}, "logo_url": {"type": "string", "nullable": true}, "logo": {"type": "object", "properties": {"url": {"type": "string", "nullable": true}, "key": {"type": "string", "nullable": true}, "version": {"type": "string", "nullable": true}}, "required": ["key", "url", "version"], "nullable": true}, "palette": {"type": "array", "items": {"type": "object", "properties": {"hex": {"type": "string"}, "label": {"type": "string"}, "context": {"type": "string"}, "order": {"type": "integer"}}, "required": ["context", "hex", "label", "order"]}}, "demo": {"type": "object", "properties": {"available": {"type": "boolean"}, "page_url": {"type": "string", "nullable": true}, "embed_url": {"type": "string", "nullable": true}}, "required": ["available", "embed_url", "page_url"]}}, "required": ["demo", "logo", "logo_url", "palette", "screenshots"], "description": "Media assets: screenshots (with key/version), logo, palette, demo embed info.", "readOnly": true}}, "required": ["assets", "data", "spec_sheet"]}, "SlotGlobalCut": {"type": "object", "description": "Result item for ?by=slot_global: one slot, aggregated across every market we track. Use this for a worldwide ranking; use ?by=slot with ?country= when you need per-market detail.", "properties": {"slot": {"type": "object", "description": "The slot this row aggregates.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "provider": {"type": "object", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["slug", "name"]}, "demo_url": {"type": "string", "format": "uri", "nullable": true, "description": "Demo-embed URL (same format as detail assets.demo.embed_url) or null."}}, "required": ["slug", "name", "provider", "demo_url"], "readOnly": true}, "volume_12m": {"type": "integer"}, "markets_with_data": {"type": "integer"}}, "required": ["markets_with_data", "slot", "volume_12m"]}, "SlotGlobalCutEnvelope": {"type": "object", "description": "Response for ?by=slot_global: each result is one slot, aggregated across every tracked market.", "properties": {"meta": {"$ref": "#/components/schemas/DemandMetaSlotGlobal"}, "next": {"type": "string", "nullable": true, "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/SlotGlobalCut"}}}, "required": ["meta", "next", "previous", "results"]}, "SlotListItem": {"type": "object", "description": "Compact slot listing item. thumbnail/thumbnail_url: CDN URL of first logo/preview screenshot. rtp_default: Decimal as string or null. mechanic: bet mechanic type.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "provider": {"type": "object", "description": "The provider that released this slot.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}}, "required": ["slug", "name"], "readOnly": true}, "game_category": {"type": "string"}, "game_subtype": {"type": "string", "nullable": true}, "rtp_default": {"type": "string", "nullable": true, "description": "Decimal → str|null.", "readOnly": true}, "volatility": {"type": "string", "nullable": true}, "max_win": {"type": "integer", "nullable": true}, "mechanic": {"type": "string", "nullable": true}, "mechanic_value": {"type": "integer", "nullable": true}, "release_date": {"type": "string", "format": "date", "nullable": true}, "release_date_precision": {"type": "string"}, "thumbnail_url": {"type": "string", "nullable": true, "description": "CDN-URL of the first screenshot of type logo or preview.\n\nLogo wins when both are present.", "readOnly": true}, "thumbnail": {"type": "object", "nullable": true, "description": "Asset-versioned thumbnail object {url, key, version} or null.", "properties": {"url": {"type": "string", "format": "uri"}, "key": {"type": "string", "description": "Storage key — the public path component of the URL."}, "version": {"type": "string", "nullable": true, "description": "SHA-256 checksum of the image."}}, "readOnly": true}, "volume_12m": {"type": "integer", "nullable": true, "readOnly": true}, "trend": {"allOf": [{"$ref": "#/components/schemas/TrendEnum"}], "readOnly": true}, "demo_url": {"type": "string", "format": "uri", "nullable": true, "description": "Demo-embed URL (same format as detail assets.demo.embed_url) or null.", "readOnly": true}, "updated_at": {"type": "string", "format": "date-time", "description": "When this record last changed on our side. This is the field ?updated_since= filters and orders by."}}, "required": ["demo_url", "game_category", "game_subtype", "max_win", "mechanic", "mechanic_value", "name", "provider", "release_date", "release_date_precision", "rtp_default", "slug", "thumbnail", "thumbnail_url", "trend", "updated_at", "volatility", "volume_12m"]}, "Source": {"type": "object", "description": "A news source: one host, with brand attribution and freshness. host is the full domain; brand is {slug, kind, name} or null; articles_count_30d counts articles published in the last 30 days; last_extracted_at is the most recent successful extraction, or null.", "properties": {"host": {"type": "string"}, "brand": {"type": "object", "nullable": true, "properties": {"slug": {"type": "string"}, "kind": {"type": "string", "nullable": true}, "name": {"type": "string"}}, "readOnly": true}, "articles_count_30d": {"type": "integer", "default": 0}, "last_extracted_at": {"type": "string", "format": "date-time", "nullable": true}}, "required": ["brand", "host", "last_extracted_at"]}, "SourceEnum": {"enum": ["careers", "linkedin"], "type": "string", "description": "* `careers` - Careers\n* `linkedin` - LinkedIn"}, "StatusPublicEnum": {"type": "string", "enum": ["live", "closed"]}, "TaxonomyDirectoryItem": {"type": "object", "description": "Taxonomy directory entry (theme or feature) with alternative spellings.", "properties": {"slug": {"type": "string"}, "name": {"type": "string"}, "slots_count": {"type": "integer"}, "aliases": {"type": "array", "items": {"type": "string"}, "description": "aliases are alternative spellings for matching a user's query to this slug (e.g. 'egypt' -> 'egyptian'). Filter by slug only — aliases are not a second filter key.", "readOnly": true}}, "required": ["aliases", "name", "slots_count", "slug"]}, "TaxonomyDirectoryPage": {"type": "object", "description": "Cursor-paginated page of a taxonomy directory (themes or features). count is the total number of entries across all pages.", "properties": {"count": {"type": "integer"}, "next": {"type": "string", "nullable": true, "description": "Absolute URL of the next page, or null on the last page."}, "previous": {"type": "string", "nullable": true, "description": "Absolute URL of the previous page, or null on the first page."}, "results": {"type": "array", "items": {"$ref": "#/components/schemas/TaxonomyDirectoryItem"}}}, "required": ["count", "next", "previous", "results"]}, "TrendEnum": {"enum": ["growing", "declining", "flat"], "type": "string", "description": "* `growing` - growing\n* `declining` - declining\n* `flat` - flat"}, "TriStateEnum": {"enum": ["yes", "no", "unknown"], "type": "string", "description": "* `yes` - Yes\n* `no` - No\n* `unknown` - Unknown"}, "VacancyLanguage": {"type": "object", "description": "One language requirement of a vacancy.", "properties": {"code": {"type": "string", "description": "ISO 639-1 lower-case language code."}, "level": {"type": "string", "description": "One of basic, intermediate, fluent, native — or empty when the vacancy does not state a level."}}, "required": ["code", "level"]}}, "securitySchemes": {"TokenAuth": {"type": "apiKey", "in": "header", "name": "Authorization", "description": "Token <hex>. Create a free account and issue your own key at /account/; the key is shown once, at creation. Free tier included, no card required."}}, "responses": {"Err400": {"description": "Bad Request — invalid filter or parameter value.", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}, "code": {"type": "string"}}}, "examples": {"default": {"value": {"detail": "Invalid value for parameter \"ordering\".", "code": "invalid_parameter"}}}}}}, "Err401": {"description": "Unauthorized — missing or invalid API token.", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}, "code": {"type": "string"}}}, "examples": {"default": {"value": {"detail": "Authentication credentials were not provided.", "code": "unauthorized"}}}}}}, "Err403": {"description": "Forbidden — this endpoint is served to a signed-in browser session, and no valid session was presented.", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}, "code": {"type": "string"}}}, "examples": {"default": {"value": {"detail": "Authentication credentials were not provided.", "code": "forbidden"}}}}}}, "Err402": {"description": "Payment Required — billing quota exhausted (monthly free tier or paid balance depleted).", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}, "code": {"type": "string"}}}, "examples": {"default": {"value": {"detail": "Quota exhausted. Top up or wait until the monthly reset.", "code": "quota_exhausted"}}}}}, "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}, "Retry-After": {"$ref": "#/components/headers/RetryAfter"}}}, "Err404": {"description": "Not Found — resource does not exist or is not yet public.", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}, "code": {"type": "string"}}}, "examples": {"default": {"value": {"detail": "Not found.", "code": "not_found"}}}}}}, "Err429": {"description": "Too Many Requests — technical per-user rate limit exceeded.", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}, "code": {"type": "string"}}}, "examples": {"default": {"value": {"detail": "Request was throttled. Try again later.", "code": "throttled"}}}}}, "headers": {"Retry-After": {"$ref": "#/components/headers/RetryAfter"}}}, "Err400WithQuota": {"description": "Bad Request — invalid filter or parameter value.", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}, "code": {"type": "string"}}}, "examples": {"default": {"value": {"detail": "Invalid value for parameter \"ordering\".", "code": "invalid_parameter"}}}}}, "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "Err404WithQuota": {"description": "Not Found — resource does not exist or is not yet public.", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}, "code": {"type": "string"}}}, "examples": {"default": {"value": {"detail": "Not found.", "code": "not_found"}}}}}, "headers": {"X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}, "Err429WithQuota": {"description": "Too Many Requests — technical per-user rate limit exceeded.", "content": {"application/json": {"schema": {"type": "object", "properties": {"detail": {"type": "string"}, "code": {"type": "string"}}}, "examples": {"default": {"value": {"detail": "Request was throttled. Try again later.", "code": "throttled"}}}}}, "headers": {"Retry-After": {"$ref": "#/components/headers/RetryAfter"}, "X-Quota-Free-Remaining": {"$ref": "#/components/headers/XQuotaFreeRemaining"}, "X-Quota-Paid-Balance": {"$ref": "#/components/headers/XQuotaPaidBalance"}, "X-Quota-Resets-At": {"$ref": "#/components/headers/XQuotaResetsAt"}}}}, "headers": {"ETag": {"schema": {"type": "string"}, "description": "Opaque validator for conditional requests. Pass it back via If-None-Match on the next request to this endpoint to receive 304 when nothing changed."}, "XSyncTimestamp": {"schema": {"type": "string"}, "description": "Start-of-request timestamp (ISO-8601 UTC). Returned on 200 and 304 responses of the endpoints that support incremental sync. Store it and pass it back as ?updated_since= on the next sync request — it is the start of this request, not its end, so changes made while you were paginating are not lost."}, "XQuotaFreeRemaining": {"schema": {"type": "integer"}, "description": "Remaining monthly free-tier requests for the caller."}, "XQuotaPaidBalance": {"schema": {"type": "integer"}, "description": "Remaining paid balance (requests) for the caller."}, "XQuotaResetsAt": {"schema": {"type": "string", "format": "date-time"}, "description": "ISO-8601 UTC timestamp of the next monthly quota reset."}, "RetryAfter": {"schema": {"type": "integer"}, "description": "Seconds to wait before retrying."}}}, "servers": [{"url": "https://i-gaming.tools"}], "externalDocs": {"url": "https://i-gaming.tools/docs/", "description": "Guides, reference and code examples"}}