Foreline/Pricing Docs/API reference
API · v1 · referenceEndpoints, parameters, fields
Base URL https://api.foreline.io. Every request carries
X-Foreline-Key. New to the API? Start with the
overview and quickstart.
What holds for every call
- Base URL:
https://api.foreline.io, HTTPS only, version in the path (/v1/…). - Auth:
X-Foreline-Key: <key>on every request, including the SSE stream. No query-string keys. - Timestamps are ISO-8601 in UTC with a
Zsuffix (2026-07-24T18:41:07Z), both in parameters and in responses. - Identifiers:
event_idis a Foreline identifier, stable for the life of the event. A mapping to your own event IDs is provided as part of onboarding. - Prices are probabilities in
[0, 1]; band widths are in percentage points (pp). Decimal odds are never returned — you apply your own margin. - Compatibility: within
v1changes are additive. New fields may appear; existing fields keep their meaning. Ignore fields you do not know. - Responses are
application/json, UTF-8, except/v1/streamwhich istext/event-stream.
Health
Confirms that the key is valid, the service is up and your clock agrees with ours. Takes no parameters. Use it as your integration smoke test and as a liveness probe.
{ "status": "ok", "server_ts": "2026-07-24T18:41:07Z", "version": "v1" }
Surface
One endpoint with four modes. The mode is selected by which parameter you
pass; the parameters are mutually exclusive except asof, which qualifies
event_id.
No parameters: a summary of what is live right now — the events currently covered, their main lines and their freshness. Use it to discover coverage, not to read ladders.
Parameters
| Parameter | Type | Mode | Description |
|---|---|---|---|
event_id | string | point · 60/min | The full ladder for one event: main_line, fair_market,
vig / vig_tier and every rung with its band. |
asof | timestamp | point · 60/min | The surface as it stood at that instant, not as later revised. Combine with
event_id. This is the call to use for backtests and for settling a
dispute about what was published when. |
since | timestamp | bulk · 4/min | Bulk delta: every surface that changed since that cursor, each returned in full.
The response carries next_since — pass it on the following call. |
Response — surface object
| Field | Type | Description |
|---|---|---|
event_id | string | Foreline event identifier. |
sport | string | football (further sports appear here as they launch). |
market | string | handicap, total or 1x2. |
main_line | number | The line the reference market currently treats as main. |
fair_market | number | De-vigged fair probability at the main line. |
vig_tier | Exact removed margin (decimal) plus vig_tier band (low/mid/high) and max_win limit with limit_tier. Redistribution of raw quotes is prohibited under all plans. | Margin measured on the reference quote before removal — what we took out. |
rungs | array | The ladder. See below. |
data_cadence_s | integer | Refresh interval that applies to this object right now: 60, 300 or 900. |
updated_ts | timestamp | When this surface was last recomputed. |
next_since | timestamp | Bulk mode only. Cursor for your next ?since= call. |
Response — rungs[]
| Field | Type | Description |
|---|---|---|
line | number | The handicap or total this rung prices. |
p_home | number | Handicap markets: fair probability that home covers this line. |
p_over | number | Totals markets: fair probability of the over at this line. |
band_pp | number | Width of the 80% uncertainty band, in percentage points. |
src | string | quote — derived from a price the reference market actually quoted. table — extended from the ladder tables because the reference does not quote this rung. |
curl -s "https://api.foreline.io/v1/surface?event_id=EVT_8F3A21" \
-H "X-Foreline-Key: $FORELINE_KEY"
{
"event_id": "EVT_8F3A21",
"sport": "football",
"market": "handicap",
"main_line": -0.5,
"fair_market": 0.5312,
"vig": 0.0214,
"data_cadence_s": 60,
"updated_ts": "2026-07-24T18:41:07Z",
"rungs": [
{ "line": -1.0, "p_home": 0.3874, "band_pp": 1.6, "src": "quote" },
{ "line": -0.75, "p_home": 0.4593, "band_pp": 1.9, "src": "table" },
{ "line": -0.5, "p_home": 0.5312, "band_pp": 1.4, "src": "quote" }
]
}
Values are illustrative. History depth available to ?asof=
follows your contract; the archive reaches back to 2022 for football.
Line events
Main-line changes as typed facts, in detection order. This is the catch-up channel: after a restart, replay from your last cursor and you have lost nothing. For live delivery use the stream or a webhook.
| Field | Type | Description |
|---|---|---|
since | timestamp | Parameter. Return events detected after this instant. |
event_id | string | The event whose line moved. |
market | string | Market the move happened in. |
from / to | number | Previous and new main line. |
detected_ts | timestamp | When we observed the change. Detection latency is p50 1 min, p95 2 min. |
next_since | timestamp | Cursor for your next call. |
Stream
A held HTTP connection emitting text/event-stream. Currently one
event type, line_change, with the same payload as
/v1/line-events. On reconnect, backfill the gap from
/v1/line-events?since= using the last detected_ts you processed.
curl -N "https://api.foreline.io/v1/stream" \
-H "X-Foreline-Key: $FORELINE_KEY"
event: line_change
data: {"event_id":"EVT_8F3A21","market":"handicap","from":-0.5,"to":-0.75,
"detected_ts":"2026-07-24T18:41:07Z","data_cadence_s":60}
Webhooks
Push delivery of line events and alerts to an endpoint you control, signed so you can prove the payload is ours before acting on it.
- Signature:
X-Foreline-Signature: sha256=<hex>— an HMAC-SHA256 over the exact raw request body using the shared secret issued with your key. Compute over the bytes you received, not over re-serialised JSON, and compare in constant time. - Replay protection:
X-Foreline-Timestampcarries the send time; reject deliveries whose timestamp is outside your tolerance window. - Idempotency:
X-Foreline-Deliveryis unique per delivery attempt. Retries reuse it, so deduplicate on it. - Retries: non-2xx responses are retried with backoff. Return 2xx as soon as you have persisted the payload; do your processing afterwards.
Score
Scores an account on closing-line value against our fair close, on the exact lines it took — including lines the reference market never quoted, which are repriced from the ladder tables. Input is an anonymised account identifier and bet parameters; no personal data is accepted or required.
Request body
| Field | Type | Description |
|---|---|---|
account_id | string | Your anonymised account identifier. Opaque to us; used only to group bets. |
bets[] | array | The bets to score. |
bets[].event_id | string | Foreline event identifier. |
bets[].market | string | handicap, total or 1x2. |
bets[].line | number | The line taken — any rung, not only the main one. |
bets[].side | string | Side of the bet, e.g. home, away, over, under. |
bets[].price | number | Decimal odds the customer received. |
bets[].stake | number | Stake, in your accounting currency. |
bets[].placed_ts | timestamp | When the bet was accepted. |
Response
| Field | Type | Description |
|---|---|---|
account_id | string | Echoed back. |
n_bets | integer | How many bets could be repriced. A first call is typically meaningful from 10–20 bets. |
score | number | The combined account score. |
components.clv | number | How far the account beats our fair close — the magnitude component. |
components.signed | number | How consistently the account is on the right side — the sign component, robust to a few large outliers. |
flag | string | Raised only when both components agree. Family-wise false-positive rate measured at 1.32% against a 2% budget. |
band_pp | number | Uncertainty carried through from the repricing of the lines this account took. |
scored_ts | timestamp | When the score was produced. |
Flag thresholds and the flag vocabulary are set per contract during onboarding, so the score can be tuned to your appetite without changing your integration. Bets on events outside your scope are reported as unscored rather than silently dropped.
Alerts
Gap warnings — where your posted price has drifted from fair — together with
their lifecycle state. Filter by state, or pass since to page
through transitions.
| Field | Type | Description |
|---|---|---|
state | string | active — open warning. confirmed — the market
subsequently moved as the gap implied. withdrawn — the gap closed
without a move. Withdrawn alerts stay in the record. |
since | timestamp | Parameter. Return alerts whose state changed after this instant. |
alert_id | string | Stable across the whole lifecycle of the alert. |
event_id, market, line | — | What the warning is about. |
gap_pp | number | Distance between your price and fair, in percentage points. |
band_pp | number | Band around fair at that rung — read the gap against it. |
opened_ts / resolved_ts | timestamp | Lifecycle timestamps; the pair gives the lead time, computed from the record. |
Comparing your price with fair requires your prices. They arrive over the integration agreed during onboarding; nothing is inferred about your book from third-party sources.
Usage
Your consumption against the quotas on the key: requests this minute and
this day, the rpm and rpd ceilings, and per-class counters for bulk, point and events. Poll it
on a schedule and alert on your own headroom rather than discovering it as
429s.
Getting a key (self-serve plans): after Stripe checkout you are redirected to a one-time page that shows the raw key exactly once — we keep only its sha256. Lost keys are reissued by support. A minimal account page (key → plan, limits, live usage) is at /v1/portal.
Status codes
| Status | Meaning | Retry? |
|---|---|---|
401 | Missing, malformed or revoked X-Foreline-Key. |
No — fix the header. |
403 | Authenticated, but the call is outside the scope of your contract: a sport, product or history depth you do not hold. Returned instead of an empty result so “not permitted” never looks like “nothing there”. | No — talk to us if the scope is wrong. |
429 | Per-class limit or account quota exceeded. The response
carries Retry-After in seconds. |
Yes — after Retry-After. |
Error bodies carry a machine-readable error code and a
human-readable message. Branch on the status and the code, never on the message
text.
Three call classes, shaped by the data cadence
The data changes once a minute at its fastest, so polling faster returns the
same bytes with the same updated_ts. The limits below are set around that, not
around packaging.
| Class | Limit | Endpoints |
|---|---|---|
| bulk | 4 / min | GET /v1/surface?since= |
| point | 60 / min | GET /v1/surface (summary, ?event_id=, ?asof=),
POST /v1/score, GET /v1/usage |
| events | 12 / min | GET /v1/line-events, GET /v1/alerts |
- Per-client quotas sit on top of the class limits: an rpm (requests per minute) and
an rpd (requests per day) ceiling on the key, both readable from
GET /v1/usage. - Schedule off
data_cadence_s. It is returned on every object and tells you when a new value can exist. A poller built on it stays correct as events approach kick-off and the cadence tightens from 15 min to 5 min to 60 s. - The stream is not rate-limited per message — it is a persistent connection. If you need everything as it happens, hold the stream rather than raising your poll rate.
Fields worth understanding before you build on them
| Term | Meaning |
|---|---|
fair_market | The de-vigged probability at the main line — the reference market's price with the margin removed by a documented methodology. |
vig_tier | Exact removed margin (decimal) plus vig_tier band (low/mid/high) and max_win limit with limit_tier. Redistribution of raw quotes is prohibited under all plans. |
band_pp | Width of the 80% uncertainty band on a rung, in percentage points. Validated coverage is 78–80% at a nominal 80%. |
src | quote: the rung is derived from a price the
reference actually quoted. table: the rung is extended from the ladder
tables. Bands are typically wider on table rungs — as they should be. |
data_cadence_s | How often this object can change right now: 60 s inside 24 h to kick-off, 300 s from 24–96 h, 900 s beyond. |
next_since | Cursor returned by delta calls. Pass it back unchanged; do not construct it from clocks on your side. |
| pp | Percentage points — an absolute difference between two probabilities, never a relative percentage. |
Request a trial key
Keys are issued per contract. Tell us which products, sports and history depth you need.
We usually reply within one business day. Pricing is quoted per scope — ask and we will send terms.
Foreline