# info.description
Public REST API for iGaming data: slot providers, slots, news, jobs, sources.

## Authentication

All endpoints require `Authorization: Token <your-token>` header.

## Response Codes

- **200** OK.
- **304** Not Modified — ETag match (If-None-Match); quota is still decremented.
- **400** Bad Request — invalid filter or parameter.
- **401** Unauthorized — missing or invalid token.
- **402** Payment Required — billing quota exhausted (monthly free tier or paid balance depleted). Retry after quota reset or top-up. Distinct from rate limiting.
- **404** Not Found — resource does not exist or is not yet public.
- **429** Too Many Requests — technical per-user rate limit exceeded (requests/min). Back off and retry. Distinct from quota (402).

## Sync Protocol

Use `?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).

# paths./api/v1/account/tokens/{id}/.delete.description
Revokes the caller's own ApiToken. Tokens belonging to other users → 404 (existence not disclosed). Already-revoked token → 204 (idempotent). Does not decrement quota (_skip_quota=True).

# paths./api/v1/jobs/.get.description
Public iGaming vacancy catalog. Cursor-pagination. No count. Field source_url — original vacancy page. Field url is ABSENT (P2-3).

# paths./api/v1/jobs/.get.parameters[].description
Location country ISO-2 (→ location_country)

# paths./api/v1/jobs/.get.parameters[].description
false (default) = ACTIVE only; true = + CLOSED within window

# paths./api/v1/jobs/{id}/.get.description
Full vacancy card by UUID. CLOSED older than window or hidden → 404.

# paths./api/v1/jobs/{id}/.get.parameters[].description
Stable identifier; used as FK in CH vacancy_snapshots and vacancy_raw_html.

# paths./api/v1/jobs/stats/.get.description
Approximate counts for landing total-badge. total_approx — from pg_class.reltuples (±5%, no count()). Does not decrement quota (_skip_quota=True).

# paths./api/v1/news/.get.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.

# paths./api/v1/news/.get.parameters[].description
Which field to use when ordering the results.

# paths./api/v1/news/{id}/.get.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".

# paths./api/v1/news/{id}/.get.parameters[].description
Stable UUID identifier; used as FK in content storage.

# paths./api/v1/providers/.get.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.

# paths./api/v1/providers/.get.parameters[].description
Which field to use when ordering the results.

# paths./api/v1/providers/.get.parameters[].description
ISO-8601 datetime. Returns only records updated at or after this moment.

# paths./api/v1/providers/{slug}/.get.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.

# paths./api/v1/providers/{slug}/demand/.get.description
Provider demand snapshot: computed_at, provider-level metrics, top_slots. Empty snapshot → 200 with nulls/empty (not 404). PUBLISHED-ONLY for top_slots.

# paths./api/v1/providers/{slug}/jobs/.get.description
Brand-scoped cursor-paginated job listings. Scoped by brand FK (not LinkedIn company). Supports all JobsFilterSet params (segment/country/remote/seniority/type). Supports ?updated_since and ETag/304.

# paths./api/v1/providers/{slug}/jobs/.get.parameters[].description
Which field to use when ordering the results.

# paths./api/v1/providers/{slug}/jobs/.get.parameters[].description
ISO-8601 datetime. Returns only records updated at or after this moment.

# paths./api/v1/providers/{slug}/news/.get.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.

# paths./api/v1/providers/{slug}/news/.get.parameters[].description
Which field to use when ordering the results.

# paths./api/v1/providers/{slug}/news/.get.parameters[].description
ISO-8601 datetime. Returns only records updated at or after this moment.

# paths./api/v1/providers/{slug}/slots/.get.description
Brand-scoped cursor-paginated slot listing. ?provider= is pinned to this brand; other SlotFilter params work. Supports ?updated_since and ETag/304. Items carry volume_12m/trend (per-slot search-demand aggregate).

# paths./api/v1/providers/{slug}/slots/.get.parameters[].description
Which field to use when ordering the results.

# paths./api/v1/providers/{slug}/slots/.get.parameters[].description
ISO-8601 datetime. Returns only records updated at or after this moment.

# paths./api/v1/providers/{slug}/team/.get.description
Visible key persons (is_visible=True, not is_suppressed). NOT paginated (bounded ≤60). profile_url NOT included (PII, OQ-PD6-4). Returns {count, results[]} envelope. ETag/304 supported.

# paths./api/v1/providers/ids/.get.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.

# paths./api/v1/regulators/.get.description
Cursor-paginated list of iGaming regulators (UKGC, MGA, GGL, ANJ, etc.). No is_public gate — regulators are seeded without a public flag.

# paths./api/v1/regulators/.get.parameters[].description
Which field to use when ordering the results.

# paths./api/v1/regulators/{slug}/.get.description
Full regulator profile with licensing authority information (jurisdiction, authority_for, statutory_authority, hosts).

# paths./api/v1/regulators/{slug}/news/.get.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.

# paths./api/v1/regulators/{slug}/news/.get.parameters[].description
Which field to use when ordering the results.

# paths./api/v1/regulators/{slug}/news/.get.parameters[].description
ISO-8601 datetime. Returns only records updated at or after this moment.

# paths./api/v1/slots/.get.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. Use /slots/ids/ for tombstone reconcile (removed slots do not appear in deltas). ETag/304 support via If-None-Match.

# paths./api/v1/slots/.get.parameters[].description
Whitelist: '-release_date' (newest first, default within this mode) 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'. Any other value → 400. Ignored when ?updated_since is present (sync order wins). Reverse traversal via `previous` through the trailing NULL-release_date group may skip items — walk forward (`next`) for a complete set.

# paths./api/v1/slots/.get.parameters[].description
ISO date (YYYY-MM-DD). release_date >= this value. Slots with release_date IS NULL are excluded. 400 on invalid format.

# paths./api/v1/slots/.get.parameters[].description
ISO date (YYYY-MM-DD). release_date <= this value. Slots with release_date IS NULL are excluded. 400 on invalid format.

# paths./api/v1/slots/.get.parameters[].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).

# paths./api/v1/slots/.get.parameters[].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.

# paths./api/v1/slots/.get.responses.200.content.application/json.examples.IncrementalSyncWithUpdatedSince.description
Pass X-Sync-Timestamp from a previous response as ?updated_since= to receive only slots modified since that moment.

# paths./api/v1/slots/{slug}/.get.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 heavy prefetch on unchanged content.

# paths./api/v1/slots/{slug}/.get.parameters[].description
URL-friendly unique identifier of the slot.

# paths./api/v1/slots/{slug}/demand/.get.description
Per-slot search-demand aggregate: volume_12m/prev_12m/yoy_pct/trend + 24-point sparkline, derived read-side from ProviderDemandSnapshot.payload. Graceful nulls when the provider has no demand snapshot yet. ETag/304 via If-None-Match (seed=computed_at, mirrors provider /demand/).

# paths./api/v1/slots/{slug}/demand/.get.parameters[].schema.description
Человекочитаемый ID для будущего публичного API (S.4).

# paths./api/v1/slots/ids/.get.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 local DB and tombstone missing slugs. X-Sync-Timestamp is included in the response header.

# paths./api/v1/slots/ids/.get.responses.200.headers.X-Sync-Timestamp.description
Start-of-request timestamp (ISO-8601). Present on 200 and 304. Store this value and pass as ?updated_since= in the next sync request.

# paths./api/v1/sources/.get.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.

# paths./api/v1/sources/.get.parameters[].description
Which field to use when ordering the results.

# paths./api/v1/sources/{host}/.get.description
Source detail by host FQDN. Out of scope for Wave-1 (list only).

# paths./api/v1/stats/.get.description
Aggregate stats for landing/dashboard display. Does not decrement quota (_skip_quota=True). Cached in Redis (TTL from api.stats_cache_ttl_seconds, default 3600 s).

# components.schemas.BrandFull.description
Serializer for detail /api/v1/providers/<slug>/ and /api/v1/regulators/<slug>/.

Additional fields beyond Short:
- hosts: list {host, display_name} for is_hidden=False only, ordered by host
- created_at: brand creation datetime
- country, jurisdiction, established, authority_for, statutory_authority (structured fields)

Brand.notes — operator-internal field, NOT published (P0 security audit 2026-05-27):
may contain sensitive data (internal assessments, contacts, investigations).
Only structured fields are published (country/jurisdiction/established/authority_for/
statutory_authority) — backfilled from notes via regex in migration 0005.

# components.schemas.BrandShort.description
Serializer for list /api/v1/providers/ and /api/v1/regulators/.

Fields (read-only, explicit whitelist):
slug, kind, name, aliases, hosts_count (annotated), news_count (annotated).

No structured profile fields — lean list payload.

# components.schemas.JobsDetail.description
Full iGaming job vacancy detail. Extends list fields with description, application URLs, and company info. secondary_apply_url: LinkedIn mirror link when available.

# components.schemas.JobsList.description
iGaming job vacancy card (list view). JSON key "url" is absent (P2-3): use source_url instead.

# components.schemas.NewsArticleFull.description
Serializer for detail /api/v1/news/<uuid>/.

Additional fields beyond Short:
- body_en (from CH via get_news_content_for_api, attached by view)
- body_unavailable_reason ('', 'extraction_failed', 'content_storage_unavailable')

body_en and body_unavailable_reason — not from model.
View attaches via setattr before serializer call.

# components.schemas.NewsArticleShort.description
Serializer for list /api/v1/news/.

Fields (read-only, explicit whitelist):
id, title_en, published_at, discovered_at, extraction_completed_at,
language, brand (nested), host (str), url.

Without body_en (CH lookup only for detail).

# components.schemas.ProviderDemand.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).

# components.schemas.ProviderDetail.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 (nh3-cleaned), never raw text.

# components.schemas.ProviderDetail.properties.about_html.description
Sanitized provider bio (nh3-cleaned HTML). Never raw text.

# components.schemas.SlotDemand.description
Per-slot search demand snapshot. computed_at: when the underlying provider snapshot was computed. volume_12m/prev_12m/yoy_pct/trend: trailing-12-month aggregate and YoY change. sparkline: 24 monthly points, oldest to newest. months: up to 12 "YYYY-MM" labels, newest first (Date1 = freshest). 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).

# components.schemas.SlotDetail.description
Serializer for detail /api/v1/slots/<slug>/.

Three top-level keys: data (verified) / spec_sheet (unverified) / assets.
Accepts a fully prefetched Slot (§6.2 slots_s4.md).
No additional DB queries inside the serializer
(except demo_source_for inside _build_demo).

# components.schemas.SlotDetail.properties.data.description
Rig-curated verified structured data block (whitelist §5.2).

# components.schemas.SlotDetail.properties.spec_sheet.description
Unverified block: raw OCR dump (§5.2, §7 slots_s4.md).

source — constant 'extracted, unverified'. raw — as-is or null.
updated_at — SlotTechInfo.filled_at (last-write, auto_now).
spec_sheet.raw is NOT written to logs/Sentry (§7).

# components.schemas.SlotDetail.properties.assets.description
Media assets: screenshots (with key/version), logo, palette, demo embed info.

# components.schemas.SlotListItem.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 (renamed from bet_mechanic field).

# components.schemas.SlotListItem.properties.provider.description
Nested {slug, name} from Slot.brand (select_related in view).

# components.schemas.SlotListItem.properties.thumbnail_url.description
CDN-URL of first screenshot type in (logo, preview) from thumb_screens.

thumb_screens — to_attr Prefetch in view._list_queryset.
Type order from queryset order_by('type','order','id'):
'logo' < 'preview' (alphabetically) → logo first if present.

# components.schemas.SlotListItem.properties.thumbnail.description
Asset-versioned thumbnail object {url, key, version} or null.

# components.schemas.Source.description
Serializer for GET /api/v1/sources/.

Fields (read-only, explicit whitelist):
- host: FQDN string (Host.host)
- brand: {slug, kind, name} or null
- articles_count_30d: annotated count of DONE articles in last 30 days
- last_extracted_at: MAX extraction_completed_at (DONE), may be null

Intentionally WITHOUT: cadence_hours (field absent in model), enabled_for_parsing,
is_hidden, health_status, notes (internal fields).

# components.schemas.TrendEnum.description
* `growing` - growing
* `declining` - declining
* `flat` - flat

# components.securitySchemes.TokenAuth.description
Token <hex>. Issued via operator CLI: `manage.py issue_api_token --user <email> --name <label>`. Self-service token management planned for Wave 2.