> ## 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.

# Place an order

> From an API key to a placed order, in eight steps, with runnable Python.

This walks the full flow: verify a key, connect the stream, find an event, register for offers, quote a selection, place an order, and watch it fill.

## Before you start

You need an API key, a funded account, and Python 3.10 or later with two libraries:

```bash theme={"dark"}
pip install requests websockets
export MAGICMARKETS_API_KEY="<your-api-key>"
```

<Tip>
  The `magicmarkets` [CLI](/developers/tools/cli) (command line interface) runs this whole flow from a terminal and is the quickest way to start. Use this guide when you want to write the code yourself.
</Tip>

<Warning>
  This places a real order with real USDT. Use a small stake and a price that will not match if you only want to see the mechanics.
</Warning>

<Steps>
  <Step title="Check the key over REST">
    A bad key fails the WebSocket handshake with HTTP 401 and a short `auth_rejected` body. REST returns a JSON error with a named code, so check the key there first.

    ```python theme={"dark"}
    import os, json, time, uuid, requests
    from websockets.sync.client import connect

    API = "https://magicmarkets.com/v2"
    WS = "wss://magicmarkets.com/v2/stream"
    KEY = os.environ["MAGICMARKETS_API_KEY"]
    H = {"X-Api-Key": KEY}

    r = requests.get(f"{API}/balance/", headers=H).json()
    assert r["status"] == "ok", r
    print("balance:", r["data"])
    ```
  </Step>

  <Step title="Connect and take the initial sync">
    Collect events until the `sync` marker. Every frame is a batch envelope, so iterate `data[]`.

    ```python theme={"dark"}
    ws = connect(f"{WS}?api_key={KEY}")

    events, synced = [], False
    while not synced:
        for entry in json.loads(ws.recv())["data"]:
            if entry[0] == "event":
                events.append(entry[1])
            elif entry[0] == "sync":
                synced = True
    print(len(events), "priced events")
    ```
  </Step>

  <Step title="Register for offers on one event">
    Events arrive without prices. Register the one you want and offers start flowing.

    ```python theme={"dark"}
    ev = next(e for e in events if e["event_type"] == "normal")
    ws.send(json.dumps(["register_event", ev["sport"], ev["event_id"]]))
    ```
  </Step>

  <Step title="Read a bet type off an offer">
    Never construct a bet type by hand for your first order. Take one the feed has already given you, with liquidity on it.

    ```python theme={"dark"}
    offer = None
    while offer is None:
        for entry in json.loads(ws.recv())["data"]:
            if entry[0] == "offer" and entry[1]["event_id"] == ev["event_id"] and entry[1]["price_list"]:
                offer = entry[1]
                break

    sport, event_id, bet_type = offer["sport"], offer["event_id"], offer["bet_type"]
    best = offer["price_list"][0]["effective"]["price"]
    print(bet_type, "best", best)
    ```
  </Step>

  <Step title="Create a betslip">
    A betslip is the first step to place an order on one selection. Create it when you are ready to place. The response has no prices yet.

    ```python theme={"dark"}
    r = requests.post(f"{API}/betslips/", headers=H,
        json={"sport": sport, "event_id": event_id, "bet_type": bet_type}).json()
    assert r["status"] == "ok", r
    betslip_id = r["data"]["betslip_id"]
    ```
  </Step>

  <Step title="Wait for the quote">
    It arrives as a `pmm` message on the socket you already hold. Give up if the betslip closes first.

    ```python theme={"dark"}
    quote = None
    while quote is None:
        for entry in json.loads(ws.recv())["data"]:
            if entry[0] == "pmm" and entry[1]["betslip_id"] == betslip_id and entry[1]["price_list"]:
                quote = entry[1]["price_list"][0]["effective"]
            elif entry[0] == "betslip_closed" and entry[1]["betslip_id"] == betslip_id:
                raise SystemExit(f"betslip closed: {entry[1]['close_reason']}")
    print("quoted", quote["price"], "up to", quote["max"])
    ```
  </Step>

  <Step title="Place the order">
    Always send a `request_uuid`. A retry with the same UUID cannot create a second order.

    ```python theme={"dark"}
    r = requests.post(f"{API}/orders/", headers=H, json={
        "betslip_id": betslip_id,
        "price": quote["price"],
        "stake": ["USDT", 5.0],
        "duration": 5.0,
        "request_uuid": str(uuid.uuid4()),
    }).json()
    if r["status"] == "error" and r["code"] == "order_already_created":
        order_id = r["data"]["order_id"]          # the earlier attempt landed
    else:
        assert r["status"] == "ok", r
        order_id = r["data"]["order_id"]
    ```
  </Step>

  <Step title="Watch the order">
    Order and bet updates arrive on the same socket.

    ```python theme={"dark"}
    while True:
        for entry in json.loads(ws.recv())["data"]:
            if entry[0] == "order" and entry[1]["order_id"] == order_id:
                o = entry[1]
                print("order:", o["status"], o.get("close_reason"))
                if o["closed"]:
                    raise SystemExit(0)
    ```

    An order starts `open`, can pass through `pending`, and finishes `done` or `failed`, at which point `closed` is true. Later updates can show `reconciled`, `partial_void` or `full_void`. A `done` order is filled, not settled: `profit_loss` lands after the event finishes.
  </Step>
</Steps>

## What to add next

* **A heartbeat**, if the strategy runs unattended. See [Safety](/developers/safety).
* **Error handling.** Check `status` before reading `data`, and honour `retry_after` on a 429. See [Errors and rate limits](/developers/errors-and-rate-limits).
* **Reconnect logic.** Treat the stream as lossy. On reconnect you get a fresh sync, so rebuild from that. See [WebSocket stream](/developers/websocket-stream).

## The same flow with curl

Set `SPORT`, `EVENT` and `BET` from an offer first. The values below are examples.

```bash theme={"dark"}
API=https://magicmarkets.com/v2
H="X-Api-Key: $MAGICMARKETS_API_KEY"
SPORT=fb EVENT="2026-09-13,1001,2002" BET="for,ah,h,1"

BS=$(curl -s -X POST "$API/betslips/" -H "$H" -H 'Content-Type: application/json' \
  -d "{\"sport\":\"$SPORT\",\"event_id\":\"$EVENT\",\"bet_type\":\"$BET\"}" \
  | jq -r .data.betslip_id)

# quotes arrive on the stream; polling the betslip also works
curl -s "$API/betslips/$BS/" -H "$H" | jq '.data.price_list'

curl -s -X POST "$API/orders/" -H "$H" -H 'Content-Type: application/json' \
  -d "{\"betslip_id\":\"$BS\",\"price\":2.0,\"stake\":[\"USDT\",5.0],\"duration\":5.0,\"request_uuid\":\"$(uuidgen)\"}" | jq
```


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