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

# Safety

> Idempotent placement, heartbeats as a dead-man's switch, off-tick limits, the read-only default of the local MCP server, and what the API cannot do.

Automated trading fails in known ways: a retried request that places twice, a crashed process that leaves orders resting, a price rounded against you, an agent with more power than intended. The API and the tooling guard against each.

## Recommended defaults

The `magicmarkets` [CLI](/developers/tools/cli) (command line interface) applies these protections for you. Use it as your default, or copy its behaviour in your own code:

* `magicmarkets order place --request-uuid` makes placement idempotent, and `magicmarkets order tracked <uuid>` recovers an order after a timeout.
* `magicmarkets heartbeat run --timeout 60` keeps a heartbeat alive in the foreground. If the process dies, the heartbeat expires and your open orders close.
* `magicmarkets order place` asks for confirmation before it commits a stake.
* `magicmarkets mcp` starts read-only. `magicmarkets mcp --print-tools` shows which tools an agent can call.

## Idempotent placement

`POST /v2/orders/` accepts an optional `request_uuid`. Reusing one returns `409 order_already_created` with the existing `order_id`, so a retry after a timeout cannot double-place.

**Always send one from automated code.** Generate a fresh UUID per intended order, store it before you send, and reuse it on retry.

```python theme={"dark"}
import uuid, requests

req = str(uuid.uuid4())          # persist this before sending
body = {"betslip_id": betslip_id, "price": 2.0,
        "stake": ["USDT", 10.0], "duration": 5.0, "request_uuid": req}
r = requests.post(f"{API}/orders/", headers=HEADERS, json=body).json()
if r["status"] == "error" and r["code"] == "order_already_created":
    order_id = r["data"]["order_id"]   # the earlier attempt landed
```

`GET /v2/orders/tracked/{uuid}/` retrieves an order by its `request_uuid` for several days after placement. This is what makes crash recovery possible: on restart, look up every UUID you persisted.

## Heartbeats

A heartbeat is a dead-man's switch. Create one with a `timeout` between 10 and 300 seconds and refresh it on a timer. If the server does not hear from you before the timeout, it closes every open order on the account, across all sessions and API keys. Matched positions are unaffected.

```
create heartbeat (timeout T)
loop:
  refresh every T/3
  quote, place, close
on exit: close orders, delete heartbeat
```

Choosing `T`: 10 to 30 seconds gives tight protection but a slow tick risks a self-inflicted closure. 120 to 300 seconds tolerates hiccups but leaves open orders resting longer after a real crash. Refresh on a timer **independent of your trading loop**, so a stall in a slow REST call cannot expire the heartbeat.

Cancelling a heartbeat with `DELETE` disarms it and closes nothing. An expired heartbeat cannot be refreshed: its close-out has already started, so create a new one.

```bash theme={"dark"}
API=https://magicmarkets.com/v2; H="X-Api-Key: $MAGICMARKETS_API_KEY"
HB=$(curl -s -X POST "$API/heartbeats/" -H "$H" -H 'Content-Type: application/json' \
  -d '{"timeout": 60}' | jq -r .data.heartbeat_id)
curl -s -X POST "$API/heartbeats/$HB/refresh/" -H "$H"
curl -s -X DELETE "$API/heartbeats/$HB/" -H "$H"
```

The CLI runs one for you: `magicmarkets heartbeat run --timeout 60`.

## Your limit is honoured

An order fills only at your price or better. An off-tick limit moves to the next tick that is no worse for you: up on a normal betslip, whether the bet type starts with `for` or `against`, and down on a lay betslip. See [Prices and ticks](/developers/prices-and-ticks).

## The API cannot move funds

There is no endpoint to deposit, withdraw, change a withdrawal address, or manage API keys. A leaked key can trade your balance. It cannot take it out of the account. Revoke a leaked key on the website, then check your open orders.

## Read-only by default for agents

The MCP server built into the CLI registers 11 read-only tools by default. Trading tools appear only when you set `MAGICMARKETS_ALLOW_TRADING=1`. The hosted MCP server at `magicmarkets.com/mcp` has no read-only default: use a client that asks before each tool call. See [MCP server](/developers/tools/mcp).

## Close before you leave

`POST /v2/orders/close_all/` closes every open order on the account, or only those on one `sport`, or one `event_id` with its `sport`. It cannot be undone. Prefer enumerating open orders and using `close_many/`, up to 500 ids per request, so you can see exactly what will close. See [Bulk close](/developers/guides/bulk-close).

## Test first

The market-making and bulk-close examples in the [Claude skill repository](/developers/tools/claude-skill) are dry-run by default and print what they would do. Read that output before passing `--live`.


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