> ## Documentation Index
> Fetch the complete documentation index at: https://docs.magicmarkets.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create order

> Places a new order on an existing betslip.



## OpenAPI

````yaml /openapi/magicmarkets-v2.json post /v2/orders/
openapi: 3.1.0
info:
  title: MagicMarkets API
  version: 0.1.0
  description: >
    REST and WebSocket API for MagicMarkets. Place orders, stream real-time

    prices, and manage your positions programmatically.


    ## Authentication


    Every request must be authenticated with an **API key**. Pass it in the

    `X-Api-Key` header:


    ```

    X-Api-Key: <your-api-key>

    ```


    ### Getting an API key


    API keys are created and managed through the MagicMarkets website at

    [magicmarkets.com](https://magicmarkets.com/):


    1. Go to **Settings -> API**.

    2. Click **Add API Key** and give it a name.

    3. Copy the key value - it is shown **once at creation**, so store it
       somewhere safe immediately.

    You can also revoke or rename existing keys from the same page. There is

    no endpoint in this API to manage keys - all key management happens on

    the website.


    ### Using a key


    Send the key on every request:


    ```bash

    curl https://magicmarkets.com/v2/xrates/ \
      -H "X-Api-Key: $MAGIC_API_KEY"
    ```


    ## Quickstart


    This walkthrough goes from zero to a placed bet: stream prices over the

    WebSocket, read a `bet_type` off the feed, quote it as a betslip, place

    an order, and watch the order update on the same socket.


    ### Before you start


    You need an API key (see **Authentication** above) and Python 3.9+ with

    two libraries:


    ```bash

    pip install requests websockets

    ```


    Every snippet reads its configuration from the environment:


    ```bash

    export MAGIC_API_URL="https://<host>/v2"

    export MAGIC_WS_URL="wss://<host>/v2/stream"

    export MAGIC_API_KEY="<your-api-key>"

    ```


    Three concepts carry the whole flow:


    - **Two steps to a bet.** A *betslip* registers your interest in one
      selection (`POST /v2/betslips/`); its live quote then arrives as
      `["pmm", ...]` entries on the WebSocket. An *order* commits a stake
      against that quote (`POST /v2/orders/`). You always create the
      betslip first.
    - **`bet_type` comes from the feed.** Offers on the WebSocket carry the
      `bet_type` string ready to use - pass it to `POST /v2/betslips/`
      verbatim. You never need to construct or parse it (the grammar in
      **Sports & bet types** is reference material, not required reading).
    - **Stakes are USDT.** Wherever money appears it is a
      `["USDT", amount]` pair.

    ### Step 1 - Check your key


    Before opening the socket, prove the key works with a cheap REST call -

    a bad key fails the WebSocket upgrade with HTTP 401, so verify the key

    first:


    ```bash

    curl "$MAGIC_API_URL/xrates/" -H "X-Api-Key: $MAGIC_API_KEY"

    ```


    A `200` with `{"status": "ok", ...}` means you are good to go.


    ### Step 2 - Connect to the stream


    Connect with the key as a query parameter. Every frame the server sends

    is a batch envelope `{"ts": ..., "data": [...]}` - iterate `data[]` and

    dispatch on each entry's leading type tag (see the **Streaming API**

    endpoint for the full wire format):


    ```python

    import json, os

    from websockets.sync.client import connect


    ws =
    connect(f"{os.environ['MAGIC_WS_URL']}?api_key={os.environ['MAGIC_API_KEY']}")


    events, synced = [], False

    while not synced:
        frame = json.loads(ws.recv())
        for entry in frame["data"]:
            if entry[0] == "event":
                events.append(entry[1])   # {"sport": ..., "event_id": ..., ...}
            elif entry[0] == "sync":
                synced = True
    ```


    After `["sync", ...]` you hold the list of currently-priced events, e.g.:


    ```json

    ["event", {"event_type": "normal", "sport": "fb",
      "event_id": "2026-06-15,1001,2002", "competition_id": 1,
      "competition_name": "England Premier League",
      "competition_country": "XE", "home": "Arsenal", "away": "Chelsea",
      "event_name": "Arsenal vs. Chelsea", "ir_status": "pre_event",
      "start_time": "2026-06-15T15:00:00Z"}]
    ```


    ### Step 3 - Register for offers on an event


    Pick an event and register. The server replies with one `["offer", ...]`

    per bet type (the snapshot), then an ok `["response", ...]`:


    ```python

    event = events[0]

    ws.send(json.dumps(["register_event", event["sport"], event["event_id"]]))


    offers, registered = [], False

    while not registered:
        frame = json.loads(ws.recv())
        for entry in frame["data"]:
            if entry[0] == "offer":
                offers.append(entry[1])
            elif entry[0] == "response":
                if entry[1]["status"] == "ok":
                    registered = True
                else:
                    raise SystemExit(f"register_event failed: {entry[1]['code']}")
    ```


    (Registering an event that has not yet appeared in the sync stream is

    not an error - you just get an empty snapshot, and offers start flowing

    if the event becomes priced. Prefer event ids you saw in the sync

    stream.) From now on the full offer set is re-broadcast whenever this

    event's prices change.


    ### Step 4 - Read the bet_type off an offer


    Each offer is one priced selection. Everything the next step needs is

    already in it:


    ```json

    ["offer", {
      "sport": "fb",
      "event_id": "2026-06-15,1001,2002",
      "bet_type": "for,ah,h,1",
      "market_type": "ah",
      "in_running": false,
      "price_list": [
        {"effective": {"price": 2.0, "min": ["USDT", 5.0], "max": ["USDT", 150.0]}},
        {"effective": {"price": 1.99, "min": null, "max": ["USDT", 80.0]}}
      ]
    }]

    ```


    `price_list` is sorted by price descending; `min` is `null` when there

    is no minimum stake. Pick a priced offer - its `sport`, `event_id` and

    `bet_type` are everything the next step needs:


    ```python

    offer = next(o for o in offers if o["price_list"])

    ```


    ### Step 5 - Create a betslip


    Quote the selection by passing the offer's fields through verbatim:


    ```python

    import requests


    API = os.environ["MAGIC_API_URL"]

    HEADERS = {"X-Api-Key": os.environ["MAGIC_API_KEY"]}


    betslip = requests.post(f"{API}/betslips/", headers=HEADERS, json={
        "sport": offer["sport"],
        "event_id": offer["event_id"],
        "bet_type": offer["bet_type"],
        "betslip_type": "normal",
    }).json()["data"]

    ```


    The response registers the betslip - note the `betslip_id` and the

    `expiry_ts` (betslips are short-lived; re-create one that expires).

    Do not expect prices in this response: your private quote arrives on

    the WebSocket you already hold open, as `["pmm", ...]` entries carrying

    your `betslip_id` - typically within a couple of seconds, refreshed

    while the betslip stays open:


    ```python

    quote = None

    while quote is None:
        frame = json.loads(ws.recv())
        for entry in frame["data"]:
            if entry[0] == "pmm" and entry[1]["betslip_id"] == betslip["betslip_id"]:
                if entry[1]["price_list"]:
                    quote = entry[1]
    ```


    ```json

    ["pmm", {
      "betslip_id": "65b6ff7da480479b9dda1c7ff765c434",
      "sport": "fb",
      "event_id": "2026-06-15,1001,2002",
      "bet_type": "for,ah,h,1",
      "status": {"code": "success"},
      "price_list": [
        {"effective": {"price": 2.0, "min": ["USDT", 5.0], "max": ["USDT", 150.0]}}
      ],
      "total": ["USDT", 150.0]
    }]

    ```


    The `price_list` uses the same format as offers - prices descending,

    stakes in USDT. A pmm whose `price_list` stays empty means there is no

    liquidity for this selection right now - pick another offer and

    re-quote.


    If you are not holding the stream open, poll

    `GET /v2/betslips/{betslip_id}/` until `price_list` populates.


    ### Step 6 - Place an order


    Commit a stake at one of the quoted prices. Three fields are

    required (`betslip_id`, `price`, `stake`); `duration` is the order's

    lifetime in **seconds** (optional, default 15):


    ```python

    best = quote["price_list"][0]["effective"]


    order = requests.post(f"{API}/orders/", headers=HEADERS, json={
        "betslip_id": betslip["betslip_id"],
        "price": best["price"],
        "stake": ["USDT", 10.0],
        "duration": 5.0,
    }).json()["data"]

    ```


    The response confirms acceptance - `order_id` and `status: "open"`

    (abridged; `bets` appear on the subsequent updates as the order fills):


    ```json

    {
      "order_id": 5001,
      "status": "open",
      "bet_type": "for,ah,h,1",
      "sport": "fb",
      "want_price": 2.0,
      "want_stake": ["USDT", 10.0],
      "closed": false,
      "price": null,
      "stake": null,
      "profit_loss": null
    }

    ```


    ### Step 7 - Watch the order on the WebSocket


    Order updates arrive on the socket you already hold open, as

    `["order", ...]` and `["bet", ...]` entries in the same envelopes as

    offers. An order moves `open -> pending -> done | failed`; when it

    closes, `price`, `stake` and `profit_loss` are filled in:


    ```python

    while True:
        frame = json.loads(ws.recv())
        for entry in frame["data"]:
            if entry[0] == "order" and entry[1]["order_id"] == order["order_id"]:
                o = entry[1]
                print("order:", o["status"], o.get("close_reason"))
                if o["status"] in ("done", "failed"):
                    raise SystemExit(0)
    ```


    Note that a `done` order is filled, not settled - the final

    `profit_loss` lands after the event finishes. To re-check an order

    later (e.g. after a restart), `GET /v2/orders/{order_id}/` returns the

    same object on demand.


    ### Handling errors


    Always check `status` before reading `data`. REST errors use the

    envelope from the **Errors** section below - `validation_error`

    bodies name the offending field (e.g. `bet_type: ["invalid_bet_type"]`),

    and on `429` honour `data.retry_after` (the limits are listed under

    **Rate limiting**). WebSocket errors arrive

    in-band as `["response", {"status": "error", ...}]` entries; the reason
    table and the

    silent-close cases are documented on the **Streaming API** endpoint.


    ### Complete example


    The whole flow, runnable as-is with the three environment variables set:


    ```python

    import json, os

    import requests

    from websockets.sync.client import connect


    API = os.environ["MAGIC_API_URL"]

    KEY = os.environ["MAGIC_API_KEY"]

    HEADERS = {"X-Api-Key": KEY}


    # 1. verify the key via REST first - the socket closes silently on a bad key

    requests.get(f"{API}/xrates/", headers=HEADERS).raise_for_status()


    with connect(f"{os.environ['MAGIC_WS_URL']}?api_key={KEY}") as ws:
        # 2. initial sync: collect events until ["sync", ...]
        events, synced = [], False
        while not synced:
            frame = json.loads(ws.recv())
            for entry in frame["data"]:
                if entry[0] == "event":
                    events.append(entry[1])
                elif entry[0] == "sync":
                    synced = True
        if not events:
            raise SystemExit("no priced events right now")

        # 3+4. register events until one returns a priced offer
        offer = None
        for event in events:
            ws.send(json.dumps(["register_event", event["sport"], event["event_id"]]))
            offers, registered = [], False
            while not registered:
                frame = json.loads(ws.recv())
                for entry in frame["data"]:
                    if entry[0] == "offer":
                        offers.append(entry[1])
                    elif entry[0] == "response":
                        if entry[1]["status"] == "ok":
                            registered = True
                        else:
                            raise SystemExit(f"register_event failed: {entry[1]['code']}")
            offer = next((o for o in offers if o["price_list"]), None)
            if offer:
                print("picked", offer["bet_type"], "on", event["event_name"])
                break
            ws.send(json.dumps(["unregister_event", event["sport"], event["event_id"]]))
        if offer is None:
            raise SystemExit("no priced offers right now")

        # 5. register a betslip (bet_type verbatim), then read the quote off the socket
        resp = requests.post(f"{API}/betslips/", headers=HEADERS, json={
            "sport": offer["sport"],
            "event_id": offer["event_id"],
            "bet_type": offer["bet_type"],
            "betslip_type": "normal",
        }).json()
        if resp["status"] != "ok":
            raise SystemExit(f"betslip rejected: {resp}")
        betslip = resp["data"]

        quote = None
        while quote is None:
            frame = json.loads(ws.recv())
            for entry in frame["data"]:
                if entry[0] == "pmm" and entry[1]["betslip_id"] == betslip["betslip_id"]:
                    if entry[1]["price_list"]:
                        quote = entry[1]
        print("quoted", quote["price_list"][0])

        # 6. place an order at the quote's best price
        best = quote["price_list"][0]["effective"]
        resp = requests.post(f"{API}/orders/", headers=HEADERS, json={
            "betslip_id": betslip["betslip_id"],
            "price": best["price"],
            "stake": ["USDT", 10.0],
            "duration": 5.0,
        }).json()
        if resp["status"] != "ok":
            raise SystemExit(f"order rejected: {resp}")
        order = resp["data"]
        print("placed order", order["order_id"], "status:", order["status"])

        # 7. watch it on the same socket
        while True:
            frame = json.loads(ws.recv())
            for entry in frame["data"]:
                if entry[0] == "order" and entry[1]["order_id"] == order["order_id"]:
                    o = entry[1]
                    print("order:", o["status"], o.get("close_reason"))
                    if o["status"] in ("done", "failed"):
                        raise SystemExit(0)
    ```


    From here: the **Streaming API** endpoint documents every message on the

    socket, **Betslips** and **Orders** cover the remaining endpoints

    (including parlays and lay orders), and **Heartbeats** provides a

    dead-man's switch for automated trading.


    ## Response format


    All JSON responses share a common envelope:


    ```json

    { "status": "ok", "data": ... }

    ```


    On error:


    ```json

    { "status": "error", "code": "<code>", "data": <details> }

    ```


    `data` may be `null`, a string, or an object - depending on the code. See

    the **Errors** section below for the most common shapes.


    ## Errors


    Error responses always include `status: "error"` and a stable string

    `code` that clients should branch on. The HTTP status conveys the

    category; `code` narrows it down.


    ### Common codes


    | HTTP | `code` | When |

    |------|--------|------|

    | 400 | `validation_error` | Request body or query failed validation.
    `data.validation_errors` is a `{ field: [reason, ...] }` map; cross-field
    problems land in `non_field_errors`. A rejected list field is index-keyed
    instead: `{ field: { "0": [reason, ...] } }`. |

    | 400 | `order_closed` | `POST /v2/orders/{id}/close/` on an order that
    exists but is already closed or settled. Distinct from `not_found`, which
    means the order id is unknown. |

    | 401 | `auth_error` | API key missing, malformed, or rejected. Also used by
    login flows for 2FA / inactive / locked accounts. |

    | 403 | `forbidden` | The key is valid but is not allowed to perform this
    action. |

    | 403 | `invalid_customer` | Market data: the feed has no record of your
    account. Contact support; a retry gets the same answer. |

    | 404 | `not_found` | The addressed resource (betslip, order, heartbeat,
    token, session) does not exist or is not visible to this key. |

    | 409 | `order_already_created` | A `request_uuid` from `POST /v2/orders/`
    was reused. `data` includes the existing `order_id`. |

    | 409 | `limit_reached` | A per-customer cap was hit (e.g. maximum API
    tokens). `data.detail` describes the cap. |

    | 429 | `throttled` | Rate limit hit - see **Rate limiting** below. `data`
    is `{ "message": "...", "retry_after": <seconds> }` and a `Retry-After`
    header is sent. |

    | 500 | `server_error` | Unexpected internal error. `data` is `["An error
    has occurred, token:", "<token>"]`; quote the token if you contact support.
    |

    | 503 | `warming` or no envelope | `warming` (market data): the feed is
    loading its market data or customer records, which clears by itself, so
    retry with backoff. No envelope (any endpoint): an upstream service could
    not be reached, and the body is `{ "detail": "Service unavailable" }`; retry
    with backoff. |


    For `validation_error` responses, branch on the inner reason - the

    keys of `data.validation_errors` (`non_field_errors` for cross-field

    rejections, otherwise the offending field name). Each endpoint

    documents the concrete codes it emits next to its 400 response.


    ## Rate limiting


    Limits are per **account**: all of an account's API keys draw from

    one budget. Each limit is a token bucket that refills continuously

    at its own rate, so capacity returns steadily rather than in one

    reset, and an account that has been idle can spend a full bucket in

    a single burst.


    | Applies to | Limit |

    |------------|-------|

    | All endpoints | 100 requests/second burst, 1200 requests/minute sustained
    |

    | `POST /v2/betslips/` | 10 requests/second, and 2000 betslips/day |

    | `POST /v2/orders/` | 5 requests/second |

    | `GET /v2/offers/` | 4 requests in flight at once |


    The placement rows are dedicated budgets: a `POST /v2/betslips/` or

    `POST /v2/orders/` call counts only against its own limit, not the

    general one. Polling market data therefore never throttles order

    placement. Every other endpoint, market data included, shares the

    general budget, so a busy poller leaves less of it for other GETs.


    Budgets are keyed on the account, not the source address, so

    spreading an integration across several hosts does not multiply it.


    A rejected request returns a `429` `throttled` error with the wait

    in `Retry-After` and `data.retry_after` (integer seconds).


    The `GET /v2/offers/` row counts open requests: an account can have 4

    of them in flight at once, because each one prices every event it names.

    A call can name up to 20 events at the same cost against the cap, so one

    call naming 20 events beats 20 calls naming one. A request over the cap

    gets the `throttled` envelope. Retry when one of your open requests

    returns; `retry_after` is a fixed 1 second, because the server cannot

    know when your open requests will finish.


    The WebSocket stream has no message-rate limit; its connection-level

    limits (registered-event cap, slow-reader disconnect) are documented

    on the **Streaming API** endpoint.


    Limits can be adjusted per account - contact support if your

    integration needs more headroom.


    ## Currencies


    Stake fields are `[currency, amount]` tuples. Stakes in responses are

    always returned in **USDT**:


    ```json

    ["USDT", 115.38]

    ```


    ## Price ticks


    Single-market prices lie on a fixed tick schedule. The tick (the smallest

    step between two valid prices) widens as the decimal price grows:


    | Price (cents) | Decimal price | Tick |

    |---------------|---------------|------|

    | 50c - 99c     | 1.01 - 2      | 0.01 |

    | 33.3c - 50c   | 2 - 3         | 0.02 |

    | 25c - 33.3c   | 3 - 4         | 0.05 |

    | 16.7c - 25c   | 4 - 6         | 0.10 |

    | 10c - 16.7c   | 6 - 10        | 0.20 |

    | 5c - 10c      | 10 - 20       | 0.50 |

    | 3.3c - 5c     | 20 - 30       | 1    |

    | 2c - 3.3c     | 30 - 50       | 2    |

    | 1c - 2c       | 50 - 100      | 5    |

    | 0.1c - 1c     | 100 - 1000    | 10   |


    Cents are the implied probability of a price: `cents = 100 / decimal

    price`. The band boundaries are exact in decimal price; the cents labels

    are only approximate, so always match a price to its band by the decimal

    value.


    ### Order prices


    The price you submit on an order is a limit. A bet is taken only at your

    price or better: better means higher for a back (`for`) order and lower for

    a lay (`against`) one.


    An off-tick limit moves to the first tick that honours it, never to one

    that does not. The market carries only ticks, so that tick is the cheapest

    price that satisfies the limit you named. A back limit of 7.15 becomes 7.20

    and is not filled at 7.00. The moved price is the one the order runs with,

    and the one the order response reports.


    ### Feed prices


    Every single-market price delivered on the stream (`price_list`, offers,

    pmms) is already on the schedule, so a price quoted straight from the feed

    is always a valid limit.


    ### Accumulator prices


    An accumulator price is the product of its legs' prices, so it lies on no

    tick schedule. Accumulator prices are quoted, and accepted as limits, at

    full precision, up to the ceiling of 1000 that every quoted price shares.


    ## Sports & bet types


    Every betslip, order and bet carries a `sport` and a `bet_type`. They

    are short, opaque-looking strings whose grammar is described here.


    ### Sport codes


    `sport` is a lowercase string. Current values:


    | Code | Sport |

    |------|-------|

    | `fb` | Football, full 90 minutes |

    | `fb_ht` | Football, first half only |

    | `fb_2h` | Football, second half only |

    | `fb_et` | Football, extra time only |

    | `fb_corn` | Football corners (90 min) |

    | `fb_corn_ht` | Football corners (1st half) |

    | `fb_book` | Football yellow cards (90 min) |

    | `fb_htft` | Football combined half-time / full-time result |

    | `basket` | Basketball, full match |

    | `basket_ht` | Basketball, first half |

    | `basket_2h` | Basketball, second half |

    | `basket_q1` | Basketball, 1st quarter |

    | `basket_q2` | Basketball, 2nd quarter |

    | `basket_q3` | Basketball, 3rd quarter |

    | `basket_q4` | Basketball, 4th quarter |

    | `tennis` | Tennis |

    | `tt` | Table tennis |

    | `ih` | Ice hockey |

    | `af` | American football |

    | `rl` | Rugby league |

    | `ru` | Rugby union |

    | `arf` | Australian rules football |

    | `hand` | Handball |

    | `volley` | Volleyball |

    | `baseball` | Baseball |

    | `cricket` | Cricket |

    | `darts` | Darts |

    | `snooker` | Snooker |

    | `boxing` | Boxing |

    | `mma` | Mixed martial arts |

    | `golf` | Golf |

    | `cycling` | Cycling |

    | `moto` | Motorsport |

    | `horse` | Horse racing |

    | `dog` | Greyhound racing |

    | `esports` | Esports |

    | `politics` | Political markets |

    | `specials` | Specials / novelty markets |


    On accumulator (parlay) orders and betslips, `sport` is the literal

    string `parlay` and the per-leg sport sits inside each `legs[]` entry.


    Treat the table above as informational, not as a closed enum - new

    sports are added over time. Do not hard-fail on unknown codes.


    ### Bet type grammar


    `bet_type` is a comma-separated string. The first token is the

    direction:


    - `for` - back the outcome (you win if it happens).

    - `against` - lay the outcome (you win if it doesn't).


    The remaining tokens identify the market and its parameters.

    **Handicaps always refer to the home team.**


    **Asian handicap lines are integers equal to 4 x the actual line.**

    This keeps the wire format integer-only across `0.25`-step lines:


    | Wire integer | Real line |

    |--------------|-----------|

    | `0`  | 0.0 |

    | `2`  | 0.5 |

    | `7`  | 1.75 |

    | `8`  | 2.0 |

    | `-4` | -1.0 |

    | `-21` | -5.25 |


    ### Markets


    Match result:


    | Bet type | Meaning |

    |----------|---------|

    | `for,h` / `for,d` / `for,a` | Home / Draw / Away win |

    | `for,sd` | Score draw (any non-0-0 draw) |

    | `for,win_90,h` | Home wins in 90 min (excluding extra time) |

    | `for,dnb,h` | Home win, void if draw (draw-no-bet) |

    | `for,hnb,a` | Away win, void if home wins (home-no-bet) |

    | `for,anb,h` | Home win, void if away wins (away-no-bet) |

    | `for,ml,h` | Moneyline - home wins, draw is void |

    | `for,dc,h,d` | Double chance: home or draw |

    | `for,uswin,h` | US-style home win (draw is half-stake split) |

    | `for,awdw,h` | Asian win/draw/win |

    | `for,ko,h` | Home team to kick off |

    | `for,qualify,h` | Home team to qualify |


    Goals (totals):


    | Bet type | Meaning |

    |----------|---------|

    | `for,over,2.5` / `for,under,2.5` | Over/under non-integer line |

    | `for,overeq,3` / `for,undereq,3` | Over/under integer line, inclusive |

    | `for,exact_total,3` | Exactly 3 goals |

    | `for,exact_total,3,inf` | 3 or more goals |

    | `for,gr,1,3` | Goal range 1-3 inclusive (use `inf` for infinity) |

    | `for,teamgr,h,0,2` | Home team scores 0-2 |

    | `for,odd` / `for,even` | Total goals odd / even |

    | `for,odd,h` / `for,even,a` | Per-team odd / even |


    Asian handicaps (lines as 4 x the actual line):


    | Bet type | Meaning |

    |----------|---------|

    | `for,ah,h,-4` | Asian handicap, home -1.0 |

    | `for,ahover,7` / `for,ahunder,7` | Asian over/under 1.75 goals |

    | `for,tahover,h,2` / `for,tahunder,a,2` | Team Asian over/under 0.5 goals |

    | `for,eh,h,1` | English handicap, home +1 |


    Correct score and margins:


    | Bet type | Meaning |

    |----------|---------|

    | `for,cs,2,1` | Correct score 2-1 |

    | `for,othercs,3,3` | Any score outside `home <= 3 AND away <= 3` |

    | `for,othercs,1,1,3,3` | Any score outside both ranges |

    | `for,wm,h,2,2` | Home wins by exactly 2 |

    | `for,wm,h,2,inf` | Home wins by 2+ |

    | `for,wmo,h,1,2.5` | Home wins by 1 + over 2.5 goals |

    | `for,awm,1` | Absolute margin 1 (either side) |

    | `for,wg,h,2` | Home wins and scores >= 2 |

    | `for,quatro,h,o,2.5` | Home wins AND over 2.5 goals |

    | `for,moou,h,over,2.5` | Match-result + over/under combo |

    | `for,mo_both_score,h,yes` | Home wins AND both teams score |

    | `for,aou,h,3` | "Any other unquoted" score, home, max draw at 3-3 |


    Score / clean sheet:


    | Bet type | Meaning |

    |----------|---------|

    | `for,score,both,yes` / `for,score,both,no` | Both teams (don't) score |

    | `for,score,either` / `for,score,neither` / `for,score,one` | Score
    patterns |

    | `for,score,h,yes` / `for,score,h,no` | Home (does not) score |

    | `for,clean,h` | Home clean sheet |

    | `for,clean,both` / `for,clean,either` / `for,clean,neither` /
    `for,clean,one` | Clean-sheet patterns |

    | `for,fg,no_goal` | No goals (first goal markets) |

    | `for,swm,no_goal` / `for,swm,sd` | Score-and-margin: no goal / score draw
    |


    ### Tennis


    Tennis bet types include a period and a void rule:


    ```

    for,tset,<period>,<void_rule>,<unit>[,<market>,<args>...]

    ```


    - `<period>` - `1`-`5` (a specific set) or `all` (whole match).

    - `<void_rule>` - `vwhole`, `vsetN`, `vgameN` - when the bet voids
      if a player retires.
    - `<unit>` - `set` or `game`, optionally followed by a market and args.


    Examples:


    - `for,tset,all,vset1,p1` - player 1 to win the match (voids unless
      set 1 completes).
    - `for,tset,1,vwhole,p1` - player 1 to win set 1.

    - `for,tset,all,vwhole,game,ahover,62` - total games in the match
      over 15.5.

    ### Time-period sports (other than tennis)


    Bets on a specific period of a match use one of these tokens:


    | Token | Meaning |

    |-------|---------|

    | `tp,<period>` | Generic period - `<period>` is `all`, `reg`, or `1`-`9` |

    | `tperiod,<n>` | Specific period (e.g. ice hockey, hand-ball) |

    | `thalf,<n>` | First or second half |

    | `tquarter,<n>` | Quarter (basketball, NFL) |

    | `tinnings,<n>` | Inning (baseball) - `<n>` is integer or `all` |

    | `tmap,<n>` | Map (esports) - `<n>` is `1`-`5` |


    The token is followed by an optional `sub,<subsport>` modifier

    (used for things like darts 180-counts) and then the regular

    market and its arguments:


    ```

    for,<period_token>[,sub,<subsport>],<market>[,<args>...]

    ```


    Examples:


    - `for,tp,all,ahunder,16` - total under 4.0 across all periods.

    - `for,thalf,1,ah,h,0` - Asian handicap, home 0.0, in the first half.

    - `for,tquarter,2,wdw,h` - home to win the second quarter.

    - `for,tmap,1,ahover,42` - esports, total kills on map 1 over 10.5.

    - `for,tp,all,sub,180,ahover,8` - darts, over 2.0 180s.


    The legacy aliases `tall`, `treg`, `tp1`, `tp2`, ... are no longer

    accepted; use the tokens above.


    ### Multirunner (outright) events


    For events with many runners (horse racing, golf, etc.):


    - `for,win,<team_id>` - runner to win outright.

    - `for,top,<n>,<team_id>` - runner to finish in the top `<n>`
      (e.g. `for,top,3,1042` means runner 1042 to place top-3).

    ### Validating a bet type


    Call `GET /v2/sports/{sport}/bet_types/{bet_type}/` with the

    candidate string. A 200 response includes a human-readable

    `bet_type_description` and the win/loss payoff grid; a 400

    (`invalid_bet_type`) means the string did not parse. This endpoint

    cannot validate multirunner bet types (`for,win,...`, `for,top,...`);

    they need an event context and always return the 400.


    ## Idempotency


    `POST /v2/orders/` accepts an optional `request_uuid`. Retrying the same

    request with the same UUID will not create a duplicate order, and the

    order can be retrieved by UUID from `GET /v2/orders/tracked/{uuid}/` for

    several days after placement (until the order is purged upstream).


    ## Machine-readable docs


    The canonical OpenAPI spec is served at:


    - `GET /v2/openapi.json` (parsed JSON)

    - `GET /v2/openapi.yaml` (raw YAML)


    Both URLs return the same schema rendered on this page.


    For LLMs and coding agents:


    - `GET /llms.txt` - index of machine-readable documentation
      ([llms.txt](https://llmstxt.org/) format)
    - `GET /docs.md` (alias `GET /llms-full.txt`) - this entire reference as a
      single Markdown document
    - `GET /docs` with an `Accept: text/markdown` header returns the Markdown
      reference instead of HTML

    All paths also work behind the `/magic-api` ingress prefix

    (e.g. `/magic-api/llms.txt`, `/magic-api/v2/openapi.yaml`).
servers:
  - url: https://magicmarkets.com
    description: Production
security:
  - ApiKeyHeader: []
tags:
  - name: Betslips
    description: >-
      Create, retrieve and close betslips. Quotes arrive asynchronously as a
      single `price_list` (stakes in USDT): poll `GET
      /v2/betslips/{betslip_id}/` or watch the stream.
  - name: Orders
    description: >-
      Place and retrieve orders. All stake fields are converted to USDT.
      Supports three order formats: normal (match sports), multirunner
      (outrights), and parlay (accumulators).
  - name: Account
    description: >-
      Account-level information: current balance, open stake, and smart credit,
      all in USDT.
  - name: Heartbeats
    description: >-
      Heartbeat timers that automatically close all open orders when they
      expire. Use these as a dead-man's switch for automated trading.
  - name: Reference
    description: Exchange rates and bet type information.
  - name: Market data
    description: >-
      Event and market discovery over REST. Entries have the same shape as the
      stream's `event` and `offer` entries. For live prices, use the streaming
      API: it pushes each change as it happens.
  - name: Streaming API
    description: >-
      Real-time WebSocket stream: event discovery, live prices with stakes in
      USDT (updated every 2 seconds), and account updates (betslips, quotes,
      orders, bets, balance) on a single socket. Runs as a separate service -
      connect directly to the stream's WebSocket server.
paths:
  /v2/orders/:
    post:
      tags:
        - Orders
      summary: Create order
      description: Places a new order on an existing betslip.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OrderCreateRequest'
            examples:
              normal:
                summary: Normal order
                value:
                  betslip_id: bs-single-001
                  price: 3.25
                  stake:
                    - USDT
                    - 100
                  duration: 5
                  exchange_mode: make_and_take
              with_options:
                summary: With optional fields
                value:
                  betslip_id: bs-single-001
                  price: 3.25
                  stake:
                    - USDT
                    - 100
                  duration: 10
                  exchange_mode: take_only
                  keep_open_ir: true
                  user_data: my-ref-001
                  accept_better_price: true
                  accept_partial_fill: true
              exclude_danger:
                summary: Order with dangerball liquidity excluded
                value:
                  betslip_id: bs-single-001
                  price: 3.25
                  stake:
                    - USDT
                    - 100
                  duration: 5
                  exclude_danger: true
      responses:
        '201':
          description: Order created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrderEnvelope'
              example:
                status: ok
                data:
                  order_id: 5001
                  order_type: normal
                  bet_type: for,h
                  bet_type_description: Home
                  sport: fb
                  want_price: 3.25
                  want_stake:
                    - USDT
                    - 115.38
                  ccy_rate: 1
                  placement_time: '2026-06-15T12:00:00+00:00'
                  expiry_time: '2026-06-15T12:05:00+00:00'
                  closed: false
                  close_reason: null
                  status: open
                  keep_open_ir: false
                  exchange_mode: make_and_take
                  price: null
                  stake: null
                  profit_loss: null
                  bet_bar_values: null
                  current_score: null
                  legs: null
                  event_info:
                    event_type: normal
                    event_id: 2026-06-15,1001,2002
                    event_name: Arsenal vs. Chelsea
                    home_id: 1001
                    home_team: Arsenal
                    away_id: 2002
                    away_team: Chelsea
                    competition_id: 1
                    competition_name: England Premier League
                    competition_country: XE
                    start_time: '2026-06-15T15:00:00+00:00'
                    date: '2026-06-15'
                    result: null
                  bets: []
                  user_data: null
        '400':
          description: Validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                status: error
                code: validation_error
                data:
                  validation_errors:
                    non_field_errors:
                      - event_stake_limit_reached
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '409':
          description: Idempotency conflict - `request_uuid` already used
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              example:
                status: error
                code: order_already_created
                data:
                  order_id: 5001
                  message: An order with this request_uuid already exists.
        '429':
          $ref: '#/components/responses/Error429'
        '500':
          $ref: '#/components/responses/Error500'
components:
  schemas:
    OrderCreateRequest:
      type: object
      required:
        - betslip_id
        - price
        - stake
      properties:
        betslip_id:
          type: string
        price:
          type: number
          description: >-
            Limit decimal price. A bet is taken only at this price or better,
            and an off-tick limit moves to the first tick that honours it; see
            "Price ticks".
        stake:
          $ref: '#/components/schemas/StakeTuple'
        duration:
          type: number
          description: Order duration in seconds (default 15)
        exchange_mode:
          type: string
          enum:
            - make_and_take
            - take_only
            - dark
          default: make_and_take
          description: >-
            How the order interacts with the exchange. `make_and_take`: fill
            against the best available liquidity first; any remaining stake is
            advertised on the exchange at your price, while the order keeps
            taking newly available liquidity. `take_only`: only consume
            available liquidity; remaining stake is never advertised and other
            orders cannot match against it. `dark`: like `make_and_take`, but
            the advertised remaining stake is hidden from other customers - they
            can still match it when their price crosses yours, but they cannot
            see your price until then. There is no post-only mode: no order type
            rests in the book without also taking crossing liquidity.
        keep_open_ir:
          type: boolean
          default: false
          description: Keep order open when event goes in-play
        user_data:
          type:
            - string
            - 'null'
          maxLength: 512
        request_uuid:
          type: string
          description: Idempotency key
        accept_partial_fill:
          type: boolean
          default: true
        accept_better_price:
          type: boolean
          default: true
        force_want_price:
          type: boolean
          default: false
        min_taker_want_stake:
          oneOf:
            - $ref: '#/components/schemas/StakeTuple'
            - type: 'null'
          description: >-
            `dark` orders only (rejected on other modes): the minimum total
            stake another order must request to be allowed to match yours at
            your price point. Cannot exceed the order's own stake. Set it
            non-zero to stop small probe orders from discovering your price.
        current_score:
          oneOf:
            - type: array
              prefixItems:
                - type: integer
                  description: Home score
                - type: integer
                  description: Away score
              items:
                type: integer
                minimum: 0
                maximum: 32767
              minItems: 2
              maxItems: 2
          description: >-
            Placement-time score assertion, `[home, away]`. When present, the
            order is rejected with `validation_error` / `non_field_errors:
            ["event_scores_dont_match"]` unless the value matches the live score
            the exchange holds for the event - use it to avoid placing on a
            price that has not reacted to a goal yet. Only meaningful for
            football: for sports without a goal-style running score, and while
            no score is known yet, the server assumes `[0, 0]` and rejects any
            other value. Omit the field to skip the check.
        exclude_danger:
          type: boolean
          default: false
          description: >-
            When true, only liquidity sources that do not hold bets in danger
            status are used. When false or omitted, all available liquidity
            sources are used.
        bookie_min_stakes:
          type: object
          description: >-
            Optional per-source minimum stakes, keyed by source. A source is
            only used if it can take at least its minimum. Values are
            `[currency, amount]` tuples.
          additionalProperties:
            $ref: '#/components/schemas/StakeTuple'
        placer_type:
          type:
            - string
            - 'null'
          description: Optional caller-supplied tag recorded against the order.
    OrderEnvelope:
      type: object
      properties:
        status:
          type: string
          enum:
            - ok
        data:
          $ref: '#/components/schemas/OrderResponse'
    ErrorEnvelope:
      type: object
      description: >-
        Standard error response. The shape of `data` varies by `code` - see the
        **Errors** section in the introduction and the named examples on each
        endpoint's error responses.
      required:
        - status
        - code
      properties:
        status:
          type: string
          enum:
            - error
        code:
          type: string
          description: >-
            Stable machine-readable error code (e.g. `validation_error`,
            `not_found`, `forbidden`).
          example: validation_error
        data:
          description: Variable. Null, string, or object - see examples.
    StakeTuple:
      type: array
      description: '[currency, amount] - e.g. ["USDT", 115.38]'
      prefixItems:
        - type: string
          description: Currency code (always USDT in responses)
        - type: number
          description: Amount
      items:
        type:
          - string
          - number
      minItems: 2
      maxItems: 2
    OrderResponse:
      type: object
      properties:
        order_id:
          type: integer
        order_type:
          type: string
          description: >-
            Order type. `normal`, `lay` and `parlay` are the types placed
            through this API; `brokerage`, `cashout` and `custom` can appear on
            orders created by other channels. Treat it as an open set.
          enum:
            - normal
            - lay
            - parlay
            - brokerage
            - cashout
            - custom
        bet_type:
          type: string
          description: Bet type string - see "Sports & bet types" in the introduction.
        bet_type_description:
          type: string
        sport:
          type: string
          description: >-
            Sport code, or `parlay` for accumulators - see "Sports & bet types"
            in the introduction.
        want_price:
          type: number
        want_stake:
          $ref: '#/components/schemas/StakeTuple'
        ccy_rate:
          type: number
          description: Exchange rate at order time
        placement_time:
          type: string
          format: date-time
        expiry_time:
          type: string
          format: date-time
        closed:
          type: boolean
        close_reason:
          type:
            - string
            - 'null'
          description: >-
            Reason the order closed, e.g. order_filled, timed_out; null while
            open
        event_info:
          oneOf:
            - $ref: '#/components/schemas/EventInfo'
            - type: 'null'
        bets:
          type: array
          items:
            $ref: '#/components/schemas/BetResponse'
        user_data:
          type:
            - string
            - 'null'
        status:
          type: string
          description: >-
            Order lifecycle status: open, pending, failed, partial_void,
            full_void, done, or reconciled
        keep_open_ir:
          type: boolean
        exchange_mode:
          type:
            - string
            - 'null'
          enum:
            - make_and_take
            - take_only
            - dark
            - null
          description: >-
            Exchange interaction mode as passed at creation; null on older
            orders
        price:
          type:
            - number
            - 'null'
          description: Achieved price (null while open)
        stake:
          oneOf:
            - $ref: '#/components/schemas/StakeTuple'
            - type: 'null'
          description: Aggregate stake across matched bets
        profit_loss:
          oneOf:
            - $ref: '#/components/schemas/StakeTuple'
            - type: 'null'
        bet_bar_values:
          type:
            - object
            - 'null'
        current_score:
          oneOf:
            - type: array
              prefixItems:
                - type: integer
                  description: Home score
                - type: integer
                  description: Away score
              items:
                type: integer
                minimum: 0
                maximum: 32767
              minItems: 2
              maxItems: 2
            - type: 'null'
          description: >-
            The in-running score the exchange held for the event when the order
            was placed, `[home, away]`; null for pre-match orders and for events
            without a running score.
        legs:
          oneOf:
            - $ref: '#/components/schemas/ParlayLegList'
            - type: 'null'
    EventInfo:
      type: object
      properties:
        event_type:
          type: string
          enum:
            - normal
            - multirunner
            - parlay
        event_id:
          type:
            - string
            - 'null'
          description: null for parlay orders
        event_name:
          type:
            - string
            - 'null'
        home_id:
          type:
            - integer
            - 'null'
          description: Normal events only
        home_team:
          type:
            - string
            - 'null'
        away_id:
          type:
            - integer
            - 'null'
          description: Normal events only
        away_team:
          type:
            - string
            - 'null'
        competition_id:
          type:
            - integer
            - 'null'
        competition_name:
          type:
            - string
            - 'null'
        competition_country:
          type:
            - string
            - 'null'
          description: ISO country code (e.g. XE for England)
        start_time:
          type:
            - string
            - 'null'
          format: date-time
        date:
          type:
            - string
            - 'null'
          format: date
        result:
          description: >-
            Match or race result. The shape depends on the sport - see the
            `EventResult*` schemas. Null while the result is unknown.
          oneOf:
            - $ref: '#/components/schemas/EventResultMatch'
            - $ref: '#/components/schemas/EventResultTennis'
            - $ref: '#/components/schemas/EventResultHockey'
            - $ref: '#/components/schemas/EventResultTableTennis'
            - $ref: '#/components/schemas/EventResultMultirunner'
            - type: 'null'
        teams:
          description: Runner list (multirunner events only)
          oneOf:
            - type: array
              items:
                type: object
                properties:
                  team_id:
                    type: integer
                  name:
                    type: string
            - type: 'null'
        end_time:
          type:
            - string
            - 'null'
          format: date-time
          description: Race end time (multirunner events only)
        leg_event_infos:
          type:
            - array
            - 'null'
          description: Sub-event info for each leg (parlay orders only)
          items:
            $ref: '#/components/schemas/EventInfo'
    BetResponse:
      type: object
      description: Individual bet within an order
      properties:
        bet_id:
          type: integer
        order_id:
          type: integer
        order_ccy_rate:
          type: number
        status:
          type: object
          description: Bet status
          properties:
            code:
              type: string
              description: e.g. matched, failed, settled
            reason:
              type: string
              description: >-
                Human-readable failure reason; present only when `code` is
                `failed`
            response_pmm:
              oneOf:
                - $ref: '#/components/schemas/PriceLevel'
                - type: 'null'
        sport:
          type: string
          description: Sport code - see "Sports & bet types" in the introduction.
        event_id:
          type:
            - string
            - 'null'
        bet_type:
          type: string
          description: Bet type string - see "Sports & bet types" in the introduction.
        ccy_rate:
          type: number
        want_price:
          type: number
        got_price:
          type:
            - number
            - 'null'
        want_stake:
          $ref: '#/components/schemas/StakeTuple'
        got_stake:
          $ref: '#/components/schemas/StakeTuple'
        profit_loss:
          oneOf:
            - $ref: '#/components/schemas/StakeTuple'
            - type: 'null'
        reconciled:
          type:
            - boolean
            - 'null'
        exchange_role:
          type:
            - string
            - 'null'
          enum:
            - maker
            - taker
            - null
    ParlayLegList:
      type: array
      items:
        $ref: '#/components/schemas/ParlayLeg'
    EventResultMatch:
      type: object
      description: >-
        Football and other two-half sports. `ht_*` are the half-time score and
        are omitted for single-period sports; `ft_*` are the full-time score.
      properties:
        ht_home:
          type:
            - integer
            - 'null'
        ht_away:
          type:
            - integer
            - 'null'
        ft_home:
          type:
            - integer
            - 'null'
        ft_away:
          type:
            - integer
            - 'null'
    EventResultTennis:
      type: object
      description: Tennis. `setN_pM` is player M's game count in set N.
      properties:
        set1_p1:
          type:
            - integer
            - 'null'
        set1_p2:
          type:
            - integer
            - 'null'
        set2_p1:
          type:
            - integer
            - 'null'
        set2_p2:
          type:
            - integer
            - 'null'
        set3_p1:
          type:
            - integer
            - 'null'
        set3_p2:
          type:
            - integer
            - 'null'
        set4_p1:
          type:
            - integer
            - 'null'
        set4_p2:
          type:
            - integer
            - 'null'
        set5_p1:
          type:
            - integer
            - 'null'
        set5_p2:
          type:
            - integer
            - 'null'
        who_retired:
          type:
            - integer
            - 'null'
          description: 1 or 2 if a player retired, else 0 or null
    EventResultHockey:
      type: object
      description: >-
        Ice hockey. `tpN_*` are the three period scores; `tall_*` the regulation
        total; `pen_*` the penalty-shootout score.
      properties:
        tp1_home:
          type:
            - integer
            - 'null'
        tp1_away:
          type:
            - integer
            - 'null'
        tp2_home:
          type:
            - integer
            - 'null'
        tp2_away:
          type:
            - integer
            - 'null'
        tp3_home:
          type:
            - integer
            - 'null'
        tp3_away:
          type:
            - integer
            - 'null'
        tall_home:
          type:
            - integer
            - 'null'
        tall_away:
          type:
            - integer
            - 'null'
        pen_home:
          type:
            - integer
            - 'null'
        pen_away:
          type:
            - integer
            - 'null'
    EventResultTableTennis:
      type: object
      description: Table tennis. `gameN_*` is the point score in game N (up to 7 games).
      properties:
        game1_home:
          type:
            - integer
            - 'null'
        game1_away:
          type:
            - integer
            - 'null'
        game2_home:
          type:
            - integer
            - 'null'
        game2_away:
          type:
            - integer
            - 'null'
        game3_home:
          type:
            - integer
            - 'null'
        game3_away:
          type:
            - integer
            - 'null'
        game4_home:
          type:
            - integer
            - 'null'
        game4_away:
          type:
            - integer
            - 'null'
        game5_home:
          type:
            - integer
            - 'null'
        game5_away:
          type:
            - integer
            - 'null'
        game6_home:
          type:
            - integer
            - 'null'
        game6_away:
          type:
            - integer
            - 'null'
        game7_home:
          type:
            - integer
            - 'null'
        game7_away:
          type:
            - integer
            - 'null'
    EventResultMultirunner:
      type: object
      description: Multirunner (outright) events.
      properties:
        runner_results:
          type: array
          description: Finishing positions per runner
          items:
            type: object
            properties:
              team_id:
                type: integer
              position:
                type: integer
                description: >-
                  1=first, 2=second, 0=unknown, -1=void, -2=non-runner,
                  -3=eliminated
        non_runner_count:
          type: integer
    PriceLevel:
      type: object
      properties:
        effective:
          type: object
          properties:
            price:
              type: number
              description: Decimal price
            min:
              oneOf:
                - $ref: '#/components/schemas/StakeTuple'
                - type: 'null'
              description: Minimum stake at this price level, or null
            max:
              oneOf:
                - $ref: '#/components/schemas/StakeTuple'
                - type: 'null'
              description: Maximum stake at this price level, or null
    ParlayLeg:
      type: object
      properties:
        id:
          type: integer
        sport:
          type: string
          description: Sport code - see "Sports & bet types" in the introduction.
        event_id:
          type: string
        bet_type:
          type: string
          description: Bet type string - see "Sports & bet types" in the introduction.
        live_score:
          oneOf:
            - $ref: '#/components/schemas/LiveScore'
            - type: 'null'
          description: In-running score this leg was selected against, or null
        bet_type_description:
          type: string
        price:
          type:
            - number
            - 'null'
        outcome:
          type:
            - string
            - 'null'
          enum:
            - w
            - l
            - v
            - v/w
            - l/v
            - l/w
            - unknown
            - null
          description: >-
            Leg settlement: `w` won, `l` lost, `v` void, `v/w` win-void, `l/v`
            void-loss, `l/w` loss-win (half outcomes), `unknown`, or null before
            settlement.
    LiveScore:
      type: object
      description: In-running score at a point in time, `{home, away}`.
      required:
        - home
        - away
      properties:
        home:
          type: integer
          minimum: 0
          maximum: 1024
          description: Home-team score
        away:
          type: integer
          minimum: 0
          maximum: 1024
          description: Away-team score
  responses:
    Error401:
      description: Authentication failed - API key missing, malformed, or rejected.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            status: error
            code: auth_error
            data:
              detail: Authentication credentials were not provided.
    Error403:
      description: Authorization failed - the key is valid but cannot perform this action.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            status: error
            code: forbidden
            data: null
    Error429:
      description: Rate limit hit. Honour the `Retry-After` header.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            status: error
            code: throttled
            data:
              message: Request was throttled. Expected available in 12 seconds.
              retry_after: 12
    Error500:
      description: >-
        Unexpected internal error. Quote `data[1]` (the token) when contacting
        support.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            status: error
            code: server_error
            data:
              - 'An error has occurred, token:'
              - ABC123abc456DEF789gh
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-Api-Key
      description: API key created from the magic-markets website

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.