SM
Statsmatics Market EngineDeveloper documentation
Contract1.0.0
Integration environmentPredictions currently use the deterministic dummy NumLab provider. The public contract and queue flow are production-shaped.

API reference · Contract 1.0.0

Pricing intelligence,
built for integration.

Submit Betfair tennis market snapshots, receive an asynchronous prediction job, and retrieve normalized pre-match closing prices and model probabilities.

Base URL

https://marketengine.statsmatics.com
TransportHTTPS + JSONAuthenticationManaged API keyExecutionAsynchronous queueScopeTennis singles

Quick start

The client-facing flow stays small. The service owns match mapping, queueing, retries, validation, and response normalization.

01

Get credentials

Your API key is issued once from the private operations console.

02

Submit a snapshot

Send the documented JSON closing-market history.

03

Poll the job

Read the status URL until the job is COMPLETED or FAILED.

No recommendation output

The API returns pricing and probabilities only. It does not produce BET / NO_BET decisions.

Authentication

Send the key in every capabilities, submission, and polling request. Keep it server-side and never embed it in browser code.

X-API-Key: tpa_live_your_key

Keys are client-scoped.Your key can only retrieve jobs created for the same client.

Rotation is immediate.A rotated or revoked key stops authenticating without a grace period.

POST /v1/betfair/prediction-jobs

Send the documented JSON body with your managed API key. Every accepted submission creates a new job.

POST/v1/betfair/prediction-jobs202 Accepted
curl --request POST 'https://marketengine.statsmatics.com/v1/betfair/prediction-jobs' \
  --header 'X-API-Key: tpa_live_your_key' \
  --data '{
  "event": {
    "event_id": "35321298",
    "event_name": "Guillen Meza v Mi Damas"
  },
  "players": [
    { "position": "PLAYER_1", "name": "Alvaro Guillen Meza" },
    { "position": "PLAYER_2", "name": "Miguel Damas" }
  ],
  "market_entries": [
    {
      "market_id": "1.254640336",
      "market_type": "MATCH_ODDS",
      "market_name": "Match Odds",
      "clk": "4491333114",
      "pt": 1772568507586,
      "market_time": "2026-03-03T20:00:00.000Z",
      "timezone": "UTC",
      "in_play": false,
      "status": "OPEN",
      "selections": [
        { "selection_id": "15745245", "name": "Alvaro Guillen Meza", "line": null, "ltp": 1.47 },
        { "selection_id": "15313770", "name": "Miguel Damas", "line": null, "ltp": 3.05 }
      ]
    },
    {
      "market_id": "1.254640336",
      "market_type": "MATCH_ODDS",
      "market_name": "Match Odds",
      "clk": "4491362894",
      "pt": 1772568624002,
      "market_time": "2026-03-03T20:00:00.000Z",
      "timezone": "UTC",
      "in_play": true,
      "status": "OPEN",
      "selections": []
    }
  ],
  "requested_markets": [
    { "type": "MONEYLINE", "periods": ["MATCH", "SET_1", "SET_2"] },
    { "type": "HANDICAP", "periods": ["MATCH", "SET_1", "SET_2"] },
    { "type": "TOTALS", "periods": ["MATCH", "SET_1", "SET_2"] }
  ],
  "metadata": {
    "event_type": "Challenger Men Singles",
    "tournament_name": "Brasilia 2",
    "round": "1/16-finals",
    "surface": "Clay",
    "indoor": false,
    "gender": "Male"
  }
}'

Accepted response

{
  "job_id": "job_d6faa1ae",
  "prediction_id": null,
  "status": "QUEUED",
  "stage": "VALIDATION_COMPLETE",
  "status_url": "/v1/prediction-jobs/job_d6faa1ae",
  "data": null,
  "error": null,
  "request_id": "req_93cce4c2",
  "accepted_at": "2026-07-31T09:30:00Z"
}

Request schema

The request body is strictly validated. All identifiers are strings, Betfair publish time stays in pt, human-readable timestamps use UTC, and missing optional values are explicit JSON nulls.

JSON pathTypeRequiredRule
event.event_idstringYesBetfair event identifier.
event.event_namestringYesNon-empty event label.
playersarray[2]YesExactly one PLAYER_1 and one PLAYER_2.
players[].namestringYesUsed with the UTC match date for mapping.
market_entriesarrayYesMust include the first in_play=true marker.
market_entries[].ptint64YesBetfair publish time as Unix epoch milliseconds.
market_entries[].clkstringYesOpaque Betfair stream cursor; never parse it as time.
market_entries[].statusenumYesOPEN, SUSPENDED, or CLOSED.
market_entries[].selectionsarrayYesMay be empty on the first in-play marker.
selections[].ltpnumber | nullNoCarried-forward Betfair rc[].ltp above 1.0, or null.
requested_marketsarray | nullNoDefaults to all entitled markets and periods.
metadataobject | nullNoSupporting mapping context; not a required key.

Normalize Betfair deltas before submission

MarketChangeMessages are incremental. Apply each mc update to your cached market state, then emit a normalized snapshot for every changed market. A definition-only history contains no source odds until a runner change supplies ltp.

Betfair stream pathRequest pathRule
message.clkmarket_entries[].clkOpaque cursor; preserve as a string.
message.ptmarket_entries[].ptPublish time in Unix epoch milliseconds.
mc[].idmarket_entries[].market_idStringify the Betfair market identifier.
marketDefinitionmarket_type, market_name, market_time, timezone, in_play, statusCarry the latest definition forward across deltas.
marketDefinition.eventId / eventNameevent.event_id / event.event_nameThe normalized request contains one event.
marketDefinition.runners[] + rc[]selections[]Join by runner id and handicap; carry the latest valid LTP forward.
rc[].ltpselections[].ltpBetfair source price; null when absent or invalid.
Start and closing-price boundary

The earliest normalized entry with in_play=true establishes match start using its pt. The scheduled market_time never replaces that observation.

Market selection

Specify the market families and periods required by your integration, or send null to use every output enabled for your client.

MO

MONEYLINE

Win pricing for each player

SelectionsNo line · PLAYER_1 / PLAYER_2Supported periodsMATCH · SET_1 · SET_2
HA

HANDICAP

Spread pricing around a modelled line

SelectionsNumeric line · PLAYER_1 / PLAYER_2Supported periodsMATCH · SET_1 · SET_2
TO

TOTALS

Over/under pricing around a modelled line

SelectionsNumeric line · OVER / UNDERSupported periodsMATCH · SET_1 · SET_2

Explicit selection

Request only the outputs your client needs.

"requested_markets": [
  {
    "type": "MONEYLINE",
    "periods": ["MATCH", "SET_1", "SET_2"]
  },
  {
    "type": "HANDICAP",
    "periods": ["MATCH", "SET_1", "SET_2"]
  },
  {
    "type": "TOTALS",
    "periods": ["MATCH", "SET_1", "SET_2"]
  }
]

Enabled defaults

Use every market and period enabled for your client.

"requested_markets": null

GET /v1/prediction-jobs/{'{job_id}'}

The poll endpoint returns HTTP 200 for a found job, including terminal failures. Use the response state and error object—not the HTTP status alone.

curl 'https://marketengine.statsmatics.com/v1/prediction-jobs/job_d6faa1ae' \
  --header 'X-API-Key: tpa_live_your_key'
1QUEUED2PROCESSING3COMPLETEDor!FAILED

Completed response

{
  "job_id": "job_d6faa1ae",
  "prediction_id": "pred_218f1619",
  "status": "COMPLETED",
  "stage": "COMPLETED",
  "data": {
    "match": { "event_id": "35321298", "match_date": "2026-03-03" },
    "markets": [{
      "type": "MONEYLINE",
      "period": "MATCH",
      "line": null,
      "source_odds_available": true,
      "selections": [
        {
          "selection": "PLAYER_1",
          "name": "Alvaro Guillen Meza",
          "source_price_timestamp_utc": "2026-03-03T20:08:27.586Z",
          "model_probability": 0.684,
          "decimal_price": 1.462
        },
        {
          "selection": "PLAYER_2",
          "name": "Miguel Damas",
          "source_price_timestamp_utc": "2026-03-03T20:08:27.586Z",
          "model_probability": 0.316,
          "decimal_price": 3.165
        }
      ]
    }]
  },
  "error": null,
  "request_id": "req_93cce4c2"
}
Model price, not source LTP

The public decimal_price is generated by NumLab. The Betfair ltp is retained privately for audit and is not returned to the client.

Stateprediction_iddataerror
QUEUEDnullnullnull
PROCESSINGnullnullnull
COMPLETEDstringobjectnull
FAILEDnullnullobject
REJECTEDnullnullobject

Error responses

Submission errors use the relevant HTTP status. Jobs that fail after acceptance return status: FAILED with a structured public error object.

CodeWhen it occursRetry guidance
VALIDATION_ERRORRequest does not satisfy contract 1.0.0.No
RATE_LIMITEDPer-minute submit or read limit was reached.Yes
QUOTA_EXCEEDEDDaily or queued-job quota was reached.After reset
CLOSING_NOT_REACHEDThe first in-play marker is missing.Yes
MATCH_UNMAPPEDNo canonical match passed mapping.No
MATCH_AMBIGUOUSMore than one candidate remained.No
NUMLAB_TIMEOUTThe private prediction worker timed out.Yes
NUMLAB_ERRORThe prediction worker returned an error.Varies
NUMLAB_INVALID_RESPONSEThe worker response failed validation.Yes
INTERNAL_ERRORAn unexpected service failure occurred.Yes
{
  "code": "VALIDATION_ERROR",
  "message": "The request does not satisfy the defined contract.",
  "retryable": false,
  "fields": [
    { "path": "market_entries", "reason": "The first in-play marker is required." }
  ]
}