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

# Cancel Buy (Self-Trade)

> Cancel every still-undelivered item in a merchant self-trade buy in one call; per-item outcomes come back in the body and refunds settle asynchronously.

Merchant-facing equivalent of [`POST /client/trading/withdraw/{tradeId}/cancel`](/docs/api-reference/trading/cancel-withdrawal). Requests cancellation of **every still-cancellable item** in one of your own **self-trade** buys, rather than one item at a time — a single call replaces a loop over [`POST /secure/buy/{tradeId}/items/{itemId}/cancel`](/docs/api-reference/secure/cancel-buy-item).

**Authentication:** Merchant API Key (`api-key` header)
**Scope:** `CORE_ACCESS`

<Note>
  CS2 (Assetpay) items only. Rust buys and autoseller-sourced items have no supplier cancel API. If **every** item in the trade is in one of those lanes the call fails with `TRADE_CANCEL_UNSUPPORTED` (44); if only some are, they are simply left out of the result. Only **self-trade** buys (`source: "self"`) are cancellable through this endpoint.
</Note>

## Request

```http theme={null}
POST https://api.assetpay.gg/secure/buy/{tradeId}/cancel
api-key: ap_...
```

### Path Parameters

| Parameter | Type   | Required | Description                                                                |
| --------- | ------ | -------- | -------------------------------------------------------------------------- |
| `tradeId` | string | Yes      | The buy trade's internal ID, or the `externalId` you supplied at creation. |

## Best effort, not all-or-nothing

The supplier decides which items still qualify, so a **partial result is still a `200`**. Read the body, not the status code:

* `requested` — how many items the supplier considered cancellable at all
* `cancelled` / `failed` — how those split
* `items[]` — one row per item the supplier reported on

Items the supplier never considered (already delivered, in Steam hold, Rust, autoseller-sourced) do not appear in `items[]` at all. An item must also be at least **30 minutes old** and not yet terminal; younger items come back as `failed` with `reason: "TRADE_CANCEL_TOO_SOON"`.

## Response

```json theme={null}
{
  "requestId": "...",
  "success": true,
  "data": {
    "tradeId": "trade-uuid",
    "requested": 3,
    "cancelled": 2,
    "failed": 1,
    "items": [
      { "itemId": "item-uuid-1", "externalId": "buy_item_a", "status": "cancelled" },
      { "itemId": "item-uuid-2", "externalId": "buy_item_b", "status": "cancelled" },
      { "itemId": "item-uuid-3", "externalId": "buy_item_c", "status": "failed", "reason": "TRADE_CANCEL_TOO_SOON" }
    ]
  }
}
```

| Field                | Type          | Description                                                                                                               |
| -------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `tradeId`            | string (uuid) | The trade's internal ID.                                                                                                  |
| `requested`          | integer       | Items the supplier considered cancellable.                                                                                |
| `cancelled`          | integer       | How many it accepted.                                                                                                     |
| `failed`             | integer       | How many it refused.                                                                                                      |
| `items[].itemId`     | string        | The item's AssetPay ID.                                                                                                   |
| `items[].externalId` | string        | Your own reference for the item, when you supplied one at buy time. Omitted otherwise.                                    |
| `items[].status`     | string        | `"cancelled"` or `"failed"`.                                                                                              |
| `items[].reason`     | string        | Stable error key when the cancel was refused — e.g. `TRADE_CANCEL_TOO_SOON`, `TRADE_NOT_CANCELLABLE`. Omitted on success. |

A `cancelled` row only confirms the marketplace **accepted** the cancellation. AssetPay does **not** change item state inline — the refund and the transition to `canceled` arrive asynchronously through the standard `trade.*` [callback](/docs/guides/callbacks). Poll [`GET /secure/trades/{tradeId}`](/docs/api-reference/secure/get-trade) or rely on callbacks for the final state.

<Note>
  A cancel lands on `canceled`, not `reverted`. `reverted` is for a delivered item that came back afterwards.
</Note>

## Rate Limits

| Merchant Status | Limit             |
| --------------- | ----------------- |
| Verified        | 60 requests / min |
| Unverified      | 10 requests / min |

## Errors

| Code | Key                        | When                                                                                   |
| ---- | -------------------------- | -------------------------------------------------------------------------------------- |
| 28   | `TRADE_NOT_CANCELLABLE`    | The trade is not a withdraw-type buy, or the marketplace refused the request outright. |
| 44   | `TRADE_CANCEL_UNSUPPORTED` | Every item in the trade is Rust or autoseller-sourced, so none can be cancelled.       |
| 1300 | `FORBIDDEN`                | The trade is not a self-trade owned by the authenticated merchant.                     |
| 2201 | `ITEM_NOT_FOUND`           | No trade matches that `tradeId` or `externalId`.                                       |
