Client Integration Guide

Odds Distribution API

JSON GET API for currently available event catalogs and odds. No request body. API version v1.

An event is a single sporting fixture (a game, race, or similar) that the API can list and, when odds exist, return with its current markets.

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.

Base URL

Production API host:

Host https://oddsfeed.starluck.cloud

All application endpoints are versioned under /api/v1:

Base URL https://oddsfeed.starluck.cloud/api/v1

There is no unversioned application surface. /metrics is for operators and is not part of the client API.

Authentication / 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.
  • 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.

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

{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. There is no /matches route.

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://oddsfeed.starluck.cloud/api/v1/health"

Available sports

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://oddsfeed.starluck.cloud/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).

Event list

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://oddsfeed.starluck.cloud/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://oddsfeed.starluck.cloud/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 structure

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 / settlement

There is no separate result or settlement 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 dedicated result/settlement endpoint can be added later as a custom endpoint after the API plan is confirmed.

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

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 limiting

Rate limits are per IP and can differ between clients.

  • 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 examples

  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://oddsfeed.starluck.cloud/api/v1/sports"

# 2–3. Event catalog (replace cricket with a key from step 1)
curl -sS "https://oddsfeed.starluck.cloud/api/v1/cricket/events"

# 4–5. One event and its odds (replace with a gmid from step 3)
curl -sS "https://oddsfeed.starluck.cloud/api/v1/cricket/events/1789048569"

JavaScript / fetch

const base = "https://oddsfeed.starluck.cloud/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.

Demo / evaluation

Demo users can call the endpoints from an enabled public IP 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 sports?

Call GET /api/v1/sports. Use each object's key in other URLs. name is for display.

How do I get events?

Call GET /api/v1/{sport}/events with a key from /sports. Use each event's gmid as the event ID.

How do I get odds?

Call GET /api/v1/{sport}/events/{eventId}. Event odds are the odds array.

How do I get result status?

Use market (and selection) status on the same event-odds response. There is no separate result endpoint in this version.

Why do I get 403?

ip_not_allowed means this public IP is not enabled. sport_not_allowed means the sport key is valid but not enabled for your IP — call /sports.

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.

Can an ngrok domain be allowlisted?

No. Hostnames are not used for access. Only the public IP of the caller matters.

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.

Does the API guarantee real-time odds?

No. Responses contain the currently available cached odds at request time.