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+ 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.
Base URL
Production API host:
https://oddsfeed.starluck.cloud
All application endpoints are versioned under /api/v1:
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
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.
- 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-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 |
{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
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://oddsfeed.starluck.cloud/api/v1/health"
Available sports
key is the value used in {sport} path segments. name is the display label. Numeric provider sport IDs are not included.
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
{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://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
| 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://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
| 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 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.
| 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 / 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.
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
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 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-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 examples
- 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://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.