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

# Cancelling orders

> Single-order cancel, batch cancel by id, cancel-all by market, and what happens under the hood.

## Cancel a single order

```http theme={null}
POST /api/orders/cancel
X-Api-Key: ps_live_<keyId>_<secret>   # needs `orders:write` scope
Content-Type: application/json

{ "orderId": "0x..." }
```

Response:

```json theme={null}
{ "orderId": "0x...", "status": "CANCELLED", "remainingQty": "42" }
```

If the order already reached a terminal state before your cancel
arrived:

```json theme={null}
{ "orderId": "0x...", "status": "not_found" }
```

<Note>
  **Field-name conventions across the orders API:**

  * The cancel request and response use **`orderId`**.
  * List/read endpoints (`GET /api/orders/open`, `/api/orders/history`,
    `/api/orders/{id}`) return the same value under **`id`**.
  * The unfilled remainder is **`remainingQty`** everywhere it surfaces
    (cancel response, order list/read).
    Treat `orderId` and `id` as the same identifier for lookup; the
    variance is purely a request-vs-response convention.
</Note>

## Cancel a batch by ID

Cancel up to **100 orders by id** in one request with
`POST /api/orders/cancel-batch`. Built for an MM re-quote tick: it collapses
N single cancels into one round-trip (one advisory-lock acquire, one COMMIT)
instead of N.

```http theme={null}
POST /api/orders/cancel-batch
X-Api-Key: ps_live_<keyId>_<secret>   # needs `orders:write` scope
Content-Type: application/json

{ "orderIds": ["0x1f...", "0x2a...", "0x3c..."] }
```

Response splits every id into `cancelled` and `notCancelled`:

```json theme={null}
{
  "cancelled": ["0x1f...", "0x3c..."],
  "notCancelled": { "0x2a...": "already_terminal" }
}
```

| `notCancelled` reason | Meaning                                                                         |
| --------------------- | ------------------------------------------------------------------------------- |
| `not_found`           | Unknown id, or an order owned by another wallet (no existence leak)             |
| `already_terminal`    | Already CANCELLED / FILLED / REJECTED / EXPIRED — idempotent                    |
| `lock_invariant`      | A per-order balance-lock invariant tripped; the rest of the batch is unaffected |
| `unknown`             | Transient failure — safe to retry                                               |

Duplicate ids are de-duplicated (cancelled once, reported once). Rate limited
at **5 req/sec/wallet**. No KYC / deposit-tier gating — cancellation is
always permitted.

## Cancel all your open orders

```http theme={null}
POST /api/orders/cancel-all
X-Api-Key: ps_live_<keyId>_<secret>   # needs `orders:write` scope
Content-Type: application/json

{ "marketId": "UAE-CUP-FINAL-20260425" }
```

Omit `marketId` to cancel every open order for the authenticated
wallet (authenticated associatedWallet or the API-key's `associatedWallet`).

Response — **`cancelled` is the array of cancelled order ids** (breaking
change 2026-06; previously a count):

```json theme={null}
{
  "cancelled": ["0x1f...", "0x2a...", "0x3c..."],
  "notCancelled": { "0x9e...": "already_terminal" },
  "marketId": "UAE-CUP-FINAL-20260425"
}
```

<Warning>
  **Breaking change (2026-06).** `cancelled` changed from a number to an
  array of order ids — read `cancelled.length` for the count. Orders that
  could not be cancelled now appear in `notCancelled` (orderId → reason,
  same vocabulary as [cancel-batch](#cancel-a-batch-by-id)) instead of being
  dropped silently from the count. Cancel-all also operates on a **snapshot**
  of the orders open when the request arrives — orders placed while the
  cancel runs are not guaranteed to be included.
</Warning>

### Scope filters — `marketId`, `side`, `outcome`

All three are optional and combine freely. Empty body = wallet-wide cancel.

| Field      | Type                                  | Effect                                                    |
| ---------- | ------------------------------------- | --------------------------------------------------------- |
| `marketId` | string                                | Cancel only orders for this market symbol                 |
| `side`     | `"buy"` / `"sell"` (case-insensitive) | Cancel only buys or only sells                            |
| `outcome`  | integer ≥ 0                           | Cancel only orders for this outcome index (`0` / `1` / …) |

```http theme={null}
POST /api/orders/cancel-all
{ "marketId": "UAE-CUP-FINAL-20260425", "side": "sell", "outcome": 0 }
```

Combines into one round-trip what previously required iterating
`/api/orders/open` client-side and firing N `cancel` calls. Common
MM use case: a price spike on outcome 0 means "pull all my asks on
outcome 0 of this one market" without touching outcome-1 asks or any
buys — single call.

<Tip>
  Cancel-all is rate-limited at **1 req/sec/wallet**. Use for
  operational interventions, not normal per-order cleanup.
</Tip>

## Under the hood

1. Server calls `matcher.cancelOrder` over gRPC. `NOT_FOUND` is
   treated as idempotent success.
2. PG authoritative flip in a single tx:
   * `UPDATE orders SET status='CANCELLED' WHERE id=$1 AND status IN ('PENDING','OPEN','PARTIAL')`
   * `refundResidualLocked()` — sums remaining locked, refunds to available.
3. Double-cancel protection via the conditional WHERE.

## Cancellation states

| Condition                           | Final status                              | Notes                           |
| ----------------------------------- | ----------------------------------------- | ------------------------------- |
| Order was `OPEN` or `PARTIAL`       | `CANCELLED`                               | Residual refunded               |
| Order was `FILLED`                  | `not_found` from API                      | Terminal — nothing to cancel    |
| Order was already `CANCELLED`       | `not_found` from API                      | Idempotent                      |
| Market was `RESOLVED` / `CANCELLED` | `CANCELLED_BY_RESOLVE` (set by admin-api) | Your DELETE returns `not_found` |
