Skip to content

API essentials

Learn the shape once.

Consistent responses across games, markets and data types.

The same rules hold everywhere, so you learn them once.

The envelope

A payload is always under data. Listings add links (cursor URLs) and meta.

Code example
{
  "data": [ … ],
  "links": { "first": null, "last": null, "prev": null, "next": "https://api.lottevo.com/v1/lotteries?cursor=eyJ…" },
  "meta": { "path": "https://api.lottevo.com/v1/lotteries", "per_page": 25, "next_cursor": "eyJ…", "prev_cursor": null }
}

Pagination is cursor-based

Page size is 25, and per_page can only shrink a page — never grow one. Follow links.next until it is null.

Offsets are deliberately not offered. The results feed is append-heavy, and an offset page shifts under you as new draws land: page 2 would repeat rows page 1 already gave you.

Filtering, sorting, including

Code example
?filter[country]=RO&filter[from]=2026-01-01&sort=-drawn_at&include=prizes

Lists are comma-separated: filter[lottery]=RO-001,HU-001. Sorting takes - for descending. Relations are opt-in, because a draw can carry a dozen or more prize categories.

An undeclared filter, sort or include is a 422 that names what is available, rather than being silently ignored — a silent drop would answer with far more rows than you asked for.

Caching

Every successful read carries a strong ETag. Send it back as If-None-Match and a poll that changed nothing costs you a 304 with no body:

Code example
curl https://api.lottevo.com/v1/draws \
  -H 'X-Api-Key: …' \
  -H 'If-None-Match: "aad0ecbef9acb3804c1db3083346d3c4"'

A 304 still counts as one request against your quota. What it saves is bandwidth and parsing, not allowance.

Rate limit headers

Every answer past authentication carries them, so you can pace yourself instead of discovering the ceiling by being refused:

Code example
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-Quota-Limit: 25000
X-Quota-Remaining: 24873
X-Quota-Reset: 2026-09-21T00:00:00+00:00

X-Quota-Limit and X-Quota-Remaining are absent on an unmetered plan. The daily window resets at UTC midnight, so it does not depend on where you are calling from. Two answers past authentication carry none of them: a 402 subscription_required, and a 429 rate_limit_exceeded, which carries Retry-After instead.

Every answer past authentication also carries an X-Request-Id, which names that call in your Request log and in a support request. A 401, or a 403 origin_not_allowed, answers before the key is accepted, so it carries neither these headers nor a request ID. See Errors.

Language

Accept-Language is honoured. Game names, schedule descriptions and error messages come back in the negotiated locale, and the response carries Content-Language with Accept-Language appended to Vary.

Values that are easy to get wrong

  • Amounts are strings, not JSON numbers. They are decimal(20,6) and a float would round them. Format from the string.
  • null is not zero. Null means no value was reported; zero means the value was zero.
  • A multi-country game has country: null — that is the model, not missing data. Use include=availability to see where it is played.
  • bonus_numbers may contain letters, not only numbers. Some games draw a letter.
  • numbers is in the order drawn where we can read it; sorted_numbers is always ascending. Many operators publish the result sorted, and then numbers is sorted too. A result recovered later from an operator's archive can be sorted even when that game's live results are not. Use sorted_numbers to compare results.
  • multiplier is exact, not always whole. A multiplier of 1.5X, such as BC Keno's, reads 1.5; whole ones read 2, 3. multiplier is 0 when the game drew no multiplier.
  • A result can be provisional. Some results are read live from the draw's TV broadcast, minutes before the operator publishes them, and carry provisional: true. They arrive as draw.published like any other. When the operator's publication matches, provisional turns false, confirmed_at is set and draw.confirmed is sent; when it does not, the numbers are corrected and draw.revised is sent. A result never published live has confirmed_at: null.

Amounts in euros and dollars

Every draw carries its amounts converted to euros and US dollars (jackpot_amount_eur, jackpot_amount_usd, next_jackpot_amount_eur, next_jackpot_amount_usd) at the official exchange rates of the draw's own day, and the rates used in exchange_rate. A game's announced jackpot (upcoming.jackpot) carries amount_eur and amount_usd at today's rates.

  • currency belongs to the draw. Romanian results from before 2005 are in old lei (ROL).
  • A converted amount is null when the amount is null or zero, or when no official rate covers that day. Before 1999 only the side the amount is already in (euros or dollars) is known.
  • A draw from today may first be converted at the previous day's rate, and moves onto its own day's rate once that is published.

Exchange rates come from the European Central Bank and from national central banks, including the Banco Central de Chile for the Chilean peso.