HZURA · Client Integration Guide

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 + display name)
  • 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.

Interactive playground

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):

Documentation URL https://docs.hzura.com/

JSON API host (API only — not documentation):

API https://api.hzura.com

JSON API Base URL:

API Base URL https://api.hzura.com/api/v1

Public JSON routes:

  • GET https://api.hzura.com/api/v1/health
  • GET https://api.hzura.com/api/v1/sports
  • GET https://api.hzura.com/api/v1/{sport}/events
  • GET 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 example cricket).
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 gtype and marketName.
odds
The currently cached markets for an event, returned as the odds array 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 Authorization header 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-After on 429.

Call GET /api/v1/sports to see which sports your IP currently has.

Endpoint summary

MethodURLPurpose
GET/api/v1/healthService health (no IP policy)
GET/api/v1/sportsSports enabled for this IP
GET/api/v1/{sport}/eventsAvailable events for a sport (no full odds)
GET/api/v1/{sport}/events/{eventId}One event with its current markets/odds
GET/metricsOperator 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

GET /api/v1/health No IP policy required Confirm the API is up and the data store is reachable.

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.

Example request
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.

GET /api/v1/sports Sports this calling IP may use, in a stable order.

key is the value used in {sport} path segments. name is the display label. Numeric provider sport IDs are not included.

Example request
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.

GET /api/v1/{sport}/events Currently published events for one sport. No full odds.

{sport} must be a supported key. The response is a JSON array. Use gmid as the event ID for the single-event URL.

Example request
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

HTTPerrorWhen
400invalid_sport{sport} is not a supported key
403ip_not_allowedCalling IP is not allowed
403sport_not_allowedSport is valid but not enabled for this IP
429rate_limitedRate limit exceeded
502malformed_upstream_dataUpstream catalog could not be read
503catalog_unavailableEvent data is not available yet
503redis_unavailableBackend data store unavailable
504redis_timeoutBackend data store timed out

Single event

GET /api/v1/{sport}/events/{eventId} One event and its current available markets / event odds.

{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": []
}
FieldMeaning
sportSport key from the request (string)
event_idNumeric event ID (same value as event.gmid)
eventThe event object (same catalog fields as the list)
oddsArray 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.

Example request
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

HTTPerrorWhen
400invalid_sport{sport} is not a supported key
400invalid_event_id{eventId} is not a positive integer
403ip_not_allowedCalling IP is not allowed
403sport_not_allowedSport is valid but not enabled for this IP
404event_not_foundEvent is not in that sport's event list
404odds_not_foundOdds are not available for this event
429rate_limitedRate limit exceeded
502malformed_upstream_dataUpstream odds could not be read
503catalog_unavailableEvent data is not available yet
503redis_unavailableBackend data store unavailable
504redis_timeoutBackend 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.

gtypeTypical marketNameShape
matchMatch Odds / Exchangerunners with back/lay
match1Bookmakerrunners with back/lay
fancy / fancy1Fancy / sessionsection 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.
Practical polling

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

  • betfairEventId
  • feed_source

Markets do not include

  • tv
  • tvChannel
  • odd_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"
}
HTTPerrorMeaningClient action
400invalid_sportUnsupported sport keyUse a key from /sports
400invalid_event_idEvent ID is not a positive integerUse gmid from the event list
403ip_not_allowedCalling IP is not allowedConfirm the server public IP with the operator
403sport_not_allowedThis sport is not enabled for your IPCall /sports or request that sport
404not_foundUnknown URLCheck the path
404event_not_foundEvent is not in that sport's listRefresh the event list
404odds_not_foundOdds are not available for this eventRetry later or skip the event
429rate_limitedRate limit exceededWait / back off; honour Retry-After
500internal_errorUnexpected server errorRetry later
502malformed_upstream_dataUpstream data could not be readRetry later
503catalog_unavailableEvent data is not available yetRetry shortly
503redis_unavailableBackend data store unavailableRetry later
503security_config_unavailableService is temporarily unavailableRetry later
504redis_timeoutBackend data store timed outRetry 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-After header 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

  1. Call GET /api/v1/sports.
  2. Choose a sport key.
  3. Call GET /api/v1/{sport}/events.
  4. Select an event ID (gmid).
  5. Call GET /api/v1/{sport}/events/{eventId}.
  6. Poll the single-event URL while the event is in use; stop when it is not.
curl
# 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:

Playground 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.