# Opono simulation API Simulate football matches before kick-off. Base URL: https://api.opono.com/v1; alternative: https://opono.com/api/v1. Authentication: Authorization: Bearer sk_live_… or sk_test_…; create a key at https://opono.com/developers. Plus: £20/month for 100,000 simulated matches · 100 credits each billing month. Pro: £200/month for 2,500,000 simulated matches · 2,500 credits each billing month. Available to customers in the United Kingdom only. The price shown is the total you pay. 1 credit = 1,000 simulated matches; default 1,000 simulated matches · 1 credit, cached or fresh. Credits are reserved at request, settled on delivery, and released on failure. Test output is synthetic and costs zero credits; Free has no API access. ## Launch endpoints - POST /v1/simulations - GET /v1/simulations/jobs/{job_id} - POST /v1/simulations/batch - GET /v1/simulations/batches/{batch_id} - GET /v1/fixtures - GET /v1/fixtures/{fixture_id}/squads - GET /v1/fixtures/{fixture_id}/expected-lineup - POST /v1/simulations/quote - GET /v1/credits - GET /v1/usage ## Documentation - [Overview](https://opono.com/docs) - [Quickstart](https://opono.com/docs/quickstarts/rest) - [Authentication](https://opono.com/docs/authentication) - [Simulations](https://opono.com/docs/simulations) - [Choosing lineups](https://opono.com/docs/choosing-lineups) - [Credits and billing](https://opono.com/docs/credits-and-usage) - [Rate limits](https://opono.com/docs/limits) - [Errors](https://opono.com/docs/errors) - [Fixtures](https://opono.com/docs/fixtures) - [Squads](https://opono.com/docs/reference/squads) - [Opono Expected Lineup](https://opono.com/docs/reference/expected-lineup) - [Simulations](https://opono.com/docs/reference/simulations) - [Jobs](https://opono.com/docs/jobs) - [Errors object](https://opono.com/docs/reference/errors) - [Changelog](https://opono.com/docs/changelog) - [Support](https://opono.com/docs/support) - [Plans](https://opono.com/pricing) - [OpenAPI](https://opono.com/docs/openapi.json) # Overview Use the Opono API to simulate football matches before kick-off. ## Start building 1. Create a test key in [Developers](/developers) with an active Plus or Pro plan. 2. Follow the [Quickstart](/docs/quickstarts/rest) to send your first request. 3. Switch to a live key and select a match from [Fixtures](/docs/fixtures). ## Base URLs ```text https://api.opono.com/v1 https://opono.com/api/v1 ``` Both base URLs accept the same path suffixes and Bearer authentication. ## Credits 1 credit = 1,000 simulated matches (the draws parameter), charged on delivery. Choose 100 to 1,000,000 simulated matches per fixture; the default is 1,000 simulated matches · 1 credit. Available to customers in the United Kingdom only. The price shown is the total you pay. See [Credits and billing](/docs/credits-and-usage) for plans and reservation rules. ## API resources - [Fixtures](/docs/fixtures): find matches and check their state. - [Squads](/docs/reference/squads): free player IDs, names and positions. - [Opono Expected Lineup](/docs/reference/expected-lineup): expected XI and bench, one credit for both sides. - [Simulations](/docs/reference/simulations): create, quote, or batch simulations. - [Jobs](/docs/jobs): collect results and check batch progress. - [Errors object](/docs/reference/errors): look up error fields and codes. # Quickstart Send a test request before you use live credits. ## 1. Create a key With an active Plus or Pro plan, create a test key in [Developers](/developers). ```bash export OPONO_API_KEY='sk_test_REPLACE_WITH_YOUR_KEY' ``` Test output is synthetic and costs zero credits; Free has no API access. ## 2. Simulate a match ```bash curl --request POST 'https://api.opono.com/v1/simulations' \ --header "Authorization: Bearer $OPONO_API_KEY" \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: match-example-1' \ --data '{"home":"Everton","away":"Chelsea","date":"2026-10-18","draws":1000}' ``` With a live key, this request reserves 1,000 simulated matches · 1 credit and settles the credits on delivery. ## 3. Read the response ```json { "object": "simulation", "fixture_id": 19609874, "state": "provisional", "data_version": "2026-10-13:predicted:b167bda637016662e2585f872b25dea30f954c9a56bd5a345ff965b1663eee10", "livemode": false, "credits_charged": 0, "simulation": { "schema": "opono.simulation.v1", "basis": { "as_of": "2026-10-12T18:30:00Z", "data_version": "2026-10-13:predicted:b167bda637016662e2585f872b25dea30f954c9a56bd5a345ff965b1663eee10" }, "result": { "home": 0.47, "draw": 0.26, "away": 0.27, "calibrated": true }, "goals": { "expected": { "home": 1.69, "away": 1.27 } }, "model": { "public_name": "Opono Robin", "status": "unqualified" } } } ``` This excerpt shows a synthetic result; result.home of 0.47 means a 47% home win estimate. If you receive 202, wait retry_after_seconds and GET the returned result_url, preserving its reservation_id query parameter. Keep polling until you receive a delivered simulation or a terminal failed or superseded job. ## 4. Use live output Set OPONO_API_KEY to a sk_live_ key and select a returned [fixture ID](/docs/fixtures). Reuse your Idempotency-Key and exact body after a network failure to avoid a second charge. See the [simulation reference](/docs/reference/simulations) for the full response and [Jobs](/docs/jobs) for polling. # Authentication Send your API key in the Authorization header on every request. ```bash curl 'https://api.opono.com/v1/fixtures' \ --header "Authorization: Bearer $OPONO_API_KEY" ``` ## Create a key Create and revoke keys in [Developers](/developers) with an active Plus or Pro plan. Free has no API access. | Key prefix | Output | Cost | | --- | --- | --- | | `sk_test_` | Synthetic simulation; fixtures return an empty list. | Zero credits. | | `sk_live_` | Real pre-match simulation. | Exactly draws / 1,000 credits per delivered simulation. | ## Set permissions | Scope | Access | | --- | --- | | `data.read` | Read fixtures, free squads, and Opono Expected Lineup (one credit for both sides). | | `simulations.run` | Submit, quote, and batch simulations; collect jobs. | | `usage.read` | Read credits and usage. | ## Store and rotate keys 1. Store your key in a server environment variable such as OPONO_API_KEY. 2. Keep it out of browser code, URLs, logs, and public repositories. 3. If a key leaks, revoke it, create a replacement, and update your server. See [Errors](/docs/errors) for 401 authentication failures and 403 permission failures. # Simulations Choose a fixture, choose how many matches to simulate, and collect the result before kick-off. ## Choose how many matches to simulate Each simulated match is one full simulated playthrough of the fixture; set the number with draws. 1 credit = 1,000 simulated matches. Send 100–1,000,000 per fixture; the default is 1,000 simulated matches · 1 credit. More simulated matches reduce Monte Carlo sampling noise, not model error; model-accuracy uncertainty remains pending. ## Submit a match or batch 1. Get a fixture_id from [Fixtures](/docs/fixtures), or supply home and away with an optional date. 2. [Quote the price](/docs/reference/simulations#quote-a-simulation) without reserving credits. 3. [Submit a simulation](/docs/reference/simulations#create-a-simulation) or a batch of up to 1,000 distinct matches. Credits are reserved at request, settled on delivery, and released on failure. ## Collect results For 202, wait retry_after_seconds (10 seconds) and follow result_url, keeping its reservation_id. When multiple requests share a job, reservation_id selects their separate funding. For 200, read object: simulation is delivered; simulation_job with failed or superseded is terminal. A replacement 202 includes superseded_job_id; follow its new job_id and result_url when provided. A failed job includes simulation_failed; submit a new request if you still need it. ## States and freshness | State | Meaning | | --- | --- | | `provisional` | Inputs can change. | | `inputs_final` | Pre-lineup inputs are finalized. | | `lineups_announced` | Announced starting elevens are available. | Every estimate states when its inputs are from: read basis.as_of and data_version. A fixture is on sale until kick-off whatever its inputs' age, and a newer simulation replaces it when the inputs change. Live deliveries and replays close at kick-off. Simulations are model estimates. Before line-ups are announced, simulations use predicted starting elevens, and player availability, such as injuries and suspensions, is estimated and can be wrong. Unavailable fixtures are not charged. See [Choosing lineups](/docs/choosing-lineups), [Jobs](/docs/jobs), and [Errors](/docs/errors). # Choosing lineups Choose lineups independently for home and away on simulations, quotes, and each batch item. Omitted sides use auto. ## Sources - auto: predicted before official team sheets, announced afterwards. - predicted: Opono Robin samples XIs. After announcement, the run is private and warns official_lineups_announced. - announced: use published team sheets; otherwise lineups_not_announced (409). Wait or use auto. - custom: supply your XI; mixing sources is supported. Custom runs are private to your organisation at the standard draws / 1,000 credit price. Their fingerprint identifies normalized inputs; XI order has no meaning. ## Custom rules Use player IDs from this side's [free squads list](/docs/reference/squads): exactly 11 distinct starters, including one labelled goalkeeper. Supply 0–12 distinct bench players outside the XI. Omit bench for automatic selection; [] means no substitutes. Omit formation or use null, or choose a supported formation whose outfield lines sum to 10. ```json { "lineups": { "home": { "source": "custom", "xi": [ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111 ], "bench": [], "formation": "4-3-3" }, "away": { "source": "auto" } } } ``` A custom lineup is your assumption. Opono Robin simulates the XI you give it; it does not check fitness, suspensions or registration. ## Warnings and refusals Warnings never block: low_start_probability means a starter's model start probability is below 5%; goalkeeper_unverified means missing squad labels prevented the goalkeeper check. Invalid lineups are free (credit_effect: not_charged). 10 lineup_invalid or player_not_in_squad refusals within 10 minutes lock custom-lineup requests across all account keys. The first lockout lasts 10 minutes; further lockouts within 24 hours double it, capped at 24 hours. The level resets after 24 hours without a lockout. Standard requests and reads remain available. For 429 lineup_cooldown, follow Retry-After and recovery.retry_at. See [Opono Expected Lineup](/docs/reference/expected-lineup) for the paid expected XI; simulated matches may display a sampled XI. # Credits and billing 1 credit = 1,000 simulated matches (draws), charged exactly. ## Plans | Plan | Monthly price | Capacity each billing month | | --- | --- | --- | | Plus | £20/month | 100,000 simulated matches · 100 credits | | Pro | £200/month | 2,500,000 simulated matches · 2,500 credits | Available to customers in the United Kingdom only. The price shown is the total you pay. Both plans include every available simulation fixture; choose a plan on [Pricing](/pricing). ## Delivery charges - A live delivery costs exactly draws / 1,000 credits, cached or fresh. - A request reserves its price before work starts and settles it on delivery. - A distinct POST reserves its own credits, even when requests share a job. - Failed or undelivered requests release their holds on failure, supersession, kick-off, or expiry within 30 minutes. - Pending 202 responses and repeated polls add no charge. - A successful Idempotency-Key replay is not charged twice; a new request can charge again. - Test output and fixture, quote, credit, and usage reads cost zero credits. - Squads are free. Opono Expected Lineup costs one credit per match for both sides; re-reading the same data_version within your organisation is free. - An unconfirmed settlement has credit_effect unknown; keep the original key or poll the same job. ## Check your balance ```bash curl 'https://api.opono.com/v1/credits' \ --header "Authorization: Bearer $OPONO_API_KEY" ``` Read total for your available credit balance; a 402 balance_too_low response refuses work your balance cannot cover. ## Track usage ```bash curl 'https://api.opono.com/v1/usage?group_by=day' \ --header "Authorization: Bearer $OPONO_API_KEY" ``` Use from and to for a date range of up to 92 days, or group_by with day, operation, api_key, or model. The response includes data and totals with credits and request counts; a request count is not a count of paid deliveries. # Rate limits Credits set your usage budget; limits apply to your account and each key. ## Request limits | Plan | Request class | Scope | Per 60s | Per 1s | Per UTC day | Concurrent | | --- | --- | --- | --- | --- | --- | --- | | Plus | Simulation submissions | account | 2 | 2 | 200 | 4 | | Plus | Simulation submissions | key | 1 | 1 | 200 | 4 | | Plus | Free reads | account | 30 | 5 | 40,000 | 8 | | Plus | Free reads | key | 15 | 3 | 20,000 | 4 | | Plus | Job polling | account | 12 | 2 | 6,000 | 2 | | Plus | Job polling | key | 6 | 1 | 3,000 | 1 | | Pro | Simulation submissions | account | 8 | 8 | 2,000 | 16 | | Pro | Simulation submissions | key | 4 | 4 | 2,000 | 16 | | Pro | Free reads | account | 120 | 20 | 160,000 | 32 | | Pro | Free reads | key | 60 | 10 | 80,000 | 16 | | Pro | Job polling | account | 48 | 8 | 24,000 | 8 | | Pro | Job polling | key | 24 | 4 | 12,000 | 4 | ## Submit and poll - A batch counts as one submission and one open job group; its items still spend credits. - Open jobs count simultaneously. - Multiple keys share your account limits; test and live keys share the same limits. - Quotes, fixtures, credits, and usage count as free reads; job and batch checks count as polling. - At most 3 identical paid submissions are allowed per account and match in 10 minutes. - Unauthenticated or unpaid requests are limited to 6 per minute per network. - Failed requests and retries still count toward request limits. ## Work limits | Plan | Scope | Processing seconds per 60s | Immediate reserve | Processing seconds per UTC day | | --- | --- | --- | --- | --- | | Plus | account | 600 | 600 | 600 | | Plus | key | 600 | 600 | 600 | | Pro | account | 6,000 | 6,000 | 6,000 | | Pro | key | 6,000 | 6,000 | 6,000 | Work limits protect processing capacity and add no credit charge. ## Handle a limit For 429 responses, wait for Retry-After and use exponential backoff with jitter. Headers may include opono-ratelimit-limit, opono-ratelimit-remaining, and opono-ratelimit-reset. Keep your Idempotency-Key unless the [error response](/docs/reference/errors) requires a new one. # Errors Read error.code, error.fix, and error.retry before you retry a failed request. ## Fix the request | Status | Action | | --- | --- | | 400 | Correct the named parameter or invalid lineup. | | 401 | Check or replace your API key. | | 402 | Check your credit balance and plan. | | 403 | Check permissions and account access. | | 404 | Use a returned ID before kick-off. | | 409 | Resolve fixture ambiguity, idempotency conflict, or wait for announced lineups. | For fixture_ambiguous, choose an ID from error.recovery.candidates. ## Retry safely 1. For 429 or retryable 503, wait for Retry-After and error.retry.after_seconds. 2. Use exponential backoff with jitter. 3. Keep the same Idempotency-Key and exact body unless error.retry.idempotency_key is new. 4. For refreshing, wait for the delay and submit again with a new key. 5. For wait_for_change, wait for the stated condition before submitting again. ## Check charging Confirmed failed attempts have credit_effect not_charged; they do not refund an earlier delivery. For lineup_invalid or player_not_in_squad, correct your selection using [Squads](/docs/reference/squads). For lineup_cooldown, wait until recovery.retry_at; see [Choosing lineups](/docs/choosing-lineups). For settlement_unknown, a charge may have committed, so retry with the original Idempotency-Key or poll the same job. If you have neither, contact [Support](/docs/support) with request_id before resubmitting. See the [Errors object](/docs/reference/errors) for every field and code. # Fixtures ## List fixtures `GET /v1/fixtures` Find pre-match fixtures at zero credits; test keys return an empty list. Authentication: Bearer API key with `data.read`; active Plus or Pro plan required. ### Parameters #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `from` | string | No | Start date or ISO 8601 timestamp with a time zone; defaults to now. | | `to` | string | No | End date (inclusive) or timestamp with a time zone; defaults to seven days after from and must be on or after from. | | `competition` | integer | No | Competition ID; use a positive integer; minimum: 1. | | `team` | string | No | Team name filter; maximum length: 200. | | `limit` | integer | No | Maximum rows to return; range: 1–200; default: 100. | #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | ### Example request ```bash curl 'https://api.opono.com/v1/fixtures?from=2026-10-18&to=2026-10-19&team=Everton&limit=20' \ --header "Authorization: Bearer $OPONO_API_KEY" ``` ### Response | Field | Type | Description | | --- | --- | --- | | `object` | string | list. | | `has_more` | boolean | True when results are truncated; narrow your date window or filters. | | `data` | array | Matching fixtures. | | `data[].fixture_id` | integer | Use this ID to request a simulation. | | `data[].kickoff` | string | Kick-off in ISO 8601 UTC. | | `data[].home / data[].away` | object | Team ID and name. | | `data[].competition` | object | Competition ID and name. | | `data[].state` | string | provisional, inputs_final, or lineups_announced. | | `data[].data_version` | string | Opaque input version. | | `data[].summary` | object or null | Cached forecast summary, or null when unavailable. | | `data[].freshness.status` | string | fresh, kickoff_passed, or unavailable. A fixture is on sale until kick-off whatever its inputs' age. | | `data[].freshness.reason` | string | When the inputs are from, or why the fixture is not on sale. | | `data[].freshness.evaluatedAt` | number | Check time in Unix milliseconds. | | `data[].freshness.validUntil` | number or null | Kick-off in Unix milliseconds, minus one; null when not on sale. | | `data[].freshness.inputsAsOf` | number | When the inputs were captured, in Unix milliseconds. Absent when unknown. | ### Example response 200 response with a test key. ```json { "object": "list", "has_more": false, "data": [] } ``` ### Errors | Status | Code | Meaning | | --- | --- | --- | | 400 | `parameter_invalid` | A parameter has an invalid value. | | 401 | `invalid_api_key` | The credential cannot authenticate. | | 401 | `key_revoked` | The API key has been revoked. | | 401 | `key_expired` | The API key has expired. | | 403 | `scope_missing` | The key lacks the required scope. | | 403 | `tier_restricted` | The organisation plan does not include this operation. | | 403 | `org_suspended` | Your account is suspended. | | 403 | `ip_not_allowed` | The caller is outside the key's IP allowlist. | | 429 | `rate_limited` | A request or authentication rate limit was exceeded. | | 503 | `dependency_unavailable` | Request admission is unavailable. | | 503 | `maintenance` | The API is paused for maintenance. | # Squads ## Retrieve fixture squads `GET /v1/fixtures/{fixture_id}/squads` Get eligible player IDs, names and positions for both sides at zero credits, plus supported formations. Start probabilities and expected selections belong to [Opono Expected Lineup](/docs/reference/expected-lineup). Authentication: Bearer API key with `data.read`; active Plus or Pro plan required. ### Parameters #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `fixture_id` | integer | Yes | Use a returned fixture ID instead of home, away, and date; minimum: 1. | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | ### Example request ```bash curl 'https://api.opono.com/v1/fixtures/19609874/squads' \ --header "Authorization: Bearer $OPONO_API_KEY" ``` ### Response | Field | Type | Description | | --- | --- | --- | | `object` | string | fixture_squads. | | `fixture_id` | integer | Selected fixture. | | `squad_version` | string | Squad identity echoed as inputs.squad_version on quotes and runs. | | `data_version` | string | Current fixture input version. | | `as_of` | string | Input watermark in ISO 8601 UTC. | | `lineups_announced` | boolean | Whether official team sheets are published. | | `formations` | array | Accepted formation strings for custom lineups. | | `home / away` | object | Each side's team and players. | | `home.team / away.team` | object | Team ID and name. | | `home.players / away.players` | array | Eligible players for this side. | | `home.players[].player_id` | integer | Player ID to use in xi or bench; the same fields apply to away. | | `home.players[].name` | string | Player label. | | `home.players[].position` | string or null | goalkeeper, defender, midfielder, attacker, or null when unlabelled. | | `freshness` | object | Fixture freshness when present; unavailable or refreshing squads return an error. | ### Example response 200 synthetic test response; every player entry has only player_id, name and position. ```json { "object": "fixture_squads", "fixture_id": 19609874, "squad_version": "sq1:example", "data_version": "example-input-version", "as_of": "2026-10-17T09:00:00Z", "lineups_announced": false, "formations": [ "4-3-3" ], "home": { "team": { "team_id": 1, "name": "Home" }, "players": [ { "player_id": 101, "name": "Home player 1", "position": "goalkeeper" }, { "player_id": 102, "name": "Home player 2", "position": "defender" }, { "player_id": 103, "name": "Home player 3", "position": "defender" }, { "player_id": 104, "name": "Home player 4", "position": "defender" }, { "player_id": 105, "name": "Home player 5", "position": "defender" }, { "player_id": 106, "name": "Home player 6", "position": "midfielder" }, { "player_id": 107, "name": "Home player 7", "position": "midfielder" }, { "player_id": 108, "name": "Home player 8", "position": "midfielder" }, { "player_id": 109, "name": "Home player 9", "position": "attacker" }, { "player_id": 110, "name": "Home player 10", "position": "attacker" }, { "player_id": 111, "name": "Home player 11", "position": "attacker" } ] }, "away": { "team": { "team_id": 2, "name": "Away" }, "players": [ { "player_id": 201, "name": "Away player 1", "position": "goalkeeper" }, { "player_id": 202, "name": "Away player 2", "position": "defender" }, { "player_id": 203, "name": "Away player 3", "position": "defender" }, { "player_id": 204, "name": "Away player 4", "position": "defender" }, { "player_id": 205, "name": "Away player 5", "position": "defender" }, { "player_id": 206, "name": "Away player 6", "position": "midfielder" }, { "player_id": 207, "name": "Away player 7", "position": "midfielder" }, { "player_id": 208, "name": "Away player 8", "position": "midfielder" }, { "player_id": 209, "name": "Away player 9", "position": "attacker" }, { "player_id": 210, "name": "Away player 10", "position": "attacker" }, { "player_id": 211, "name": "Away player 11", "position": "attacker" } ] } } ``` ### Errors | Status | Code | Meaning | | --- | --- | --- | | 400 | `parameter_invalid` | A parameter has an invalid value. | | 401 | `invalid_api_key` | The credential cannot authenticate. | | 401 | `key_revoked` | The API key has been revoked. | | 401 | `key_expired` | The API key has expired. | | 403 | `scope_missing` | The key lacks the required scope. | | 403 | `tier_restricted` | The organisation plan does not include this operation. | | 403 | `org_suspended` | Your account is suspended. | | 403 | `ip_not_allowed` | The caller is outside the key's IP allowlist. | | 429 | `rate_limited` | A request or authentication rate limit was exceeded. | | 503 | `dependency_unavailable` | Request admission is unavailable. | | 503 | `maintenance` | The API is paused for maintenance. | | 404 | `fixture_not_found` | No matching simulation fixture exists. | | 400 | `fixture_not_covered` | The requested fixture is not available for simulation. | | 503 | `refreshing` | The request can't be checked right now. | # Opono Expected Lineup ## Opono Expected Lineup `GET /v1/fixtures/{fixture_id}/expected-lineup` Read Opono Robin's expected XI, bench, formation and player start probabilities for 1 credit per match, covering both sides, cached or fresh. Re-reading the same fixture data_version within your organisation is a free replay. After announcement, official_lineups_announced is true; this remains the model's expectation. Authentication: Bearer API key with `data.read`; active Plus or Pro plan required. ### Parameters #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `fixture_id` | integer | Yes | Use a returned fixture ID instead of home, away, and date; minimum: 1. | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | ### Example request ```bash curl 'https://api.opono.com/v1/fixtures/19609874/expected-lineup' \ --header "Authorization: Bearer $OPONO_API_KEY" ``` ### Response | Field | Type | Description | | --- | --- | --- | | `object` | string | expected_lineup. | | `fixture_id` | integer | Selected fixture. | | `as_of` | string | Input watermark in ISO 8601 UTC. | | `data_version` | string | Input version; the same organisation and version replay for free. | | `squad_version` | string | Squad version when present. | | `official_lineups_announced` | boolean | True after announcement; the returned XI remains the model's expectation. | | `home / away` | object | Each side's team, expected xi, bench and expected_formation. | | `home.team / away.team` | object | Team ID and name. | | `home.xi / away.xi` | array | Expected starting eleven. | | `home.bench / away.bench` | array | Expected substitutes. | | `home.expected_formation / away.expected_formation` | string or null | Expected formation, when available. | | `home.xi[].player_id / name / position` | integer / string / string or null | Player labels; the same fields apply to both sides and benches. | | `home.xi[].p_start` | number | Opono Robin's start probability from 0 to 1; also included for away and bench players. | | `model.public_name` | string | Opono Robin. | | `model.version` | string | Model release when present. | | `credits_charged` | number | One credit on first live delivery; zero for a same-version replay or test key. | | `replay` | boolean | Whether this is a previously delivered version. | | `livemode` | boolean | False for synthetic test output, when present. | | `receipt` | object | Credit receipt on live delivery or replay when present. | ### Example response 200 synthetic test response; zero credits. Includes model outputs and player labels only. ```json { "object": "expected_lineup", "fixture_id": 19609874, "as_of": "2026-10-17T09:00:00Z", "data_version": "example-input-version", "squad_version": "sq1:example", "official_lineups_announced": false, "home": { "team": { "team_id": 1, "name": "Home" }, "expected_formation": "4-3-3", "xi": [ { "player_id": 101, "name": "Home player 1", "position": "goalkeeper", "p_start": 0.8 }, { "player_id": 102, "name": "Home player 2", "position": "defender", "p_start": 0.8 }, { "player_id": 103, "name": "Home player 3", "position": "defender", "p_start": 0.8 }, { "player_id": 104, "name": "Home player 4", "position": "defender", "p_start": 0.8 }, { "player_id": 105, "name": "Home player 5", "position": "defender", "p_start": 0.8 }, { "player_id": 106, "name": "Home player 6", "position": "midfielder", "p_start": 0.8 }, { "player_id": 107, "name": "Home player 7", "position": "midfielder", "p_start": 0.8 }, { "player_id": 108, "name": "Home player 8", "position": "midfielder", "p_start": 0.8 }, { "player_id": 109, "name": "Home player 9", "position": "attacker", "p_start": 0.8 }, { "player_id": 110, "name": "Home player 10", "position": "attacker", "p_start": 0.8 }, { "player_id": 111, "name": "Home player 11", "position": "attacker", "p_start": 0.8 } ], "bench": [] }, "away": { "team": { "team_id": 2, "name": "Away" }, "expected_formation": "4-3-3", "xi": [ { "player_id": 201, "name": "Away player 1", "position": "goalkeeper", "p_start": 0.8 }, { "player_id": 202, "name": "Away player 2", "position": "defender", "p_start": 0.8 }, { "player_id": 203, "name": "Away player 3", "position": "defender", "p_start": 0.8 }, { "player_id": 204, "name": "Away player 4", "position": "defender", "p_start": 0.8 }, { "player_id": 205, "name": "Away player 5", "position": "defender", "p_start": 0.8 }, { "player_id": 206, "name": "Away player 6", "position": "midfielder", "p_start": 0.8 }, { "player_id": 207, "name": "Away player 7", "position": "midfielder", "p_start": 0.8 }, { "player_id": 208, "name": "Away player 8", "position": "midfielder", "p_start": 0.8 }, { "player_id": 209, "name": "Away player 9", "position": "attacker", "p_start": 0.8 }, { "player_id": 210, "name": "Away player 10", "position": "attacker", "p_start": 0.8 }, { "player_id": 211, "name": "Away player 11", "position": "attacker", "p_start": 0.8 } ], "bench": [] }, "model": { "public_name": "Opono Robin" }, "credits_charged": 0, "replay": false, "livemode": false } ``` ### Errors | Status | Code | Meaning | | --- | --- | --- | | 400 | `parameter_invalid` | A parameter has an invalid value. | | 401 | `invalid_api_key` | The credential cannot authenticate. | | 401 | `key_revoked` | The API key has been revoked. | | 401 | `key_expired` | The API key has expired. | | 403 | `scope_missing` | The key lacks the required scope. | | 403 | `tier_restricted` | The organisation plan does not include this operation. | | 403 | `org_suspended` | Your account is suspended. | | 403 | `ip_not_allowed` | The caller is outside the key's IP allowlist. | | 429 | `rate_limited` | A request or authentication rate limit was exceeded. | | 503 | `dependency_unavailable` | Request admission is unavailable. | | 503 | `maintenance` | The API is paused for maintenance. | | 402 | `balance_too_low` | Your available balance cannot cover the request. | | 404 | `fixture_not_found` | No matching simulation fixture exists. | | 400 | `fixture_not_covered` | The requested fixture is not available for simulation. | | 503 | `refreshing` | The request can't be checked right now. | # Simulations ## Create a simulation `POST /v1/simulations` Simulate one match for exactly draws / 1,000 credits per live delivery, including cached results. Authentication: Bearer API key with `simulations.run`; active Plus or Pro plan required. ### Parameters Send an optional `Idempotency-Key` header (1–255 characters) and keep the same key and exact JSON body for retries. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `fixture_id` | integer or string | Conditional | Use a returned fixture ID instead of home, away, and date. | | `home` | string | Conditional | Home team name; required with away when you omit fixture_id; maximum length: 200. | | `away` | string | Conditional | Away team name; required with home when you omit fixture_id; maximum length: 200. | | `draws` | integer | No | Number of simulated matches: full simulated playthroughs of the fixture; range: 100–1,000,000; default: 1,000. | | `date` | string | No | Fixture date in YYYY-MM-DD format; omit it to select the next match. | | `wait_seconds` | integer | No | Maximum time to wait for a result before returning a job; range: 0–25; default: 25. | | `lineups` | object | No | Optional home and away source choices: auto (default), predicted, announced, or custom; see [Choosing lineups](/docs/choosing-lineups). | ### Example request ```bash curl --request POST 'https://api.opono.com/v1/simulations' \ --header "Authorization: Bearer $OPONO_API_KEY" \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: match-example-1' \ --data '{"fixture_id":19609874,"draws":1000}' ``` ### Response | Field | Type | Description | | --- | --- | --- | | `object` | string | simulation for delivery; simulation_job for pending or terminal jobs. | | `id` | string | Delivered simulation ID. | | `fixture_id` | integer | Resolved fixture ID. | | `state` | string | provisional, inputs_final, or lineups_announced. | | `data_version` | string | Opaque input version; compare it only for equality. | | `created_at` | string | Simulation creation time in ISO 8601 UTC. | | `livemode` | boolean | False for synthetic test output. | | `price.draws` | integer | Number of simulated matches for this simulation. | | `price.credits` | string | Exact credit price with three decimal places. | | `credits_charged` | number | Credits settled for this request; zero in test mode. | | `scope` | string | shared or private; private results belong only to your organisation. | | `inputs.lineups` | object | Resolved home and away sources; custom sides include normalized XI, bench and formation. Announced sides show only source. | | `inputs.squad_version` | string | Squad version used to validate your lineups. | | `warnings` | array | Non-blocking low_start_probability, goalkeeper_unverified, or official_lineups_announced warnings. | | `receipt` | object | Request ID, idempotency key, price, charged credits, and remaining credits. | | `receipt.inputs_hash` | string or null | The run's input fingerprint; null on older receipts. | | `simulation` | object | The opono.simulation.v1 document; see the fields below. | | `simulation.fixture` | object | Fixture ID, kick-off, competition, and both team labels. | | `simulation.basis` | object | Input state, as_of timestamp, data_version, lineups, number of simulated matches (draws), and seed. | | `simulation.basis.lineups` | string | predicted, announced, or custom; this enum can gain values. | | `simulation.basis.lineup_sources` | object | Optional home and away sources: predicted, announced, or custom; absent on older results. | | `simulation.result` | object | Home win, draw, and away win probabilities from 0 to 1. | | `simulation.goals` | object | Expected goals, score grid, and goal distributions. | | `simulation.markets` | object | Scorelines, totals, and both-teams-to-score probabilities. | | `simulation.half_time` | object | Half-time and joint half-time/full-time probabilities. | | `simulation.timeline` | object | 110 steps with minute_label, team event series, and score-state probabilities. | | `simulation.goal_events` | object | First-goal, first-scorer, and comeback probabilities when present. | | `simulation.players` | array | Per-player probabilities, expected minutes, and box-score statistics. | | `simulation.lineups` | object | Starting elevens and benches used for each side when present. | | `simulation.samples` | array | 20 illustrative match paths when present. | | `simulation.uncertainty` | object | Number of simulated matches, Monte Carlo estimates, and model-accuracy status. | | `simulation.model` | object | Public model name, version, and qualification status. | | `simulation.timing` | object | Simulation duration when present. | ### Example response 200 test response (excerpt); see [OpenAPI](/docs/openapi.json) for the full schema. ```json { "object": "simulation", "fixture_id": 19609874, "state": "provisional", "data_version": "2026-10-13:predicted:b167bda637016662e2585f872b25dea30f954c9a56bd5a345ff965b1663eee10", "livemode": false, "credits_charged": 0, "simulation": { "schema": "opono.simulation.v1", "basis": { "as_of": "2026-10-12T18:30:00Z", "data_version": "2026-10-13:predicted:b167bda637016662e2585f872b25dea30f954c9a56bd5a345ff965b1663eee10" }, "result": { "home": 0.47, "draw": 0.26, "away": 0.27, "calibrated": true }, "goals": { "expected": { "home": 1.69, "away": 1.27 } }, "model": { "public_name": "Opono Robin", "status": "unqualified" } }, "price": { "draws": 1000, "credits": "0.000" } } ``` 202 response. ```json { "object": "simulation_job", "job_id": "job_example", "fixture_id": 19609874, "reservation_id": "reservationexample", "result_url": "/v1/simulations/jobs/job_example?reservation_id=reservationexample", "status": "queued", "price": { "draws": 1000, "credits": "1.000" }, "credits_reserved": 1, "retry_after_seconds": 10 } ``` ### Errors | Status | Code | Meaning | | --- | --- | --- | | 400 | `parameter_invalid` | A parameter has an invalid value. | | 401 | `invalid_api_key` | The credential cannot authenticate. | | 401 | `key_revoked` | The API key has been revoked. | | 401 | `key_expired` | The API key has expired. | | 403 | `scope_missing` | The key lacks the required scope. | | 403 | `tier_restricted` | The organisation plan does not include this operation. | | 403 | `org_suspended` | Your account is suspended. | | 403 | `ip_not_allowed` | The caller is outside the key's IP allowlist. | | 429 | `rate_limited` | A request or authentication rate limit was exceeded. | | 503 | `dependency_unavailable` | Request admission is unavailable. | | 503 | `maintenance` | The API is paused for maintenance. | | 402 | `balance_too_low` | Your available balance cannot cover the request. | | 404 | `fixture_not_found` | No matching simulation fixture exists. | | 409 | `fixture_ambiguous` | The fixture lookup identifies multiple candidates. | | 503 | `fixture_lookup_incomplete` | The fixture lookup could not prove a unique match. | | 409 | `idempotency_conflict` | An idempotency key conflicts with a prior request. | | 429 | `concurrency_limited` | The organisation has no available concurrent slot. | | 429 | `loop_guard` | A repeated-call burst was refused. | | 400 | `fixture_not_covered` | The requested fixture is not available for simulation. | | 503 | `refreshing` | The request can't be checked right now. | | 503 | `settlement_unknown` | Delivery settlement could not be confirmed. | | 400 | `lineup_invalid` | The custom XI, bench, goalkeeper selection, or formation is invalid; recovery.reason identifies the rule. | | 400 | `player_not_in_squad` | A custom player ID is outside this side's squad; check recovery.unknown_id against the free squads list. | | 409 | `lineups_not_announced` | Announced team sheets are unavailable; wait for publication or use auto. | | 429 | `lineup_cooldown` | Custom-lineup requests are temporarily locked; wait for Retry-After and recovery.retry_at. | ## Quote a simulation `POST /v1/simulations/quote` Get the exact price without running a simulation or reserving credits. Non-auto sources also validate lineups against current fixture inputs; that validation holds while squad_version and data_version remain unchanged. Authentication: Bearer API key with `simulations.run`; active Plus or Pro plan required. ### Parameters #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `fixture_id` | integer or string | Conditional | Use a returned fixture ID instead of home, away, and date. | | `home` | string | Conditional | Home team name; required with away when you omit fixture_id; maximum length: 200. | | `away` | string | Conditional | Away team name; required with home when you omit fixture_id; maximum length: 200. | | `draws` | integer | No | Number of simulated matches: full simulated playthroughs of the fixture; range: 100–1,000,000; default: 1,000. | | `date` | string | No | Fixture date in YYYY-MM-DD format; omit it to select the next match. | | `wait_seconds` | integer | No | Maximum time to wait for a result before returning a job; range: 0–25; default: 25. | | `lineups` | object | No | Optional home and away source choices: auto (default), predicted, announced, or custom; see [Choosing lineups](/docs/choosing-lineups). | ### Example request ```bash curl --request POST 'https://api.opono.com/v1/simulations/quote' \ --header "Authorization: Bearer $OPONO_API_KEY" \ --header 'Content-Type: application/json' \ --data '{"fixture_id":19609874,"draws":1000}' ``` ### Response | Field | Type | Description | | --- | --- | --- | | `object` | string | simulation_quote. | | `price.draws` | integer | Requested number of simulated matches. | | `price.credits` | string | Exact credit price; zero in test mode. | | `credits` | number | Numeric credit price. | | `credits_charged` | number | Always zero. | | `rate_card` | string | Price revision. | | `livemode` | boolean | False for a test key. | | `scope / inputs / warnings` | string / object / array | Proposed run scope, normalized lineup inputs and non-blocking warnings when present. | ### Example response 200 response. ```json { "object": "simulation_quote", "price": { "draws": 1000, "credits": "1.000" }, "credits": 1, "credits_charged": 0, "rate_card": "draws-2026-10-13", "livemode": true } ``` ### Errors | Status | Code | Meaning | | --- | --- | --- | | 400 | `parameter_invalid` | A parameter has an invalid value. | | 401 | `invalid_api_key` | The credential cannot authenticate. | | 401 | `key_revoked` | The API key has been revoked. | | 401 | `key_expired` | The API key has expired. | | 403 | `scope_missing` | The key lacks the required scope. | | 403 | `tier_restricted` | The organisation plan does not include this operation. | | 403 | `org_suspended` | Your account is suspended. | | 403 | `ip_not_allowed` | The caller is outside the key's IP allowlist. | | 429 | `rate_limited` | A request or authentication rate limit was exceeded. | | 503 | `dependency_unavailable` | Request admission is unavailable. | | 503 | `maintenance` | The API is paused for maintenance. | | 400 | `lineup_invalid` | The custom XI, bench, goalkeeper selection, or formation is invalid; recovery.reason identifies the rule. | | 400 | `player_not_in_squad` | A custom player ID is outside this side's squad; check recovery.unknown_id against the free squads list. | | 409 | `lineups_not_announced` | Announced team sheets are unavailable; wait for publication or use auto. | | 429 | `lineup_cooldown` | Custom-lineup requests are temporarily locked; wait for Retry-After and recovery.retry_at. | | 404 | `fixture_not_found` | No matching simulation fixture exists. | | 400 | `fixture_not_covered` | The requested fixture is not available for simulation. | | 503 | `refreshing` | The request can't be checked right now. | ## Create a batch `POST /v1/simulations/batch` Reserve credits for up to 1,000 matches before queuing them; each delivered item costs draws / 1,000 credits. Authentication: Bearer API key with `simulations.run`; active Plus or Pro plan required. ### Parameters Send an optional `Idempotency-Key` header (1–255 characters) and keep the same key and exact JSON body for retries. #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `fixtures` | array | Yes | Distinct match selectors; each uses fixture_id or home and away with an optional date; items: 1–1,000. | | `fixtures[].fixture_id` | integer or string | Conditional | Fixture ID; omit team names and date when you use it. | | `fixtures[].home` | string | Conditional | Home team name; required without fixture_id; maximum length: 200. | | `fixtures[].away` | string | Conditional | Away team name; required without fixture_id; maximum length: 200. | | `fixtures[].date` | string | No | Optional fixture date in YYYY-MM-DD format. | | `fixtures[].lineups` | object | No | Optional per-side lineup choices for this item; all items are validated before reserving credits. | | `draws` | integer | No | Number of simulated matches: full simulated playthroughs of the fixture; range: 100–1,000,000; default: 1,000. | ### Example request ```bash curl --request POST 'https://api.opono.com/v1/simulations/batch' \ --header "Authorization: Bearer $OPONO_API_KEY" \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: match-example-1' \ --data '{"fixtures":[{"fixture_id":19609874}],"draws":1000}' ``` ### Response | Field | Type | Description | | --- | --- | --- | | `object` | string | simulation_batch. | | `batch_id` | string | Your batch ID. | | `items` | array | Per-fixture jobs and reservations. | | `items[].fixture` | integer | Fixture ID. | | `items[].job_id` | string | Job ID for this fixture. | | `items[].status` | string | Current item status. | | `items[].credits_reserved` | number | Credits held for this item. | | `items[].reservation_id` | string | Request-bound reservation when present on submission. | | `items[].result_url` | string | Follow this path to deliver and settle the item. | | `items[].error` | object | Error code and message when an item is unavailable. | | `items[].credits_charged` | number | Settled credits when present on a status response. | | `items[].freshness` | object | Freshness state and check times when present on a status response. | | `items[].scope / inputs / warnings` | string / object / array | Each item's run scope, normalized inputs and lineup warnings when present. | | `validUntil` | number or null | Earliest item freshness deadline in Unix milliseconds when present. | | `credits_reserved_total` | number | Total credits reserved for this batch when present. | ### Example response 202 response; the JSON body must fit within 2 MiB. ```json { "object": "simulation_batch", "batch_id": "batch_example", "items": [ { "fixture": 19609874, "job_id": "job_example", "status": "queued", "credits_reserved": 1, "reservation_id": "reservationexample", "result_url": "/v1/simulations/jobs/job_example?reservation_id=reservationexample" } ], "credits_reserved_total": 1 } ``` ### Errors | Status | Code | Meaning | | --- | --- | --- | | 400 | `parameter_invalid` | A parameter has an invalid value. | | 401 | `invalid_api_key` | The credential cannot authenticate. | | 401 | `key_revoked` | The API key has been revoked. | | 401 | `key_expired` | The API key has expired. | | 403 | `scope_missing` | The key lacks the required scope. | | 403 | `tier_restricted` | The organisation plan does not include this operation. | | 403 | `org_suspended` | Your account is suspended. | | 403 | `ip_not_allowed` | The caller is outside the key's IP allowlist. | | 429 | `rate_limited` | A request or authentication rate limit was exceeded. | | 503 | `dependency_unavailable` | Request admission is unavailable. | | 503 | `maintenance` | The API is paused for maintenance. | | 402 | `balance_too_low` | Your available balance cannot cover the request. | | 404 | `fixture_not_found` | No matching simulation fixture exists. | | 409 | `fixture_ambiguous` | The fixture lookup identifies multiple candidates. | | 503 | `fixture_lookup_incomplete` | The fixture lookup could not prove a unique match. | | 409 | `idempotency_conflict` | An idempotency key conflicts with a prior request. | | 429 | `concurrency_limited` | The organisation has no available concurrent slot. | | 429 | `loop_guard` | A repeated-call burst was refused. | | 400 | `fixture_not_covered` | The requested fixture is not available for simulation. | | 503 | `refreshing` | The request can't be checked right now. | | 503 | `settlement_unknown` | Delivery settlement could not be confirmed. | | 400 | `lineup_invalid` | The custom XI, bench, goalkeeper selection, or formation is invalid; recovery.reason identifies the rule. | | 400 | `player_not_in_squad` | A custom player ID is outside this side's squad; check recovery.unknown_id against the free squads list. | | 409 | `lineups_not_announced` | Announced team sheets are unavailable; wait for publication or use auto. | | 429 | `lineup_cooldown` | Custom-lineup requests are temporarily locked; wait for Retry-After and recovery.retry_at. | # Jobs ## Retrieve a job `GET /v1/simulations/jobs/{job_id}` Poll your request's result_url to settle its reserved price on first delivery and replay that delivery for free. Authentication: Bearer API key with `simulations.run`; active Plus or Pro plan required. ### Parameters #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `job_id` | string | Yes | Job ID returned by your simulation request. | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `reservation_id` | string | No | Request value; maximum length: 100. | | `reservation_id` | string | Conditional | Preserve this value from result_url to select your request's funding when requests share a job. | #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | ### Example request ```bash curl 'https://api.opono.com/v1/simulations/jobs/job_example?reservation_id=reservationexample' \ --header "Authorization: Bearer $OPONO_API_KEY" ``` ### Response | Field | Type | Description | | --- | --- | --- | | `object` | string | simulation_job while pending or terminal; simulation when delivered. | | `job_id` | string | Current job ID. | | `fixture_id` | integer | Fixture ID when present. | | `status` | string | queued or leased on 202; failed or superseded on a terminal 200. | | `reservation_id` | string | Your credit reservation when present. | | `result_url` | string | Request-bound delivery path when present; preserve its query string. | | `price` | object | Number of simulated matches and exact credit price when present. | | `credits_reserved` | number | Credits held for your request when present. | | `retry_after_seconds` | integer | Wait this many seconds before your next poll on 202. | | `scope / inputs / warnings` | string / object / array | Run scope, normalized inputs and non-blocking lineup warnings when present. | | `superseded_job_id` | string | Previous job ID; equals job_id when no replacement exists. | | `error` | object | Code and message for a failed job. | | `simulation` | object | Present on delivery; see the [simulation response](/docs/reference/simulations#response). | ### Example response 200 response for a failed job; a successful 200 uses the [simulation response](/docs/reference/simulations#response). ```json { "object": "simulation_job", "job_id": "job_example", "fixture_id": 19609874, "status": "failed", "error": { "code": "simulation_failed", "message": "The simulation could not be completed. Submit a new request for the current fixture inputs." } } ``` 202 response. ```json { "object": "simulation_job", "job_id": "job_example", "fixture_id": 19609874, "reservation_id": "reservationexample", "result_url": "/v1/simulations/jobs/job_example?reservation_id=reservationexample", "status": "queued", "price": { "draws": 1000, "credits": "1.000" }, "credits_reserved": 1, "retry_after_seconds": 10 } ``` ### Errors | Status | Code | Meaning | | --- | --- | --- | | 400 | `parameter_invalid` | A parameter has an invalid value. | | 401 | `invalid_api_key` | The credential cannot authenticate. | | 401 | `key_revoked` | The API key has been revoked. | | 401 | `key_expired` | The API key has expired. | | 403 | `scope_missing` | The key lacks the required scope. | | 403 | `tier_restricted` | The organisation plan does not include this operation. | | 403 | `org_suspended` | Your account is suspended. | | 403 | `ip_not_allowed` | The caller is outside the key's IP allowlist. | | 429 | `rate_limited` | A request or authentication rate limit was exceeded. | | 503 | `dependency_unavailable` | Request admission is unavailable. | | 503 | `maintenance` | The API is paused for maintenance. | | 404 | `resource_missing` | The resource is missing or belongs to another account. | | 404 | `fixture_not_found` | No matching simulation fixture exists. | | 409 | `reservation_expired` | The simulation credit hold ended without delivery. | | 402 | `balance_too_low` | Your available balance cannot cover the request. | | 400 | `fixture_not_covered` | The requested fixture is not available for simulation. | | 503 | `refreshing` | The request can't be checked right now. | | 503 | `settlement_unknown` | Delivery settlement could not be confirmed. | ## Retrieve a batch `GET /v1/simulations/batches/{batch_id}` Read your batch's per-fixture status and delivery links at zero credits. Authentication: Bearer API key with `simulations.run`; active Plus or Pro plan required. ### Parameters #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `batch_id` | string | Yes | Batch ID returned by your batch request. | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | None. | ### Example request ```bash curl 'https://api.opono.com/v1/simulations/batches/batch_example' \ --header "Authorization: Bearer $OPONO_API_KEY" ``` ### Response | Field | Type | Description | | --- | --- | --- | | `object` | string | simulation_batch. | | `batch_id` | string | Your batch ID. | | `items` | array | Per-fixture jobs and reservations. | | `items[].fixture` | integer | Fixture ID. | | `items[].job_id` | string | Job ID for this fixture. | | `items[].status` | string | Current item status. | | `items[].credits_reserved` | number | Credits held for this item. | | `items[].reservation_id` | string | Request-bound reservation when present on submission. | | `items[].result_url` | string | Follow this path to deliver and settle the item. | | `items[].error` | object | Error code and message when an item is unavailable. | | `items[].credits_charged` | number | Settled credits when present on a status response. | | `items[].freshness` | object | Freshness state and check times when present on a status response. | | `items[].scope / inputs / warnings` | string / object / array | Each item's run scope, normalized inputs and lineup warnings when present. | | `validUntil` | number or null | Earliest item freshness deadline in Unix milliseconds when present. | | `credits_reserved_total` | number | Total credits reserved for this batch when present. | ### Example response 200 response. ```json { "object": "simulation_batch", "batch_id": "batch_example", "items": [ { "fixture": 19609874, "job_id": "job_example", "status": "queued", "credits_reserved": 1, "result_url": "/v1/simulations/jobs/job_example?reservation_id=reservationexample", "credits_charged": 0, "freshness": { "status": "fresh", "reason": "Inputs as of 2026-10-17T07:10:00.000Z.", "evaluatedAt": 1792310400000, "validUntil": 1792336499999, "inputsAsOf": 1792307400000 } } ], "credits_reserved_total": 1, "validUntil": 1792336499999 } ``` ### Errors | Status | Code | Meaning | | --- | --- | --- | | 400 | `parameter_invalid` | A parameter has an invalid value. | | 401 | `invalid_api_key` | The credential cannot authenticate. | | 401 | `key_revoked` | The API key has been revoked. | | 401 | `key_expired` | The API key has expired. | | 403 | `scope_missing` | The key lacks the required scope. | | 403 | `tier_restricted` | The organisation plan does not include this operation. | | 403 | `org_suspended` | Your account is suspended. | | 403 | `ip_not_allowed` | The caller is outside the key's IP allowlist. | | 429 | `rate_limited` | A request or authentication rate limit was exceeded. | | 503 | `dependency_unavailable` | Request admission is unavailable. | | 503 | `maintenance` | The API is paused for maintenance. | | 404 | `resource_missing` | The resource is missing or belongs to another account. | | 400 | `fixture_not_covered` | The requested fixture is not available for simulation. | | 503 | `refreshing` | The request can't be checked right now. | | 404 | `fixture_not_found` | No matching simulation fixture exists. | # Errors object ## Inspect an error `GET /v1/fixtures?limit=0` Read the error object returned when a request fails. Authentication: Bearer API key; authentication failures also use this object. ### Parameters #### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | This response object accepts no parameters. | #### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | This response object accepts no parameters. | #### Body parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | — | — | — | This response object accepts no parameters. | ### Example request ```bash curl 'https://api.opono.com/v1/fixtures?limit=0' \ --header "Authorization: Bearer $OPONO_API_KEY" ``` ### Response | Field | Type | Description | | --- | --- | --- | | `error.type` | string | Broad error category. | | `error.code` | string | Stable code listed below. | | `error.message` | string | Explanation of the failure. | | `error.cause` | string | Cause when present. | | `error.fix` | string | Recovery instruction when present. | | `error.param` | string or null | Invalid parameter, if known. | | `error.doc_url` | string | Documentation link. | | `error.request_id` | string or null | Request ID for support. | | `error.retry.retryable` | boolean | Whether you can retry; may be absent during maintenance. | | `error.retry.after_seconds` | number or null | Minimum retry delay in seconds. | | `error.retry.strategy` | string | correct_request, backoff_with_jitter, or wait_for_change. | | `error.retry.idempotency_key` | string | same, new, or not_applicable. | | `error.credit_effect` | string | not_charged for confirmed failure; unknown if settlement is unconfirmed. | | `error.recovery` | object or null | Recovery details, including fixture candidates when ambiguous. | | `error.recovery.reason` | string | For lineup_invalid: xi_size, duplicate_player, bench_size, xi_bench_overlap, goalkeeper_count, formation_invalid, or formation_unsupported. | | `error.recovery.unknown_id` | string | Player ID outside this side's squad when present. | | `error.recovery.next_step` | string | Correction guidance, including the free squads URL when relevant. | | `error.recovery.retry_at` | string | Account lockout expiry in ISO 8601 UTC when present; also honour Retry-After. | | `error.rule / error.reason` | string | Rate-limit details when present. | | `error.retryAfterMs` | number | Rate-limit retry delay in milliseconds when present. | ### Example response 400 response. ```json { "error": { "type": "invalid_request_error", "code": "parameter_invalid", "message": "'limit' must be a positive integer up to 200.", "cause": "The request does not satisfy the operation's input contract.", "fix": "Set limit to an integer from 1 to 200.", "param": "limit", "doc_url": "https://opono.com/docs#parameter_invalid", "request_id": "req_example", "retry": { "retryable": false, "after_seconds": null, "strategy": "correct_request", "idempotency_key": "not_applicable" }, "credit_effect": "not_charged", "recovery": null } } ``` ### Errors | Status | Code | Meaning | | --- | --- | --- | | 400 | `parameter_invalid` | A parameter has an invalid value. | | 400 | `parameter_missing` | A required parameter is absent. | | 401 | `invalid_api_key` | The credential cannot authenticate. | | 401 | `key_revoked` | The API key has been revoked. | | 401 | `key_expired` | The API key has expired. | | 402 | `balance_too_low` | Your available balance cannot cover the request. | | 403 | `scope_missing` | The key lacks the required scope. | | 403 | `org_suspended` | Your account is suspended. | | 403 | `ip_not_allowed` | The caller is outside the key's IP allowlist. | | 403 | `tier_restricted` | The organisation plan does not include this operation. | | 404 | `resource_missing` | The resource is missing or belongs to another account. | | 409 | `idempotency_conflict` | An idempotency key conflicts with a prior request. | | 429 | `rate_limited` | A request or authentication rate limit was exceeded. | | 429 | `concurrency_limited` | The organisation has no available concurrent slot. | | 429 | `loop_guard` | A repeated-call burst was refused. | | 409 | `reservation_expired` | The simulation credit hold ended without delivery. | | 503 | `fixture_lookup_incomplete` | The fixture lookup could not prove a unique match. | | 404 | `fixture_not_found` | No matching simulation fixture exists. | | 409 | `fixture_ambiguous` | The fixture lookup identifies multiple candidates. | | 500 | `simulation_failed` | The simulation job failed. | | 503 | `settlement_unknown` | Delivery settlement could not be confirmed. | | 500 | `internal_error` | An unexpected internal failure occurred. | | 503 | `dependency_unavailable` | Request admission is unavailable. | | 500 | `payload_missing` | The result is unavailable or incomplete. | | 502 | `provenance_missing` | The result is missing required source information. | | 502 | `provenance_mismatch` | The result does not match the requested inputs. | | 503 | `engine_cold_start` | The simulation service is starting. | | 503 | `engine_overloaded` | The simulation service is at capacity. | | 503 | `engine_unavailable` | The simulation service is unavailable. | | 503 | `storage_unavailable` | The result is temporarily unavailable. | | 400 | `fixture_not_covered` | The requested fixture is not available for simulation. | | 503 | `refreshing` | The request can't be checked right now. | | 503 | `capability_off` | This output is no longer available. | | 503 | `capability_disabled` | This output is disabled. | | 503 | `maintenance` | The API is paused for maintenance. | | 400 | `lineup_invalid` | The custom XI, bench, goalkeeper selection, or formation is invalid; recovery.reason identifies the rule. | | 400 | `player_not_in_squad` | A custom player ID is outside this side's squad; check recovery.unknown_id against the free squads list. | | 409 | `lineups_not_announced` | Announced team sheets are unavailable; wait for publication or use auto. | | 429 | `lineup_cooldown` | Custom-lineup requests are temporarily locked; wait for Retry-After and recovery.retry_at. | # Changelog ## 17 October 2026 - Choose auto, predicted, announced, or custom lineups independently per side. - Use free squads to build custom inputs; read Opono Expected Lineup for one credit per match covering both sides. - Custom inputs and predicted lineups after announcement make private runs, with normalized inputs and warnings in the response. - Invalid lineups remain free; repeated invalid lineups trigger an escalating account lockout. See [Choosing lineups](/docs/choosing-lineups) for rules and warnings. ## 14 October 2026 - Set draws (simulated matches) from 100 to 1,000,000 per fixture, with a default of 1,000. - Pay exactly draws / 1,000 credits per live delivery. - Quote a simulation without reserving credits. - Submit up to 1,000 distinct matches in one batch. - Follow request-bound result_url links to collect and settle each job. See the [simulation reference](/docs/reference/simulations) and [Jobs](/docs/jobs) for request and response fields. # Support Contact [support@opono.com](mailto:support@opono.com) for API or billing help. ## Include these details - Your request_id from the response or receipt. - The endpoint, HTTP status, and error.code. - The time of the request in UTC. - Your job_id or batch_id, if available. - The behavior you expect and the response you receive. Remove API keys and other secrets before you share a request or response. ## Before you resubmit For settlement_unknown, keep the original Idempotency-Key or job link until the charge is confirmed. See [Errors](/docs/errors) for recovery steps and [Credits and billing](/docs/credits-and-usage) for charging rules.