Diamond API API v1 REST + live streams diamondapi.live → Quickstart Streams Results
Diamond API · v1

Live odds and results for your sportsbook, as they move.

Cricket, soccer, tennis and more, from pre-match to the last ball: every market with its prices, pushed to you the moment it changes, and every result kept for you to collect.

Live streamServer-Sent Events: a snapshot, then only what changed
RESTSnapshots, batch odds and a changes feed for pollers
ResultsPushed live and kept for at least 48 hours
JSONGzip, ETags and stable ids that never change
GET /v1/stream?events=all example

Quickstart

From a new key to live prices in a few minutes.

  1. Send us your server IPs. Requests are accepted only from addresses on your allowlist. We issue your sandbox key once they are added.
  2. Check your key. GET /v1/me returns your name and the sports on your plan.
  3. List what is in play with /v1/events?state=inplay, then fetch one event in full.
  4. Open a stream and keep your copy current from what it sends.
# Your key goes in a header, never in the URL.
export DIAMONDAPI_KEY="fg_test_…"
export BASE="https://api-sandbox.diamondapi.live"

curl -s -H "X-API-Key: $DIAMONDAPI_KEY" "$BASE/v1/me"
curl -s --compressed -H "X-API-Key: $DIAMONDAPI_KEY" "$BASE/v1/events?sport=cricket&state=inplay"

# A live stream: one connection, kept open. Sandbox keys list up to 5 event ids;
# a live key may use events=all.
curl -N -H "X-API-Key: $DIAMONDAPI_KEY" "$BASE/v1/stream?events=ev_k9wtucdybikxygkh,ev_zizcp33rpuucwacg"

Keys and IP allowlist

Every request needs both a valid key and a source address on your allowlist. A key on its own, from anywhere else, is refused.

HeaderValue
X-API-KeyYour key, for example fg_live_k7q2…. Keys are never accepted in the URL, where they would end up in logs.
Accept-Encodinggzip is supported and recommended: event snapshots shrink about tenfold.
  • A key is shown once, when it is issued. We keep only a hash of it and cannot show it again; if it is lost, we issue a new one.
  • Keys can overlap: ask for a second key, move your servers to it, then ask us to revoke the old one.
  • Keep keys on your servers. The API is built for server-to-server use, not for browsers or apps.

Sandbox and live

EnvironmentKeysBase URL
Sandboxfg_test_…https://api-sandbox.diamondapi.live
Livefg_live_…https://api.diamondapi.live

The sandbox carries real, current markets, so you can build against real behaviour: prices that move, markets that suspend, results that arrive. A key works only in its own environment, and the sandbox has no uptime commitment.

The sandbox is for building, not for running a site, so every sandbox key has these limits, whatever its plan says:

Sandbox limitValue
Odds delayPrices are 60 seconds old. Results are not delayed.
Open streams1 at a time
Events per stream5, listed by id (events=ev_a,ev_b). events=all needs a live key.
Requests2 per second

GET /v1/me shows the limits your key has.

Sports, events, markets

Four kinds of thing, each with an id that never changes. Store them as you receive them.

IdWhatExample
sportA sport, by name: cricket, soccer, tennis, basketball, boxing. You receive the sports on your plan.cricket
cp_…A competitionOne Day Internationals
ev_…An event: a match or a fixturePakistan v Sri Lanka
mk_…A market on that eventMatch Odds, Bookmaker, 10 over run PAK
rn_…A runner (selection) in that marketPakistan, The Draw, Yes

Event

FieldMeaning
stateupcoming before the start, inplay once it is live
startScheduled start, ISO 8601 UTC
updatedAtWhen any price in it last changed, in epoch milliseconds
marketsEvery open market. A market leaves the event when it is settled or closed.

Market

FieldMeaning
typematch_odds, bookmaker or fancy
formatHow to read its prices: decimal, rate, line or range. See Reading prices.
statusWhether it can be traded now. See Market status.
runners[]Each with back and lay ladders, best price first: {"price", "size"}
stalePresent and true when we have stopped receiving prices for it. The market is shown as SUSPENDED until prices flow again.

Reading prices

Markets quote prices in four ways. Always read format before using price and size.

decimalMatch odds, Yes/No, Odd/Even

Decimal odds and the amount available. A stake of 100 at 2.88 returns 288.

Runner
Back
Lay
Sri Lanka
2.88
3.00
Pakistan
1.49
1.52
rateBookmaker

Profit per 100 staked. A rate of 13 is decimal 1.13; a rate of 625 is 7.25. Decimal = 1 + rate / 100.

Runner
Back
Lay
England Lions
13
16
West Indies A
625
769
lineFancy: runs, wickets, totals

price is the line and size the rate. Back is Yes: wins if the result reaches the line. Lay is No: wins if it stays below.

10 over run NZ
No
Yes
Line · rate
91 · 110
91 · 90
Yes 100 at 91 wins 90 on 91+ runs. No 100 at 91 wins 100 below 91, else loses 110.
rangeKhado

A line with a width: Yes wins if the result is one of the width scores from price upward. size is the rate. Back only.

10 over Khado run SL
Yes
Width
From · rate
45 · 90
8
Wins on 45 to 52 runs. A stake of 100 wins 90.

Market status

StatusMeaning
OPENPrices are live.
SUSPENDEDTemporarily not tradeable: a goal, a wicket, a review, or our own feed check ("stale": true). Prices may still be shown; do not take bets on them.
BALL_RUNNINGCricket: the ball is in play. Not tradeable until it is dead.
CLOSEDFinished. A closed market leaves the event shortly after; its result follows on the results feed.

Your account

GET/v1/meWho you are and what you receive
200 OK
{
  "client": "Acme Sportsbook",
  "sports": ["cricket", "soccer", "tennis"],
  "limits": { "maxStreams": 5, "minPollMs": 1000, "maxEventsPerStream": 0, "delayMs": 0 },
  "serverTime": "2026-11-09T21:08:31Z"
}

limits are what your key may do: open streams, events per stream and how old prices are (delayMs). 0 means no limit or no delay.

Sports

GET/v1/sportsYour sports, with event counts
200 OK
{
  "sports": [
    { "sport": "cricket", "events": 17, "inplay": 3, "upcoming": 14 },
    { "sport": "soccer",  "events": 99, "inplay": 17, "upcoming": 82 },
    { "sport": "tennis",  "events": 143, "inplay": 11, "upcoming": 132 }
  ]
}

Competitions

GET/v1/competitions?sport=cricketCompetitions with events now
ParameterMeaning
sportOptional. One of your sports.
200 OK
{
  "competitions": [
    { "id": "cp_uumfx68hmdwne54u", "name": "One Day Internationals", "sport": "cricket", "events": 2 },
    { "id": "cp_snvzngyxyb44tc94", "name": "South Africa T20 Challenge", "sport": "cricket", "events": 3 }
  ]
}

Events

GET/v1/events?sport=&state=&competition=Every event you receive, without prices
ParameterMeaning
sportOptional. cricket, soccer, …
stateOptional. inplay or upcoming
competitionOptional. A cp_… id
200 OK
{
  "events": [
    {
      "id": "ev_k9wtucdybikxygkh",
      "name": "Pakistan v Sri Lanka",
      "sport": "cricket",
      "competition": { "id": "cp_uumfx68hmdwne54u", "name": "One Day Internationals" },
      "start": "2026-11-09T04:30:00Z",
      "state": "inplay",
      "inplay": true,
      "updatedAt": 1794258468524,
      "marketCount": 41
    }
  ]
}

One event, in full

GET/v1/events/{id}Every market and price

Responses carry an ETag: send it back as If-None-Match and you get 304 Not Modified while nothing has changed. X-Resume-Token lets you continue from this snapshot with /v1/changes.

200 OK · trimmed to three markets
{
  "id": "ev_k9wtucdybikxygkh",
  "name": "Pakistan v Sri Lanka",
  "sport": "cricket",
  "competition": { "id": "cp_uumfx68hmdwne54u", "name": "One Day Internationals" },
  "start": "2026-11-09T04:30:00Z",
  "state": "inplay",
  "inplay": true,
  "updatedAt": 1794258468524,
  "markets": [
    {
      "id": "mk_vf86825bva46g3qq", "name": "Match Odds",
      "type": "match_odds", "format": "decimal", "status": "OPEN", "inplay": true,
      "updatedAt": 1794258468524,
      "runners": [
        { "id": "rn_svsqaaiuqdfnetsf", "name": "Pakistan", "status": "OPEN",
          "back": [{ "price": 1.49, "size": 1525.4 }, { "price": 1.48, "size": 980 }],
          "lay":  [{ "price": 1.52, "size": 640.1 }] },
        { "id": "rn_jz7r79c78pm4y3dp", "name": "Sri Lanka", "status": "OPEN",
          "back": [{ "price": 2.88, "size": 257.53 }],
          "lay":  [{ "price": 3, "size": 25 }] }
      ]
    },
    {
      "id": "mk_3hb7tq2wc9xzv5ne", "name": "Bookmaker",
      "type": "bookmaker", "format": "rate", "status": "OPEN",
      "runners": [
        { "id": "rn_q3m8x2wte6kd7bpa", "name": "Pakistan", "status": "OPEN",
          "back": [{ "price": 48, "size": 100000 }], "lay": [{ "price": 52, "size": 100000 }] }
      ]
    },
    {
      "id": "mk_juc56fmwfmqx7gag", "name": "10 over run PAK",
      "type": "fancy", "format": "line", "status": "BALL_RUNNING",
      "runners": [
        { "id": "rn_p8vd2hsn4c6xqe9w", "name": "10 over run PAK", "status": "OPEN",
          "back": [{ "price": 61, "size": 90 }], "lay": [{ "price": 61, "size": 110 }] }
      ]
    }
  ]
}

Odds for several events

GET/v1/odds?events=ev_…,ev_…Up to 50 events in one call

The same objects as /v1/events/{id}, in the order you asked. Ids you do not receive, or that have ended, are listed in missing. next is a token for /v1/changes.

200 OK
{
  "events":  [ { "id": "ev_k9wtucdybikxygkh", "name": "Pakistan v Sri Lanka", "markets": [ … ] } ],
  "missing": ["ev_zizcp33rpuucwacg"],
  "next":    "9f2c41d0.18342"
}

Live stream

SSE/v1/stream?events=allOne connection, everything that moves

The fastest and cheapest way to stay current. You receive each event in full once, then only what changes: usually within a fraction of a second of the price moving.

ParameterMeaning
eventsall (default): every event you receive, including ones that start later. Or a list of up to 500 ev_… ids.

Messages

snapshot

An event in full, as from /v1/events/{id}. Sent for each event when you connect, and when a new event joins.

ready

The opening snapshots are done. From here on you receive changes.

market-update

{"event", "market"}: one market, whole, as it is now. Replace your copy of that market.

market-remove

{"event", "market"}: the market closed or was settled. Its result follows as a result.

event-update

The event's name, start or state changed (it went in play). Same fields as in /v1/events.

event-remove

{"id"}: the event has ended or is no longer offered. Drop it.

result

A market's result. See Results.

reconnect

We are restarting this server: reconnect now, with your last id.

error

{"code", "message"}, then the stream closes: for example your key was revoked or your stream allowance is used up.

Resuming after a disconnect

Every message has an id. Reconnect with the last one in the Last-Event-ID header and you receive exactly what you missed, results included. If it is too old to resume, you receive fresh snapshots instead, so your copy is always whole. Standard EventSource clients do this for you.

A comment line (: heartbeat) arrives every 15 seconds so you can tell a quiet market from a dead connection. If you hear nothing for 45 seconds, reconnect. The stream also sets retry: to spread reconnects out after a restart.

Tip

If your server falls behind, you are not sent every tick you missed: you get the latest state of each market. You never have to process a backlog of stale prices.

// npm install eventsource
import { EventSource } from 'eventsource'

const book = new Map()           // event id -> event, kept current
const es = new EventSource('https://api-sandbox.diamondapi.live/v1/stream?events=all', {
  fetch: (url, init) => fetch(url, { ...init, headers: { ...init.headers, 'X-API-Key': process.env.DIAMONDAPI_KEY } }),
})

es.addEventListener('snapshot', (m) => { const ev = JSON.parse(m.data); book.set(ev.id, ev) })
es.addEventListener('market-update', (m) => {
  const { event, market } = JSON.parse(m.data)
  const ev = book.get(event); if (!ev) return
  const i = ev.markets.findIndex((x) => x.id === market.id)
  i >= 0 ? (ev.markets[i] = market) : ev.markets.push(market)
})
es.addEventListener('market-remove', (m) => {
  const { event, market } = JSON.parse(m.data)
  const ev = book.get(event); if (ev) ev.markets = ev.markets.filter((x) => x.id !== market)
})
es.addEventListener('event-remove', (m) => book.delete(JSON.parse(m.data).id))
es.addEventListener('result', (m) => settle(JSON.parse(m.data)))   // your settlement
// EventSource reconnects by itself and sends Last-Event-ID.

Changes, for pollers

GET/v1/changes?since={token}&events=What a stream would have sent since your last call

If you cannot hold a stream open, take a snapshot with /v1/odds, keep its next token, and call /v1/changes?since= with it every second or two. You receive the latest state of each market that changed and a new next. A token too old to continue answers 410 resync: take a fresh snapshot and carry on from its token.

200 OK
{
  "changes": [
    { "type": "market-update", "data": { "event": "ev_k9wtucdybikxygkh", "market": { "id": "mk_vf86825bva46g3qq", … } } },
    { "type": "event-remove",  "data": { "id": "ev_qfsu3ku7736ttn69" } }
  ],
  "next": "9f2c41d0.18390"
}

How results work

Every result is recorded in order, kept for at least 48 hours, and reaches you three ways: pushed on your stream, listed by /v1/results, and looked up by market or event.

ActionMeaningWhat to do
SETTLEDThe market's first resultSettle bets
CORRECTEDThe result changed: the earlier one was wrongRe-settle to the new result
VOIDEDThe market was cancelled, or corrected to voidRefund stakes
UNSETTLEDThe result was withdrawn; a new one will followHold settlement

A market can have several entries over time. The one with the highest seq is current. Each entry carries a winner (winner), a number (result, for line and range markets) or neither (void).

A result entry
{
  "seq": 17327,
  "action": "SETTLED",
  "sport": "cricket",
  "event":  { "id": "ev_k9wtucdybikxygkh", "name": "Pakistan v Sri Lanka", "start": "2026-11-09T04:30:00Z" },
  "market": { "id": "mk_ywxwfni42227wbyh", "name": "To Win The Toss", "type": "bookmaker" },
  "winner": { "id": "rn_jz7r79c78pm4y3dp", "name": "Sri Lanka" },
  "settledAt": "2026-11-09T04:23:43Z",
  "at": "2026-11-09T04:23:45Z"
}
Keeping

Results are kept for 7 days and guaranteed for at least 48 hours after settlement. Collect them well within that window.

Results since your last call

GET/v1/results?since=0&limit=500Every entry, in order
ParameterMeaning
sinceThe next from your previous call; 0 for everything kept
limitUp to 1000 per page; default 500
200 OK
{
  "results": [ { "seq": 17326, … }, { "seq": 17327, … } ],
  "next": 17327,
  "more": false
}

Keep next and pass it as since next time: you never miss or repeat an entry. While more is true, call again at once. If since is older than what we keep, you get 410 resync: start again from since=0.

One market's result

GET/v1/markets/{id}/resultThe current result of a market
200 OK
{
  "market": { "id": "mk_r4nd8w2qk6t3pyvh", "name": "29.3 over run NZ", "type": "fancy" },
  "event":  { "id": "ev_zizcp33rpuucwacg", "name": "New Zealand v India" },
  "status": "SETTLED",
  "result": 91,
  "settledAt": "2026-11-07T05:27:16Z",
  "seq": 17001,
  "corrected": false
}

status is SETTLED, VOIDED or PENDING (no result yet, or withdrawn). corrected is true once a result has been changed. A market that is not yours, or whose result is older than we keep, answers 404.

All results of an event

GET/v1/events/{id}/resultsEvery market, settled or pending
200 OK
{
  "event": "ev_k9wtucdybikxygkh",
  "markets": [
    { "market": { "id": "mk_ywxwfni42227wbyh", "name": "To Win The Toss", "type": "bookmaker" },
      "status": "SETTLED", "winner": { "id": "rn_jz7r79c78pm4y3dp", "name": "Sri Lanka" }, … },
    { "market": { "id": "mk_vf86825bva46g3qq", "name": "Match Odds", "type": "match_odds" },
      "status": "PENDING", "corrected": false }
  ]
}

Limits and allowances

Your plan sets how much you can use. /v1/me shows your limits.

LimitWhen you reach it
Requests per second429 rate_limited with Retry-After. A stream counts as one request however long it stays open.
Open streams429 too_many_streams for a new stream. Close one first.
Events per stream400 when you name more than your plan allows.
Daily requests, monthly stream hours and dataDepends on your plan: refused with 429 quota_exceeded, served with a delay, or billed as overage. Daily counts reset at 00:00 UTC, monthly ones on the 1st.

Every response on a plan with allowances carries what is left, for example:

Response header
X-Quota-Remaining: requests-today=187234; stream-minutes-month=118400

Some plans receive odds after a short delay. The delay applies to prices only; results are never delayed.

Errors

Errors come with a code you can act on and a sentence you can log. Every response carries X-Request-Id: quote it when you contact us.

403 Forbidden
{ "code": "forbidden", "message": "request IP is not on this client's allowlist" }
StatusCodeMeaning
400bad_requestA parameter is wrong; the message says which.
401unauthorizedNo key, or a key we do not know.
403forbiddenThe IP is not on your allowlist, the key is for the other environment, or the account is suspended.
404not_foundNo such id, or not one of yours.
410resyncYour token is too old to continue: take a fresh snapshot.
429rate_limitedToo many requests per second. Wait for Retry-After.
429too_many_streamsAll your stream slots are in use.
429quota_exceededAn allowance on your plan is used up.
503startingThe server is loading the feed after a restart. Retry in a few seconds.
503maintenancePlanned maintenance; the message says until when.

Good practice

  • Stream, do not poll. One stream replaces hundreds of requests a minute and delivers changes sooner.
  • Treat updates as replacements. A market-update carries the whole market: replace your copy, do not merge it.
  • Trade only on OPEN. Close your book on SUSPENDED, BALL_RUNNING or "stale": true.
  • Read format before you use a price. A bookmaker rate of 13 is not decimal 13.
  • Settle on result entries, and handle CORRECTED and VOIDED as well as SETTLED.
  • Send Accept-Encoding: gzip and If-None-Match on REST calls.
  • Store our ids, not names. Names can change; ids never do.