# Cancel offers

`POST /api/market/offers/cancel`

Withdraws active offers with a BIP-322 signature of `raresats.net cancel-offer <id,id,…> <unix seconds>` by the bidder's payment or ordinals address, at most 10 minutes old, accepted once. The server deletes your signatures. The signed transaction itself has no expiry: to be certain, spend the funding UTXO(s).

#### Request body

`content-type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ids` | array of string | yes | 1 to 100 offer ids, all made by `address`. (1-100 items) |
| `address` | string | yes | The bidder's payment or ordinals address. |
| `message` | string | yes | Exactly `raresats.net cancel-offer <id,id,…> <unix seconds>`: the ids comma-separated in the same order as `ids`, and the current time. At most 10 minutes old, at most 2 minutes ahead. |
| `signature` | string | yes | A BIP-322 signature of `message` by `address`, base64. |

#### Example request

```bash title="cURL"
curl -X POST 'https://raresats.net/api/market/offers/cancel' \
  -H 'content-type: application/json' \
  -d '{
  "ids": ["f035c509de153a30"],
  "address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
  "message": "raresats.net cancel-offer f035c509de153a30 1790967012",
  "signature": "AUAbWZZUOrgfeSJtVYKiSdx9j70KWLVcI8nznx1U8hP+zet/ojilZjtbfyFlZc/VGHpLrTT4dDfzEKaf/9dcnn3i"
}'
```
```js title="JavaScript"
const response = await fetch('https://raresats.net/api/market/offers/cancel', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    "ids": ["f035c509de153a30"],
    "address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
    "message": "raresats.net cancel-offer f035c509de153a30 1790967012",
    "signature": "AUAbWZZUOrgfeSJtVYKiSdx9j70KWLVcI8nznx1U8hP+zet/ojilZjtbfyFlZc/VGHpLrTT4dDfzEKaf/9dcnn3i"
  })
});
const data = await response.json();
```
```python title="Python"
import requests

data = requests.post("https://raresats.net/api/market/offers/cancel", json={
    "ids": ["f035c509de153a30"],
    "address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
    "message": "raresats.net cancel-offer f035c509de153a30 1790967012",
    "signature": "AUAbWZZUOrgfeSJtVYKiSdx9j70KWLVcI8nznx1U8hP+zet/ojilZjtbfyFlZc/VGHpLrTT4dDfzEKaf/9dcnn3i"
}).json()
```

#### Response `200`

| Field | Type | Description |
| --- | --- | --- |
| `offers` | array of [Offer](https://raresats.net/docs/offer-object.md) | The offers, now `cancelled`. |
| `warning` | string | Present when some of them have a public acceptance (see `exposed`). |
| `exposed` | array of string | Ids of cancelled offers whose acceptance was broadcast and dropped (`acceptance_public`): that transaction can still confirm until you spend a funding UTXO. |

```json title="Response"
{
  "offers": [
    {
      "id": "f035c509de153a30",
      "kind": "item",
      "outpoint": "d184375c0e70392605961f20ae083a53cdac71fc923a9df49a4daf5bbc511b4a:0",
      "value": 2000,
      "seller_address": "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
      "price": 52000,
      "platform_fee": 1300,
      "fee_rate": 3,
      "network_fee": 963,
      "total": 54263,
      "vsize": 321,
      "unit_price": 50000,
      "row": "uncommon",
      "count": 1,
      "rows": {"uncommon": 1},
      "types": [
        {"types": ["uncommon"], "count": 1, "sats": [1925389375000000]}
      ],
      "sats": 1,
      "ranges": [[1925389375000000, 1925389375000000]],
      "years": {"2023": 1},
      "bidder_payment_address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
      "bidder_ordinals_address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
      "status": "cancelled",
      "created_at": 1790967012,
      "updated_at": 1790967012,
      "expires_at": 1791053412,
      "reason": null,
      "txid": null,
      "listing_id": null,
      "acceptance_public": false
    }
  ]
}
```

#### Errors

`{error, code}`; see [Errors](https://raresats.net/docs/errors.md) for every code.

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad_address` | An address is not a mainnet address (or not one at all). |
| 400 | `bad_ids` | `ids` / `listing_ids` is empty, too long, malformed, or lists an id twice. |
| 400 | `bad_json` | The body is not valid JSON. |
| 400 | `bad_message` | The cancel message is not `raresats.net cancel <ids> <unix seconds>` or names other listings than `ids`; or the registration message is not `raresats.net affiliate <address> <unix seconds>` for `address`. |
| 400 | `bad_request` | The body is not a JSON object, or the engine refused a malformed request (e.g. a purchase submitted without its signed setup PSBT). |
| 400, 422 | `bad_signature` | 400: `signature` is missing or too long. 422: a signature does not verify (cancel or registration message, listing or purchase input). |
| 403 | `cross_origin` | A POST from a browser on another site (a foreign `Origin`, or `sec-fetch-site: cross-site`). Server-side clients send no `Origin` and are not affected. |
| 404 | `not_found` | No such listing or offer. In chat: no such message. A display name to remove that does not exist. |
| 405 | `method_not_allowed` | Wrong method for the route; the `allow` header lists the right ones. |
| 409 | `not_active` | A listing (or offer) is not active: being bought (or accepted), sold, cancelled, moved, expired or invalid. Carries `id` and `status`. |
| 409 | `replayed` | That cancel or registration message was used already. |
| 413 | `too_large` | The body is over 256 KiB. |
| 415 | `not_json` | A POST without `content-type: application/json`. |
| 422 | `future_message` | The cancel or registration message is dated more than 2 minutes ahead: check the clock. |
| 422 | `not_bidder` | An offer was not made by `address` (neither its payment nor its ordinals address). |
| 422 | `stale_message` | The cancel or registration message is more than 10 minutes old: sign a new one. |
| 429 | `rate_limited` | Over 30 POSTs a minute from your IPv4 address or IPv6 /64 (in chat, counted per IPv4 address or IPv6 /64: 30 writes and 20 login nonces a minute, 20 sign-ins and 10 display-name changes an hour; one message every 3 s and 20 a minute per address); wait `retry-after` seconds. With the AI moderation on, at most 6 messages and names a minute are checked per address and per IPv4 address or IPv6 /48. |
| 503 | `engine_unavailable` | The trade engine is not available. |

```json title="Error (409)"
{"error": "that cancel message was used already", "code": "replayed"}
```

The message is `raresats.net cancel-offer <id,id,…> <unix seconds>` (`offer_cancel_prefix` in [settings](https://raresats.net/docs/get-config.md)), signed with BIP-322 by the address that made the offers (its payment or its ordinals address), exactly as [cancelling listings](https://raresats.net/docs/guide-cancel.md) works: at most 10 minutes old, accepted once, whatever the signature's encoding. Only `active` offers can be cancelled; one being accepted (`pending`) answers 409 `not_active`.

The server deletes your signatures, so no one can complete the trade through it. The signed transaction itself has no expiry, though: **to cancel for certain, spend the funding UTXO(s)**.

When an acceptance of an offer was already broadcast and dropped (`acceptance_public: true` on the [Offer](https://raresats.net/docs/offer-object.md)), the answer carries `warning` and `exposed` (the ids concerned): that transaction is public and can still confirm until you spend a funding UTXO.
