# ABP Authentication - API Key Setup Source: https://docs.55-tech.com/abp-api/authentication Learn how to authenticate ABP API requests using the x-api-key header. API key resolution, rate limiting, and WebSocket authentication. All ABP endpoints require authentication via the `x-api-key` header, except the unauthenticated infrastructure endpoints (`/health`, `/ready`, `/status`, `/metrics`). ## How to authenticate Pass your API key in the `x-api-key` request header: ```bash theme={null} curl -H "x-api-key: your-api-key" \ https://v2.55-tech.com/accounts ``` ## API key metadata Each API key resolves to a client and its access rules: | Field | Description | | ------------ | -------------------------------------------------------------------------- | | `clientName` | Your client identifier — all accounts, orders, and bets are filtered by it | | `bookmakers` | Allowed bookmaker slugs (empty = all bookmakers) | | `rps` | Requests per second allowed for this client (default: 100) | | `active` | Whether this key is active | Your API key determines which accounts, orders, and bets you can access. All data is filtered by your `clientName`. ## WebSocket authentication For WebSocket connections, authenticate via the login message after connecting (the `/ws` endpoint itself does not require the `x-api-key` header): ```json theme={null} { "type": "login", "apiKey": "your-api-key", "channels": [] } ``` You must send the login message within **30 seconds** of connecting, or the connection is closed. See [WebSocket](/abp-api/websocket) for details. ## Rate limiting Requests are rate-limited **per client** — by default 100 requests per second, with a maximum of 5 concurrent WebSocket connections per API key. Full detail, headers, and backoff guidance live in [Rate limits](/abp-api/reliability#rate-limits). ## Error responses The auth middleware emits errors under an `error` key, while application-level errors (validation, not-found, etc.) use the FastAPI-standard `detail` key. Handle both shapes. | Situation | Status | Body | | -------------------------------------------------------------- | ------ | --------------------------------------------------------------------- | | Header missing | `403` | `{"detail": "Missing API key header: x-api-key"}` | | Key invalid / inactive / expired | `401` | `{"error": "Invalid or inactive API key"}` | | Key valid but not allowed for the endpoint / sport / bookmaker | `403` | `{"error": "Access denied to endpoint"}` | | Rate limit exceeded | `429` | `{"detail": "Rate limit exceeded", "limit": "100", "retry_after": 1}` | ## Next steps Place your first bet in 5 steps. Per-client rate limits, circuit breakers, and emergency mode. # ABP Bookmaker Capability Matrix Source: https://docs.55-tech.com/abp-api/bookmakers All 32 bookmakers supported by ABP, grouped by type, with slugs and capability notes — sportsbooks, betting exchanges, prediction markets, and punter platforms. ABP integrates **32 bookmakers** behind one API. Identify each by its **slug** (e.g. `pinnacle`, `betfair-ex`, `polymarket`). Call `GET /bookmakers` for the live list plus each bookmaker's default stake limits. Your API key may be scoped to a subset — see [Authentication](/abp-api/authentication). ## How to read this page * **Slug** — the string you pass in account creation, `bookmakers` filters, and order routing. * **Type** — Sportsbook, Exchange, Prediction market, or Punter platform. Type affects fill behaviour. * **Sweep** — whether the venue fills a partial-stake order by sweeping its order book in a single shot (see [Order Placement → exchange sweep](/abp-api/order-placement)). Non-sweep venues are filled by ABP's multi-pass weighted-average logic instead. ## Traditional sportsbooks Single-price venues. ABP places one or more bets against the quoted line and applies the [limit cascade](/abp-api/currency#stake-limit-cascade). | Bookmaker | Slug | Sweep | | -------------- | ---------------- | :---: | | Pinnacle | `pinnacle` | — | | Pinnacle B2B | `pinnacleb2b` | — | | Betamapola | `betamapola` | — | | Betcris | `betcris` | — | | Bookmaker.eu | `bookmaker.eu` | — | | Cloudbet | `cloudbet` | — | | Cloudbet B2B | `cloudbetb2b` | — | | Justbet | `justbet` | — | | Kaiyun | `kaiyun` | — | | Monkeyline | `monkeyline.vip` | — | | 198bet | `198bet` | — | | Paradise Wager | `paradisewager` | — | | Sharpbet | `sharpbet` | — | | Singbet | `singbet` | — | | Sports411.ag | `sports411.ag` | — | | 3et | `3et` | — | | 3et++ | `3et++` | — | ## Betting exchanges Order-book venues. ABP places against available liquidity. | Bookmaker | Slug | Sweep | | ------------------ | -------------- | :---: | | Betfair Exchange | `betfair-ex` | ✅ | | Smarkets | `smarkets` | — | | Limitless Exchange | `limitless-ex` | — | | Matchbook | `matchbook` | — | ## Prediction markets Binary/outcome markets, mostly sweep-capable — the venue fills against its book in one shot at your `orderPrice` floor. | Bookmaker | Slug | Sweep | | ------------- | --------------- | :---: | | Polymarket | `polymarket` | ✅ | | Polymarket US | `polymarket.us` | ✅ | | Kalshi | `kalshi` | ✅ | | Predict.fun | `predict.fun` | ✅ | | ProphetX | `prophetx` | — | | SX Bet | `sx.bet` | ✅ | | Vertex | `vertex` | — | | 4casters | `4casters` | ✅ | | Novig | `novig.us` | ✅ | ## Punter platforms | Bookmaker | Slug | Sweep | | ----------- | ------------- | :---: | | Punter.io | `punter.io` | — | | Punter.io++ | `punter.io++` | — | ## Live betslip fetching Most bookmakers' odds and limits arrive through [OddsPapi v5](https://docs.oddspapi.io/). A few venues (for example `singbet`) don't expose limits through OddsPapi, so ABP fetches them live from the bookmaker API whenever a subscribed price changes — deduplicated per unique account. This is transparent to you: the `betslip` WebSocket channel and `GET /betslip` behave identically regardless of the source. ## Stake limits Each bookmaker carries default `minStake` / `maxStake` values, returned by `GET /bookmakers`. These are the *bookmaker* tier of the [limit cascade](/abp-api/currency#stake-limit-cascade) — an account override or a tighter odds limit can supersede them. ## Next steps How sweep and multi-pass fills differ. Native currencies and the limit cascade. # ABP Changelog Source: https://docs.55-tech.com/abp-api/changelog Changelog for the ABP API — new features, changes, breaking changes, and fixes across releases. All timestamps are **UTC**. Breaking changes are called out explicitly — subscribe to the `status` WebSocket channel and watch this page before upgrading. ## 2026-06-17 ### 🔁 Changed * **Single source of truth for currency.** An account's trading currency now comes solely from `accounts.currencyId`; default-currency guessing has been removed. Order stakes are denominated in `orderCurrency` (default `USD`), while bets and balances use the account's native currency. See [Currency & Limits](/abp-api/currency). * **REST betslip path aligned with the WebSocket path** so both transports return identical odds/limit shapes. ### 🔧 Improved * Expanded documentation: new [Core Concepts](/abp-api/concepts), [Order Placement](/abp-api/order-placement) (with copy-paste recipes), [Currency & Limits](/abp-api/currency), [Limits & Reliability](/abp-api/reliability), and [Bookmakers](/abp-api/bookmakers) guides, plus a full message/payload reference folded into the [WebSocket](/abp-api/websocket) guide. ## Earlier ### ✨ Added * **32 bookmaker integrations** behind a single API — sportsbooks, betting exchanges, prediction markets, and punter platforms. See [Bookmakers](/abp-api/bookmakers). * **Smart order routing** with single and multi-bookmaker placement, partial fills, and exchange sweep mode. * **Reliable WebSocket delivery** — opt-in at-least-once delivery with `ack` / `ack_batch` / `replay` and a 100-message buffer. * **Request deduplication** — per-order `requestUuid` idempotency with a 30-minute TTL. * **Analytics** — `GET /positions` and `GET /pnl` for aggregated exposure and profit/loss. ### 🚧 Coming soon * **Futures / outright markets** via `futureId` + `participantId`. The data model is in place; order placement and betslip retrieval currently return `501 Not Implemented`. # ABP Core Concepts - IDs, Lifecycles & Idempotency Source: https://docs.55-tech.com/abp-api/concepts The mental model behind ABP: identifiers (fixtureId, outcomeId, playerId, participantId), orders vs bets, request idempotency, the order/bet/settlement lifecycles, and a glossary of key terms. **TL;DR** — An **order** is your instruction; a **bet** is the wager that lands at a bookmaker. Identifiers (`fixtureId`, `outcomeId`, `playerId`, `participantId`) are shared with [OddsPapi v5](https://docs.oddspapi.io/). Every order carries a unique `requestUuid` for idempotency. Three independent lifecycles track an order, each of its bets, and each bet's settlement. ## Identifiers ABP shares its identifier space with [OddsPapi v5](https://docs.oddspapi.io/) — the same `fixtureId` and `outcomeId` you discover via OddsPapi are the ones you send to ABP. No translation layer is needed. | Identifier | Type | Description | | --------------- | ------- | --------------------------------------------------------------------------------------------------------- | | `fixtureId` | string | A single event/match (e.g. `id1000004461512432`). Used for standard fixture markets. | | `futureId` | string | An outright/futures market (e.g. tournament winner). Mutually exclusive with `fixtureId`. *(coming soon)* | | `outcomeId` | integer | The specific selection within a market (e.g. home win, over 2.5). | | `playerId` | integer | Player for player-prop markets. Use `0` when the market is not player-specific. | | `participantId` | integer | The selection within a futures market (team/player to win). Futures only. *(coming soon)* | ### Market keys Internally, every priced selection is addressed by a composite market key: ``` Fixture: fixtureId:bookmaker:outcomeId:playerId Future: futureId:bookmaker:outcomeId:playerId:participantId ``` You rarely build these by hand — `GET /betslip` returns them keyed and ready — but understanding the shape helps when reading WebSocket `betslip` payloads. ## Orders vs bets This distinction is the single most important thing to internalize. | | Order | Bet | | ------------- | ------------------------------------- | ------------------------------------------- | | What it is | Your **instruction** to place a stake | An **actual wager** accepted by a bookmaker | | Created by | You, via `POST /place-orders` | ABP, while fulfilling an order | | Cardinality | 1 order | 0..N bets | | Currency | `orderCurrency` (default USD) | the account's native currency | | Identified by | `orderId` (+ your `requestUuid`) | `betId` | One order can produce **multiple bets** when partial fills or multi-bookmaker routing apply. For example, a single order for 5,000 USD might fill as three bets across two bookmakers. See [Order Placement](/abp-api/order-placement) for how fills are distributed. ## Idempotency & request deduplication Network retries are unavoidable, and a retried placement must never double-stake. ABP guarantees this through **request deduplication**. Each order in a `POST /place-orders` batch carries its own `requestUuid` (standard 8-4-4-4-12 UUID). Generate it client-side, once, and reuse the **same** value on every retry of that order. The server records each `requestUuid` it has seen with a **30-minute TTL**. Within that window, a repeat of the same `requestUuid` is recognised as a duplicate. A duplicate order is **not** placed again and is **not** returned as a decline. It is simply skipped, so the rest of the batch processes normally. If *every* order in a request is a duplicate, there is nothing to do, so the request returns `409 Conflict` with the offending UUIDs. ```json theme={null} { "detail": { "error": "All orders are duplicates", "duplicateUuids": ["eb45b192-317b-42d5-9f65-af497b9fa8c1"], "inProgressUuids": [] } } ``` Reusing a `requestUuid` for a *different* order within 30 minutes will cause that order to be skipped. Always generate a fresh UUID for each distinct placement, and only reuse it verbatim when retrying that exact placement. ## Order lifecycle ``` PENDING → PROCESSING → FILLED / PARTIALLY_FILLED / REJECTED / EXPIRED / CANCELLED / FAILED ``` | Status | Meaning | | ------------------ | -------------------------------------------------------- | | `PENDING` | Order received and queued | | `PROCESSING` | Routing to bookmakers | | `FILLED` | All stake placed successfully | | `PARTIALLY_FILLED` | Some stake placed; remainder expired or no capacity | | `REJECTED` | Failed validation (bad odds, invalid fixture, etc.) | | `EXPIRED` | `expiresAt` reached before filling (default 5s, max 24h) | | `CANCELLED` | Explicitly cancelled by the client | | `FAILED` | Internal error during placement | ## Bet lifecycle ``` PENDING → PLACED → CONFIRMED / REJECTED / CANCELLED / FAILED / VOID ``` | Status | Meaning | | ----------- | ---------------------------------------- | | `PENDING` | Bet created, awaiting bookmaker response | | `PLACED` | Sent to bookmaker, awaiting confirmation | | `CONFIRMED` | Bookmaker accepted the bet | | `REJECTED` | Bookmaker rejected the bet | | `CANCELLED` | Bet cancelled before confirmation | | `FAILED` | Internal error during placement | | `VOID` | Bet voided by the bookmaker | ## Settlement lifecycle Once a bet is `CONFIRMED`, settlement tracks the financial result: ``` UNSETTLED → WON / LOST / VOID / HALF_WON / HALF_LOST / PUSH / CASHOUT ``` | Status | Meaning | | ----------- | -------------------------------------- | | `UNSETTLED` | Bet is live, awaiting result | | `WON` | Full win | | `LOST` | Full loss | | `VOID` | Voided (stake returned) | | `HALF_WON` | Asian-handicap partial win | | `HALF_LOST` | Asian-handicap partial loss | | `PUSH` | Stake returned (tie on the line) | | `CASHOUT` | Early withdrawal at a negotiated price | Settlement changes are pushed over the `settlements` WebSocket channel. See [Order Placement](/abp-api/order-placement) and [WebSocket](/abp-api/websocket). ## Account priority & the limit cascade Each bookmaker account has a `priority` (higher = preferred). When routing, ABP selects the highest-priority active account first for each bookmaker. Stake limits resolve in priority order — **account limits > bookmaker limits > odds limits** — with the first non-null value winning: ``` account.maxStake → bookmaker.maxStake → odds.limit account.minStake → bookmaker.minStake → odds.limitMin ``` Full detail and worked examples live in [Currency & Limits](/abp-api/currency). ## Glossary | Term | Definition | | ------------------- | -------------------------------------------------------------------------------------------- | | **Order** | A client instruction to place a stake, identified by `orderId` and your `requestUuid`. | | **Bet** | A wager accepted by a bookmaker, identified by `betId`, belonging to one order. | | **Fill** | The act of placing stake. A *partial fill* places only part of the requested stake. | | **Betslip** | The aggregated view of live odds and limits across your configured accounts for a selection. | | **requestUuid** | Client-generated idempotency key (UUID) for an order; deduplicated for 30 minutes. | | **orderCurrency** | The currency an order's stakes are denominated in (default USD). | | **Native currency** | An account's own trading currency; bets and balances are denominated in it. | | **Priority** | Per-account ranking; higher priority accounts are selected first. | | **Limit cascade** | Resolution order for stake limits: account → bookmaker → odds. | | **Sweep** | Multi-bookmaker partial-fill mode that exhausts the best price before moving on. | | **Slug** | A bookmaker's string identifier (e.g. `pinnacle`, `betfair-ex`). | | **Settlement** | The financial result of a confirmed bet (WON/LOST/VOID/…). | ## Next steps How fills, pricing, and the limit cascade work. Denomination, conversion, and limits in depth. Place your first bet in 5 steps. Capability matrix across all 32 bookmakers. # ABP Currency & Limits - Denomination and Conversion Source: https://docs.55-tech.com/abp-api/currency How ABP denominates orders, bets, balances, and limits across currencies, when conversion happens, what currencyInfo and the USD fields mean, and the stake-limit cascade. ABP is multi-currency. This page explains which value is in which currency, where conversion happens (and where it deliberately doesn't), and how to read the currency fields in API responses. ## Two currencies, always For any bet there are only ever **two** currencies in play: 1. **Account currency** — the currency of the bookmaker account (`accounts.currencyId`). Everything the bookmaker actually touches is in this currency: the placed stake, the balance, the settlement payout. 2. **Order currency** — what you express the order in (`orderCurrency`, default `USD`). Everything on the order is in this currency. Conversion only ever happens between these two. The account currency is the single authoritative trading currency — it is read from the account, never guessed. If an account's currency can't be resolved, ABP **rejects the placement** rather than proceeding in a guessed currency. ## Which field is in which currency | Object | Field | Currency | | ----------- | --------------------------------------------- | ----------------------------------- | | **Order** | `orderStake`, `filledStake`, `remainingStake` | order currency (`orderCurrency`) | | **Bet** | `placedStake`, `settlementAmount` | account currency (`placedCurrency`) | | **Account** | `balance`, `credit` | account currency (`currencyId`) | Within a single order, `orderStake`, `filledStake`, and `remainingStake` are always in the same currency and directly comparable. On a bet, `placedCurrency` always equals the account's `currencyId`. ## Exchange rates: `currencyValue` Rates live on the `currencies` channel/table. The convention is: ``` 1 USD = currencyValue × ``` So `currencyValue` is **units of that currency per 1 USD**. USD itself has `currencyValue = 1`. To convert between currencies A and B: ``` amount_B = amount_A / currencyValue(A) × currencyValue(B) ``` To get the USD equivalent of any amount, divide by its `currencyValue`: ``` usd = placedStake / currencyInfo.currencyValue ``` ## `currencyInfo` on reads `GET /orders`, `GET /bets`, and `GET /accounts` enrich each row with a `currencyInfo` object so you can compute USD without a second lookup: ```json theme={null} { "currency": "EUR", "currencyValue": 0.84788876, "updatedAt": "2026-02-07T17:29:32+00:00" } ``` * On an **order**, `currencyInfo` describes its `orderCurrency`. * On a **bet**, `currencyInfo` describes its `placedCurrency`. * On an **account**, `currencyInfo` describes its `currencyId` (so you can convert `balance` / `credit` to USD). `updatedAt` is the ISO 8601 timestamp of the rate snapshot. `currencyInfo` is **nullable** — it is `null` when no exchange rate is available for that currency. The stored `placedStake` / `placedCurrency` are never rewritten — `currencyInfo` is additive, for display/conversion only. ## Worked example Order `orderStake = 100`, `orderCurrency = USD`, filled on a Betfair **EUR** account where `1 USD ≈ 0.92 EUR`: * `bets.placedStake = 92`, `placedCurrency = EUR` (what was sent to Betfair). * Converted back: `92 EUR ÷ 0.92 = 100 USD`. * `orders.filledStake = 100`, `remainingStake = 0`, status `FILLED`. ## Betslip limits: native + USD `GET /betslip` (and the betslip WebSocket payload) returns both the native limit and a USD equivalent for every bookmaker, so you can compare across accounts in different currencies: ```json theme={null} { "price": 1.52, "limit": 751.34, "limitMin": 1, "limitCurrency": "EUR", "limitUsd": 886.13, "limitMinUsd": 1.18, "active": true, "account": "sharpbet_user", "currencyInfo": { "currency": "EUR", "currencyValue": 0.84788876, "updatedAt": "2026-02-07T17:29:32+00:00" } } ``` * `limit` / `limitMin` are in `limitCurrency` (the account currency). * `limitUsd` / `limitMinUsd` are the same values normalized to USD. ## Stake-limit cascade The effective min/max stake for a placement is resolved in priority order (first non-null wins): ``` effective max = account.maxStake → bookmaker.maxStake → odds.limit effective min = account.minStake → bookmaker.minStake → odds.limitMin → 0 ``` Limit comparisons during placement are performed in the account currency (your order stake is converted in, and the allowable stake is converted back to the order currency for reporting). See [Order Placement](/abp-api/order-placement) for how limits interact with the fill logic. ## Where conversion does NOT happen * **Balances & credits** (`accounts.balance`, `accounts.credit`) are reported raw in the account's native currency — there is no USD-normalized balance. * **Settlement amounts** (`bets.settlementAmount`) come straight from the bookmaker in native (account) currency. **Cross-currency aggregation caveat.** `GET /pnl` and `GET /positions` sum stakes/settlements **without** converting to a common currency, and their responses carry no currency field. For a group within a single bookmaker (one currency) this is fine, but a group spanning accounts in different currencies (e.g. `groupBy=userRef`, or top-level totals) sums mixed currencies into one number. Per-order values are always correct — only the cross-currency aggregate is unsafe. Convert per-row to USD yourself if you need a mixed-currency total. ## Next steps How modes, pricing, and limits drive fills. Handle decline reasons and error responses. # ABP Error Handling Source: https://docs.55-tech.com/abp-api/errors ABP API error codes, validation errors, order decline reasons, and resilience patterns. Learn how to handle errors in your integration. ## Error response format ABP returns errors in two shapes, depending on where the error originates: * **Auth / rate-limit middleware** errors use an `error` key: ```json theme={null} { "error": "Invalid or inactive API key" } ``` * **Application / route** errors (validation, not-found, etc.) use the FastAPI-standard `detail` key: ```json theme={null} { "detail": "No odds found for given parameters" } ``` Handle both shapes in your client. Request-body validation errors (`422`) include field-level detail with a `loc` path: ```json theme={null} { "detail": [ { "loc": ["body", "orders", 0, "orderStake"], "msg": "Input should be greater than 0", "type": "greater_than" } ] } ``` ## HTTP status codes | Status | Description | | ------ | ----------------------------------------------------------------------------- | | `200` | Success | | `201` | Resource created | | `204` | Resource deleted (no content) | | `400` | Bad request — invalid or missing parameters | | `401` | Unauthorized — invalid or inactive API key | | `403` | Forbidden — missing API key header, or resource belongs to a different client | | `404` | Not found — resource does not exist | | `409` | Conflict — every order in the request is a duplicate (see below) | | `422` | Validation error — request body failed validation | | `429` | Rate limited — exceeded requests per second | | `500` | Internal server error | | `501` | Not implemented — futures markets (`futureId`) are not yet supported | | `503` | Service unavailable — database or dependency down | ## Order decline reasons When placing orders via `POST /place-orders`, orders that fail business validation are returned in the `declinedOrders` array (not as HTTP errors). Each declined order includes a `declineReason`: | Decline reason | Description | | ---------------------------- | --------------------------------------------------------------- | | Stake exceeds limit | `orderStake` is higher than the available bookmaker limit | | Stake below minimum | `orderStake` is below the bookmaker's or account's minimum | | Invalid odds | `orderPrice` is not available or the market is suspended | | No active accounts | No active bookmaker accounts available for this market | | Currency conversion failed | Could not convert between order currency and bookmaker currency | | Bookmaker not available | Specified bookmaker doesn't have odds for this fixture/outcome | | Odds temporarily unavailable | Odds could not be fetched in time; retry | Duplicate `requestUuid`s are not returned as decline reasons. A duplicate is silently skipped; if **every** order in the request is a duplicate, the request returns `409` instead (see below). **Example declined order response:** ```json theme={null} { "status": "declined", "acceptedOrders": [], "declinedOrders": [ { "requestUuid": "fb5f2dd9-c855-4ba9-8ef9-4c2278ca2f1d", "fixtureId": "id1000000861624412", "outcomeId": 161, "declineReason": "Order stake 15000.00 USD exceeds available limit 5000.00 USD", "bets": [] } ] } ``` ## Common errors ### Authentication (401 / 403) ```bash theme={null} # Missing header → 403 {"detail": "Missing API key header: x-api-key"} curl https://v2.55-tech.com/accounts # Invalid or inactive key → 401 {"error": "Invalid or inactive API key"} curl -H "x-api-key: wrong-key" https://v2.55-tech.com/accounts ``` ### Forbidden (403) Returned when the API key header is missing, when the key is not allowed for the requested endpoint/sport/bookmaker, or when you attempt to access a resource that belongs to a different client. ```json theme={null} {"detail": "Missing API key header: x-api-key"} {"error": "Access denied to endpoint"} {"detail": "Access denied: client not resolved"} ``` ### Duplicate request (409) Returned only when **every** order in the request is a duplicate — each `requestUuid` was already processed or is in progress within the last **30 minutes**. If only some orders are duplicates, those are skipped and the rest are processed normally. ```json theme={null} { "detail": { "error": "All orders are duplicates", "duplicateUuids": ["eb45b192-317b-42d5-9f65-af497b9fa8c1"], "inProgressUuids": [] } } ``` ### Validation error (422) Request body contains invalid data. Check the `loc` field for the problematic path: ```json theme={null} { "detail": [ { "loc": ["body", "orders", 0, "fixtureId"], "msg": "Field required", "type": "missing" } ] } ``` ### Rate limiting (429) Exceeded requests per **second** for your client. Default limit: 100/second (configurable per client). ```json theme={null} { "detail": "Rate limit exceeded", "limit": "100", "retry_after": 1 } ``` The window resets every second — wait the `retry_after` interval before retrying. ## Resilience patterns ABP implements several resilience mechanisms that may affect your integration: ### Circuit breakers Per-bookmaker circuit breakers prevent cascading failures. If a bookmaker is experiencing issues, orders targeting that bookmaker may be declined until the circuit recovers. Opens after consecutive failures, automatically tests recovery, and resumes normal operation once the bookmaker responds successfully. ### Emergency mode In rare cases, the system may temporarily pause order processing during maintenance or upstream issues. The `emergency` WebSocket channel broadcasts status changes. ### Order expiry Orders have a default `expiresAt` of 5 seconds from creation (capped at 24 hours maximum). If a bet hasn't been placed within this window, the order status changes to `EXPIRED`. Set a custom `expiresAt` for longer-lived orders. ## Next steps Understand fills, partial stakes, and decline reasons. Track order and bet status in real time. # ABP Order Placement - Modes, Pricing & Limits Source: https://docs.55-tech.com/abp-api/order-placement How ABP fills orders: the four placement modes, the first-bet and weighted-average price rules, the stake-limit cascade, retries, expiry, and cancellation. This page explains exactly how ABP turns an **order** (your instruction) into one or more **bets** (actual wagers at bookmakers): which bookmaker(s) it picks, how it respects your price, how stake limits are resolved, and when it retries or gives up. ## The two inputs that decide behavior Placement behavior is determined by two things on each order: 1. **How many bookmakers you target** — one (`bookmakers: ["pinnacle"]`) or many (`bookmakers: ["pinnacle", "sharpbet"]`, or `*` for all allowed). 2. **`acceptPartialStake`** (default `true`) — whether ABP may split your stake across multiple bets to fill more of it. These combine into four placement modes: | Mode | Bookmakers | `acceptPartialStake` | Fill strategy | | ----- | ---------- | -------------------- | ------------------------------------------------------------------------------------ | | **1** | Single | `false` | All-or-nothing at one book. Declines immediately if `orderStake` exceeds the limit. | | **2** | Single | `true` | Multiple bets at one book until the stake is filled or the limit/price is exhausted. | | **3** | Multiple | `false` | One bet per bookmaker, placed in parallel. | | **4** | Multiple | `true` | Sequential by best price — exhaust each bookmaker before moving to the next. | You don't select a mode explicitly — ABP infers it from your `bookmakers` list and `acceptPartialStake`. Leaving `bookmakers` empty (or `*`) lets ABP route across every bookmaker your key is allowed to use. ## Price rules ### First-bet rule (all modes) The **first** bet of an order must always be placed at a price **`>= orderPrice`**. If no bookmaker currently offers a good enough price, ABP waits and retries (it does not place a worse first bet) until the order expires. ### Weighted-average rule (modes 2, 3, 4) After at least one bet is filled, subsequent bets may be placed **below** `orderPrice`, as long as the running **weighted-average price** of the order stays `>= orderPrice`: ``` weighted_avg = Σ(stake × price) / Σ(stake) ``` The maximum stake ABP will place at a below-target price is: ``` max_stake = (weighted_sum − orderPrice × filled_stake) / (orderPrice − bookmaker_price) ``` If `max_stake <= 0`, that bookmaker is **exhausted** for this order. This lets ABP capture extra liquidity at slightly worse prices without ever breaching your average target. ### Mode 1 exception In **Mode 1** (single book, no partial), *every* bet must clear `orderPrice` — the weighted-average relaxation does not apply. Set `acceptBetterOdds: true` (the default) to allow fills at prices **better** than `orderPrice`. This never hurts you — it only ever improves the average. ## Worked example (Mode 2) Order: `orderStake = 1000`, `orderPrice = 2.00`, `bookmakers = ["pinnacle"]`, `acceptPartialStake = true`. | Pass | Market price | Limit | Action | Filled | Weighted avg | | ---- | ------------ | ----- | ----------------------------------------------- | ------ | ------------ | | 1 | 2.10 | 400 | Place 400 @ 2.10 (first bet ≥ 2.00 ✓) | 400 | 2.10 | | 2 | 1.95 | 300 | `max_stake` keeps avg ≥ 2.00 → place 300 @ 1.95 | 700 | 2.04 | | 3 | 1.90 | 500 | `max_stake = 280` → place 280 @ 1.90 | 980 | 2.00 | | 4 | price moved | — | `max_stake ≤ 0` → exhausted | 980 | 2.00 | Result: order is `PARTIALLY_FILLED` with **980** staked at an average of **2.00** — never below your target. ## Exchange sweep mode For prediction-market exchanges (`betfair-ex`, `polymarket`, `polymarket.us`, `kalshi`, `predict.fun`, `sx.bet`, `novig.us`, `4casters`) with partial fills enabled, ABP sends a **single** order for the full stake with `orderPrice` as the minimum, and lets the exchange sweep its own order book in one shot. This is faster and more accurate than ABP's multi-pass logic, and there are no per-pass retries — the exchange fills everything available at or above your price immediately. ## Stake-limit cascade The effective min/max stake for each placement is resolved in priority order (first non-null wins): ``` effective max = account.maxStake → bookmaker.maxStake → odds.limit effective min = account.minStake → bookmaker.minStake → odds.limitMin → 0 ``` For example, if your account has `maxStake: 500`, the bookmaker default is `1000`, and the live odds limit is `300`, the effective max is **500** (the account override wins, even though it is lower than the bookmaker default). You can preview the effective limits for any selection with `GET /betslip` before placing — it returns the resolved `limit`/`limitMin` (and their USD equivalents) per bookmaker. See [Currency & Limits](/abp-api/currency). ## Account priority When you target a bookmaker that has several accounts, ABP picks the **highest-`priority`** active account first. Configure priority per account via `POST`/`PATCH /accounts`. ## Retries & expiry * **Retry throttle:** ABP retries a failing `(order, bookmaker)` pair no more than once every **2 seconds**. * **Fresh odds:** before each retry pass, ABP re-fetches live odds so placement always uses current market prices, not stale data. * **Expiry:** every order has an `expiresAt` (default **5 seconds** from creation, max **24 hours**). When it's reached, ABP stops trying. Whatever filled so far determines the final status. ## Final order status | Status | Meaning | | ------------------ | ---------------------------------------------------------------------- | | `FILLED` | Entire stake placed. | | `PARTIALLY_FILLED` | Some stake placed; the rest expired or ran out of price/limit. | | `REJECTED` | Failed validation (e.g. stake above limit in Mode 1, invalid fixture). | | `EXPIRED` | `expiresAt` reached before anything could be placed. | | `CANCELLED` | You cancelled it (see below). | | `FAILED` | Internal error during placement. | The `POST /place-orders` response summarizes a batch as `accepted` (all placed), `partial-success` (some declined), or `declined` (all declined), with per-order detail in `acceptedOrders` / `declinedOrders`. ## Cancellation `POST /cancel-orders` (by `orderIds`, `requestUuids`, or `userRef`) or `POST /cancel-all-orders` cancels orders that are still `PENDING` or `PARTIALLY_FILLED`. Cancellation is **asynchronous and cooperative**: 1. The order's status is set to `CANCELLED` and its `expiresAt` is moved to now. 2. A cross-request signal is set so any in-flight placement loop stops at its next pass. 3. Pending bets already sent to bookmakers are cancelled where the bookmaker supports it. Bets that were already **confirmed** at a bookmaker cannot be cancelled — only un-filled remainder is stopped. ## Recipes Each recipe uses the same running selection — fixture `id1000004461512432`, outcome `103`, player `0`. Swap in your own IDs (discover them via [OddsPapi v5](https://docs.oddspapi.io/)) and your `x-api-key`. Base URL: `https://v2.55-tech.com`. ### Place across multiple bookmakers (best price) List several bookmakers and let ABP route to the best price/limits. With `acceptPartialStake: true`, ABP sweeps the cheapest first and moves on until the stake is filled (Mode 4 above). ```bash theme={null} curl -X POST https://v2.55-tech.com/place-orders \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "orders": [{ "requestUuid": "eb45b192-317b-42d5-9f65-af497b9fa8c1", "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0, "orderPrice": 1.95, "orderStake": 5000.0, "bookmakers": ["pinnacle", "betfair-ex", "polymarket"], "acceptPartialStake": true, "userRef": "user1" }] }' ``` ```python theme={null} import uuid, requests resp = requests.post( "https://v2.55-tech.com/place-orders", headers={"x-api-key": "YOUR_API_KEY"}, json={"orders": [{ "requestUuid": str(uuid.uuid4()), "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0, "orderPrice": 1.95, "orderStake": 5000.0, "bookmakers": ["pinnacle", "betfair-ex", "polymarket"], "acceptPartialStake": True, "userRef": "user1", }]}, ) data = resp.json() for order in data["acceptedOrders"]: print(order["orderId"], "→", len(order["bets"]), "bets") ``` The response splits orders into `acceptedOrders` and `declinedOrders`. An accepted order may carry multiple `bets` (one per bookmaker it filled against). ### Allow partial fills, reject the rest To fill *only* what's available at your price and never chase, set `acceptPartialStake: true` on a single bookmaker. The order ends `PARTIALLY_FILLED` if liquidity runs out — no bets are placed above your `orderPrice` floor beyond the weighted-average rule. The order's `remainingStake` stays unplaced and the order settles to `PARTIALLY_FILLED` once `expiresAt` is reached. No further bets are attempted. If you instead want all-or-nothing, set `acceptPartialStake: false` — the order is declined entirely if the full stake can't clear at `orderPrice`. ### Reconnect & replay missed messages Enable `reliableDelivery` so you can recover anything dropped during a disconnect. Track the last `seq` you processed per subscription; on reconnect, `replay` from there. ```python theme={null} import asyncio, json, websockets LAST_SEQ = 0 # persist this across reconnects async def run(): global LAST_SEQ async with websockets.connect("wss://v2.55-tech.com/ws") as ws: await ws.send(json.dumps({ "type": "login", "apiKey": "YOUR_API_KEY", "channels": ["orders", "bets", "settlements"], "reliableDelivery": True, })) if LAST_SEQ: await ws.send(json.dumps({"type": "replay", "fromSeq": LAST_SEQ})) async for raw in ws: msg = json.loads(raw) if msg["type"] == "ping": await ws.send(json.dumps({"type": "pong"})) elif msg["type"] == "data": LAST_SEQ = msg["seq"] # ... handle msg["payload"] ... await ws.send(json.dumps({"type": "ack_batch", "upToSeq": LAST_SEQ})) asyncio.run(run()) ``` A gap in `seq` means you missed a message. If a `replay` can't fill the gap (you fell behind the 100-message buffer), reconcile via `GET /orders` and `GET /bets`. See [WebSocket → reliable delivery](/abp-api/websocket#reliable-delivery-acknowledgments). ### Reconcile settlements Subscribe to `settlements` for push updates, and periodically sweep `GET /bets` as a backstop. Settlement statuses are `WON`, `LOST`, `VOID`, `HALF_WON`, `HALF_LOST`, `PUSH`, `CASHOUT` (see [Core Concepts](/abp-api/concepts#settlement-lifecycle)). ```python theme={null} import requests resp = requests.get( "https://v2.55-tech.com/bets", headers={"x-api-key": "YOUR_API_KEY"}, params={"settlementStatus": "WON", "limit": 100}, ) for bet in resp.json().get("bets", []): print(bet["betId"], bet["settlementStatus"], bet["settlementAmount"]) ``` ### Cancel orders Cancellation is asynchronous: ABP marks the order, cancels any pending bets at the bookmaker, and the placement loop stops at its next pass (see [Cancellation](#cancellation) above). Already-confirmed bets are not recalled. ```bash theme={null} curl -X POST https://v2.55-tech.com/cancel-orders \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"orderIds": [123456]}' ``` ```bash theme={null} curl -X POST https://v2.55-tech.com/cancel-all-orders \ -H "x-api-key: YOUR_API_KEY" ``` Watch the `orders` channel for the resulting `CANCELLED` (or `PARTIALLY_FILLED`) status. ## Next steps How stakes, balances, and limits are denominated and converted. Track fills and settlements in real time. # ABP API Overview - Automated Bet Placing Source: https://docs.55-tech.com/abp-api/overview ABP v2 API overview. Place bets across 32 bookmakers through a single unified API with smart routing, partial fills, real-time tracking, and automated settlement. ## What is ABP? The **Automated Bet Placing (ABP)** API by [55-Tech](https://55-tech.com/) lets you place bets across 32 bookmakers through a single integration. Instead of building and maintaining individual bookmaker connectors, ABP handles the entire lifecycle from bet placement through settlement. **Core capabilities:** * **Account management** — Full CRUD for bookmaker accounts with priority-based selection, per-account stake limits, and multi-currency support * **Betslip retrieval** — Get real-time odds and limits for any fixture/outcome across all configured bookmakers before placing * **Futures support** — Place bets on outright/futures markets using `futureId` and `participantId` (coming soon) * **Smart order routing** — Place single or bulk orders; ABP automatically selects the best bookmaker by odds and limits * **Bet tracking** — Monitor every bet from placement through confirmation and settlement with full audit trail * **Position & PnL analytics** — Aggregated views of exposure and profit/loss grouped by bookmaker, account, or user reference ## Data flow Your app consumes the [OddsPapi v5 API](https://docs.oddspapi.io/) to discover fixtures, markets, outcomes, and real-time odds across all supported bookmakers. OddsPapi is the data layer — ABP is the execution layer built on top of it. Fixture IDs and outcome IDs are shared between both APIs. Before placing, call `GET /betslip?fixtureId=...&outcomeId=...&playerId=...` to get aggregated odds and limits from all your configured bookmaker accounts. For futures markets, use `futureId` instead of `fixtureId` along with `participantId`. ABP resolves stake limits (account > bookmaker > odds) and returns the effective min/max per bookmaker. Send `POST /place-orders` with one or more orders. Each order targets either a fixture (`fixtureId`) or a future (`futureId`). ABP routes each order to the best bookmaker(s) based on price, available limits, and account priority. Each bookmaker integration places the bet and reports back confirmation or decline. Connect to `WS /ws` to receive real-time pushes for order status changes, bet confirmations, settlements, balance updates, and system events. No polling needed. Use `GET /orders`, `GET /bets` for order/bet history with keyset pagination, and `GET /positions`, `GET /pnl` for aggregated exposure and profit/loss analytics. ## Base URL ``` https://v2.55-tech.com ``` ## Key concepts ### Orders vs bets An **order** is your instruction to place a bet. A **bet** is the actual wager placed on a bookmaker. One order can result in multiple bets when using partial fills or multi-bookmaker routing. ### Request deduplication Each order requires a unique `requestUuid` (UUID format). ABP uses server-side deduplication (30-minute TTL) to prevent duplicate placements. A duplicate order is silently skipped; if **every** order in a request is a duplicate, the request returns `409 Conflict`. ### Order lifecycle ``` PENDING → PROCESSING → FILLED / PARTIALLY_FILLED / REJECTED / EXPIRED / CANCELLED / FAILED ``` * **PENDING** — Order received and queued * **PROCESSING** — Routing to bookmakers * **FILLED** — All stake placed successfully * **PARTIALLY\_FILLED** — Some stake placed, remaining expired or no capacity * **REJECTED** — Failed validation (bad odds, invalid fixture, etc.) * **EXPIRED** — Order `expiresAt` time reached (default: 5 seconds, max: 24 hours) * **CANCELLED** — Explicitly cancelled by client * **FAILED** — Internal error during placement ### Bet lifecycle ``` PENDING → PLACED → CONFIRMED / REJECTED / CANCELLED / FAILED / VOID ``` * **PENDING** — Bet created, awaiting bookmaker response * **PLACED** — Sent to bookmaker, awaiting confirmation * **CONFIRMED** — Bookmaker accepted the bet * **REJECTED** — Bookmaker rejected the bet * **CANCELLED** — Bet cancelled before confirmation * **FAILED** — Internal error during placement * **VOID** — Bet voided by bookmaker ### Settlement lifecycle ``` UNSETTLED → WON / LOST / VOID / HALF_WON / HALF_LOST / PUSH / CASHOUT ``` * **UNSETTLED** — Bet is live, awaiting result * **WON** — Full win * **LOST** — Full loss * **VOID** — Bet voided (stake returned) * **HALF\_WON** — Asian handicap partial win * **HALF\_LOST** — Asian handicap partial loss * **PUSH** — Stake returned (tie on the line) * **CASHOUT** — Early withdrawal at negotiated price ### Account priority Each bookmaker account has a `priority` field (higher = preferred). When placing an order, ABP selects the highest-priority active account first for each bookmaker. ### Limit cascade Stake limits are resolved in priority order: **account limits > bookmaker limits > odds limits**. For example, if an account has `minStake: 10`, the bookmaker default is `minStake: 1`, and the odds entry shows `limitMin: 5`, the effective minimum is `10` (from the account override). ### Futures (coming soon) ABP supports **futures** (outright) markets alongside standard fixture-based markets. Instead of a `fixtureId`, futures use a `futureId` to identify the outright market and a `participantId` to specify the selection (e.g., a team or player to win a league/tournament). **Key differences from fixture orders:** | | Fixture orders | Futures orders | | ----------------- | ---------------------------------------- | ----------------------------------------------------- | | Identifier | `fixtureId` | `futureId` | | Selection | `outcomeId` + `playerId` | `outcomeId` + `playerId` + `participantId` | | Betslip | `GET /betslip?fixtureId=...` | `GET /betslip?futureId=...` | | Market key format | `fixtureId:bookmaker:outcomeId:playerId` | `futureId:bookmaker:outcomeId:playerId:participantId` | **Current status:** The data model and betslip infrastructure are in place. Order placement and betslip retrieval for futures currently return `501 Not Implemented`. This will be enabled in an upcoming release. ### Bookmaker slugs Bookmakers are identified by slug strings (e.g., `pinnacle`, `betfair-ex`, `polymarket`). Use `GET /bookmakers` to list all 32 supported bookmakers with their default stake limits. ## Endpoints at a glance | Category | Endpoints | Description | | -------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | **Accounts** | `GET/POST/PATCH/DELETE /accounts` | Manage bookmaker accounts (credentials, balances, priority, limits) | | **Betslip** | `GET /betslip` | Get live odds & limits before placing (fixtures and futures) | | **Orders** | `POST /place-orders`, `POST /cancel-orders`, `POST /cancel-all-orders`, `GET /orders` | Place, cancel, and track orders | | **Bets** | `GET /bets`, `GET /bets/{bet_id}` | View individual bet results | | **Analytics** | `GET /positions`, `GET /pnl` | Aggregated exposure and P\&L | | **Bookmakers** | `GET /bookmakers` | List all supported bookmakers | | **Markets** | `GET /markets` | Available markets and odds types | | **WebSocket** | `WS /ws` | Real-time updates | ## Supported bookmakers **Traditional sportsbooks:** pinnacle, pinnacleb2b, betamapola, betcris, bookmaker.eu, cloudbet, cloudbetb2b, justbet, kaiyun, matchbook, monkeyline.vip, novig.us, 198bet, paradisewager, sharpbet, singbet, sports411.ag, 3et, 3et++ **Betting exchanges:** betfair-ex, smarkets, limitless-ex **Prediction markets:** polymarket, polymarket.us, kalshi, predict.fun, prophetx, sx.bet, vertex, 4casters **Punter platforms:** punter.io, punter.io++ ## Resilience ABP includes production-grade reliability features: * **Circuit breakers** — Per-bookmaker circuit breakers prevent cascading failures and auto-recover * **Retry with exponential backoff** — for transient failures * **Emergency controls** — Orders may be temporarily paused during system maintenance * **Rate limiting** — Per-API-key rate limits (configurable per client) ## Data source ABP consumes real-time odds data from [OddsPapi v5](https://docs.oddspapi.io/). Fixture IDs and outcome IDs in ABP correspond directly to OddsPapi identifiers. Your app should use OddsPapi to discover fixtures and markets, then use ABP to execute bets. ## Frequently asked questions ABP places bets **directly** with bookmakers — it is not a hedging or market-making engine. You send an order, ABP routes it to the best account(s), places the wager, and tracks it through settlement. The data layer is [OddsPapi v5](https://docs.oddspapi.io/); ABP is the execution layer on top. An **order** is your instruction to place a wager. A **bet** is the actual wager placed at a bookmaker. One order can produce multiple bets when partial fills or multi-bookmaker routing apply. See [Concepts](/abp-api/concepts#orders-vs-bets). Each order carries a unique `requestUuid` (UUID format). ABP deduplicates server-side for **30 minutes** — a duplicate is silently skipped, and if **every** order in a request is a duplicate the request returns `409 Conflict`. Deep dive in [Concepts](/abp-api/concepts#idempotency-request-deduplication). Yes. Set `acceptPartialStake: true` to let a single order fill across multiple bets, and pass a `bookmakers` list (or omit it for auto-selection) to spread across venues. ABP enforces a weighted-average price constraint so your effective price stays at or above `orderPrice`. See [Order Placement](/abp-api/order-placement). 32 bookmakers across traditional sportsbooks, betting exchanges, prediction markets, and punter platforms. Capabilities (sweep, settlement, betslip) vary per venue — see the [Bookmaker capability matrix](/abp-api/bookmakers). Yes. Set `testOrder: true` to run the full validation and routing path (limits, price checks, account selection) **without** sending a bet to the bookmaker — the fastest way to confirm your payload and IDs before going live. See [Quickstart](/abp-api/quickstart#step-3-place-an-order). ABP runs per-bookmaker circuit breakers, retry with backoff, a two-tier emergency control, and reliable WebSocket delivery with ack/replay. Recovery from disconnects is bounded — reconcile any `seq` gaps via REST. See [Reliability & Operations](/abp-api/reliability). ## 💬 Ask an AI Assistant Want to explore or ask questions about this API using your favorite AI? Click one of the links below — each one opens the full docs bundle in the selected tool with a pre-filled prompt: * [Ask ChatGPT](https://chatgpt.com/?prompt=Read+from+https%3A%2F%2Fdocs.55-tech.com%2Fllms-full.txt+and+help+me+with+this+API.) * [Ask Claude](https://claude.ai/?prompt=Please+read+https%3A%2F%2Fdocs.55-tech.com%2Fllms-full.txt+and+help+me+use+this+API.) * [Ask Perplexity](https://www.perplexity.ai/search?q=Read+from+https%3A%2F%2Fdocs.55-tech.com%2Fllms-full.txt) * [Ask Gemini](https://gemini.google.com/app?query=Read+from+https%3A%2F%2Fdocs.55-tech.com%2Fllms-full.txt+and+help+me+use+this+API.) ## Next steps Set up your API key. Place your first bet in 5 steps. How fills, pricing, and limits work. Denomination, conversion, and the limit cascade. # ABP Quickstart - Place Your First Bet Source: https://docs.55-tech.com/abp-api/quickstart Step-by-step guide to placing your first bet through the ABP API. From account setup to order placement, tracking, and settlement. ## Step 1: List your accounts Check which bookmaker accounts are configured for your API key: ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" \ https://v2.55-tech.com/accounts ``` Response includes each account's bookmaker, balance, priority, stake limits, and currency: ```json theme={null} [ { "bookmaker": "pinnacle", "username": "pinnacle_main", "client": "your-client", "password": "***", "balance": 5000.0, "active": true, "priority": 10, "maxStake": 1000.0, "minStake": 5.0, "currencyId": "USD", "verifyBetslip": false, "meta": {}, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-02-01T12:00:00Z", "multiLimitAllowed": true, "currencyInfo": { "currency": "USD", "currencyValue": 1, "updatedAt": "2026-02-07T17:29:32+00:00" } } ] ``` ## Step 2: Get a betslip Before placing a bet, retrieve current odds and limits. You need a `fixtureId`, `outcomeId`, and `playerId`: ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" \ "https://v2.55-tech.com/betslip?fixtureId=id1000004461512432&outcomeId=103&playerId=0" ``` ```python theme={null} import requests resp = requests.get( "https://v2.55-tech.com/betslip", headers={"x-api-key": "YOUR_API_KEY"}, params={"fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0}, ) print(resp.json()["odds"]) ``` The betslip returns live odds from every bookmaker that has this market, with fixture and outcome metadata: ```json theme={null} { "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0, "client": "your-client", "fixtureInfo": { "sport": { "sportId": 10, "sportName": "Soccer" }, "tournament": { "tournamentName": "Serie A", "categoryName": "Italy" }, "participants": { "participant1Name": "AS Roma", "participant2Name": "Cagliari Calcio" } }, "outcomeInfo": { "marketName": "Full Time Result", "outcomeName": "2" }, "odds": { "pinnacle": { "id1000004461512432:pinnacle:103:0": { "price": 1.98, "limit": 15000, "limitMin": 5, "limitCurrency": "USD", "limitUsd": 15000, "limitMinUsd": 5, "active": true, "account": "pinnacle_main", "currencyInfo": { "currency": "USD", "currencyValue": 1, "updatedAt": "2026-02-07T17:29:32+00:00" } } }, "sharpbet": { "id1000004461512432:sharpbet:103:0": { "price": 2.0, "limit": 751.34, "limitMin": 1, "limitCurrency": "EUR", "limitUsd": 886.13, "limitMinUsd": 1.18, "active": true, "account": "sharpbet_user", "currencyInfo": { "currency": "EUR", "currencyValue": 0.84788876, "updatedAt": "2026-02-07T17:29:32+00:00" } } } } } ``` Use the [OddsPapi API](https://docs.oddspapi.io/) to discover fixture IDs and outcome IDs. ABP uses OddsPapi identifiers directly. ## Step 3: Place an order Place a bet order with minimum price protection. Each order needs a unique `requestUuid` for idempotency: ```bash theme={null} curl -X POST \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "orders": [ { "requestUuid": "eb45b192-317b-42d5-9f65-af497b9fa8c1", "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0, "orderStake": 10.0, "orderPrice": 1.95, "userRef": "my-strategy-1", "testOrder": false } ] }' \ https://v2.55-tech.com/place-orders ``` ```python theme={null} import uuid, requests resp = requests.post( "https://v2.55-tech.com/place-orders", headers={"x-api-key": "YOUR_API_KEY"}, json={"orders": [{ "requestUuid": str(uuid.uuid4()), "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0, "orderStake": 10.0, "orderPrice": 1.95, "userRef": "my-strategy-1", "testOrder": False, }]}, ) print(resp.json()) ``` ```javascript theme={null} import { randomUUID } from "crypto"; const resp = await fetch("https://v2.55-tech.com/place-orders", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ orders: [{ requestUuid: randomUUID(), fixtureId: "id1000004461512432", outcomeId: 103, playerId: 0, orderStake: 10.0, orderPrice: 1.95, userRef: "my-strategy-1", testOrder: false, }], }), }); console.log(await resp.json()); ``` **Key fields:** | Field | Required | Description | | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------- | | `requestUuid` | Yes | Unique UUID for idempotency (a duplicate within 30 min is skipped; 409 only if every order is a duplicate) | | `fixtureId` | Yes | OddsPapi fixture ID | | `outcomeId` | Yes | Market outcome (e.g., 103 = away win) | | `playerId` | Yes | Set to `0` for non-player-prop markets | | `orderStake` | Yes | Total amount to wager | | `orderPrice` | Yes | Minimum acceptable decimal odds | | `userRef` | Yes | Your reference for grouping related orders | | `testOrder` | Yes | Validate only, don't actually place (`false` for real bets) | | `bookmakers` | No | Comma-separated slugs to target (omit for automatic selection) | | `orderCurrency` | No | Currency code (default: `USD`) | | `acceptBetterOdds` | No | Accept odds better than `orderPrice` (default: `true`) | | `acceptPartialStake` | No | Allow partial fills (default: `true`) | | `back` | No | Back bet (`true`) or lay bet (`false`) (default: `true`) | | `expiresAt` | No | ISO 8601 expiry time (default: 5 seconds from now) | | `meta` | No | Custom metadata object (stored but not sent to bookmaker) | **Response:** ```json theme={null} { "status": "accepted", "acceptedOrders": [ { "orderId": 327, "requestUuid": "eb45b192-317b-42d5-9f65-af497b9fa8c1", "orderStatus": "FILLED", "fixtureId": "id1000004461512432", "outcomeId": 103, "orderStake": 10.0, "orderPrice": 1.95, "filledStake": 10.0, "bets": [ { "betId": 73, "bookmaker": "pinnacle", "placedPrice": 1.98, "placedStake": 10.0, "betStatus": "CONFIRMED" } ] } ], "declinedOrders": [] } ``` Orders that fail validation appear in `declinedOrders` with a `declineReason`: ```json theme={null} { "status": "declined", "acceptedOrders": [], "declinedOrders": [ { "requestUuid": "fb5f2dd9-c855-4ba9-8ef9-4c2278ca2f1d", "fixtureId": "id1000004461512432", "outcomeId": 103, "declineReason": "Order stake 100.00 USD exceeds available limit 50.00 USD" } ] } ``` **Test without staking real money.** Set `testOrder: true` to run the full validation and routing path (limits, price checks, account selection) **without** sending a bet to the bookmaker. It's the fastest way to confirm your payloads and IDs are correct before going live. ## Step 4: Track your order Query orders by `userRef`, `orderIds`, or `requestUuids` (at least one filter required): ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" \ "https://v2.55-tech.com/orders?userRef=my-strategy-1" ``` View individual bets for specific orders: ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" \ "https://v2.55-tech.com/bets?orderIds=327" ``` Each bet includes the bookmaker, placed price, placed stake, bet status, and settlement status: ```json theme={null} { "status": "success", "bets": [ { "betId": 73, "orderId": 327, "bookmaker": "pinnacle", "bookmakerBetId": "3332684214", "placedStake": 10.0, "placedPrice": 1.98, "placedCurrency": "USD", "betStatus": "CONFIRMED", "settlementStatus": "UNSETTLED", "account": "pinnacle_main", "userRef": "my-strategy-1" } ], "count": 1, "hasMore": false, "nextCursor": null } ``` ## Step 5: Check positions & PnL View your aggregated open positions (grouped by bookmaker by default): ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" \ https://v2.55-tech.com/positions ``` ```json theme={null} { "status": "success", "groupBy": "bookmaker", "positions": [ { "bookmaker": "pinnacle", "openBets": 5, "totalStake": 250.0, "avgPrice": 1.92 } ], "count": 1, "totalStake": 250.0, "totalOpenBets": 5 } ``` View profit and loss: ```bash theme={null} curl -H "x-api-key: YOUR_API_KEY" \ https://v2.55-tech.com/pnl ``` ```json theme={null} { "status": "success", "groupBy": "bookmaker", "pnl": [ { "bookmaker": "pinnacle", "settledBets": 42, "wins": 23, "losses": 17, "netPnl": 250.0, "winRate": 54.8 } ], "count": 1, "totalNetPnl": 250.0, "totalStaked": 2100.0, "totalSettledBets": 42 } ``` ## Next steps Orders vs bets, identifiers, idempotency, and lifecycles. Fills, pricing, and the limit cascade. Real-time order, bet, and settlement updates. Copy-paste patterns for common integrations. # ABP Limits & Reliability Source: https://docs.55-tech.com/abp-api/reliability ABP production reliability and rate limits: per-client requests-per-second limits and 429 backoff, circuit breakers, retry with backoff, emergency mode, order expiry, system status, and incident escalation. ABP is built for production trading. Per-bookmaker circuit breakers, automatic retries, and a two-tier emergency system keep a single failing bookmaker from cascading into a wider outage. This page explains the behaviours that can affect your integration and how to respond to them. ## System status Live system status is exposed at `GET /status` (unauthenticated). Poll it for health, or subscribe to the `status` and `emergency` WebSocket channels for push notifications. Unauthenticated infrastructure endpoints: | Endpoint | Purpose | | -------------- | ------------------------------------------------------------ | | `GET /health` | Liveness — is the process up | | `GET /ready` | Readiness — are dependencies (DB, cache, OddsPapi) connected | | `GET /status` | System status, including emergency state | | `GET /metrics` | Prometheus metrics | ## Rate limits Rate limits are enforced **per client** (resolved from your `x-api-key`) using a sliding window. The default is **100 requests per second** and is configurable per client via the `rps` field. | Property | Value | | ----------------- | --------------------------------------------------- | | Default limit | **100 requests / second** (configurable per client) | | Window | 1 second, sliding | | Scope | Per client (`clientName`) | | Exceeded response | `429 Too Many Requests` | Every throttled response includes standard headers so you can pace your client: | Header | Description | | ----------------------- | ------------------------------------------- | | `X-RateLimit-Limit` | Your configured limit (requests per window) | | `X-RateLimit-Remaining` | Requests remaining in the current window | | `X-RateLimit-Reset` | Seconds until the window resets | | `Retry-After` | Seconds to wait before retrying | A `429` response body: ```json theme={null} { "detail": "Rate limit exceeded", "limit": "100", "retry_after": 1 } ``` Because the window resets every second, a short backoff — honouring `retry_after` — is all that's needed. There's no benefit to exponential backoff for rate limiting; just wait out the window. ```python theme={null} import time import requests def call_with_backoff(method, url, **kwargs): while True: resp = requests.request(method, url, **kwargs) if resp.status_code != 429: return resp time.sleep(float(resp.headers.get("Retry-After", 1))) ``` To stay under the limit: **batch placements** (`POST /place-orders` accepts many orders per request), **prefer the WebSocket over polling**, and **request a higher `rps`** via [support](mailto:contact@55-tech.com) if your workload needs more headroom. WebSocket connection caps are listed under [WebSocket → Connection limits](/abp-api/websocket#connection-limits). ## Circuit breakers ABP runs a **per-bookmaker circuit breaker**. If a bookmaker starts failing, its breaker opens and orders targeting it are declined (`Bookmaker not available`) instead of hanging. * **Opens** after consecutive failures to a bookmaker. * **Half-opens** automatically after a cooldown to test recovery. * **Closes** and resumes normal routing once the bookmaker responds successfully. Because breakers are per-bookmaker, multi-bookmaker orders continue routing to healthy bookmakers while an unhealthy one is isolated. ## Retry with backoff Transient bookmaker failures are retried automatically with exponential backoff, and ABP fetches fresh odds before each retry pass. Retries are bounded by your order's `expiresAt`, so a slow bookmaker can never block an order past its expiry. ## Order expiry ``` Default expiresAt = now + 5 seconds (maximum 24 hours) ``` If an order can't be filled within its window, it transitions to `EXPIRED` (or `PARTIALLY_FILLED` if some stake landed). Set a longer `expiresAt` for less time-sensitive orders, or a shorter one for tighter price discipline. ## Emergency mode (two-tier) In rare cases — maintenance or an upstream incident — ABP can pause order processing. There are two tiers: | Tier | Effect | Recovery | | -------------- | -------------------------------------------- | ---------------------------------- | | **Soft pause** | Blocks *new* orders; in-flight bets continue | Auto-recovers after a set interval | | **Emergency** | Blocks new orders and cancels pending ones | Manual resume by an operator | Status changes are broadcast on the `emergency` WebSocket channel. When emergency mode is active, `POST /place-orders` returns a decline rather than queuing work. ## Reliable WebSocket delivery For state you cannot afford to miss (order fills, settlements), enable `reliableDelivery: true` at login to get at-least-once delivery with acknowledgments and replay. A `seq` gap signals a missed message; recover via `replay` or reconcile through `GET /orders` / `GET /bets`. See [WebSocket](/abp-api/websocket#reliable-delivery-acknowledgments). ## Your responsibilities To stay resilient on the client side: * **Treat declines as normal flow** — handle `declinedOrders` and decline reasons rather than assuming every order fills. * **Reconcile on reconnect** — after a WebSocket drop, replay from your last `seq` or re-query the REST endpoints. * **Honour idempotency** — reuse the same `requestUuid` on retries so reconnect storms can't double-stake. See [Core Concepts](/abp-api/concepts#idempotency-request-deduplication). * **Back off on 429** — see [Rate limits](#rate-limits) above. ## Support & incidents | Channel | Use for | | ------------------------------------------------- | ----------------------------------------------------- | | [contact@55-tech.com](mailto:contact@55-tech.com) | Integration help, account/limit changes, higher `rps` | | `GET /status` + `emergency` channel | Real-time system state | | [GitHub](https://github.com/55-Tech-Limited) | Public issues and references | When reporting an incident, include the affected `orderId` / `requestUuid`, the bookmaker slug, and a UTC timestamp — it dramatically speeds up diagnosis. ## Next steps Real-time updates with reliable delivery and replay. Status codes and decline reasons. # ABP WebSocket - Real-Time Updates Source: https://docs.55-tech.com/abp-api/websocket Connect to the ABP WebSocket for real-time order, bet, settlement, account, and system updates. Server-backed with message ordering and reliable delivery. ## Endpoint ``` wss://v2.55-tech.com/ws ``` ## Connection flow ### 1. Connect Open a WebSocket connection. No authentication is needed at connection time. ### 2. Login Send a login message within **30 seconds** of connecting: ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY", "channels": [] } ``` An empty `channels` array subscribes to all available channels. To subscribe to specific channels: ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY", "channels": ["orders", "bets", "settlements"] } ``` You can optionally enable reliable delivery with message acknowledgments: ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY", "channels": ["orders", "bets"], "reliableDelivery": true } ``` ### 3. Login confirmation On success, the server responds with: ```json theme={null} { "type": "login_ok", "clientName": "your-client", "channels": ["orders", "bets", "settlements"], "subscriptionId": 1, "access": { "clientFiltered": ["orders", "bets", "settlements", "accounts", "balance", "betslip"], "global": ["fixtures", "currencies", "status", "emergency"] }, "reliableDelivery": false, "features": { "messageOrdering": true, "acknowledgments": false, "batchAck": false } } ``` ### 4. Receive updates Data messages follow this format: ```json theme={null} { "type": "data", "channel": "orders", "event": "INSERT", "payload": { ... }, "ts": 1771183909789, "seq": 1 } ``` | Field | Description | | ------------ | ----------------------------------------------------------------- | | `type` | Always `"data"` for data messages | | `channel` | Which channel this message belongs to | | `event` | Event type: `INSERT`, `UPDATE`, `DELETE`, `SETTLED`, `STATUS` | | `payload` | The full updated object (see [payload schemas](#payload-schemas)) | | `ts` | Server timestamp (milliseconds since epoch) | | `seq` | Per-subscription monotonically increasing sequence number | | `requireAck` | Present and `true` only when reliable delivery is enabled | Full per-channel payload schemas are in [Payload schemas](#payload-schemas) below. ### 5. Keep alive The server sends a ping every **30 seconds**: ```json theme={null} {"type": "ping"} ``` Respond with pong within **120 seconds** or the connection is closed: ```json theme={null} {"type": "pong"} ``` You can also send pings from the client — the server responds with pong. ## Channels ### Client-filtered channels These channels only deliver data belonging to your `clientName`: | Channel | Events | Description | | ------------- | ---------------------- | --------------------------------------------------------------------------------------------------- | | `orders` | INSERT, UPDATE, DELETE | Order placement, fills, status changes | | `bets` | INSERT, UPDATE, DELETE | Bet placement, confirmation, and removal | | `settlements` | SETTLED | Settlement status updates (same payload as bets) | | `accounts` | INSERT, UPDATE, DELETE | Account creation, updates, and deletion | | `balance` | UPDATE | Balance change notifications | | `betslip` | UPDATE | Real-time odds updates for active betslip subscriptions (60s window after each `GET /betslip` call) | ### Global channels All subscribers receive these: | Channel | Events | Description | | ------------ | ------ | -------------------------------------- | | `fixtures` | UPDATE | Fixture metadata and score changes | | `currencies` | UPDATE | Currency exchange rate updates | | `status` | STATUS | System status changes | | `emergency` | STATUS | Emergency mode activation/deactivation | ## Reliable delivery & acknowledgments By default, messages are fire-and-forget — fast, but a message dropped during a disconnect is gone. Enable **reliable delivery** at login to get at-least-once delivery with acknowledgments and replay: ```json theme={null} { "type": "login", "apiKey": "YOUR_API_KEY", "channels": ["orders", "bets"], "reliableDelivery": true } ``` When enabled, each `data` message carries `requireAck: true`, and you must acknowledge it so the server can release it from its buffer: ```json theme={null} { "type": "ack", "seq": 42 } ``` Or acknowledge a range in one message (recommended for throughput): ```json theme={null} { "type": "ack_batch", "upToSeq": 50 } ``` **How it works:** * The server buffers up to **100 unacknowledged messages** per subscription and re-sends any not acked within **30 seconds**. * `seq` is per-subscription and strictly increasing, so a gap in `seq` means you missed a message. * To recover missed messages after a reconnect, request a replay from the last `seq` you processed: ```json theme={null} { "type": "replay", "fromSeq": 40 } ``` If you fall further behind than the 100-message buffer, the oldest unacked messages are dropped. Ack promptly (or use `ack_batch`) and treat any `seq` gap that a `replay` can't fill as a signal to reconcile via the REST endpoints (`GET /orders`, `GET /bets`). ## Changing subscriptions Change channels without reconnecting: ```json theme={null} { "type": "update_channels", "channels": ["orders", "bets", "settlements"] } ``` The server replies with `channels_updated`. ## Betslip subscriptions The `betslip` channel pushes real-time odds for selections you're watching. There are two ways to subscribe: 1. **Via REST** — calling `GET /betslip` registers a **60-second sliding window**; each call resets the timer and immediately broadcasts a snapshot over the WebSocket. 2. **Via WebSocket** — send a `subscribe_betslip` message with an explicit `ttl` (10–3600s) and bookmaker list for finer control, and `unsubscribe_betslip` to cancel early. A maximum of **20** active betslip subscriptions per client (REST + WS combined) is enforced. Message shapes and the `subscribed_betslip` confirmation are documented in [Betslip subscription messages](#betslip-subscription-messages) below. ## Example: Python client ```python theme={null} import asyncio import json import websockets async def connect(): uri = "wss://v2.55-tech.com/ws" async with websockets.connect(uri) as ws: # Login await ws.send(json.dumps({ "type": "login", "apiKey": "YOUR_API_KEY", "channels": ["orders", "bets", "settlements"] })) # Wait for login confirmation login_resp = json.loads(await ws.recv()) print(f"Logged in as {login_resp.get('clientName')}") # Listen for updates (respond to pings automatically) async for message in ws: data = json.loads(message) if data["type"] == "ping": await ws.send(json.dumps({"type": "pong"})) elif data["type"] == "data": print(f"[{data['channel']}:{data['event']}] {data['payload']}") asyncio.run(connect()) ``` ## Example: JavaScript client ```javascript theme={null} const WebSocket = require('ws'); const ws = new WebSocket('wss://v2.55-tech.com/ws'); ws.on('open', () => { ws.send(JSON.stringify({ type: 'login', apiKey: 'YOUR_API_KEY', channels: ['orders', 'bets', 'settlements'] })); }); ws.on('message', (raw) => { const msg = JSON.parse(raw); if (msg.type === 'login_ok') { console.log(`Logged in as ${msg.clientName}`); } if (msg.type === 'ping') { ws.send(JSON.stringify({ type: 'pong' })); } if (msg.type === 'data') { console.log(`[${msg.channel}:${msg.event}]`, msg.payload); } }); ``` ## Message types ### Client → Server | Type | Example | Purpose | | --------------------- | ----------------------------------------------------- | -------------------------------------------------------------- | | `login` | `{"type": "login", "apiKey": "...", "channels": []}` | Authenticate and subscribe. Optional `reliableDelivery: true`. | | `update_channels` | `{"type": "update_channels", "channels": ["orders"]}` | Change channel subscriptions after login. | | `pong` | `{"type": "pong"}` | Reply to a server `ping`. | | `ping` | `{"type": "ping"}` | Liveness check; server replies with `pong`. | | `ack` | `{"type": "ack", "seq": 42}` | Acknowledge one message (reliable delivery). | | `ack_batch` | `{"type": "ack_batch", "upToSeq": 50}` | Acknowledge all messages up to and including `upToSeq`. | | `replay` | `{"type": "replay", "fromSeq": 40}` | Request re-send of buffered messages from `fromSeq`. | | `subscribe_betslip` | see [below](#betslip-subscription-messages) | Open a betslip price subscription with a custom TTL. | | `unsubscribe_betslip` | see [below](#betslip-subscription-messages) | Cancel a betslip subscription early. | ### Server → Client | Type | Purpose | | -------------------- | --------------------------------------------------------------------------- | | `login_ok` | Login succeeded; echoes channels, access scopes, and enabled features. | | `channels_updated` | Confirms an `update_channels` change. | | `data` | A channel event (see [payload schemas](#payload-schemas)). | | `ping` | Server keep-alive; reply with `pong`. | | `subscribed_betslip` | Confirms a `subscribe_betslip`, with effective `expiresAt`. | | `error` | A problem with a client message; includes a `ref` to the offending request. | ## Betslip subscription messages The `betslip` channel must be in your subscriptions (add it via `login` or `update_channels`) before subscribing. ### `subscribe_betslip` ```json theme={null} { "type": "subscribe_betslip", "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0, "bookmakers": ["pinnacle", "sharpbet"], "ttl": 300 } ``` * `bookmakers` must be a non-empty list. * `ttl` is optional (seconds), clamped to **10–3600s**; defaults to **60s** if omitted. * Re-subscribing to the same selection refreshes the TTL and updates the bookmaker set (idempotent). Server confirms: ```json theme={null} { "type": "subscribed_betslip", "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0, "bookmakers": ["pinnacle", "sharpbet"], "expiresAt": "2026-02-07T17:34:37+00:00" } ``` Bookmakers you have no account for are dropped from the subscription and returned in a `skipped` array. A maximum of **20** active betslip subscriptions per client (REST + WS combined) is enforced. ### `unsubscribe_betslip` ```json theme={null} { "type": "unsubscribe_betslip", "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0 } ``` ## Payload schemas Data messages wrap channel payloads in the envelope `{type, channel, event, payload, ts, seq}`. The `orders`, `bets`, and `settlements` channels deliver the **full database row** (`row_to_json`); `balance`, `emergency`, and `betslip` use custom shapes. ### Orders Full row from the `orders` table: ```json theme={null} { "orderId": 327, "requestUuid": "eb45b192-317b-42d5-9f65-af497b9fa8c1", "client": "demo", "clientName": "demo", "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0, "orderPrice": 1.95, "orderStake": 10.0, "filledStake": 10.0, "remainingStake": 0.0, "orderStatus": "FILLED", "statusReason": null, "userRef": "bettor1234", "testOrder": false, "acceptBetterOdds": true, "acceptPartialStake": true, "orderCurrency": "USD", "back": true, "allowedBookmakers": "*", "oddsInfo": null, "meta": {}, "expiresAt": "2026-02-07T17:29:37+00:00", "filledAt": "2026-02-07T17:29:32+00:00", "createdAt": "2026-02-07T17:29:32+00:00", "updatedAt": "2026-02-07T17:29:32+00:00" } ``` ### Bets / settlements Full row from the `bets` table (the `settlements` channel uses the same shape): ```json theme={null} { "betId": 73, "orderId": 327, "bookmaker": "pinnacle", "bookmakerBetId": "3332684214", "betStatus": "CONFIRMED", "settlementStatus": "UNSETTLED", "placedStake": 10.0, "placedPrice": 1.98, "placedCurrency": "USD", "account": "pinnacle_main", "requestUuid": "eb45b192-317b-42d5-9f65-af497b9fa8c1", "userRef": "bettor1234", "testBet": false, "client": "demo", "clientName": "demo", "sentData": { "stake": 10.0, "price": 1.95 }, "receivedData": { "betId": "3332684214", "status": "accepted", "price": 1.98 }, "settlementAmount": null, "settlementReason": null, "settledAt": null, "declineReason": null, "betRequestId": null, "oddsInfo": null, "meta": {}, "placedAt": "2026-02-07T17:29:32+00:00", "createdAt": "2026-02-07T17:29:32+00:00", "updatedAt": "2026-02-07T17:29:32+00:00" } ``` Subscribing to both `bets` and `settlements` delivers settlement updates **twice** — once on each channel. Use `settlements` alone if you want a dedicated settlement feed. ### Balance ```json theme={null} { "clientName": "demo", "bookmaker": "pinnacle", "username": "pinnacle_main", "balance": 4990.0, "currency": "USD", "ts": 1771183910123 } ``` ### Emergency ```json theme={null} { "active": true, "reason": "Upstream provider maintenance", "ts": 1771183910123 } ``` ### Betslip Pushed while a betslip subscription is active. For futures the payload carries `futureId` and `participantId` instead of `fixtureId`. **Fixture:** ```json theme={null} { "clientName": "demo", "fixtureId": "id1000004461512432", "outcomeId": 103, "playerId": 0, "odds": { "pinnacle": { "id1000004461512432:pinnacle:103:0": { "price": 1.98, "limit": 500.0, "limitMin": 1.0, "limitCurrency": "USD", "limitUsd": 500.0, "limitMinUsd": 1.0, "active": true, "account": "pinnacle_main", "currencyInfo": { "currency": "USD", "currencyValue": 1, "updatedAt": "2026-02-07T17:29:32+00:00" } } } } } ``` **Futures (coming soon):** ```json theme={null} { "clientName": "demo", "futureId": "fut_123456", "outcomeId": 0, "playerId": 0, "participantId": 5, "odds": { "pinnacle": { "fut_123456:pinnacle:0:0:5": { "price": 3.50, "limit": 1000.0, "limitMin": 5.0, "limitCurrency": "USD", "limitUsd": 1000.0, "limitMinUsd": 5.0, "active": true, "account": "pinnacle_main", "currencyInfo": { "currency": "USD", "currencyValue": 1, "updatedAt": "2026-02-07T17:29:32+00:00" } } } } } ``` Futures betslip WebSocket broadcasting is not yet enabled. The subscription infrastructure is in place and will be activated in an upcoming release. ## Connection limits | Setting | Value | | ------------------------------------ | ------------- | | Max connections per API key | 5 | | Auth timeout | 30 seconds | | Server ping interval | 30 seconds | | Pong timeout (disconnect) | 120 seconds | | Message buffer (reliable delivery) | 100 messages | | Output queue per client | 2000 messages | | Max betslip subscriptions per client | 20 | ## Next steps How values are denominated and converted across currencies. Handle error responses and decline reasons. # AI & LLM Integration - Documentation Export Source: https://docs.55-tech.com/ai Download 55-Tech API documentation for AI tools, LLMs, and offline use. Machine-readable exports in TXT and OpenAPI JSON formats for ChatGPT, Claude, and other AI assistants. ## Download | Resource | Format | Description | | ---------------------------------------------------------- | ------ | --------------------------------------------------------------------- | | [`/llms.txt`](/llms.txt) | TXT | Docs index — page titles and paths | | [`/llms-full.txt`](/llms-full.txt) | TXT | Full docs bundle — all guide pages concatenated | | [`/abp-api/openapi.json`](/abp-api/openapi.json) | JSON | ABP OpenAPI 3.1 spec — 13 endpoints, full schemas | | [`/mm-api/openapi.json`](/mm-api/openapi.json) | JSON | MM OpenAPI 3.1 spec — REST reference | | [`/scraping-api/openapi.json`](/scraping-api/openapi.json) | JSON | Scraping OpenAPI 3.1 spec — fetch, WebSocket, AMQP, network endpoints | ## What's included The **ABP API** documentation covers: * **Account management** — Full CRUD for bookmaker accounts across 32 integrations * **Betslip** — Real-time odds and limits from OddsPapi v5 with multi-bookmaker aggregation * **Order placement** — Smart routing with idempotency (`requestUuid`), partial fills, and configurable expiry * **Bet tracking** — Keyset-paginated queries with full lifecycle from PENDING through CONFIRMED/SETTLED * **Analytics** — Positions and PnL grouped by bookmaker, account, or user reference * **WebSocket** — Real-time push for orders, bets, settlements, balance, fixtures, currencies, status, emergency * **32 bookmaker integrations** — pinnacle, betfair-ex, polymarket, kalshi, cloudbet, smarkets, and more The **MM API** documentation covers: * Market-making engine configuration and real-time spread management * WebSocket-driven pricing updates and order book interaction The **Scraping API** documentation covers: * **HTTP fetch** — GET/POST/PUT/PATCH/DELETE through 80+ geo-distributed agents * **WebSocket relay** — Bidirectional frame relay for Socket.IO, SignalR, Centrifugo, GraphQL-WS, raw WS * **AMQP consumer** — Stream RabbitMQ messages via SSE * **Network intelligence** — Agent listing, geo distribution, per-domain health checks * **Usage & rate limits** — Per-key metrics, request analytics, top domains ## Recommended usage **For AI assistants that fetch URLs:** * `/llms-full.txt` — best for "read everything once" (all guide pages + examples) * `/abp-api/openapi.json` — ABP endpoint + schema accuracy (typed, with examples) * `/mm-api/openapi.json` — MM endpoint + schema reference * `/scraping-api/openapi.json` — Scraping endpoint + schema reference **For copy/paste workflows:** Open `/llms-full.txt` in the browser and paste the contents into your AI tool. **For code generation:** Feed the OpenAPI specs to tools like `openapi-generator`, `oapi-codegen`, or Cursor/Copilot for typed client SDK generation. ## Notes * `llms-full.txt` follows the same ordering as the site navigation * WebSocket protocols are documented in the guide pages; REST endpoints are in the OpenAPI specs * All three APIs (ABP, MM, Scraping) are included in a single bundle * All APIs authenticate via the `x-api-key` header *** ## Ask an AI Assistant Want to explore or ask questions about this API using your favorite AI? Click one of the links below — each one opens the full docs bundle in the selected tool with a pre-filled prompt: * [Ask ChatGPT](https://chatgpt.com/?prompt=Read+from+https%3A%2F%2Fdocs.55-tech.com%2Fllms-full.txt+and+help+me+with+this+API.) * [Ask Claude](https://claude.ai/?prompt=Please+read+https%3A%2F%2Fdocs.55-tech.com%2Fllms-full.txt+and+help+me+use+this+API.) * [Ask Perplexity](https://www.perplexity.ai/search?q=Read+from+https%3A%2F%2Fdocs.55-tech.com%2Fllms-full.txt) * [Ask Gemini](https://gemini.google.com/app?query=Read+from+https%3A%2F%2Fdocs.55-tech.com%2Fllms-full.txt+and+help+me+use+this+API.) # Create account Source: https://docs.55-tech.com/api-reference/accounts/create-account /zh/abp-api/openapi.json post /accounts Create a new bookmaker account for the authenticated client. Each client can have multiple accounts per bookmaker. Use `priority` to control which account is selected first. # Delete account Source: https://docs.55-tech.com/api-reference/accounts/delete-account /zh/abp-api/openapi.json delete /accounts/{bookmaker}/{username} Delete a bookmaker account. Only the account owner (client) can delete their accounts. # Get account by key Source: https://docs.55-tech.com/api-reference/accounts/get-account-by-key /zh/abp-api/openapi.json get /accounts/{bookmaker}/{username} Get a specific account by its primary key (bookmaker, username). Only returns the account if it belongs to the authenticated client. # List accounts for client Source: https://docs.55-tech.com/api-reference/accounts/list-accounts-for-client /zh/abp-api/openapi.json get /accounts Get all bookmaker accounts for the authenticated client. Optionally filter by bookmaker. Accounts are ordered by priority (highest first). Passwords are always masked. # Update account Source: https://docs.55-tech.com/api-reference/accounts/update-account /zh/abp-api/openapi.json patch /accounts/{bookmaker}/{username} Update an existing bookmaker account. Only provided fields are updated. Only the account owner (client) can update their accounts. # AMQP consumer relay (SSE) Source: https://docs.55-tech.com/api-reference/amqp/amqp-consumer-relay-sse /zh/scraping-api/openapi.json post /amqp Connect to an AMQP/RabbitMQ broker through an agent and stream messages as Server-Sent Events. The agent creates a temporary auto-delete queue, binds it to the specified exchange, and streams messages back as SSE events (connected, message, error). # Get open positions Source: https://docs.55-tech.com/api-reference/analytics/get-open-positions /zh/abp-api/openapi.json get /positions Retrieve aggregated open (unsettled) positions grouped by bookmaker, account, or userRef. Returns the number of open bets, total stake, and average price for each group. Only includes bets with `betStatus` in (PLACED, CONFIRMED) and `settlementStatus` = UNSETTLED. **Group by options:** - `bookmaker` (default) — Group by bookmaker - `account` — Group by bookmaker account - `userRef` — Group by user reference **Filters:** - `bookmaker` — Filter to a specific bookmaker - `userRef` — Filter to a specific user reference - `account` — Filter to a specific account # Get profit and loss Source: https://docs.55-tech.com/api-reference/analytics/get-profit-and-loss /zh/abp-api/openapi.json get /pnl Retrieve aggregated PnL (profit and loss) grouped by bookmaker, account, or userRef. Only includes settled bets (`settlementStatus` NOT in UNSETTLED). **Group by options:** - `bookmaker` (default) — Group by bookmaker - `account` — Group by bookmaker account - `userRef` — Group by user reference **Filters:** - `bookmaker` — Filter to a specific bookmaker - `userRef` — Filter to a specific user reference - `account` — Filter to a specific account # Get a single bet Source: https://docs.55-tech.com/api-reference/bets/get-a-single-bet /zh/abp-api/openapi.json get /bets/{bet_id} Retrieve a single bet by its ID. The bet must belong to the authenticated client. # Get bets Source: https://docs.55-tech.com/api-reference/bets/get-bets /zh/abp-api/openapi.json get /bets Retrieve bets by various filters with keyset pagination. At least one filter must be provided. **Filters (OR logic):** - `betIds` — Comma-separated list of bet IDs - `orderIds` — Comma-separated list of order IDs - `userRef` — User reference string **Pagination:** - Results are ordered by betId descending (newest first) - Use `afterBetId` from `nextCursor` in the response to fetch the next page - `hasMore` indicates if more results are available **Bet Status Values:** PENDING, PLACED, CONFIRMED, REJECTED, CANCELLED, FAILED, VOID **Settlement Status Values:** UNSETTLED, WON, LOST, VOID, HALF_WON, HALF_LOST, PUSH, CASHOUT # Get live odds and betslip metadata Source: https://docs.55-tech.com/api-reference/betslip/get-live-odds-and-betslip-metadata /zh/abp-api/openapi.json get /betslip Get current odds and metadata for a specific fixture/outcome/player combination, or a futures/outright market. **Fixture mode (required parameters):** `fixtureId`, `outcomeId`, `playerId` **Futures mode (coming soon):** `futureId`, `outcomeId`, `playerId`, `participantId` — currently returns `501 Not Implemented` **Response includes:** - `fixtureInfo` — Fixture details (teams, sport, tournament, start time, scores) - `outcomeInfo` — Market and outcome details (market name, type, handicap) - `playerInfo` — Player details (for player prop markets, null otherwise) - `odds` — Live odds keyed by bookmaker, each containing: - `price` — Current decimal odds - `limit` / `limitMin` — Max/min stake in account currency - `limitUsd` / `limitMinUsd` — Limits converted to USD - `limitCurrency` — Account currency for limits - `active` — Whether odds are currently available - `account` — Account username for this bookmaker - `currencyInfo` — Currency exchange rate details - `bookmakerFixtureId/MarketId/OutcomeId` — Bookmaker's internal IDs - `meta` — Exchange orderbook data (for Betfair, Polymarket, etc.) **Limit cascade:** account.minStake/maxStake > bookmaker.minStake/maxStake > odds.limitMin/limit # List all bookmakers Source: https://docs.55-tech.com/api-reference/bookmakers/list-all-bookmakers /zh/abp-api/openapi.json get /bookmakers Returns all supported bookmakers with their stake limits. Results are cached for 60 seconds. # Fetch with JavaScript rendering Source: https://docs.55-tech.com/api-reference/browser/fetch-with-javascript-rendering /zh/scraping-api/openapi.json get /browser Fetch a URL with full JavaScript rendering. Works just like /fetch — all parameters via headers. Use this instead of /fetch when the target requires JavaScript to load content. If X-Expect-Selector or X-Expect-Contains is set and the page doesn't match, the API retries on a different node. # Live browser session with event streaming (SSE) Source: https://docs.55-tech.com/api-reference/browser/live-browser-session-with-event-streaming-sse /zh/scraping-api/openapi.json get /browser/stream Open a browser, navigate to the target URL, and stream back events in real-time via Server-Sent Events. Only captures XHR/Fetch API responses and WebSocket frames — HTML documents and scripts are filtered out. No resources are blocked by default so pages load fully. Default wait strategy is networkidle. All data is gzip-compressed. The session runs until the client disconnects (24h safety cap). Set X-Timeout to limit the duration. # Activate tournament Source: https://docs.55-tech.com/api-reference/configuration/activate-tournament /zh/mm-api/openapi.json post /api/v1/tournaments/{tournament_id}/activate Start trading a tournament. # Deactivate tournament Source: https://docs.55-tech.com/api-reference/configuration/deactivate-tournament /zh/mm-api/openapi.json post /api/v1/tournaments/{tournament_id}/deactivate Stop trading a tournament. Cancels its open orders. # List market types Source: https://docs.55-tech.com/api-reference/configuration/list-market-types /zh/mm-api/openapi.json get /api/v1/sports/{sport_id}/market-types Tradeable market types for a sport with your per-client toggle status. # List tournaments Source: https://docs.55-tech.com/api-reference/configuration/list-tournaments /zh/mm-api/openapi.json get /api/v1/tournaments Your tournaments with activation status. # Re-enable all market types Source: https://docs.55-tech.com/api-reference/configuration/re-enable-all-market-types /zh/mm-api/openapi.json post /api/v1/client/market-allowlist/activate-all Reset all market type toggles to enabled. # Toggle market types Source: https://docs.55-tech.com/api-reference/configuration/toggle-market-types /zh/mm-api/openapi.json patch /api/v1/client/market-allowlist Enable or disable market types in bulk. # Debug agent selection Source: https://docs.55-tech.com/api-reference/debug/debug-agent-selection /zh/scraping-api/openapi.json get /debug/pick Preview which agent would be selected for a given URL without making the actual request. # Fetch a URL (DELETE) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-delete /zh/scraping-api/openapi.json delete /fetch Send a DELETE request to the target URL through the agent network. # Fetch a URL (GET) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-get /zh/scraping-api/openapi.json get /fetch Send a GET request to the target URL through the agent network. Pass the target URL via the X-Target-URL header. # Fetch a URL (PATCH) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-patch /zh/scraping-api/openapi.json patch /fetch Send a PATCH request to the target URL through the agent network. # Fetch a URL (POST) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-post /zh/scraping-api/openapi.json post /fetch Send a request to the target URL through the agent network. Pass the target URL via the X-Target-URL header. Optionally include a JSON body with method, headers, body, timeout, and allow_redirects fields. # Fetch a URL (PUT) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-put /zh/scraping-api/openapi.json put /fetch Send a PUT request to the target URL through the agent network. # Get all markets with odds types Source: https://docs.55-tech.com/api-reference/markets/get-all-markets-with-odds-types /zh/abp-api/openapi.json get /markets Returns all available markets and their outcome types. Optionally filter by sportId. Markets are sorted by sportId then marketId. # Agents by country Source: https://docs.55-tech.com/api-reference/network/agents-by-country /zh/scraping-api/openapi.json get /network/geo Agents grouped by country with slugs listed for each. # Domain health Source: https://docs.55-tech.com/api-reference/network/domain-health /zh/scraping-api/openapi.json get /network/health/{domain} Per-agent health state for a specific domain. Shows which agents are healthy, soft-blocked, or hard-blocked. # List all agents Source: https://docs.55-tech.com/api-reference/network/list-all-agents /zh/scraping-api/openapi.json get /network/agents Returns the full agent registry with slug, name, and country for each agent. # Network status Source: https://docs.55-tech.com/api-reference/network/network-status /zh/scraping-api/openapi.json get /network/status Summary of total nodes and nodes per country. # Bets summary Source: https://docs.55-tech.com/api-reference/orders-&-bets/bets-summary /zh/mm-api/openapi.json get /api/v1/bets/summary Aggregated hedge bet statistics. # Cancel all orders Source: https://docs.55-tech.com/api-reference/orders-&-bets/cancel-all-orders /zh/mm-api/openapi.json post /api/v1/orders/cancel-all Cancel every open order across all exchanges. # Cancel order Source: https://docs.55-tech.com/api-reference/orders-&-bets/cancel-order /zh/mm-api/openapi.json post /api/v1/orders/{order_id}/cancel Cancel a single order by ID. # List hedge bets Source: https://docs.55-tech.com/api-reference/orders-&-bets/list-hedge-bets /zh/mm-api/openapi.json get /api/v1/bets Paginated list of bets placed on bookmakers to hedge filled orders. # List open orders Source: https://docs.55-tech.com/api-reference/orders-&-bets/list-open-orders /zh/mm-api/openapi.json get /api/v1/orders/open All currently resting or partially-filled orders. # List orders Source: https://docs.55-tech.com/api-reference/orders-&-bets/list-orders /zh/mm-api/openapi.json get /api/v1/orders Paginated list of exchange orders. Filter by fixture, status, exchange, or date. # Orders summary Source: https://docs.55-tech.com/api-reference/orders-&-bets/orders-summary /zh/mm-api/openapi.json get /api/v1/orders/summary Aggregated order statistics. # Cancel all pending orders Source: https://docs.55-tech.com/api-reference/orders/cancel-all-pending-orders /zh/abp-api/openapi.json post /cancel-all-orders Cancel ALL pending and partially-placed orders for the authenticated client. **Behavior:** - Cancels orders in PENDING or PARTIALLY_FILLED status - Only cancels orders without already-placed bets - Orders with confirmed bets are returned in `notCancelled` - No request body required # Cancel specific orders Source: https://docs.55-tech.com/api-reference/orders/cancel-specific-orders /zh/abp-api/openapi.json post /cancel-orders Cancel one or more orders by filter. At least one filter must be provided. Multiple filters are combined with OR logic. **Filters:** - `orderIds` — List of numeric order IDs - `requestUuids` — List of request UUID strings - `userRef` — Cancel all orders matching this user reference **Behavior:** - Only orders in PENDING or PARTIALLY_FILLED status can be cancelled - Orders with confirmed bets cannot be fully cancelled - Cancelled orders are marked in the database and signaled to stop retry loops # Get orders Source: https://docs.55-tech.com/api-reference/orders/get-orders /zh/abp-api/openapi.json get /orders Retrieve orders with keyset pagination. At least one filter must be provided. **Filters (OR logic):** - `orderIds` — Comma-separated list of order IDs - `requestUuids` — Comma-separated list of request UUIDs - `userRef` — User reference string **Pagination:** - Results are ordered by orderId descending (newest first) - Use `afterOrderId` from `nextCursor` in the response to fetch the next page - `hasMore` indicates if more results are available Returns enriched order details including bets and fixture/outcome/player metadata. **Order Status Values:** PENDING, PROCESSING, FILLED, PARTIALLY_FILLED, CANCELLED, REJECTED, EXPIRED, FAILED # Place betting orders Source: https://docs.55-tech.com/api-reference/orders/place-betting-orders /zh/abp-api/openapi.json post /place-orders Submit one or more betting orders for placement with bookmakers. **Required fields per order:** `requestUuid`, `outcomeId`, `playerId`, `orderPrice`, `orderStake`, `userRef`, `testOrder`, and either `fixtureId` (fixture markets) or `futureId` (futures/outright markets — coming soon) **Optional fields:** `bookmakers`, `participantId` (futures only), `orderCurrency`, `back`, `expiresAt`, `acceptBetterOdds`, `acceptPartialStake`, `meta` **Key behavior:** - `requestUuid` must be a valid UUID format (used for idempotency — a duplicate within 30 minutes is skipped; if ALL orders in the request are duplicates the request returns 409) - `fixtureId` and `futureId` are mutually exclusive — provide one or the other - Orders with `futureId` currently return `501 Not Implemented` (coming soon) - Server automatically selects the best bookmaker by odds/limits unless `bookmakers` is specified - `clientName` is set automatically from your API key - Orders expire after `expiresAt` (default: 5 seconds from now, capped at 24 hours maximum) - Maximum 100 orders per request **Response:** - `status`: "accepted" (all accepted), "partial-success" (some declined), or "declined" (all declined) - `acceptedOrders`: Orders that passed validation with their current status and any placed bets - `declinedOrders`: Orders that failed validation with `declineReason` # List accounts Source: https://docs.55-tech.com/api-reference/performance/list-accounts /zh/mm-api/openapi.json get /api/v1/accounts Exchange account balances and status. Credentials are never returned. # List positions Source: https://docs.55-tech.com/api-reference/performance/list-positions /zh/mm-api/openapi.json get /api/v1/positions Hedged positions grouped by fixture. # P&L Source: https://docs.55-tech.com/api-reference/performance/p&l /zh/mm-api/openapi.json get /api/v1/pnl Profit & loss and turnover for your client. # Positions summary Source: https://docs.55-tech.com/api-reference/performance/positions-summary /zh/mm-api/openapi.json get /api/v1/positions/summary Aggregated position statistics. # Health check Source: https://docs.55-tech.com/api-reference/system/health-check /zh/scraping-api/openapi.json get /healthz Liveness probe. Returns {"ok": true}. # Pause trading Source: https://docs.55-tech.com/api-reference/trading-controls/pause-trading /zh/mm-api/openapi.json post /api/v1/trading/pause Stop posting new orders. Existing resting orders stay live. # Resume trading Source: https://docs.55-tech.com/api-reference/trading-controls/resume-trading /zh/mm-api/openapi.json post /api/v1/trading/resume Resume after a manual pause. # Start trading Source: https://docs.55-tech.com/api-reference/trading-controls/start-trading /zh/mm-api/openapi.json post /api/v1/trading/start Start the engine after a stop. # Stop trading Source: https://docs.55-tech.com/api-reference/trading-controls/stop-trading /zh/mm-api/openapi.json post /api/v1/trading/stop Stop the engine and cancel all open orders. # Trading status Source: https://docs.55-tech.com/api-reference/trading-controls/trading-status /zh/mm-api/openapi.json get /api/v1/trading/status Current engine state for your client. # Usage metrics Source: https://docs.55-tech.com/api-reference/usage/usage-metrics /zh/scraping-api/openapi.json get /usage Per-key usage metrics: request counts, success/fail rates, bytes transferred, protocol breakdown, and top domains. # 55-Tech API Documentation Source: https://docs.55-tech.com/index Official API documentation for 55-Tech's Market Making (MM) and Automated Bet Placing (ABP) platforms. High-frequency trading and automated bet execution for sports betting markets. **B2B Platform** — 55-Tech provides enterprise trading infrastructure for licensed operators, trading firms, and sports betting platforms. Contact [contact@55-tech.com](mailto:contact@55-tech.com) for API access. ## What is 55-Tech? 55-Tech operates two core trading platforms: * **Market Making (MM)** — High-frequency market making engine that places orders on prediction market exchanges (Polymarket, Kalshi) and hedges on bookmakers * **Automated Bet Placing (ABP)** — Place bets across 32 bookmakers through a single API with real-time tracking and settlement Both platforms consume real-time odds data from [OddsPapi](https://docs.oddspapi.io/) and expose REST + WebSocket APIs for client integration. *** ## Get started Get connected to both APIs in minutes. Place bets across 32 bookmakers through a single API. High-frequency market making on prediction exchanges. Real-time odds data powering both platforms. # MM Authentication - API Key Setup Source: https://docs.55-tech.com/mm-api/authentication Learn how to authenticate MM API requests using the X-API-Key header (UUID). Client API and WebSocket authentication. All MM client endpoints require authentication via the `X-API-Key` header (UUID format). ## Client API authentication Pass your API key (UUID format) in the `X-API-Key` header: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/trading/status ``` Your API key is bound to a specific `client` entity. All data returned is filtered to your client — you only see your own orders, bets, and positions. ## WebSocket authentication For WebSocket connections, send your API key in the subscribe message: ```json theme={null} { "type": "subscribe", "apiKey": "YOUR_API_KEY", "channels": ["orders", "bets", "accounts", "scores"] } ``` You must send the subscribe message within **30 seconds** of connecting, or the connection is closed. See [WebSocket](/mm-api/websocket) for details. ## Rate limiting Rate limits are enforced per client: * **REST API:** 2,000 requests per minute * **WebSocket:** Maximum 5 concurrent connections per API key * **Exceeded:** Returns `429 Too Many Requests` ## Error responses | Status | Meaning | | ------ | ----------------------------------------------- | | `401` | Invalid or missing API key (must be valid UUID) | | `429` | Rate limit exceeded | ```json theme={null} {"detail": "Invalid or missing API key"} ``` # MM Error Handling Source: https://docs.55-tech.com/mm-api/errors MM API error codes, HTTP status codes, error response format, and emergency mode behavior. ## Error response format All API errors return a JSON body: ```json theme={null} { "detail": "Invalid or missing API key" } ``` Validation errors include location details: ```json theme={null} { "detail": [ { "loc": ["query", "fixtureId"], "msg": "Field required", "type": "missing" } ] } ``` ## HTTP status codes | Status | Description | | ------ | --------------------------------------------------------------------------------------------------- | | `200` | Success | | `401` | Unauthorized — invalid or missing API key (must be valid UUID) | | `404` | Not found — resource does not exist | | `409` | Conflict — action blocked by current state (e.g. deactivating with open orders, duplicate resource) | | `422` | Validation error — request parameters failed validation | | `426` | Upgrade required — WebSocket endpoint called via HTTP | | `429` | Rate limited — exceeded 2,000 requests/minute | | `500` | Internal server error | | `502` | Bad gateway — partial failure (e.g. cancel request succeeded for some orders but not all) | ## Rate limiting The MM API enforces **2,000 requests/minute** per client API key. When rate limited, the API returns `429 Too Many Requests`. Implement exponential backoff in your retry logic (wait 1s, 2s, 4s, etc.). ## Common errors ### Authentication (401) Your API key is missing, invalid, or not in UUID format. ```bash theme={null} # Correct curl -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/trading/status # Wrong header name — returns 401 curl -H "Authorization: Bearer your-key" \ https://mmapi.55-tech.com/api/v1/trading/status # Missing header — returns 401 curl https://mmapi.55-tech.com/api/v1/trading/status ``` ### Not found (404) The requested resource doesn't exist: ```bash theme={null} curl -X POST -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/orders/999999/cancel # Returns: 404 ``` ### Validation errors (422) Required query parameters are missing or have invalid values. Check the `loc` field to identify the problematic parameter. ### WebSocket upgrade (426) If you call the WebSocket endpoint via HTTP instead of upgrading to a WebSocket connection, you receive a `426 Upgrade Required`. ## WebSocket errors When subscribing to the WebSocket, these error messages may be returned: | Error | Description | | --------------------------- | -------------------------------------------- | | `apiKey required` | No `apiKey` field in subscribe message | | `Invalid apiKey format` | Not a valid UUID | | `Invalid API key` | Key not found or client inactive | | `Connection limit exceeded` | Already at 5 active connections for this key | # MM API Overview - Market Making Engine Source: https://docs.55-tech.com/mm-api/overview MM-V2 Trading API overview. Monitor and control the market making engine that posts orders on prediction market exchanges (Polymarket, Kalshi) and hedges on bookmakers (Pinnacle, Vertex). ## What this API does The MM engine places orders, hedges positions, and tracks P\&L automatically. This API lets you: * **Monitor** — orders, bets, positions, P\&L, and account balances in real-time * **Control** — pause, resume, stop, and start your engine * **Configure** — choose which market types and tournaments to trade ## Base URL ``` https://mmapi.55-tech.com ``` All REST endpoints are prefixed with `/api/v1/`. The WebSocket endpoint lives at `/ws/subscribe`. ## Key concepts ### Your client account When we onboard you, we create a client account and issue you an API key. This key authenticates every API and WebSocket request. You use the API to monitor your engine's activity, control its trading state, and configure which markets and tournaments to trade. Everything else — exchange accounts, bookmaker selection, trading parameters — is configured by our team during setup. ### Bookmaker (pricing source) Your engine is connected to a bookmaker — a sportsbook whose live odds feed drives your market-making activity. When the bookmaker's odds change, the engine recomputes order prices and posts them to your connected exchanges. You don't choose or change your bookmaker through the API. It's assigned during onboarding. The bookmaker's odds are the input signal; the exchanges are where your orders land. ### Exchanges and prediction markets The platforms where the engine places orders on your behalf. Your exchange accounts are set up by our team and linked to your client. You can view balances and status via `GET /accounts`, but account creation, credentials, and activation are handled on our side. | Exchange | Auth method | Order type | | ------------- | ----------------------- | --------------------- | | Polymarket | Ethereum wallet signing | CLOB limit orders | | Polymarket US | JWT + gRPC | gRPC order submission | | Kalshi | RSA-PSS signing | REST limit orders | | Novig.us | OAuth 2.0 | REST orders | | SX.bet | API key + EIP712 | Signed orders | | Betfair | SSL certificate | Exchange API | | ProphetX | JWT | REST orders | | Matchbook | Session token | REST orders | | Smarkets | Session token | REST orders | | 4casters | Token auth | Socket.IO | | Predict.fun | JWT + EIP712 | Signed orders | ### What you control vs what we configure | You control (via API) | We configure (during setup) | | ----------------------------------------- | ---------------------------------------------- | | Pause / resume / stop / start your engine | Your bookmaker (pricing source) | | Cancel open orders | Exchange accounts and credentials | | Toggle market types on/off per sport | Trading parameters (limit ratios, price ticks) | | Activate / deactivate tournaments | Client account creation and API keys | | Monitor orders, bets, positions, P\&L | | ### Order status Returned on every order in `GET /orders`, `GET /orders/open`, and WebSocket events. Full enum: ``` PENDING_PLACEMENT → PLACED → FILLED / CANCELLED / FAILED / EXPIRED ``` Filter `GET /orders` with `?status=PLACED,FILLED` (comma-separated for multiple values). ### Match status Independent of order status — tracks how much of the order has been matched on the exchange. ``` NOT_MATCHED → PARTIALLY_MATCHED → FULLY_MATCHED ``` ### Settlement status Set when the underlying market settles after the event finishes. ``` UNDECIDED → WON / LOST / VOID / HALF_WON / HALF_LOST ``` ### Market types Market types are discovered automatically. The engine continuously maps bookmaker outcomes to exchange markets — a market type becomes available when the engine proves it's tradeable on your connected exchanges. **Discover available market types:** ```bash theme={null} curl -H "X-API-Key: YOUR_KEY" \ https://mmapi.55-tech.com/api/v1/sports/1/market-types ``` Each item in the response shows `marketType`, `enabled` (your current toggle), and `marketName`. Use `PATCH /client/market-allowlist` to disable types you don't want to trade, and `POST /client/market-allowlist/activate-all` to re-enable all. ## Rate limits * REST API: **2,000 requests/minute** per client * WebSocket: **5 concurrent connections** per API key ## Next steps Set up your API key. Make your first API calls. # MM Quickstart - First API Calls Source: https://docs.55-tech.com/mm-api/quickstart Step-by-step guide to using the MM Trading API. Check trading state, view exchange orders and hedge bets, monitor positions and PnL, control your engine. The MM engine places orders automatically based on bookmaker odds. The client API lets you **monitor** all trading activity, **control** your trading state (pause, resume, stop, start), and cancel orders. You do not place orders through this API — the engine does it for you. ## Step 1: Check trading state Confirm your API key works and see your engine's current state: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/trading/status ``` Response: ```json theme={null} { "client": "your-client", "clientName": "Your Client Name", "oddsFormat": "decimal", "status": "live", "tradingActive": true, "manualPaused": false, "manualStopped": false, "systemTradingBlocked": false, "emergencyActive": false, "systemStopped": false, "softPaused": false } ``` **Response fields:** | Field | Description | | ---------------------- | ------------------------------------------------------------------------------------------------------ | | `status` | Summary state: `live`, `manual_paused`, `manual_stopped`, `soft_paused`, `system_stopped`, `emergency` | | `tradingActive` | `true` when the engine is actively posting orders | | `manualPaused` | `true` if you paused via `POST /trading/pause` | | `manualStopped` | `true` if you stopped via `POST /trading/stop` | | `systemTradingBlocked` | `true` if the system is globally paused or stopped (not client-specific) | ## Step 2: View exchange orders Paginated list of orders the engine has placed on exchanges. **Query parameters:** | Parameter | Required | Description | | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `fixtureId` | No | Filter by fixture ID | | `exchange` | No | Filter by exchange (`polymarket`, `kalshi`, etc.) | | `status` | No | Filter by order status, comma-separated (e.g. `PLACED,FILLED`). Values: `PENDING_PLACEMENT`, `PLACED`, `FILLED`, `FAILED`, `CANCELLED`, `EXPIRED` | | `fromDate` | No | Orders created on or after this date (`YYYY-MM-DD`) | | `toDate` | No | Orders created on or before this date (`YYYY-MM-DD`) | | `page` | No | Page number, 1-indexed (default: `1`) | | `pageSize` | No | Items per page, max `2000` (default: `50`) | ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ "https://mmapi.55-tech.com/api/v1/orders?exchange=polymarket&status=FILLED" ``` Response: ```json theme={null} { "data": [ { "orderId": 123, "fixtureId": "id1000000861624412", "outcomeId": 161, "playerId": 0, "exchange": "polymarket", "account": "0x1234...abcd", "exchangeOrderId": "0x1a2b3c...", "exchangeOutcomeId": "12345678", "orderStatus": "FILLED", "matchedStatus": "FULLY_MATCHED", "orderCents": 0.45, "orderStake": 100.0, "matchedStake": 100.0, "back": true, "side": "buy", "settlementStatus": "UNDECIDED", "settledAt": null, "bookmakerOutcomePrice": 1.808, "bookmakerOutcomeLimit": 5000.0, "bookmakerOutcomeActive": true, "createdAt": "2026-02-15T10:30:00Z", "matchedAt": "2026-02-15T10:30:05Z", "updatedAt": "2026-02-15T10:30:05Z" } ], "total": 1, "page": 1, "pageSize": 50, "totalPages": 1 } ``` **Response fields** (non-obvious only): | Field | Description | | ------------------------ | ------------------------------------------------------------------------- | | `playerId` | Player ID for player-prop markets, `0` for non-player-prop | | `account` | Exchange account username used for this order | | `orderStatus` | `PENDING_PLACEMENT`, `PLACED`, `FILLED`, `FAILED`, `CANCELLED`, `EXPIRED` | | `matchedStatus` | `NOT_MATCHED`, `PARTIALLY_MATCHED`, or `FULLY_MATCHED` | | `orderCents` | Order price in cents (`0.01`–`0.99` for prediction markets) | | `orderStake` | Amount the engine intended to place | | `matchedStake` | Amount actually filled on the exchange | | `back` | `true` = buy, `false` = sell | | `side` | Computed string form: `buy` or `sell` (derived from `back`) | | `bookmakerOutcomePrice` | Bookmaker decimal odds that triggered this order | | `bookmakerOutcomeLimit` | Bookmaker's available limit at time of order | | `bookmakerOutcomeActive` | Whether the bookmaker side was active at order time | | `settlementStatus` | `UNDECIDED`, `WON`, `LOST`, `VOID`, `HALF_WON`, `HALF_LOST` | | `settledAt` | Timestamp when settlement was applied (null until settled) | | `updatedAt` | Last status-change timestamp | For only currently-resting orders (no pagination), use `GET /orders/open` with optional `fixtureId` and `exchange` filters. ## Step 3: View hedge bets When an order fills on an exchange, the engine automatically places a hedge bet on the bookmaker. Each bet is linked to its order via `orderId`. **Query parameters:** | Parameter | Required | Description | | ----------- | -------- | ------------------------------------------------------------- | | `orderId` | No | Filter bets linked to a specific order | | `bookmaker` | No | Filter by bookmaker | | `status` | No | Bet status, comma-separated (`placed`, `pending`, `declined`) | | `fromDate` | No | Bets created on or after this date (`YYYY-MM-DD`) | | `toDate` | No | Bets created on or before this date (`YYYY-MM-DD`) | | `page` | No | Page number (default: `1`) | | `pageSize` | No | Items per page, max `2000` (default: `50`) | ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ "https://mmapi.55-tech.com/api/v1/bets?orderId=123" ``` Response: ```json theme={null} { "data": [ { "betId": 456, "orderId": 123, "client": "your-client", "bookmaker": "vertex", "bookmakerBetId": "789", "placedPrice": 1.808, "placedStake": 100.0, "betStatus": "placed", "settlementStatus": "undecided", "placedAt": "2026-02-15T10:30:08Z" } ], "total": 1, "page": 1, "pageSize": 50, "totalPages": 1 } ``` ## Step 4: Check positions Aggregated positions across all fixtures and exchanges. **Query parameters:** | Parameter | Required | Description | | ----------- | -------- | --------------------------------------------------------------------------------------- | | `fixtureId` | No | Filter by fixture ID | | `exchange` | No | Filter by exchange | | `status` | No | Comma-separated: `open`, `matched`, `filled`, `cancelled`, `active` (default: `active`) | | `fromDate` | No | Filter from this date (`YYYY-MM-DD`) | | `toDate` | No | Filter to this date (`YYYY-MM-DD`) | ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/positions ``` Response: ```json theme={null} { "positions": [ { "fixtureId": "id1000000861624412", "outcomeId": 161, "playerId": 0, "exchange": "polymarket", "orderCount": 3, "totalStake": 100.0, "matchedStake": 100.0, "openStake": 0.0, "avgPrice": 0.45, "participant1Name": "Los Angeles Lakers", "participant2Name": "Houston Rockets", "sportId": 2, "sportName": "Basketball", "tournamentId": 132, "tournamentName": "NBA", "categoryName": "USA", "startTime": 1712000000 } ], "count": 1 } ``` Each position now includes fixture metadata resolved from the database. The fixture fields (`participant1Name`, `participant2Name`, `sportId`, `sportName`, `tournamentId`, `tournamentName`, `categoryName`, `startTime`) are **nullable** — they will be `null` if the fixture was never seen by the backend. For a single rolled-up summary across every fixture, use `GET /positions/summary`. ## Step 5: View profit & loss Market-making P\&L: spread captured between exchange fills and their bookmaker hedges. **Query parameters:** | Parameter | Required | Description | | ---------- | -------- | ----------------------------------------------------------- | | `fromDate` | No | Filter orders created on or after this date (`YYYY-MM-DD`) | | `toDate` | No | Filter orders created on or before this date (`YYYY-MM-DD`) | ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ "https://mmapi.55-tech.com/api/v1/pnl?fromDate=2026-01-29&toDate=2026-02-01" ``` Response: ```json theme={null} { "totalTurnover": 98350.00, "validTurnover": 72415.30, "netRealizedSpread": 1482.55, "nakedPnl": -220.10, "unhedgedStake": 1622.78, "unhedgedStakePct": 1.65, "avgHedgePriceDrift": 0.0042, "settledPairCount": 1420, "nakedSettledCount": 12, "fromDate": "2026-01-29", "toDate": "2026-02-01" } ``` **Response fields:** | Field | Description | | -------------------- | --------------------------------------------------------------------------- | | `totalTurnover` | Total stake hedged on the bookmaker side | | `validTurnover` | Risk-adjusted turnover. A $100 bet at 1.1 contributes less than $100 at 3.0 | | `netRealizedSpread` | P\&L from settled exchange + bookmaker pairs (both legs terminal) | | `nakedPnl` | P\&L from settled exchange fills that never got a successful hedge | | `unhedgedStake` | Dollar amount of matched exchange stake with no successful hedge | | `unhedgedStakePct` | Percentage of matched exchange stake with no successful hedge | | `avgHedgePriceDrift` | Stake-weighted average hedge slippage (decimal-odds points) | | `settledPairCount` | Number of fully-settled exchange + hedge pairs | | `nakedSettledCount` | Number of settled exchange fills counted in `nakedPnl` | ## Step 6: List accounts View your exchange accounts (credentials are never exposed): ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/accounts ``` ```json theme={null} { "oddsFormat": "decimal", "accounts": [ { "username": "0x1234...abcd", "exchange": "polymarket", "active": true, "maxOutcomeStake": 1000.0, "balance": 50000.0, "createdAt": "2026-01-15T12:00:00Z" } ], "count": 1 } ``` ## Step 7: Trading controls Pause keeps existing orders on the exchange but stops new ones. Stop cancels all open orders immediately. ```bash theme={null} # Pause — keeps existing orders, stops new ones curl -X POST -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/trading/pause # Resume after a pause curl -X POST -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/trading/resume # Stop — cancels every open order curl -X POST -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/trading/stop # Start after a stop curl -X POST -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/trading/start ``` ## Step 8: Cancel orders Cancel a single order by ID, or every open order at once. **Query parameters for `cancel-all`:** | Parameter | Required | Description | | ----------- | -------- | ----------------------------------------- | | `exchange` | No | Only cancel orders on a specific exchange | | `fixtureId` | No | Only cancel orders for a specific fixture | ```bash theme={null} # Cancel everything curl -X POST -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/orders/cancel-all # Cancel only Polymarket orders curl -X POST -H "X-API-Key: YOUR_API_KEY" \ "https://mmapi.55-tech.com/api/v1/orders/cancel-all?exchange=polymarket" # Cancel a specific order curl -X POST -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/orders/123/cancel ``` ## Next steps Get real-time order fills, hedge bets, and score updates. Explore all available endpoints. # MM WebSocket - Real-Time Updates Source: https://docs.55-tech.com/mm-api/websocket Connect to the MM WebSocket for real-time order fills, hedge bet updates, account balance changes, live scores, and emergency status. server-backed broadcasting. ## Endpoint ``` wss://mmapi.55-tech.com/ws/subscribe ``` ## Connection flow ### 1. Connect Open a WebSocket connection. No authentication is needed at connection time. ### 2. Subscribe Send a subscribe message within **30 seconds** of connecting: ```json theme={null} { "type": "subscribe", "apiKey": "your-uuid-api-key", "channels": ["orders", "bets", "accounts", "scores", "emergency"] } ``` ### 3. Confirmation On success, the server responds with: ```json theme={null} { "type": "subscribed", "subscriptionId": 1, "channels": ["orders", "bets", "accounts", "scores", "emergency"] } ``` ### 4. Receive broadcasts Data updates arrive as broadcast messages with both current and previous state: ```json theme={null} { "type": "broadcast", "channel": "orders", "event": "UPDATE", "payload": { "orderId": 123, "orderStatus": "FILLED", "matchedStake": 100.0, "matchedAt": "2026-02-15T10:30:05Z" }, "old": { "orderStatus": "PLACED", "matchedStake": 0 } } ``` The `old` field contains the previous state, making it easy to detect what changed. ### 5. Keep alive Send a ping to keep the connection alive: ```json theme={null} {"type": "ping"} ``` Server responds with: ```json theme={null} {"type": "pong"} ``` The server also sends heartbeats every **60 seconds**: ```json theme={null} {"type": "heartbeat"} ``` ### 6. Unsubscribe To stop receiving updates and clean up your subscription: ```json theme={null} {"type": "unsubscribe"} ``` ## Channels ### Client-filtered channels These channels only deliver data belonging to your client: | Channel | Events | Description | | ---------- | -------------- | ----------------------------------------------- | | `orders` | INSERT, UPDATE | Exchange order placement, status changes, fills | | `bets` | INSERT, UPDATE | Hedge bet placement, confirmation, settlement | | `accounts` | UPDATE | Account balance and status changes | ### Global channels All subscribers receive these: | Channel | Events | Description | | ----------- | ------ | ----------------------------------------- | | `scores` | UPDATE | Live score updates (goals, sets, periods) | | `emergency` | UPDATE | Emergency mode activation/deactivation | ## Payload examples ### Order fill When an exchange order gets matched: ```json theme={null} { "type": "broadcast", "channel": "orders", "event": "UPDATE", "payload": { "orderId": 123, "fixtureId": "id1000000861624412", "outcomeId": 161, "exchange": "polymarket", "exchangeOrderId": "0x1a2b3c...", "orderStatus": "FILLED", "matchedStatus": "FULLY_MATCHED", "orderCents": 0.45, "orderStake": 100.0, "matchedStake": 100.0, "matchedAt": "2026-02-15T10:30:05Z" }, "old": { "orderStatus": "PLACED", "matchedStatus": "NOT_MATCHED", "matchedStake": 0 } } ``` ### Hedge bet placed After an order fills, the system automatically places a hedge bet: ```json theme={null} { "type": "broadcast", "channel": "bets", "event": "INSERT", "payload": { "betId": 456, "orderId": 123, "client": "your-client", "bookmaker": "vertex", "placedPrice": 1.808, "placedStake": 100.0, "betStatus": "placed", "sentData": { "requestId": "a1b2c3d4", "eventId": 98765, "price": 1.808, "amount": 100.0, "side": "1" }, "receivedData": { "betId": "789", "status": "U" }, "placedAt": "2026-02-15T10:30:08Z" } } ``` ### Live score update ```json theme={null} { "type": "broadcast", "channel": "scores", "event": "UPDATE", "payload": { "fixtureId": "id1000000861624412", "live": true, "statusId": 1, "currentPeriod": "2nd Half", "currentMinute": 67, "scores": { "home": 2, "away": 1, "period1Home": 1, "period1Away": 0 } } } ``` ### Emergency status ```json theme={null} { "type": "broadcast", "channel": "emergency", "event": "UPDATE", "payload": { "emergency": true, "reason": "Manual trigger", "triggeredAt": "2026-02-15T10:30:00Z" } } ``` ## Connection limits | Setting | Value | | --------------------------- | ------------------------ | | Max connections per API key | 5 | | Auth timeout | 30 seconds | | Server heartbeat interval | 60 seconds | | Client ping interval | 30 seconds (recommended) | ## Error messages | Error | Description | | --------------------------- | -------------------------------------------------- | | `apiKey required` | Missing `apiKey` field in subscribe message | | `Invalid apiKey format` | API key must be a valid UUID | | `Invalid API key` | API key not found or client is inactive | | `Connection limit exceeded` | Already have 5 active connections for this API key | ## Example: Python client ```python theme={null} import asyncio import json import websockets async def connect(): uri = "wss://mmapi.55-tech.com/ws/subscribe" async with websockets.connect(uri) as ws: # Subscribe await ws.send(json.dumps({ "type": "subscribe", "apiKey": "YOUR_API_KEY", "channels": ["orders", "bets", "scores"] })) # Wait for confirmation sub_resp = json.loads(await ws.recv()) print(f"Subscribed (id={sub_resp.get('subscriptionId')})") # Keep-alive task async def keep_alive(): while True: await asyncio.sleep(30) await ws.send(json.dumps({"type": "ping"})) asyncio.create_task(keep_alive()) # Listen for updates async for message in ws: data = json.loads(message) if data["type"] == "broadcast": channel = data["channel"] event = data["event"] print(f"[{channel}:{event}] {data['payload']}") if "old" in data: print(f" Changed from: {data['old']}") asyncio.run(connect()) ``` ## Example: JavaScript client ```javascript theme={null} const WebSocket = require('ws'); const ws = new WebSocket('wss://mmapi.55-tech.com/ws/subscribe'); ws.on('open', () => { ws.send(JSON.stringify({ type: 'subscribe', apiKey: 'YOUR_API_KEY', channels: ['orders', 'bets', 'scores'] })); }); ws.on('message', (raw) => { const msg = JSON.parse(raw); if (msg.type === 'subscribed') { console.log(`Subscribed (id=${msg.subscriptionId})`); } if (msg.type === 'broadcast') { console.log(`[${msg.channel}:${msg.event}]`, msg.payload); if (msg.old) { console.log(' Changed from:', msg.old); } } }); // Keep alive setInterval(() => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'ping' })); } }, 30000); ``` # Quickstart - Connect to 55-Tech APIs Source: https://docs.55-tech.com/quickstart Get started with the 55-Tech ABP, MM, and Scraping APIs. Check connectivity, authenticate, and make your first API calls. ## Prerequisites You need an API key for the platform you want to use. Contact [contact@55-tech.com](mailto:contact@55-tech.com) to get your key. | Platform | Auth Header | Base URL | | --------------------------- | ----------- | ---------------------------------- | | ABP (Automated Bet Placing) | `X-API-Key` | `https://v2.55-tech.com` | | MM (Market Making) | `X-API-Key` | `https://mmapi.55-tech.com` | | Scraping API | `X-API-Key` | `https://scraping-api.55-tech.com` | *** ## ABP — Automated Bet Placing ### 1. List your accounts ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://v2.55-tech.com/accounts ``` ### 2. Get a betslip (live odds) ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ "https://v2.55-tech.com/betslip?fixtureId=ID&outcomeId=161&playerId=0" ``` ### 3. Place an order ```bash theme={null} curl -X POST -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"orders": [{"fixtureId": "...", "outcomeId": 161, "playerId": 0, "stake": 10, "minPrice": 1.5}]}' \ https://v2.55-tech.com/place-orders ``` ### 4. Track results ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://v2.55-tech.com/orders ``` *** ## MM — Market Making ### 1. Verify your identity ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/me ``` ### 2. List your orders ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/orders ``` ### 3. Check positions ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/positions ``` ### 4. View profit & loss ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://mmapi.55-tech.com/api/v1/pnl ``` *** ## Scraping API ### 1. Check network status ```bash theme={null} curl https://scraping-api.55-tech.com/network/status ``` ### 2. Fetch a page ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com" \ https://scraping-api.55-tech.com/fetch ``` ### 3. Fetch with geo targeting ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com" \ -H "X-Geo: DE" \ https://scraping-api.55-tech.com/fetch ``` ### 4. Check usage ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://scraping-api.55-tech.com/usage ``` *** ## Real-time updates All APIs support WebSocket connections for live updates. See the WebSocket documentation for each API: Real-time order, bet, and settlement updates. Live order fills, bets, and score updates. Real-time fetch results via WebSocket relay. # Scraping API Authentication Source: https://docs.55-tech.com/scraping-api/authentication Authenticate Scraping API requests using the X-API-Key header. Rate limits, key formats, and per-key configuration. All Scraping API endpoints (except `GET /healthz`) require authentication. ## Passing your API key Pass your API key using one of these methods depending on the endpoint: **HTTP header (for `/fetch`, `/usage`, `/network/*`, `/debug/pick`):** ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com" \ https://scraping-api.55-tech.com/fetch ``` **JSON body field (for `/browser`, `/ws`, and `/amqp`):** ```json theme={null} { "apiKey": "YOUR_API_KEY", "url": "https://example.com" } ``` For `/browser`, `/ws`, and `/amqp`, the key is read from the JSON body field `apiKey` (or `key`), or the `X-API-Key` header. ## Error responses | Status | Meaning | | ------ | --------------- | | `401` | Missing API key | | `403` | Invalid API key | ## Rate limits Each API key has a configurable rate limit: | Setting | Default | | ------------------------- | ------------ | | Requests per second (RPS) | 10 | | Burst capacity | Equal to RPS | When rate limited, the API returns `429 Too Many Requests` with these headers: ``` Retry-After: 1 X-RateLimit-Limit: 10 X-RateLimit-Remaining: 0 ``` Implement exponential backoff: wait 1s, 2s, 4s, etc. Check your current rate limit and usage: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://scraping-api.55-tech.com/usage ``` The response includes your remaining tokens: ```json theme={null} { "rate_limit": { "rps": 10.0, "burst": 10, "remaining": 9.3 } } ``` ## Public endpoints These endpoints do **not** require authentication: | Endpoint | Description | | -------------- | ------------------------------- | | `GET /` | API overview and status | | `GET /healthz` | Liveness probe (`{"ok": true}`) | ## Obtaining a key Contact [contact@55-tech.com](mailto:contact@55-tech.com) to obtain an API key with a rate limit configured for your use case. # Browser Rendering Source: https://docs.55-tech.com/scraping-api/browser Fetch with full JavaScript rendering. Returns cookies, screenshots, and supports custom JS evaluation. ## Overview The `/browser` endpoint fetches a URL with full JavaScript rendering. Unlike `/fetch` (which returns the raw HTTP response), `/browser` waits for dynamic content and returns the fully rendered page. **When to use `/browser` instead of `/fetch`:** * The page requires JavaScript to load content (SPAs, dynamic sites) * You need all cookies, including those set by client-side scripts * You want to execute custom JavaScript to extract data * You need a screenshot of the rendered page The response format is the same as `/fetch` (`meta`, `raw`, `raw_json`), with additional fields for cookies, screenshots, and JS evaluation results. ## Endpoint ``` GET https://scraping-api.55-tech.com/browser ``` Works just like `/fetch` — all parameters are passed via headers. ## Request headers | Header | Required | Default | Description | | ------------------- | -------- | ------- | ------------------------------------------------------------------------------------------- | | `X-API-Key` | Yes | — | Your API key | | `X-Target-URL` | Yes | — | Target URL to render | | `X-Wait-Strategy` | No | `load` | `load`, `networkidle`, or `selector` ([details](#wait-strategies)) | | `X-Wait-Selector` | No | — | CSS selector to wait for (with `selector` strategy) | | `X-Timeout` | No | `30` | Timeout in seconds (max 300 for `/browser`) | | `X-JS-Expression` | No | — | JavaScript to evaluate after the page is ready | | `X-Screenshot` | No | `false` | Capture screenshot (`1` or `true`) | | `X-Expect-Selector` | No | — | CSS selector that must exist — retries on a different node if missing | | `X-Expect-Contains` | No | — | Substring that must exist in the body — retries if missing | | `X-Proxy` | No | — | Route through a proxy (`http://` or `socks5://`) | | `X-Steps` | No | — | JSON array of sequential browser actions ([details](#steps)) | | `X-Block-Resources` | No | — | Comma-separated resource types to block: `image,font,stylesheet,media`. No default blocking | | `X-Geo` | No | — | Country filter for node selection (e.g. `US`, `DE,AT`) | | `X-Agent` | No | — | Pin to a specific node (e.g. `de1`, `us3`) | | `Cookie` | No | — | Cookies to inject (`name=value; name2=value2`) | | `X-Cookies` | No | — | Cookies as JSON array for full control ([format](#cookies)) | ### Wait strategies | Strategy | Description | | ------------- | ----------------------------------------------------------------------------------- | | `load` | Wait for `DOMContentLoaded` event (fastest, works for server-rendered pages) | | `networkidle` | Wait until there are no more than 2 network connections for 500ms (best for SPAs) | | `selector` | Wait for `X-Wait-Selector` to appear in the DOM (most precise for specific content) | ### Cookies **Simple cookies** — use the standard `Cookie` header: ``` Cookie: session=abc123; token=xyz ``` **Full cookie objects** — use the `X-Cookies` header with a JSON array when you need domain, path, or httpOnly control: ``` X-Cookies: [{"name":"session","value":"abc","domain":".example.com","secure":true}] ``` Cookie object fields: `name`, `value`, `domain`, `path`, `secure`, `httpOnly`, `sameSite`, `expires`. ### Steps Execute sequential browser actions after the page loads — login flows, multi-page navigation, form filling, clicking through to specific content. Pass a JSON array via the `X-Steps` header: ``` X-Steps: [{"action":"type","selector":"#email","value":"user@example.com"},{"action":"click","selector":"#submit"},{"action":"waitNavigation"}] ``` **Available actions:** | Action | Params | Description | | ---------------- | ---------------------------- | ----------------------------------------------- | | `navigate` | `url`, `waitUntil` | Navigate to a new URL | | `click` | `selector` | Click an element (human-like mouse movement) | | `type` | `selector`, `value`, `delay` | Type text into an input (auto-clears first) | | `clear` | `selector` | Clear an input field | | `select` | `selector`, `value` | Select a dropdown option by value | | `wait` | `selector`, `timeout` | Wait for an element to appear | | `waitNavigation` | `waitUntil` | Wait for page navigation to complete | | `waitHidden` | `selector` | Wait for an element to disappear | | `scroll` | `selector` or `x`, `y` | Scroll to an element or by pixels | | `hover` | `selector` | Hover over an element | | `press` | `key` | Press a keyboard key (`Enter`, `Tab`, `Escape`) | | `focus` | `selector` | Focus an element | | `evaluate` | `expression` | Run JavaScript in the page | | `screenshot` | `fullPage` | Capture a screenshot (returned in step result) | | `sleep` | `ms` | Wait a fixed number of milliseconds | Each step runs after the previous completes. Add `"continueOnError": true` to a step to keep going if it fails. **Example: Login → navigate → capture** ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com/login" \ -H 'X-Steps: [ {"action": "wait", "selector": "#login-form"}, {"action": "type", "selector": "#email", "value": "user@example.com"}, {"action": "type", "selector": "#password", "value": "secret"}, {"action": "click", "selector": "#submit"}, {"action": "waitNavigation"}, {"action": "navigate", "url": "https://example.com/dashboard"}, {"action": "wait", "selector": ".data-table"} ]' \ -H "X-Timeout: 120" \ https://scraping-api.55-tech.com/browser ``` **Example: Login then stream live data** ```bash theme={null} curl -N -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com/login" \ -H "X-Capture: ws,network" \ -H 'X-Steps: [ {"action": "type", "selector": "#email", "value": "user@example.com"}, {"action": "type", "selector": "#password", "value": "secret"}, {"action": "click", "selector": "#submit"}, {"action": "waitNavigation"}, {"action": "navigate", "url": "https://example.com/live-feed"}, {"action": "wait", "selector": ".feed-container"} ]' \ https://scraping-api.55-tech.com/browser/stream ``` Steps work on both `/browser` (capture after all steps complete) and `/browser/stream` (stream during and after steps — step results arrive as `step_ok` / `step_error` events). ### Resource blocking Block specific resource types to speed up rendering: ``` X-Block-Resources: image,font,stylesheet ``` Blocking images and fonts can reduce render time by 50%+ on media-heavy pages. ## Response ```json theme={null} { "meta": { "status": 200, "final_url": "https://example.com/", "http_version": "", "elapsed_ms": 3200, "blocked": false, "headers": { "content-type": "text/html; charset=utf-8" }, "agent": { "id": "scraping-de5" }, "bytes": 45210 }, "raw": "...", "raw_json": null, "cookies": [ { "name": "session_id", "value": "a1b2c3...", "domain": ".example.com", "path": "/", "secure": true, "httpOnly": true, "sameSite": "Lax", "expires": 1735689600 } ], "screenshot": null, "js_result": null } ``` | Field | Description | | ------------ | ------------------------------------------------------------------------- | | `meta` | Same as `/fetch` — status, final URL, headers, timing, node ID | | `raw` | Rendered page body as text (HTML or other). `null` if body was valid JSON | | `raw_json` | Parsed JSON object if the response was valid JSON, otherwise `null` | | `cookies` | All cookies set during rendering, including `httpOnly` cookies | | `screenshot` | Base64-encoded PNG of the full page (`null` if not requested) | | `js_result` | Return value of `X-JS-Expression` (`null` if not provided) | ### Response validation Use `X-Expect-Selector` and `X-Expect-Contains` to verify the rendered page has the content you expect. If validation fails, the API automatically retries on a different node before returning an error. ## Examples ### Render a JavaScript-heavy page ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com" \ -H "X-Wait-Strategy: networkidle" \ https://scraping-api.55-tech.com/browser ``` ### Wait for specific content ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com/dashboard" \ -H "X-Wait-Strategy: selector" \ -H "X-Wait-Selector: #data-table" \ -H "X-Timeout: 45" \ https://scraping-api.55-tech.com/browser ``` ### Extract data with JavaScript ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com" \ -H "X-Wait-Strategy: networkidle" \ -H "X-JS-Expression: JSON.stringify({title: document.title, links: document.querySelectorAll('a').length})" \ https://scraping-api.55-tech.com/browser ``` The `js_result` field in the response contains the return value: ```json theme={null} { "js_result": "{\"title\":\"Example\",\"links\":42}" } ``` ### Capture a screenshot ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com" \ -H "X-Screenshot: 1" \ -H "X-Block-Resources: image,font" \ https://scraping-api.55-tech.com/browser ``` ### With cookies ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com" \ -H "Cookie: session=abc123; token=xyz" \ -H "X-Wait-Strategy: networkidle" \ https://scraping-api.55-tech.com/browser ``` ### Route through a proxy ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://geo-restricted-site.com" \ -H "X-Proxy: http://user:pass@proxy.example.com:8080" \ -H "X-Geo: US" \ https://scraping-api.55-tech.com/browser ``` ### Validate response content If the page doesn't contain the expected content, the API retries on a different node: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com/products" \ -H "X-Wait-Strategy: selector" \ -H "X-Wait-Selector: .product-list" \ -H "X-Expect-Selector: .product-list" \ -H "X-Expect-Contains: price" \ https://scraping-api.55-tech.com/browser ``` ### Python ```python theme={null} import requests resp = requests.get("https://scraping-api.55-tech.com/browser", headers={ "X-API-Key": "YOUR_API_KEY", "X-Target-URL": "https://example.com", "X-Wait-Strategy": "networkidle", "X-JS-Expression": "document.title", "X-Block-Resources": "image,font", }) data = resp.json() print(data["meta"]["status"]) # 200 print(data["raw"][:200]) # rendered HTML print(data["js_result"]) # "Example Domain" for c in data["cookies"]: print(f"{c['name']}={c['value']} (httpOnly={c['httpOnly']})") ``` ### JavaScript ```javascript theme={null} const resp = await fetch("https://scraping-api.55-tech.com/browser", { headers: { "X-API-Key": "YOUR_API_KEY", "X-Target-URL": "https://example.com", "X-Wait-Strategy": "networkidle", "X-JS-Expression": "document.title", }, }); const data = await resp.json(); console.log(data.meta.status); // 200 console.log(data.raw.slice(0, 200)); // rendered HTML console.log(data.js_result); // "Example Domain" console.log(data.cookies.length); // number of cookies captured ``` *** ## Browser Stream (SSE) For live, long-running sessions, use `/browser/stream`. Instead of capturing a single snapshot, the browser stays open and streams events in real-time via Server-Sent Events. Only data-carrying requests (XHR/Fetch API calls) are captured — full HTML pages and JavaScript bundles are filtered out. No resources are blocked by default so pages load fully and widgets initialize correctly. Default wait strategy is `networkidle`. All data is gzip-compressed. **Use cases:** * Capture WebSocket frames that the page receives (live data feeds) * Monitor XHR/Fetch API calls the page makes (the actual data endpoints) * Watch DOM elements for changes (price updates, content changes) ### Endpoint ``` GET https://scraping-api.55-tech.com/browser/stream ``` ### Headers Same headers as `/browser`, with different defaults: `X-Wait-Strategy` defaults to `networkidle`, `X-Timeout` defaults to `0` (runs until disconnect, 24h safety cap), no resource blocking. Plus: | Header | Default | Description | | ------------------ | -------------------- | ------------------------------------------------------------------------------- | | `X-Capture` | `network,ws,console` | Comma-separated event types to stream | | `X-DOM-Selector` | — | CSS selector to watch for DOM mutations (requires `dom` in capture) | | `X-Network-Filter` | — | Regex filter for network URLs (only matching are streamed) | | `X-WS-Filter` | — | Regex filter for WebSocket URLs (only matching are streamed) | | `X-JS-After-Load` | — | JavaScript to execute after page load (e.g. click a button, trigger navigation) | ### SSE events | Event | Description | | ------------ | ------------------------------------------------------------------------ | | `connected` | Browser loaded, streaming started (includes cookies) | | `network` | HTTP response captured (XHR/Fetch) — includes body, status, content-type | | `ws_open` | Page opened a WebSocket connection | | `ws_message` | WebSocket frame received by the page | | `ws_close` | Page WebSocket closed | | `dom` | DOM mutation on watched selector | | `console` | Console output (warn/error) | | `error` | Error, stream ends | | `done` | Session ended (timeout, if set, or client disconnect) | ### Example: Capture page WebSocket feed ```bash Request theme={null} curl -N -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com/live" \ -H "X-Capture: ws" \ -H "X-Timeout: 120" \ https://scraping-api.55-tech.com/browser/stream ``` ```text SSE Response Stream theme={null} event: connected data: {"type":"connected","url":"https://example.com/live","agent_id":"scraping-us5","timestamp":1775511253069} event: ws_open data: {"type":"ws_open","url":"wss://feed.example.com/ws","ws_id":"req-1234","timestamp":1775511254100} event: ws_message data: {"type":"ws_message","data":"{\"price\":1.95,\"market\":\"moneyline\",\"event\":\"update\"}","ws_id":"req-1234","timestamp":1775511254500} event: ws_message data: {"type":"ws_message","data":"{\"price\":2.10,\"market\":\"moneyline\",\"event\":\"update\"}","ws_id":"req-1234","timestamp":1775511255200} event: ws_message data: {"type":"ws_message","data":"{\"price\":1.85,\"market\":\"spread\",\"event\":\"update\"}","ws_id":"req-1234","timestamp":1775511256800} ``` ```json connected theme={null} { "type": "connected", "url": "https://example.com/live", "status": 200, "agent_id": "scraping-us5", "data": "{\"title\":\"Live Feed\",\"cookies\":[{\"name\":\"session\",\"value\":\"abc\"}]}", "timestamp": 1775511253069 } ``` ```json ws_open theme={null} { "type": "ws_open", "url": "wss://feed.example.com/ws", "ws_id": "req-1234", "timestamp": 1775511254100 } ``` ```json ws_message theme={null} { "type": "ws_message", "data": "{\"price\":1.95,\"market\":\"moneyline\",\"event\":\"update\"}", "ws_id": "req-1234", "timestamp": 1775511254500 } ``` ```json network theme={null} { "type": "network", "url": "https://api.example.com/v1/odds?eventId=12345", "method": "GET", "status": 200, "data": "{\"odds\":[{\"market\":\"moneyline\",\"home\":1.95,\"away\":2.10}]}", "content_type": "application/json", "timestamp": 1775511255000 } ``` ```json dom theme={null} { "type": "dom", "url": "https://example.com/live", "data": "
1.95
", "timestamp": 1775511256000 } ``` ```json done theme={null} { "type": "done", "data": "closed", "timestamp": 1775511300000 } ```
The SSE stream runs until you disconnect. Each event arrives as `event: type\ndata: json\n\n`. Use `curl -N` (no buffering) to see events in real-time. ### Example: Monitor network API calls ```bash theme={null} curl -N -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com/dashboard" \ -H "X-Capture: network" \ -H "X-Network-Filter: api\\.example\\.com" \ https://scraping-api.55-tech.com/browser/stream ``` ### Example: Watch DOM element for changes ```bash theme={null} curl -N -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://example.com/prices" \ -H "X-Capture: dom" \ -H "X-DOM-Selector: #price-table" \ -H "X-Wait-Strategy: selector" \ -H "X-Wait-Selector: #price-table" \ https://scraping-api.55-tech.com/browser/stream ``` ### Python ```python theme={null} import requests import json resp = requests.get("https://scraping-api.55-tech.com/browser/stream", headers={ "X-API-Key": "YOUR_API_KEY", "X-Target-URL": "https://example.com/live", "X-Capture": "ws", }, stream=True) for line in resp.iter_lines(): if line.startswith(b"data: "): event = json.loads(line[6:]) if event["type"] == "ws_message": print(event["data"]) ``` ## Next steps For pages that don't need JavaScript rendering, use the faster `/fetch` endpoint. Status codes, block detection, and retry strategies. # Scraping API Error Handling Source: https://docs.55-tech.com/scraping-api/errors Scraping API error codes, block detection, and retry strategies. ## Error response format All API errors return JSON: ```json theme={null} { "detail": "Invalid or missing API key" } ``` ## HTTP status codes | Status | Description | | ------ | ----------------------------------------------------------------------------------------- | | `200` | Success — target response proxied (check `meta.blocked` for block detection) | | `401` | Unauthorized — missing API key | | `403` | Forbidden — invalid API key | | `429` | Rate limited. Headers include `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` | | `502` | Bad gateway — no healthy agent available for this domain | | `504` | Gateway timeout — target did not respond within timeout (default 30s) | ## Block detection Every fetch response includes a `meta.blocked` field: ```json theme={null} { "meta": { "status": 403, "blocked": true, "agent": { "id": "scraping-de5" } }, "raw": "Access Denied..." } ``` When `meta.blocked` is `true`: * The agent that got blocked is automatically excluded for this domain * Your next request will be routed to a different agent * The response still contains the full target body for your inspection You can check per-agent health for a domain: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://scraping-api.55-tech.com/network/health/example.com ``` The health endpoint returns three states: `available`, `limited` (temporary), and `unavailable` (longer exclusion). Agents recover automatically. ## Retry strategies ### Rate limit (429) Use exponential backoff: wait 1s, 2s, 4s, etc. Check `GET /usage` for your current rate limit status. ### Blocked (meta.blocked = true) No client-side retry needed. The API automatically routes your next request to a different, healthy agent. Just keep making requests normally. ### Browser validation failure (502) If you use `expectSelector` or `expectContains` on `/browser` and the rendered page doesn't match, the API automatically retries on a different node before returning the error. No client-side retry needed for the first failure. ### Timeout (504) * Increase the `timeout` field in your POST body (default: 30 seconds) * For `/browser`, note that browser startup adds \~2-5 seconds — set `timeout` accordingly * Use `X-Geo` to pick agents geographically closer to the target * Use `GET /debug/pick?url=...` to preview which agent would be selected ### No agents available (502) All agents for this domain are temporarily excluded. Wait a moment and retry, or use `GET /network/health/{domain}` to check recovery status. ## WebSocket errors | Error | Cause | Close code | | ------------------------ | ---------------------------------------------- | ---------- | | Invalid API key | Missing or unrecognized key in connect message | `1008` | | Rate limit exceeded | Too many concurrent connections | `1008` | | Connect timeout | No JSON connect message within 10 seconds | `1008` | | Target connection failed | Agent could not connect to target WS | `1011` | | Agent unavailable | No agent available for the request | `1011` | ## AMQP errors AMQP errors are delivered as SSE events: ``` event: error data: {"message": "agent error: connection refused", "code": 1011} ``` After an error event, the SSE stream closes. ## Common errors ### Missing target URL ```bash theme={null} # Wrong: no URL specified curl -H "X-API-Key: YOUR_KEY" https://scraping-api.55-tech.com/fetch # Right: URL in header curl -H "X-API-Key: YOUR_KEY" \ -H "X-Target-URL: https://example.com" \ https://scraping-api.55-tech.com/fetch ``` ### Invalid geo code ```bash theme={null} # Wrong: full country name -H "X-Geo: Germany" # Right: ISO 2-char code -H "X-Geo: DE" ``` ### WebSocket connect timeout The first JSON message with `apiKey` and `url` must be sent within **10 seconds** of opening the WebSocket connection, or it will be closed with code `1008`. # Scraping API Overview Source: https://docs.55-tech.com/scraping-api/overview 55 Tech Scraping API — route HTTP, browser, WebSocket, and AMQP requests through 80+ geo-distributed agents. ## What is the Scraping API? The **Scraping API** by [55 Tech](https://55-tech.com/) lets you route HTTP, browser, WebSocket, and AMQP requests through a network of 80+ geo-distributed agents. Within seconds, the API automatically identifies the most reliable agents for each target — so your requests become instantly more successful without managing proxies yourself. **What you can do:** * **HTTP fetch** — GET/POST/PUT/PATCH/DELETE with full header, body, and cookie control * **Browser fetch** — Full JavaScript rendering with cookies, screenshots, and custom JS evaluation * **WebSocket relay** — Bidirectional frame relay (Socket.IO, SignalR, Centrifugo, GraphQL-WS, raw WebSocket) * **AMQP consumer** — Stream RabbitMQ messages via Server-Sent Events (SSE) * **Geo targeting** — Pin requests to specific countries or individual agents * **Proxy chaining** — Route requests through your own proxy for additional IP flexibility * **Response validation** — Verify expected content exists in rendered pages, auto-retry on different node if missing * **Block detection** — Responses include a `meta.blocked` flag when access restrictions are detected ## Base URL ``` https://scraping-api.55-tech.com ``` ## Authentication Pass your API key via the `X-API-Key` header or (for WebSocket/AMQP) the `apiKey` body field. See [Authentication](/scraping-api/authentication) for details. ## Endpoints | Endpoint | Method | Auth | Description | | -------------------------- | ----------------------------- | ------------------ | ------------------------------------------------------------------ | | `/fetch` | GET, POST, PUT, PATCH, DELETE | Required | Proxy HTTP requests through the agent network | | `/browser` | GET | Header | Fetch with JavaScript rendering (cookies, screenshots, JS eval) | | `/browser/stream` | GET | Header | Live browser session — stream network, WebSocket, DOM events (SSE) | | `/ws` | WebSocket | In connect message | Bidirectional WebSocket relay | | `/amqp` | POST | In body or header | AMQP/RabbitMQ consumer relay (SSE stream) | | `/usage` | GET | Required | Per-key usage metrics (requests, bytes, top domains) | | `/network/agents` | GET | Required | List all agents with slug, name, country | | `/network/status` | GET | Required | Network summary (total nodes, nodes per country) | | `/network/geo` | GET | Required | Agents grouped by country | | `/network/health/{domain}` | GET | Required | Per-agent health state for a domain | | `/debug/pick` | GET | Required | Preview which agent would be selected | | `/healthz` | GET | No | Liveness probe | ## Control headers These headers control routing and are stripped before forwarding to the target: | Header | Aliases | Description | Example | | --------------- | ----------------------------------------------- | --------------------------------------------------------------- | ------------------------------ | | `X-Target-URL` | — | Target URL (no encoding needed, recommended) | `https://example.com/?foo=bar` | | `X-Geo` | `X-Geo-CC`, `X-Geo-Strict`, `X-CC`, `X-Country` | Restrict to specific countries (comma-separated ISO codes) | `US,DE,AT` | | `X-Expect-JSON` | — | Hint that the target response is JSON | `1` | | `X-Agent` | — | Route through specific agent(s) by slug or comma-separated list | `de1`, `de1,at5,us3` | | `X-Timeout` | — | Override request timeout in seconds | `60` | ## Rate limiting Rate limits are **per API key**: * Default: 10 requests/second * WebSocket connections consume 1 token on connect (not per frame) When rate limited, the API returns `429` with headers: ``` Retry-After: 1 X-RateLimit-Limit: 10 X-RateLimit-Remaining: 0 ``` Check your current usage with `GET /usage`. ## Fetch response format All HTTP fetch responses follow this structure: ```json theme={null} { "meta": { "status": 200, "final_url": "https://example.com/", "http_version": "HTTP/2", "elapsed_ms": 245, "blocked": false, "headers": { "content-type": "text/html" }, "agent": { "id": "scraping-de1" }, "bytes": 4521 }, "raw": "...", "raw_json": null } ``` | Field | Description | | ------------------- | ------------------------------------------------------------------ | | `meta.status` | HTTP status code from the target | | `meta.final_url` | Final URL after redirects | | `meta.http_version` | HTTP version used (e.g. HTTP/2) | | `meta.elapsed_ms` | Round-trip time in milliseconds | | `meta.blocked` | `true` if access restrictions were detected in the response | | `meta.agent.id` | Node that served the request (e.g. `scraping-de1`) | | `meta.bytes` | Response body size in bytes | | `raw` | Response body as text (when not JSON) | | `raw_json` | Parsed JSON object (when response is valid JSON, otherwise `null`) | ## Next steps Set up your API key. Make your first proxied request. Render JavaScript-heavy pages with a real browser. # Scraping API Quickstart Source: https://docs.55-tech.com/scraping-api/quickstart Make your first proxied HTTP request, WebSocket connection, and AMQP stream through the 55 Tech Scraping API. ## Step 1: Check the network See which agents are available: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://scraping-api.55-tech.com/network/status ``` ```json theme={null} { "total_nodes": 80, "total_countries": 12, "nodes_by_country": { "AT": 22, "DE": 20, "US": 7, "IT": 5, "UK": 4, "AU": 3, "GR": 3, "ES": 2, "FR": 2, "BG": 1, "BR": 1, "PL": 1 } } ``` ## Step 2: Make an HTTP GET request Fetch a URL through the agent network using the `X-Target-URL` header: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://httpbin.org/headers" \ https://scraping-api.55-tech.com/fetch ``` ```json theme={null} { "meta": { "status": 200, "final_url": "https://httpbin.org/headers", "http_version": "HTTP/2", "elapsed_ms": 312, "blocked": false, "headers": { "content-type": "application/json" }, "agent": { "id": "scraping-de5" }, "bytes": 128 }, "raw": null, "raw_json": { "headers": { "Accept": "*/*", "Host": "httpbin.org" } } } ``` JSON responses are automatically parsed into `raw_json`. ## Step 3: POST with a custom body and headers Use `POST /fetch` with a JSON body to send custom headers, body, and method to the target: ```bash theme={null} curl -X POST \ -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://httpbin.org/post" \ -H "Content-Type: application/json" \ -d '{ "headers": { "Authorization": "Bearer my-token", "Accept": "application/json" }, "body": "{\"key\": \"value\"}", "timeout": 30 }' \ https://scraping-api.55-tech.com/fetch ``` **POST body fields:** | Field | Required | Default | Description | | ----------------- | -------- | ---------------------- | ---------------------------------------- | | `method` | No | Matches request method | Override HTTP method | | `headers` | No | `{}` | Custom headers sent to the target origin | | `body` | No | `""` | Request body sent to the target | | `timeout` | No | `30` | Request timeout in seconds | | `allow_redirects` | No | `true` | Follow redirects | ## Step 4: Target a specific country Use `X-Geo` to route through agents in specific countries: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://httpbin.org/headers" \ -H "X-Geo: US" \ https://scraping-api.55-tech.com/fetch ``` Pin to a specific agent with `X-Agent`: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://httpbin.org/headers" \ -H "X-Agent: de1" \ https://scraping-api.55-tech.com/fetch ``` Or pick randomly from a set of agents: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Target-URL: https://httpbin.org/headers" \ -H "X-Agent: de1,at5,us3" \ https://scraping-api.55-tech.com/fetch ``` ## Step 5: Check domain health See which agents can reach a specific domain: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://scraping-api.55-tech.com/network/health/example.com ``` ```json theme={null} { "domain": "example.com", "nodes_checked": 62, "available": 58, "limited": 2, "unavailable": 2, "details": [ { "node_id": "scraping-de1", "slug": "de1", "state": "available", "rtt_ms": 145, "last_check": "2026-03-13T22:15:00Z" } ] } ``` ## Step 6: Preview agent selection Debug which agent would be picked without making the actual request: ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ -H "X-Geo: US" \ "https://scraping-api.55-tech.com/debug/pick?url=https://example.com" ``` ```json theme={null} { "node_id": "scraping-us3", "node_slug": "us3", "country": "US", "valid_until_epoch_ms": 1710374100000 } ``` ## Step 7: Check your usage ```bash theme={null} curl -H "X-API-Key: YOUR_API_KEY" \ https://scraping-api.55-tech.com/usage ``` ```json theme={null} { "api_key": "YOUR_KEY", "total": 15243, "success": 14821, "fail": 422, "success_rate": 97.2, "bytes_transferred": 1847362541, "rate_limit": { "rps": 10.0, "burst": 10, "remaining": 9.3 }, "by_protocol": { "fetch": 14200, "ws": 923, "amqp": 120 }, "top_domains": [ { "domain": "example.com", "requests": 4521 } ] } ``` ## Next steps Render JavaScript-heavy pages with a real browser. Relay WebSocket connections and consume AMQP streams. Status codes, block detection, and retry strategies. # Scraping API WebSocket & AMQP Source: https://docs.55-tech.com/scraping-api/websocket Relay WebSocket connections and consume AMQP/RabbitMQ streams through the 55 Tech Scraping API agent network. ## WebSocket relay The Scraping API can relay bidirectional WebSocket connections through the agent network. All WS sub-protocols are relayed transparently — Socket.IO, SignalR, Centrifugo, GraphQL-WS, raw WebSocket, etc. ### Endpoint ``` wss://scraping-api.55-tech.com/ws ``` ### Connection flow Open a WebSocket connection to `wss://scraping-api.55-tech.com/ws`. No authentication happens at connection time — the gateway accepts immediately. Send a JSON message specifying the target URL and your API key. You have **10 seconds** to send this message before the connection is closed with code `1008`. ```json theme={null} { "apiKey": "YOUR_API_KEY", "url": "wss://target.example.com/stream", "headers": { "Authorization": "Bearer token" }, "cookies": { "session": "abc123" }, "geo": "US,DE", "agent": "de1", "idle_timeout": 0, "proxy": "socks5://user:pass@host:1080" } ``` | Field | Type | Required | Default | Description | | -------------- | ------ | -------- | ------- | ------------------------------------------------------------------------------ | | `apiKey` | string | Yes | — | Your API key (also accepts `key`) | | `url` | string | Yes | — | Target WebSocket URL (`wss://...` or `ws://...`) | | `headers` | object | No | `{}` | Custom headers for the WS upgrade request | | `cookies` | object | No | `{}` | Cookies to send with the WS upgrade request | | `geo` | string | No | — | Country filter for agent selection (e.g., `US,DE`) | | `agent` | string | No | — | Pin to specific agent(s) by slug (e.g., `de1` or `de1,at5`) | | `idle_timeout` | float | No | `0` | Seconds to wait for a target message before disconnect. `0` = no timeout | | `subprotocols` | array | No | `[]` | WS sub-protocol negotiation (e.g. `["centrifuge-protobuf"]`, `["graphql-ws"]`) | | `proxy` | string | No | `""` | Proxy URL for the WebSocket connection (`http://` or `socks5://`) | The gateway picks an agent, the agent connects to the target WebSocket, and the gateway responds with: ```json theme={null} { "type": "connected", "status": 101, "node_id": "scraping-de1" } ``` | Field | Description | | --------- | ----------------------------------- | | `type` | Always `"connected"` | | `status` | HTTP status of the WS upgrade (101) | | `node_id` | Proxy node handling the connection | After connection, all text and binary frames are relayed transparently: * **Client → target**: Your messages are forwarded to the target as-is * **Target → client**: Target messages are forwarded to you as-is * **Close frame**: Triggers graceful shutdown on both sides * **PONG frames**: From target are forwarded to client ### Error responses If something goes wrong, the gateway sends: ```json theme={null} { "type": "error", "message": "description of what went wrong" } ``` Common errors: * Invalid or missing API key (close code `1008`) * Rate limit exceeded (close code `1008`) * Connect message not received within 10 seconds (close code `1008`) * Target connection failed * Agent unavailable ### Example: Python ```python theme={null} import asyncio import json import websockets async def relay(): async with websockets.connect("wss://scraping-api.55-tech.com/ws") as ws: # 1. Send connect message await ws.send(json.dumps({ "apiKey": "YOUR_API_KEY", "url": "wss://echo.websocket.events", "geo": "DE" })) # 2. Wait for connected confirmation resp = json.loads(await ws.recv()) if resp.get("type") == "error": print(f"Error: {resp['message']}") return assert resp["type"] == "connected" print(f"Connected via {resp['node_id']}") # 3. Send and receive frames await ws.send("hello from scraping api") async for msg in ws: print(f"Received: {msg}") asyncio.run(relay()) ``` ### Example: JavaScript ```javascript theme={null} const WebSocket = require('ws'); const ws = new WebSocket('wss://scraping-api.55-tech.com/ws'); ws.on('open', () => { ws.send(JSON.stringify({ apiKey: 'YOUR_API_KEY', url: 'wss://echo.websocket.events', geo: 'DE' })); }); let connected = false; ws.on('message', (raw) => { if (!connected) { const msg = JSON.parse(raw.toString()); if (msg.type === 'connected') { console.log(`Connected via ${msg.node_id}`); connected = true; ws.send('hello from scraping api'); } else if (msg.type === 'error') { console.error(`Error: ${msg.message}`); } return; } // After connected, frames are relayed transparently console.log('Received:', raw.toString()); }); ``` ### Supported sub-protocols All sub-protocols are relayed transparently — no special configuration needed: * Raw WebSocket (text/binary) * Socket.IO (engine.io transport) * SignalR (JSON + `\x1e` delimiter) * Centrifugo (JSON-RPC) * GraphQL-WS subscriptions *** ## AMQP consumer (SSE) Stream messages from a RabbitMQ broker through the agent network, delivered as Server-Sent Events. The agent connects to the broker, creates a temporary auto-delete queue, binds it to the specified exchange, and streams messages back. ### Endpoint ``` POST https://scraping-api.55-tech.com/amqp ``` ### Request body ```json theme={null} { "apiKey": "YOUR_API_KEY", "host": "broker.example.com", "port": 5671, "virtual_host": "/", "username": "user", "password": "pass", "exchange": "my_exchange", "routing_key": "#", "queue_prefix": "scrape", "ssl": true, "heartbeat": 60, "geo": "DE", "agent": "de1" } ``` Authentication can also be provided via the `X-API-Key` header instead of the `apiKey` body field. | Field | Type | Required | Default | Description | | -------------- | ------ | -------- | -------- | --------------------------------------------------------------- | | `apiKey` | string | Yes | — | Your API key (or use `X-API-Key` header) | | `host` | string | Yes | — | AMQP broker hostname | | `port` | int | No | `5672` | Broker port (use `5671` for SSL) | | `virtual_host` | string | No | `/` | AMQP virtual host | | `username` | string | No | `""` | Broker username | | `password` | string | No | `""` | Broker password | | `exchange` | string | No | `""` | Exchange to bind to | | `routing_key` | string | No | `#` | Routing key pattern (`#` = all messages) | | `queue_prefix` | string | No | `scrape` | Prefix for the auto-delete queue name (e.g., `scrape_de1_4821`) | | `ssl` | bool | No | `false` | Use AMQPS (TLS) connection | | `heartbeat` | int | No | `60` | AMQP heartbeat interval in seconds | | `geo` | string | No | — | Country filter for agent selection | | `agent` | string | No | — | Pin to specific agent by slug | ### SSE events **`connected`** — Agent connected to broker, queue created and bound: ``` event: connected data: {"node_id": "scraping-de1", "queue_name": "scrape_de1_4821"} ``` **`message`** — Message received from queue: ``` event: message data: {"body": "{\"odds\": 1.95}", "routing_key": "19454.match.123", "exchange": "19454.all", "delivery_tag": 1, "content_type": "application/json"} ``` **`error`** — Connection or consumption error (stream ends): ``` event: error data: {"message": "agent error: connection refused", "code": 1011} ``` ### Example: curl ```bash theme={null} curl -N -X POST \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{ "host": "broker.example.com", "port": 5671, "ssl": true, "username": "user", "password": "pass", "exchange": "my_exchange", "routing_key": "#" }' \ https://scraping-api.55-tech.com/amqp ``` ### Example: Python ```python theme={null} import requests import json resp = requests.post( "https://scraping-api.55-tech.com/amqp", headers={"X-API-Key": "YOUR_API_KEY"}, json={ "host": "broker.example.com", "port": 5671, "ssl": True, "username": "user", "password": "pass", "exchange": "my_exchange", "routing_key": "#", }, stream=True, ) for line in resp.iter_lines(): if line.startswith(b"data: "): event = json.loads(line[6:]) print(event) ``` # WebSocket Protocol Reference Source: https://docs.55-tech.com/scraping-api/ws-reference Complete reference for the Scraping API WebSocket relay protocol. Connect message schema, response types, error codes, and interactive testing. ## Protocol overview The WebSocket relay at `wss://scraping-api.55-tech.com/ws` provides a bidirectional proxy. You connect to the gateway, send a JSON connect message, and the gateway relays all frames between you and the target through a geo-distributed agent. ``` Client ←→ Gateway (wss://.../ws) ←→ Agent ←→ Target WebSocket ``` ## Interactive playground The `/ws/docs` endpoint returns the protocol schema: ```bash theme={null} curl https://scraping-api.55-tech.com/ws/docs ``` ```json theme={null} { "type": "connected", "status": 101, "node_id": "scraping-de1", "message": "This is a documentation endpoint. Connect via WebSocket at wss://scraping-api.55-tech.com/ws" } ``` For interactive WebSocket testing, use `wscat`: ```bash theme={null} npm install -g wscat wscat -c wss://scraping-api.55-tech.com/ws ``` Then paste: ```json theme={null} {"apiKey":"YOUR_API_KEY","url":"wss://echo.websocket.events"} ``` ## Connect message (client → gateway) Must be sent as JSON text within **10 seconds** of connecting. ```json theme={null} { "apiKey": "YOUR_API_KEY", "url": "wss://target.example.com/stream", "headers": { "Authorization": "Bearer token", "Origin": "https://target.example.com" }, "cookies": { "session": "abc123" }, "geo": "US,DE", "agent": "de1", "idle_timeout": 0, "proxy": "socks5://user:pass@host:1080" } ``` ### Fields | Field | Type | Required | Default | Description | | -------------- | ------ | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `apiKey` | string | **Yes** | — | API key for authentication. `key` is also accepted. | | `url` | string | **Yes** | — | Target WebSocket URL (`wss://...` or `ws://...`) | | `headers` | object | No | `{}` | Custom headers sent with the WS upgrade request to the target. Useful for `Authorization`, `Origin`, `Cookie` headers that the target expects. | | `cookies` | object | No | `{}` | Cookies sent with the WS upgrade request. Merged into the `Cookie` header. | | `geo` | string | No | — | Restrict agent selection to specific countries. Comma-separated ISO codes (e.g. `US,DE,AT`). | | `agent` | string | No | — | Pin to specific agent(s) by slug (e.g. `de1`) or comma-separated for random pick (`de1,at5,us3`). | | `idle_timeout` | float | No | `0` | If > 0, disconnect after this many seconds without receiving a message from the target. `0` = no idle timeout. | | `subprotocols` | array | No | `[]` | WS sub-protocol negotiation. Sent as `Sec-WebSocket-Protocol` header during upgrade. E.g. `["centrifuge-protobuf"]` | | `proxy` | string | No | `""` | Proxy URL for the WebSocket connection (`http://` or `socks5://`). Useful for routing through residential or ISP proxies. | ## Response messages (gateway → client) ### Connected Sent once after the agent successfully connects to the target WebSocket. ```json theme={null} { "type": "connected", "status": 101, "node_id": "scraping-de1" } ``` | Field | Type | Description | | --------- | ------ | ---------------------------------------------------- | | `type` | string | Always `"connected"` | | `status` | int | HTTP status code of the WS upgrade. `101` = success. | | `node_id` | string | Proxy node handling the relay (e.g. `scraping-de1`) | ### Error Sent when connection fails or an error occurs. The WebSocket is closed after this message. ```json theme={null} { "type": "error", "message": "Invalid or missing API key" } ``` | Field | Type | Description | | --------- | ------ | -------------------------------- | | `type` | string | Always `"error"` | | `message` | string | Human-readable error description | ### Relayed frames After the `connected` message, all subsequent frames are raw relay — no JSON wrapping. Text frames stay text, binary frames stay binary. ## Close codes | Code | Meaning | | ------ | -------------------------------------------------------------------- | | `1000` | Normal closure (you or target closed) | | `1008` | Policy violation — invalid API key, rate limited, or connect timeout | | `1011` | Internal error — agent failure or target unreachable | ## Error scenarios | Scenario | What happens | | ----------------------------- | -------------------------------------------------------------- | | No connect message within 10s | Gateway closes with code `1008` | | Invalid API key | Gateway sends error JSON, closes with `1008` | | Rate limit exceeded | Gateway sends error JSON, closes with `1008` | | Target connection refused | Gateway sends error JSON, closes with `1011` | | No agent available | Gateway sends error JSON, closes with `1011` | | Target closes connection | Gateway forwards the close frame with target's code and reason | | Client closes connection | Gateway forwards close to target, cleans up | | Agent crashes mid-relay | Gateway closes with `1011` | ## Frame flow diagram ```mermaid theme={null} sequenceDiagram participant C as Client participant G as Gateway participant A as Agent participant T as Target C->>G: WS connect G-->>C: accept C->>G: {"apiKey", "url"} G->>A: pick agent A->>T: WS upgrade T-->>A: 101 Switching A-->>G: connected G-->>C: {"type":"connected"} rect rgb(240, 248, 255) Note over C,T: Bidirectional relay C->>G: text/binary frame G->>A: relay A->>T: relay T-->>A: text/binary frame A-->>G: relay G-->>C: relay end C->>G: close G->>A: close A->>T: close T-->>A: close A-->>G: close G-->>C: close ``` ## Rate limiting WebSocket connections consume **1 token** from your rate limit bucket on connect (when the connect message is validated). Subsequent frames do not consume tokens. If your bucket is empty, the gateway sends an error and closes with `1008`. ## Testing with wscat ```bash theme={null} # Install npm install -g wscat # Connect wscat -c wss://scraping-api.55-tech.com/ws # Paste connect message: {"apiKey":"YOUR_KEY","url":"wss://echo.websocket.events"} # After "connected" response, type messages to relay: hello world # The echo server will send back your message ``` ## Testing with Python ```python theme={null} import asyncio import json import websockets async def test(): async with websockets.connect("wss://scraping-api.55-tech.com/ws") as ws: await ws.send(json.dumps({ "apiKey": "YOUR_API_KEY", "url": "wss://echo.websocket.events", })) resp = json.loads(await ws.recv()) print(f"Status: {resp}") if resp["type"] == "connected": await ws.send("test message") reply = await ws.recv() print(f"Echo: {reply}") asyncio.run(test()) ``` # AMQP consumer relay (SSE) Source: https://docs.55-tech.com/api-reference/amqp/amqp-consumer-relay-sse /zh/scraping-api/openapi.json post /amqp Connect to an AMQP/RabbitMQ broker through an agent and stream messages as Server-Sent Events. The agent creates a temporary auto-delete queue, binds it to the specified exchange, and streams messages back as SSE events (connected, message, error). # Get open positions Source: https://docs.55-tech.com/api-reference/analytics/get-open-positions /zh/abp-api/openapi.json get /positions Retrieve aggregated open (unsettled) positions grouped by bookmaker, account, or userRef. Returns the number of open bets, total stake, and average price for each group. Only includes bets with `betStatus` in (PLACED, CONFIRMED) and `settlementStatus` = UNSETTLED. **Group by options:** - `bookmaker` (default) — Group by bookmaker - `account` — Group by bookmaker account - `userRef` — Group by user reference **Filters:** - `bookmaker` — Filter to a specific bookmaker - `userRef` — Filter to a specific user reference - `account` — Filter to a specific account # Get profit and loss Source: https://docs.55-tech.com/api-reference/analytics/get-profit-and-loss /zh/abp-api/openapi.json get /pnl Retrieve aggregated PnL (profit and loss) grouped by bookmaker, account, or userRef. Only includes settled bets (`settlementStatus` NOT in UNSETTLED). **Group by options:** - `bookmaker` (default) — Group by bookmaker - `account` — Group by bookmaker account - `userRef` — Group by user reference **Filters:** - `bookmaker` — Filter to a specific bookmaker - `userRef` — Filter to a specific user reference - `account` — Filter to a specific account # Get a single bet Source: https://docs.55-tech.com/api-reference/bets/get-a-single-bet /zh/abp-api/openapi.json get /bets/{bet_id} Retrieve a single bet by its ID. The bet must belong to the authenticated client. # Get bets Source: https://docs.55-tech.com/api-reference/bets/get-bets /zh/abp-api/openapi.json get /bets Retrieve bets by various filters with keyset pagination. At least one filter must be provided. **Filters (OR logic):** - `betIds` — Comma-separated list of bet IDs - `orderIds` — Comma-separated list of order IDs - `userRef` — User reference string **Pagination:** - Results are ordered by betId descending (newest first) - Use `afterBetId` from `nextCursor` in the response to fetch the next page - `hasMore` indicates if more results are available **Bet Status Values:** PENDING, PLACED, CONFIRMED, REJECTED, CANCELLED, FAILED, VOID **Settlement Status Values:** UNSETTLED, WON, LOST, VOID, HALF_WON, HALF_LOST, PUSH, CASHOUT # List all bookmakers Source: https://docs.55-tech.com/api-reference/bookmakers/list-all-bookmakers /zh/abp-api/openapi.json get /bookmakers Returns all supported bookmakers with their stake limits. Results are cached for 60 seconds. # Fetch with JavaScript rendering Source: https://docs.55-tech.com/api-reference/browser/fetch-with-javascript-rendering /zh/scraping-api/openapi.json get /browser Fetch a URL with full JavaScript rendering. Works just like /fetch — all parameters via headers. Use this instead of /fetch when the target requires JavaScript to load content. If X-Expect-Selector or X-Expect-Contains is set and the page doesn't match, the API retries on a different node. # Live browser session with event streaming (SSE) Source: https://docs.55-tech.com/api-reference/browser/live-browser-session-with-event-streaming-sse /zh/scraping-api/openapi.json get /browser/stream Open a browser, navigate to the target URL, and stream back events in real-time via Server-Sent Events. Only captures XHR/Fetch API responses and WebSocket frames — HTML documents and scripts are filtered out. No resources are blocked by default so pages load fully. Default wait strategy is networkidle. All data is gzip-compressed. The session runs until the client disconnects (24h safety cap). Set X-Timeout to limit the duration. # Activate tournament Source: https://docs.55-tech.com/api-reference/configuration/activate-tournament /zh/mm-api/openapi.json post /api/v1/tournaments/{tournament_id}/activate Start trading a tournament. # Deactivate tournament Source: https://docs.55-tech.com/api-reference/configuration/deactivate-tournament /zh/mm-api/openapi.json post /api/v1/tournaments/{tournament_id}/deactivate Stop trading a tournament. Cancels its open orders. # List market types Source: https://docs.55-tech.com/api-reference/configuration/list-market-types /zh/mm-api/openapi.json get /api/v1/sports/{sport_id}/market-types Tradeable market types for a sport with your per-client toggle status. # List tournaments Source: https://docs.55-tech.com/api-reference/configuration/list-tournaments /zh/mm-api/openapi.json get /api/v1/tournaments Your tournaments with activation status. # Re-enable all market types Source: https://docs.55-tech.com/api-reference/configuration/re-enable-all-market-types /zh/mm-api/openapi.json post /api/v1/client/market-allowlist/activate-all Reset all market type toggles to enabled. # Toggle market types Source: https://docs.55-tech.com/api-reference/configuration/toggle-market-types /zh/mm-api/openapi.json patch /api/v1/client/market-allowlist Enable or disable market types in bulk. # Debug agent selection Source: https://docs.55-tech.com/api-reference/debug/debug-agent-selection /zh/scraping-api/openapi.json get /debug/pick Preview which agent would be selected for a given URL without making the actual request. # Fetch a URL (DELETE) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-delete /zh/scraping-api/openapi.json delete /fetch Send a DELETE request to the target URL through the agent network. # Fetch a URL (GET) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-get /zh/scraping-api/openapi.json get /fetch Send a GET request to the target URL through the agent network. Pass the target URL via the X-Target-URL header. # Fetch a URL (PATCH) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-patch /zh/scraping-api/openapi.json patch /fetch Send a PATCH request to the target URL through the agent network. # Fetch a URL (POST) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-post /zh/scraping-api/openapi.json post /fetch Send a request to the target URL through the agent network. Pass the target URL via the X-Target-URL header. Optionally include a JSON body with method, headers, body, timeout, and allow_redirects fields. # Fetch a URL (PUT) Source: https://docs.55-tech.com/api-reference/fetch/fetch-a-url-put /zh/scraping-api/openapi.json put /fetch Send a PUT request to the target URL through the agent network. # Get all markets with odds types Source: https://docs.55-tech.com/api-reference/markets/get-all-markets-with-odds-types /zh/abp-api/openapi.json get /markets Returns all available markets and their outcome types. Optionally filter by sportId. Markets are sorted by sportId then marketId. # Agents by country Source: https://docs.55-tech.com/api-reference/network/agents-by-country /zh/scraping-api/openapi.json get /network/geo Agents grouped by country with slugs listed for each. # Domain health Source: https://docs.55-tech.com/api-reference/network/domain-health /zh/scraping-api/openapi.json get /network/health/{domain} Per-agent health state for a specific domain. Shows which agents are healthy, soft-blocked, or hard-blocked. # List all agents Source: https://docs.55-tech.com/api-reference/network/list-all-agents /zh/scraping-api/openapi.json get /network/agents Returns the full agent registry with slug, name, and country for each agent. # Network status Source: https://docs.55-tech.com/api-reference/network/network-status /zh/scraping-api/openapi.json get /network/status Summary of total nodes and nodes per country. # Bets summary Source: https://docs.55-tech.com/api-reference/orders-&-bets/bets-summary /zh/mm-api/openapi.json get /api/v1/bets/summary Aggregated hedge bet statistics. # Cancel all orders Source: https://docs.55-tech.com/api-reference/orders-&-bets/cancel-all-orders /zh/mm-api/openapi.json post /api/v1/orders/cancel-all Cancel every open order across all exchanges. # Cancel order Source: https://docs.55-tech.com/api-reference/orders-&-bets/cancel-order /zh/mm-api/openapi.json post /api/v1/orders/{order_id}/cancel Cancel a single order by ID. # List hedge bets Source: https://docs.55-tech.com/api-reference/orders-&-bets/list-hedge-bets /zh/mm-api/openapi.json get /api/v1/bets Paginated list of bets placed on bookmakers to hedge filled orders. # List open orders Source: https://docs.55-tech.com/api-reference/orders-&-bets/list-open-orders /zh/mm-api/openapi.json get /api/v1/orders/open All currently resting or partially-filled orders. # List orders Source: https://docs.55-tech.com/api-reference/orders-&-bets/list-orders /zh/mm-api/openapi.json get /api/v1/orders Paginated list of exchange orders. Filter by fixture, status, exchange, or date. # Orders summary Source: https://docs.55-tech.com/api-reference/orders-&-bets/orders-summary /zh/mm-api/openapi.json get /api/v1/orders/summary Aggregated order statistics. # Cancel all pending orders Source: https://docs.55-tech.com/api-reference/orders/cancel-all-pending-orders /zh/abp-api/openapi.json post /cancel-all-orders Cancel ALL pending and partially-placed orders for the authenticated client. **Behavior:** - Cancels orders in PENDING or PARTIALLY_FILLED status - Only cancels orders without already-placed bets - Orders with confirmed bets are returned in `notCancelled` - No request body required # Cancel specific orders Source: https://docs.55-tech.com/api-reference/orders/cancel-specific-orders /zh/abp-api/openapi.json post /cancel-orders Cancel one or more orders by filter. At least one filter must be provided. Multiple filters are combined with OR logic. **Filters:** - `orderIds` — List of numeric order IDs - `requestUuids` — List of request UUID strings - `userRef` — Cancel all orders matching this user reference **Behavior:** - Only orders in PENDING or PARTIALLY_FILLED status can be cancelled - Orders with confirmed bets cannot be fully cancelled - Cancelled orders are marked in the database and signaled to stop retry loops # List accounts Source: https://docs.55-tech.com/api-reference/performance/list-accounts /zh/mm-api/openapi.json get /api/v1/accounts Exchange account balances and status. Credentials are never returned. # List positions Source: https://docs.55-tech.com/api-reference/performance/list-positions /zh/mm-api/openapi.json get /api/v1/positions Hedged positions grouped by fixture. # P&L Source: https://docs.55-tech.com/api-reference/performance/p&l /zh/mm-api/openapi.json get /api/v1/pnl Profit & loss and turnover for your client. # Positions summary Source: https://docs.55-tech.com/api-reference/performance/positions-summary /zh/mm-api/openapi.json get /api/v1/positions/summary Aggregated position statistics. # Health check Source: https://docs.55-tech.com/api-reference/system/health-check /zh/scraping-api/openapi.json get /healthz Liveness probe. Returns {"ok": true}. # Pause trading Source: https://docs.55-tech.com/api-reference/trading-controls/pause-trading /zh/mm-api/openapi.json post /api/v1/trading/pause Stop posting new orders. Existing resting orders stay live. # Resume trading Source: https://docs.55-tech.com/api-reference/trading-controls/resume-trading /zh/mm-api/openapi.json post /api/v1/trading/resume Resume after a manual pause. # Start trading Source: https://docs.55-tech.com/api-reference/trading-controls/start-trading /zh/mm-api/openapi.json post /api/v1/trading/start Start the engine after a stop. # Stop trading Source: https://docs.55-tech.com/api-reference/trading-controls/stop-trading /zh/mm-api/openapi.json post /api/v1/trading/stop Stop the engine and cancel all open orders. # Trading status Source: https://docs.55-tech.com/api-reference/trading-controls/trading-status /zh/mm-api/openapi.json get /api/v1/trading/status Current engine state for your client. # Usage metrics Source: https://docs.55-tech.com/api-reference/usage/usage-metrics /zh/scraping-api/openapi.json get /usage Per-key usage metrics: request counts, success/fail rates, bytes transferred, protocol breakdown, and top domains.