HZURA Sports API Documentation
The HZURA Sports API is a JSON GET service for currently available sports event catalogs and odds. No request body. API version v1.
This page is the human-readable documentation at https://docs.hzura.com/. JSON API requests use https://api.hzura.com/api/v1.
The API returns:
- The sports your IP may use (
key+ displayname) - The current event catalog for a sport (no full odds)
- One event plus its complete currently available 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 in this version.
Try live requests in the HZURA API playground. Your server still needs an enabled public IP for production calls.
Getting started
Documentation and the JSON API use separate hosts. This page is documentation only. Application endpoints live on api.hzura.com under /api/v1. There is no unversioned JSON surface.
Human-readable documentation (this page):
https://docs.hzura.com/
JSON API host (API only — not documentation):
https://api.hzura.com
JSON API Base URL:
https://api.hzura.com/api/v1
Public JSON routes:
GET https://api.hzura.com/api/v1/healthGET https://api.hzura.com/api/v1/sportsGET https://api.hzura.com/api/v1/{sport}/eventsGET https://api.hzura.com/api/v1/{sport}/events/{eventId}
GET /metrics is an operational endpoint on the API service. It is not part of the client JSON API and is not served by this documentation page.
Terms
- sport
- A sport key from
GET /api/v1/sports, used as the{sport}path segment (for examplecricket). - event
- A single sporting fixture the API can list and, when odds exist, return with its current markets.
- event ID
- The event's
gmid, a positive integer used as{eventId}. - market
- One betting market on an event, identified by
gtypeandmarketName. - odds
- The currently cached markets for an event, returned as the
oddsarray on the event-odds response. - status
- The current state of an event, market, or selection as provided by the feed (for example
OPEN). - client IP
- The public IP address of the server that calls the API. Access and rate limits are applied to this IP.
Access
- Call the API over HTTPS.
- No API key and no
Authorizationheader are used. - Access is IP-based. Each public client IP is configured separately.
- The API identifies you by the public IP of the server that opens the connection.
- Each IP can be limited to specific sports and has its own rate limit.
- Domain names and ngrok hostnames cannot be used for access control.
Local development from a laptop, home NAT, or Docker network often presents a private or changing address. For calls to the production API you need a stable, reachable public egress IP that has been enabled for your account.
GET /api/v1/health does not require a client IP policy. Sports, event list, and event-odds endpoints do.
Before integration, send the public IP address(es) of the servers that will call the API.
Can I use an ngrok domain?
No. Hostnames are not used for access. Only the public IP of the caller matters.
Client IP policy
Each calling IP has its own policy. Policies can change without an API restart; a change typically becomes visible within about one second.
For each IP, access can independently:
- Be enabled or disabled
- Allow every currently supported sport, or an explicit sport list
- Use its own rate-limit enabled flag, request count, and window
- An IP with no policy, a disabled policy, or an unusable policy receives
403 ip_not_allowed. The error does not say which of those applied. - A valid sport that is not on your IP's list receives
403 sport_not_allowed. - An unknown sport key receives
400 invalid_sport. - Rate limits can differ between IPs. Honour
Retry-Afteron429.
Call GET /api/v1/sports to see which sports your IP currently has.
Endpoint summary
| Method | URL | Purpose |
|---|---|---|
| GET | /api/v1/health | Service health (no IP policy) |
| GET | /api/v1/sports | Sports enabled for this IP |
| GET | /api/v1/{sport}/events | Available events for a sport (no full odds) |
| GET | /api/v1/{sport}/events/{eventId} | One event with its current markets/odds |
| GET | /metrics | Operator Prometheus metrics (not a client JSON route) |
{sport} is a key from /sports. {eventId} is the event's gmid — a positive integer from the event-list response. There are no query parameters.
Health
Success — HTTP 200
{"status":"ok","redis":"ok"}
Degraded — HTTP 503
{"status":"degraded","redis":"unreachable"}
status is "ok" only when the backend data store is reachable. This body is not the standard error / message document.
curl -sS "https://api.hzura.com/api/v1/health"
Sports
How do I get the available sports?
Call GET https://api.hzura.com/api/v1/sports. Use each object's key in other URLs. name is for display.
key is the value used in {sport} path segments. name is the display label. Numeric provider sport IDs are not included.
curl -sS "https://api.hzura.com/api/v1/sports"
HTTP 200 — every currently supported sport
{
"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" }
]
}
An IP limited to cricket and football receives only those two objects, still in that stable order. An enabled IP with no usable sports receives {"sports":[]}.
Keys are lowercase and must match exactly (Cricket is not cricket).
Events
How do I get events?
Call GET https://api.hzura.com/api/v1/{sport}/events with a key from /sports. Use each event's gmid as the event ID.
{sport} must be a supported key. The response is a JSON array. Use gmid as the event ID for the single-event URL.
curl -sS "https://api.hzura.com/api/v1/cricket/events"
HTTP 200
[
{
"gmid": 1789048569,
"ename": "India v Australia",
"etid": 4,
"status": "OPEN"
}
]
Catalog rows are passed through from upstream after internal fields are removed. Typical fields include gmid, ename, etid, and status. Additional metadata may be present. Ignore unused fields. An empty array means no events are currently published for that sport.
Errors
| HTTP | error | When |
|---|---|---|
| 400 | invalid_sport | {sport} is not a supported key |
| 403 | ip_not_allowed | Calling IP is not allowed |
| 403 | sport_not_allowed | Sport is valid but not enabled for this IP |
| 429 | rate_limited | Rate limit exceeded |
| 502 | malformed_upstream_data | Upstream catalog could not be read |
| 503 | catalog_unavailable | Event data is not available yet |
| 503 | redis_unavailable | Backend data store unavailable |
| 504 | redis_timeout | Backend data store timed out |
Single event
{eventId} must be a positive integer (the event's gmid). The event must exist in that sport's event list.
The top-level response is always:
{
"sport": "cricket",
"event_id": 1789048569,
"event": {},
"odds": []
}
| Field | Meaning |
|---|---|
sport | Sport key from the request (string) |
event_id | Numeric event ID (same value as event.gmid) |
event | The event object (same catalog fields as the list) |
odds | Array of currently available markets |
odds is the complete market list currently held for that event. It can be an empty array when the event exists but no markets are published yet.
curl -sS "https://api.hzura.com/api/v1/cricket/events/1789048569"
HTTP 200
{
"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 }]
}
}
]
},
{
"marketId": "8.111",
"marketName": "Bookmaker",
"gtype": "match1",
"status": "OPEN",
"runners": [
{
"selectionId": 48000,
"runnerName": "India",
"status": "ACTIVE",
"ex": {
"availableToBack": [{ "price": 1.97, "size": 100.0 }],
"availableToLay": [{ "price": 2.02, "size": 80.0 }]
}
}
]
}
]
}
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 | Calling IP is not allowed |
| 403 | sport_not_allowed | Sport is valid but not enabled for this IP |
| 404 | event_not_found | Event is not in that sport's event list |
| 404 | odds_not_found | Odds are not available for this event |
| 429 | rate_limited | Rate limit exceeded |
| 502 | malformed_upstream_data | Upstream odds could not be read |
| 503 | catalog_unavailable | Event data is not available yet |
| 503 | redis_unavailable | Backend data store unavailable |
| 504 | redis_timeout | Backend data store timed out |
Odds
How do I get event odds?
Call GET https://api.hzura.com/api/v1/{sport}/events/{eventId}. Event odds are the odds array on that response.
The API does not reshape markets. After dropping a few internal fields, each element of odds is the upstream market object. Identify a market by gtype and marketName.
| gtype | Typical marketName | Shape |
|---|---|---|
match | Match Odds / Exchange | runners with back/lay |
match1 | Bookmaker | runners with back/lay |
fancy / fancy1 | Fancy / session | section ladder |
Other gtype values may appear. Parse unknown markets defensively. Odds prices are decimal. size is the available stake at that price, as provided by upstream.
Match Odds and Bookmaker (runners)
- marketNameMarket display name
- marketIdMarket identifier
- gtypeMarket type
- statusMarket status
- runners[]Selections in the market
- runners[].selectionIdSelection identifier
- runners[].runnerNameSelection name
- runners[].statusSelection status
- runners[].ex.availableToBackBack prices (price, size)
- runners[].ex.availableToLayLay prices (price, size)
selectionId may be a string or a number. Bookmaker (match1) prices may also include price1.
Fancy (section)
Fancy markets use section instead of runners. Each section entry has a name (nat), a status (gstatus), and an odds ladder.
Results
Where can I find the result status?
Result information is currently available through market status in the event odds response. There is no separate result endpoint in this version.
Settlement information is carried on the same event-odds response. Market status (and selection status where present) reflects the current upstream state, including a settled state when the feed provides one.
A separate result/settlement endpoint can be added later based on the confirmed API plan.
Data updates
- Each response contains the catalog or odds currently cached at request time.
- The API does not wait for a new upstream update before responding.
- Calling the single-event endpoint records interest in that event. Repeated requests keep it prioritized for upstream refresh.
- Continue polling while the event is displayed or otherwise in use.
- Stop polling when the event is no longer required.
- There is no hard real-time guarantee and no published freshness window.
Poll GET /api/v1/{sport}/events/{eventId} at the frequency your UI needs while the event is active. There is no push feed. A later poll may return the same payload if upstream has not changed. Reuse HTTP connections where possible.
Fields not exposed
Certain provider-only fields are omitted from the public API.
Event objects do not include
betfairEventIdfeed_source
Markets do not include
tvtvChannelodd_type
Do not depend on these fields. Additional public fields may be added later; ignore unused keys.
Errors
Why am I receiving 403 ip_not_allowed?
The public IP the API sees is not enabled. Access is by client IP policy. Confirm the server public IP with the operator. A missing, disabled, or unusable policy all return the same 403 ip_not_allowed error.
Every error body has the same shape:
{
"error": "event_not_found",
"message": "Event not found"
}
| HTTP | error | Meaning | Client action |
|---|---|---|---|
| 400 | invalid_sport | Unsupported sport key | Use a key from /sports |
| 400 | invalid_event_id | Event ID is not a positive integer | Use gmid from the event list |
| 403 | ip_not_allowed | Calling IP is not allowed | Confirm the server public IP with the operator |
| 403 | sport_not_allowed | This sport is not enabled for your IP | Call /sports or request that sport |
| 404 | not_found | Unknown URL | Check the path |
| 404 | event_not_found | Event is not in that sport's list | Refresh the event list |
| 404 | odds_not_found | Odds are not available for this event | Retry later or skip the event |
| 429 | rate_limited | Rate limit exceeded | Wait / back off; honour Retry-After |
| 500 | internal_error | Unexpected server error | Retry later |
| 502 | malformed_upstream_data | Upstream data could not be read | Retry later |
| 503 | catalog_unavailable | Event data is not available yet | Retry shortly |
| 503 | redis_unavailable | Backend data store unavailable | Retry later |
| 503 | security_config_unavailable | Service is temporarily unavailable | Retry later |
| 504 | redis_timeout | Backend data store timed out | Retry later |
sport_not_allowed example:
{
"error": "sport_not_allowed",
"message": "This sport is not enabled for your IP."
}
invalid_sport message:
Unsupported sport. Supported sports: cricket, football, tennis, horse-racing, greyhound-racing
Rate limits
How is rate limiting applied?
Rate limits are per client IP and can differ between IPs.
- HTTP 429 with
"error": "rate_limited"means the current limit for your IP has been exceeded. - Wait and back off before retrying.
- If a
Retry-Afterheader is present, wait that many seconds.
/api/v1/sports and the event endpoints share that IP's limit. /api/v1/health is not rate limited. The numeric budget is not a fixed public contract.
Integration
- Call
GET /api/v1/sports. - Choose a sport
key. - Call
GET /api/v1/{sport}/events. - Select an event ID (
gmid). - Call
GET /api/v1/{sport}/events/{eventId}. - Poll the single-event URL while the event is in use; stop when it is not.
# 1. Sports available to this IP
curl -sS "https://api.hzura.com/api/v1/sports"
# 2–3. Event catalog (replace cricket with a key from step 1)
curl -sS "https://api.hzura.com/api/v1/cricket/events"
# 4–5. One event and its odds (replace with a gmid from step 3)
curl -sS "https://api.hzura.com/api/v1/cricket/events/1789048569"
JavaScript / fetch
const base = "https://api.hzura.com/api/v1";
const sports = await fetch(`${base}/sports`).then((r) => r.json());
const sportKey = sports.sports[0].key;
const events = await fetch(`${base}/${sportKey}/events`).then((r) => r.json());
const eventId = events[0].gmid;
const response = await fetch(`${base}/${sportKey}/events/${eventId}`);
const data = await response.json();
On success, data.event_id is the event ID, data.event is the event object, and data.odds is the market list.
API playground
Use the interactive playground to send requests and inspect JSON without writing a client first:
https://www.hzura.com/api-playground
Open the HZURA API playground. Production access still requires your server public IP to be enabled. The playground does not replace IP policy.
Demo / evaluation
Demo users can call the endpoints from an enabled public IP — or explore them in the playground — and store the JSON responses locally for evaluation (sports list, event catalog, and event-odds payloads).
Saved responses are snapshots of what was available at request time. They are not a live feed. When evaluating updates, poll an in-use event and compare successive responses.
FAQ
How do I get the available sports?
Call GET https://api.hzura.com/api/v1/sports. Use each object's key in other URLs. name is for display.
How do I get events?
Call GET https://api.hzura.com/api/v1/{sport}/events with a key from /sports. Use each event's gmid as the event ID.
How do I get event odds?
Call GET https://api.hzura.com/api/v1/{sport}/events/{eventId}. Event odds are the odds array on that response.
Where can I find the result status?
Result information is currently available through market status in the event odds response. There is no separate result endpoint in this version.
How is rate limiting applied?
Rate limits are per client IP and can differ between IPs. When the limit is exceeded the API returns 429 with rate_limited. Honour Retry-After.
Why am I receiving 403 ip_not_allowed?
The public IP the API sees is not enabled. Access is by client IP policy. Confirm the server public IP with the operator.
Can I use an ngrok domain?
No. Hostnames are not used for access. Only the public IP of the caller matters.
Why do I get 429?
Your IP exceeded its rate limit (rate_limited). Back off and honour Retry-After.
Why can a local IP be a problem?
Access is tied to the public IP the API actually sees. 127.0.0.1, a Docker bridge address, or a home NAT IP is not your production server IP. Enable the stable public egress IP of the machine that will call the API.
Are custom endpoints possible?
Yes, as additional/custom work after the API plan is confirmed. This version exposes health, sports discovery, the event list, and single-event odds only.
Do I need an API key?
No. Access is by public IP policy.
What is the production API URL?
The documentation URL is https://docs.hzura.com/. The API host is https://api.hzura.com. JSON application calls use https://api.hzura.com/api/v1.
Where is the official documentation?
This page: https://docs.hzura.com/.
Is there an API playground?
Yes. Open https://www.hzura.com/api-playground to try endpoints in the browser.
Does the API guarantee real-time odds?
No. Responses contain the currently available cached odds at request time.