Foreline/Pricing Docs/API reference

API · v1 · reference

Endpoints, parameters, fields

Base URL https://api.foreline.io. Every request carries X-Foreline-Key. New to the API? Start with the overview and quickstart.

Conventions

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 Z suffix (2026-07-24T18:41:07Z), both in parameters and in responses.
  • Identifiers: event_id is 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 v1 changes 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/stream which is text/event-stream.
Service

Health

GET/v1/health no rate class · no scope required

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" }
Fair-Price Surface

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.

GET/v1/surface point · 60/min

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

ParameterTypeModeDescription
event_idstringpoint · 60/min The full ladder for one event: main_line, fair_market, vig / vig_tier and every rung with its band.
asoftimestamppoint · 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.
sincetimestampbulk · 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

FieldTypeDescription
event_idstringForeline event identifier.
sportstringfootball (further sports appear here as they launch).
marketstringhandicap, total or 1x2.
main_linenumberThe line the reference market currently treats as main.
fair_marketnumberDe-vigged fair probability at the main line.
vig_tierExact 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.
rungsarrayThe ladder. See below.
data_cadence_sintegerRefresh interval that applies to this object right now: 60, 300 or 900.
updated_tstimestampWhen this surface was last recomputed.
next_sincetimestampBulk mode only. Cursor for your next ?since= call.

Response — rungs[]

FieldTypeDescription
linenumberThe handicap or total this rung prices.
p_homenumberHandicap markets: fair probability that home covers this line.
p_overnumberTotals markets: fair probability of the over at this line.
band_ppnumberWidth of the 80% uncertainty band, in percentage points.
srcstringquote — 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 Radar

Line events

GET/v1/line-events?since= events · 12/min

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.

FieldTypeDescription
sincetimestampParameter. Return events detected after this instant.
event_idstringThe event whose line moved.
marketstringMarket the move happened in.
from / tonumberPrevious and new main line.
detected_tstimestampWhen we observed the change. Detection latency is p50 1 min, p95 2 min.
next_sincetimestampCursor for your next call.
Line Radar

Stream

GET/v1/stream server-sent events · persistent connection

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}
Line Radar

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-Timestamp carries the send time; reject deliveries whose timestamp is outside your tolerance window.
  • Idempotency: X-Foreline-Delivery is 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.
Player Radar

Score

POST/v1/score point · 60/min

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

FieldTypeDescription
account_idstringYour anonymised account identifier. Opaque to us; used only to group bets.
bets[]arrayThe bets to score.
bets[].event_idstringForeline event identifier.
bets[].marketstringhandicap, total or 1x2.
bets[].linenumberThe line taken — any rung, not only the main one.
bets[].sidestringSide of the bet, e.g. home, away, over, under.
bets[].pricenumberDecimal odds the customer received.
bets[].stakenumberStake, in your accounting currency.
bets[].placed_tstimestampWhen the bet was accepted.

Response

FieldTypeDescription
account_idstringEchoed back.
n_betsintegerHow many bets could be repriced. A first call is typically meaningful from 10–20 bets.
scorenumberThe combined account score.
components.clvnumberHow far the account beats our fair close — the magnitude component.
components.signednumberHow consistently the account is on the right side — the sign component, robust to a few large outliers.
flagstringRaised only when both components agree. Family-wise false-positive rate measured at 1.32% against a 2% budget.
band_ppnumberUncertainty carried through from the repricing of the lines this account took.
scored_tstimestampWhen 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.

Line Radar

Alerts

GET/v1/alerts events · 12/min

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.

FieldTypeDescription
statestring 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.
sincetimestampParameter. Return alerts whose state changed after this instant.
alert_idstringStable across the whole lifecycle of the alert.
event_id, market, lineWhat the warning is about.
gap_ppnumberDistance between your price and fair, in percentage points.
band_ppnumberBand around fair at that rung — read the gap against it.
opened_ts / resolved_tstimestampLifecycle 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.

Service

Usage

GET/v1/usage point · 60/min

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.

Errors

Status codes

StatusMeaningRetry?
401Missing, malformed or revoked X-Foreline-Key. No — fix the header.
403Authenticated, 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.
429Per-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.

Rate limits

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.

ClassLimitEndpoints
bulk4 / minGET /v1/surface?since=
point60 / min GET /v1/surface (summary, ?event_id=, ?asof=), POST /v1/score, GET /v1/usage
events12 / 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.
Glossary

Fields worth understanding before you build on them

TermMeaning
fair_marketThe de-vigged probability at the main line — the reference market's price with the margin removed by a documented methodology.
vig_tierExact 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_ppWidth of the 80% uncertainty band on a rung, in percentage points. Validated coverage is 78–80% at a nominal 80%.
srcquote: 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_sHow 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_sinceCursor returned by delta calls. Pass it back unchanged; do not construct it from clocks on your side.
ppPercentage points — an absolute difference between two probabilities, never a relative percentage.

← Back to the overview

Access

Request a trial key

Keys are issued per contract. Tell us which products, sports and history depth you need.

Email us

We usually reply within one business day. Pricing is quoted per scope — ask and we will send terms.

Request trial