Sports API · v1
Sports API
The HZURA Sports API provides sports, events, and event odds through HTTP GET endpoints. Customers authenticate with an API key, or with a legacy public-IP policy. GET /api/v1/health is public and is not rate limited.
Overview
The API returns the sports enabled for this client (key and display name), the published events for one sport, and one event plus its odds. It does not scrape providers and does not wait for a new upstream update before responding.
Result information, when the feed provides it, is on market status in the event-odds response. There is no separate settlement endpoint.
Sport keys are lowercase and must match exactly. Cricket is not cricket. Supported keys are cricket, football, tennis, horse-racing, and greyhound-racing. A client only receives the subset its policy allows.
- sport
- A key from
GET /api/v1/sports, used as{sport}. - event ID
- The event
gmid, a positive integer used as{eventId}. - odds
- The
oddsarray on the single-event response. There is no separate odds URL.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/health | Service health. No API key or IP policy. |
| GET | /api/v1/sports | Sports enabled for this client. |
| GET | /api/v1/{sport}/events | Published events. No full odds. |
| GET | /api/v1/{sport}/events/{eventId} | One event and its markets. |
There are no query parameters on these routes. GET /metrics is an operator endpoint and is not part of this client API.
Health
- Public endpoint
- No API key
- Not rate limited
Endpoint
/api/v1/healthConfirm the API is up and both stores used by this route answer.
status is ok and redis is ok only when both the odds store and the platform store answer. If either does not, the status is 503 and the body is { "status": "degraded", "redis": "unreachable" }. This is not the { error, message } document.
Authentication
None. No API key and no IP policy.
Request
This endpoint does not take parameters or a request body.
Response
200Both data stores answered.
{
"status": "ok",
"redis": "ok"
}503A data store did not answer. This is still the health document, not an error code.
{
"status": "degraded",
"redis": "unreachable"
}Example request
curl -sS "https://api.hzura.com/api/v1/health"Sports
- API key or legacy IP
- Rate limited
Endpoint
/api/v1/sportsSports this API client may use, in a stable order.
key is the {sport} path segment. name is the display label. Numeric provider sport ids are not included.
A client limited to cricket and football receives only those objects, still in canonical order. An enabled client with no usable sports receives { "sports": [] }.
Authentication
API key plus calling IP, or a legacy IP policy. See Authentication.
Request
Headers
| Field | Type | Required | Description |
|---|---|---|---|
| Authorization | — | No | Bearer API key for admin-managed customers. |
| X-Api-Key | — | No | Alternative API key header. Ignored when Bearer is present. |
Response
200Every sport currently enabled for this client.
{
"sports": [
{ "key": "cricket", "name": "Cricket" },
{ "key": "football", "name": "Football" },
{ "key": "tennis", "name": "Tennis" },
{ "key": "horse-racing", "name": "Horse Racing" },
{ "key": "greyhound-racing", "name": "Greyhound Racing" }
]
}Example request
curl -sS "https://api.hzura.com/api/v1/sports" \
-H "Authorization: Bearer YOUR_API_KEY"GET /api/v1/sports HTTP/1.1
Host: api.hzura.com
Authorization: Bearer YOUR_API_KEY
Accept: application/jsonErrors
| HTTP | error | When |
|---|---|---|
| 403 | ip_not_allowed | Caller is not allowed. |
| 429 | rate_limited | Client budget for this window is used. |
| 503 | security_config_unavailable | Access policy could not be read. |
Events
- API key or legacy IP
- Rate limited
Endpoint
/api/v1/{sport}/eventsCurrently published events for one sport. No full odds.
The response is a JSON array. Use each object's gmid as {eventId}. Catalog rows are passed through after internal fields are removed. Typical fields include gmid, ename, etid, and status. Additional metadata may be present. Ignore unused fields.
A published catalog with no rows is []. A catalog that has not been published is 503 catalog_unavailable, not an empty array.
Authentication
API key plus calling IP, or a legacy IP policy. See Authentication.
Request
Parameters
| Field | Type | In | Required | Description |
|---|---|---|---|---|
| sport | string | path | Yes | A sport key from GET /api/v1/sports. |
Response
200Event catalog for the sport.
[
{
"gmid": 1789048569,
"ename": "India v Australia",
"etid": 4,
"status": "OPEN"
}
]Example request
curl -sS "https://api.hzura.com/api/v1/cricket/events" \
-H "Authorization: Bearer YOUR_API_KEY"Errors
| HTTP | error | When |
|---|---|---|
| 400 | invalid_sport | {sport} is not a supported key. |
| 403 | ip_not_allowed | Caller is not allowed. |
| 403 | sport_not_allowed | The sport is valid but not enabled for this client. |
| 429 | rate_limited | Rate limit exceeded. |
| 503 | security_config_unavailable | Access policy could not be read. |
| 502 | malformed_upstream_data | The stored catalog could not be read. |
| 503 | catalog_unavailable | Event data is not published yet. |
| 503 | redis_unavailable | The data store is unavailable. |
| 504 | redis_timeout | The data store timed out. |
Event Details
- API key or legacy IP
- Rate limited
Endpoint
/api/v1/{sport}/events/{eventId}One event and its current markets.
{eventId} must be a positive integer, the event's gmid, at most 20 digits. The event must exist in that sport's catalog.
odds is the complete market list currently held for the event. It can be an empty array when the event exists but no markets are published. If the odds payload itself is missing, the API returns 404 odds_not_found instead of an empty array.
Calling this route records interest in the event so it stays prioritized for upstream refresh. Keep polling while the event is on screen. Stop when it is not.
Authentication
API key plus calling IP, or a legacy IP policy. See Authentication.
Request
Parameters
| Field | Type | In | Required | Description |
|---|---|---|---|---|
| sport | string | path | Yes | Sport key from GET /api/v1/sports. |
| eventId | integer | path | Yes | Positive integer gmid from the event list. |
Response
200Event row plus markets.
{
"sport": "cricket",
"event_id": 1789048569,
"event": {
"gmid": 1789048569,
"ename": "India v Australia",
"etid": 4,
"status": "OPEN"
},
"odds": [
{
"marketId": "1.234",
"marketName": "Match Odds",
"gtype": "match",
"status": "OPEN",
"runners": [
{
"selectionId": "47999",
"runnerName": "India",
"status": "ACTIVE",
"ex": {
"availableToBack": [{ "price": 1.75, "size": 50.0 }],
"availableToLay": [{ "price": 1.76, "size": 40.0 }]
}
}
]
}
]
}Example request
curl -sS "https://api.hzura.com/api/v1/cricket/events/1789048569" \
-H "Authorization: Bearer YOUR_API_KEY"Errors
| HTTP | error | When |
|---|---|---|
| 400 | invalid_sport | {sport} is not a supported key. |
| 400 | invalid_event_id | {eventId} is not a positive integer. |
| 403 | ip_not_allowed | Caller is not allowed. |
| 403 | sport_not_allowed | Sport is not enabled for this client. |
| 404 | event_not_found | Event is not in that sport's catalog. |
| 404 | odds_not_found | No odds payload is stored for this event. |
| 429 | rate_limited | Rate limit exceeded. |
| 503 | security_config_unavailable | Access policy could not be read. |
| 502 | malformed_upstream_data | The event or odds payload could not be read. |
| 503 | catalog_unavailable | Event data is not published yet. |
| 503 | redis_unavailable | The data store is unavailable. |
| 504 | redis_timeout | The data store timed out. |
| Field | Meaning |
|---|---|
sport | Sport key from the request. |
event_id | Numeric event id. Same value as event.gmid. |
event | Catalog object for that event. |
odds | Array of currently available markets. |
Odds
Event odds are the odds array from GET /api/v1/{sport}/events/{eventId}. Markets are not reshaped. After a few internal fields are removed, each element is the upstream market object. Identify a market by gtype and marketName.
Prices are decimal. size is the available stake at that price, as provided upstream. Other gtype values may appear. Parse unknown markets defensively.
| gtype | Typical marketName | Shape |
|---|---|---|
match | Match Odds / Exchange | runners with back and lay prices |
match1 | Bookmaker | runners with back and lay prices |
fancy / fancy1 | Fancy / session | section ladder |
- marketName
- Market display name.
- marketId
- Market identifier.
- gtype
- Market type.
- status
- Market status, including a settled state when the feed sends one.
- runners[].selectionId
- Selection id. It may be a string or a number.
- runners[].runnerName
- Selection name.
- runners[].status
- Selection status.
- runners[].ex.availableToBack
- Back prices as
{ price, size }. - runners[].ex.availableToLay
- Lay prices as
{ price, size }.
Bookmaker (match1) prices may also include price1. Fancy markets use section instead of runners. Each section entry has a name (nat), a status (gstatus), and an odds ladder.
Results
There is no result or settlement endpoint in this version. Settlement state is on the same event-odds response. Market status, and selection status where present, reflects the current upstream state.
Data updates
- Each response is the catalog or odds cached at request time.
- The API does not wait for a new upstream update.
- There is no push feed, no hard real-time guarantee, and no published freshness window.
- A later poll may return the same payload if upstream has not changed.
- Poll
GET /api/v1/{sport}/events/{eventId}while the event is in use, at the frequency the UI needs. Reuse HTTP connections.
Fields not exposed
These provider-only fields are removed before the response is sent. Do not depend on them. Additional public fields may appear later; ignore unused keys.
| Object | Omitted |
|---|---|
| Event | betfairEventId, feed_source |
| Market | tv, tvChannel, odd_type |
Rate limits and access
/api/v1/sports and both event routes share the caller's sportsbook limit. Health is not rate limited. API-key accounts are limited per customer. Legacy IP clients are limited per public IP. The request count and window are per client and are not a fixed public contract.
HTTP 429 uses rate_limited and a Retry-After header with the seconds remaining in the current window. Rejected requests still count.
Integration example
- Call
GET /api/v1/sportsand choose akey. - Call
GET /api/v1/{sport}/events. - Take an event
gmid. - Call
GET /api/v1/{sport}/events/{eventId}and readodds. - Poll that URL while the event is in use.
const base = "https://api.hzura.com/api/v1";
const headers = {
Authorization: "Bearer YOUR_API_KEY",
Accept: "application/json",
};
const sports = await fetch(`${base}/sports`, { headers }).then((r) => r.json());
const sportKey = sports.sports[0].key;
const events = await fetch(`${base}/${sportKey}/events`, { headers }).then((r) => r.json());
const eventId = events[0].gmid;
const response = await fetch(`${base}/${sportKey}/events/${eventId}`, { headers });
const data = await response.json();On success, data.event_id is the event id, data.event is the catalog object, and data.odds is the market list. The API playground can exercise the same reads. Production calls still require an API key, when one was issued, and a calling IP that matches the configured CIDR.