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.
{
"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
?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:
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:
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. nullis 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. Useinclude=availabilityto see where it is played. bonus_numbersmay contain letters, not only numbers. Some games draw a letter.numbersis in the order drawn where we can read it;sorted_numbersis always ascending. Many operators publish the result sorted, and thennumbersis sorted too. A result recovered later from an operator's archive can be sorted even when that game's live results are not. Usesorted_numbersto compare results.multiplieris exact, not always whole. A multiplier of 1.5X, such as BC Keno's, reads1.5; whole ones read2,3.multiplieris0when 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 asdraw.publishedlike any other. When the operator's publication matches,provisionalturnsfalse,confirmed_atis set anddraw.confirmedis sent; when it does not, the numbers are corrected anddraw.revisedis sent. A result never published live hasconfirmed_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.
currencybelongs to the draw. Romanian results from before 2005 are in old lei (ROL).- A converted amount is
nullwhen 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.