Data Licence API
A REST feed of the UK prize-competition market: every live competition across 414 sites with prices, odds and live sell-through, plus (on higher tiers) derived value data and a full sell-through history archive. JSON by default, CSV on request.
On this page
Base URL & versioning
All endpoints are served under a single versioned prefix. Paths below are relative to it.
https://compwatch.co.uk/api/v1
Authentication
Every request needs a bearer token sent in the Authorization header. Generate a token
from the API Data Access section of your account
page (available once your licence is active). The token is shown once — store it securely; regenerating revokes the old one.
curl https://compwatch.co.uk/api/v1/competitions \
-H "Authorization: Bearer <YOUR_TOKEN>" \
-H "Accept: application/json"
A valid token without an active licence returns 402; a missing or bad token returns 401.
Tiers & rate limits
Your tier determines which fields and endpoints you can access, and your request budget. Limits are per token.
| Tier | Price | Req/min | Req/day | Incremental sync | Value layer | History archive |
|---|---|---|---|---|---|---|
| snapshot | £500/mo | 30 | 2,000 | — | — | — |
| sync | £1,500/mo | 120 | 100,000 | Yes | Yes | — |
| intelligence | On application | 300 | 1,000,000 | Yes | Yes | Yes |
Exceeding a limit returns 429 Too Many Requests. The value layer = predicted final odds, expected value and cash alternative. The history archive = the per-competition sell-through time-series.
Pagination & formats
List responses are cursor-paginated at up to 200 rows per page. Each response's
meta.next_cursor is the cursor for the next page (null on the last page).
Pass it back as ?cursor=.
curl "https://compwatch.co.uk/api/v1/competitions?cursor=eyJfcG9pbnRzVG8iO..." \
-H "Authorization: Bearer <YOUR_TOKEN>"
Responses are JSON by default. Add ?format=csv to the list endpoint for a streamed CSV
download (nested objects such as forecast are serialised to JSON within their column).
Endpoints
/competitions
All tiers
Active competitions (not yet ended), ordered by closing time, tier-enriched.
Query parameters
cursor— pagination cursor frommeta.next_cursor.format=csv— return a streamed CSV instead of JSON.updated_since=<ISO 8601>— (Sync tier +) return everything changed since a timestamp, including just-closed competitions, ordered byupdated_at. Ideal for keeping a local copy in step. Requesting it below the Sync tier returns403; a malformed timestamp returns422.
curl "https://compwatch.co.uk/api/v1/competitions?updated_since=2026-09-01T00:00:00Z" \
-H "Authorization: Bearer <YOUR_TOKEN>"
/competitions/{id}
All tiers
A single competition by its id (the opaque id returned in list responses).
curl https://compwatch.co.uk/api/v1/competitions/aB3xY \
-H "Authorization: Bearer <YOUR_TOKEN>"
/competitions/{id}/history
Intelligence tier
The sell-through time-series for one competition — a series of observations of tickets sold over time. This is the historical data that can't be reconstructed by scraping the live site. Below the Intelligence tier this returns 403.
curl https://compwatch.co.uk/api/v1/competitions/aB3xY/history \
-H "Authorization: Bearer <YOUR_TOKEN>"
/sites
All tiers
The site directory with trust ratings (letter grades) and qualitative trust indicators. Numeric trust scores, weights and thresholds are never exposed.
curl https://compwatch.co.uk/api/v1/sites \
-H "Authorization: Bearer <YOUR_TOKEN>"
Response schema
Competition object
Returned by /competitions and /competitions/{id}. Base fields are on every tier; the value layer is included only on tiers with value-layer access (Sync and above).
| Field | Type | Tier | Description |
|---|---|---|---|
| id | string | All | Opaque competition id (use in single/history paths). |
| site | string | All | Site slug (join to /sites). |
| prize | string | All | Prize / competition title. |
| category | string|null | All | Prize category name. |
| url | string | All | Canonical URL of the competition on the operator site. |
| image_url | string|null | All | Proxied image URL (or null). |
| price_in_pence | integer|null | All | Ticket price in pence. |
| currency | string | All | Always "GBP". |
| ends_at | string|null | All | Closing time (ISO 8601). |
| tickets_sold | integer|null | All | Tickets sold so far. |
| total_tickets | integer|null | All | Ticket cap (null if uncapped/unknown). |
| tickets_available | integer|null | All | Remaining tickets. |
| percent_sold | number|null | All | Percent of the cap sold. |
| odds | string|null | All | Current odds of winning at present sales. |
| odds_if_sold_out | string|null | All | Odds if the competition sells out. |
| closed | boolean | All | Whether the closing time has passed. |
| updated_at | string | All | Last update (ISO 8601) — the field updated_since filters on. |
| forecast.predicted_odds | string|null | Sync + | Model-predicted final odds. |
| forecast.confidence | string|null | Sync + | Forecast confidence label: "high", "medium" or "low" (how much site-specific history backs it). |
| ev_per_pound | number|null | Sync + | Expected value per £1 spent. |
| cash_alternative_in_pence | integer|null | Sync + | Cash alternative to the prize, if offered. |
| estimated_value_pence | integer|null | Sync + | Estimated prize value. |
List responses wrap the array in data and carry a meta object:
tier— the licence tier this response was served under.count— number of rows in this page (not the total across all pages).next_cursor— cursor for the next page;nullon the last page.generated_at— ISO 8601 time the response was built.
History object (Intelligence)
{
"data": {
"id": "aB3xY",
"prize": "…",
"series": [
{ "observed_at": "…", "tickets_sold": 1200, "total_tickets": 5000,
"price_in_pence": 199, "ends_at": "…" }
]
},
"meta": { "points": 1, "tier": "intelligence" }
}
series is ordered oldest-first; each entry is one observation we recorded:
observed_at— when we captured this reading.tickets_sold/total_tickets— sold and cap at that moment (cap may benull).price_in_pence— ticket price at that moment.ends_at— the closing time as it stood then (operators sometimes extend it).
meta.points = the number of observations in series (its length); meta.tier = the tier served.
Site object
Returned by /sites. Fields: slug, name, base_url, company_name, company_founded_year, trust_rating (letter grade only, e.g. "A", "B"), and an indicators object of booleans:
odds_published, transparent_draw, winners_published, free_entry, secure_payments, voluntary_code, player_protection, age_verification, charitable_contributions
Each indicator mirrors a public trust indicator (true = the site meets it). The /sites response isn't paginated; its meta carries count (total sites returned) and generated_at.
Errors
Errors return the appropriate HTTP status with a JSON error message.
| Status | Meaning |
|---|---|
| 401 | Missing or invalid bearer token. |
| 402 | Token is valid but the account has no active data licence. |
| 403 | Feature not in your tier (e.g. updated_since below Sync, or history below Intelligence). |
| 404 | Competition not found. |
| 422 | Invalid parameter (e.g. a malformed updated_since timestamp). |
| 429 | Rate limit exceeded for your tier. |
Ready to start?
Choose a tier and subscribe, then generate your API token from your account page.
View tiers & subscribe