# Submit an offer

`POST /api/market/offers`

Step 2 of making an offer: the PSBT from prepare with your inputs signed. The server checks it is exactly the prepared transaction, verifies your signatures (SIGHASH_DEFAULT or ALL), checks the UTXO and your funding are unspent, and stores the offer, `active`. Your signatures stay on the server: no API ever returns them. Resubmitting answers the stored offer.

#### Request body

`content-type: application/json`

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | The id from prepare. |
| `psbt` | string | yes | The PSBT from prepare with your inputs signed (not finalized), base64. |

#### Example request

```bash title="cURL"
curl -X POST 'https://raresats.net/api/market/offers' \
  -H 'content-type: application/json' \
  -d '{
  "id": "dbf866a1e7e209e7",
  "psbt": "cHNidP8BAP0fAQIAAAACfxBUfmD8FgvLWC+rhhI7HaW1ncmmlqpbSg8h3N/usyQAAAAAAP3///9Bihr4H2G1iZuygx3BHzwLSWfyqpoJAv/W0tpPJYLJAQAAAAAA/f///wUQJwAAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7sK0BAAAAAAAiUSCmCGnw288dxlnJzsuvgFATXqnozcSHBT8dxogJSdxoTL4KAAAAAAAAIlEgrfKcXTOb9Ymo27aSDRsOplytPApoV4riNE46JGW7F/AvJxUAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7AAAAAAAAAAAOagxyYXJlc2F0cy5uZXQAAAAAAAEBKxAnAAAAAAAAIlEgpghp8NvPHcZZyc7Lr4BQE16p6M3EhwU/HcaICUncaEwAAQErYOMWAAAAAAAiUSCoLymUTWW4aua15cx14pTq1sWTkaHtxeAW40mMZ/x7uwETQJva/GuDEmjt90kgar0keIovQZv62QXPHzpZgAH+Fjr29SIhlsTWs98306xkWYcBMCulKINXRtRZ1fPDLp3C5ScBFyCD3+haMVHSUXKQ2kYf4oFVke9p8rGKLOY/AWl6izExRQAAAAAAAA=="
}'
```
```js title="JavaScript"
const response = await fetch('https://raresats.net/api/market/offers', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    "id": "dbf866a1e7e209e7",
    "psbt": "cHNidP8BAP0fAQIAAAACfxBUfmD8FgvLWC+rhhI7HaW1ncmmlqpbSg8h3N/usyQAAAAAAP3///9Bihr4H2G1iZuygx3BHzwLSWfyqpoJAv/W0tpPJYLJAQAAAAAA/f///wUQJwAAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7sK0BAAAAAAAiUSCmCGnw288dxlnJzsuvgFATXqnozcSHBT8dxogJSdxoTL4KAAAAAAAAIlEgrfKcXTOb9Ymo27aSDRsOplytPApoV4riNE46JGW7F/AvJxUAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7AAAAAAAAAAAOagxyYXJlc2F0cy5uZXQAAAAAAAEBKxAnAAAAAAAAIlEgpghp8NvPHcZZyc7Lr4BQE16p6M3EhwU/HcaICUncaEwAAQErYOMWAAAAAAAiUSCoLymUTWW4aua15cx14pTq1sWTkaHtxeAW40mMZ/x7uwETQJva/GuDEmjt90kgar0keIovQZv62QXPHzpZgAH+Fjr29SIhlsTWs98306xkWYcBMCulKINXRtRZ1fPDLp3C5ScBFyCD3+haMVHSUXKQ2kYf4oFVke9p8rGKLOY/AWl6izExRQAAAAAAAA=="
  })
});
const data = await response.json();
```
```python title="Python"
import requests

data = requests.post("https://raresats.net/api/market/offers", json={
    "id": "dbf866a1e7e209e7",
    "psbt": "cHNidP8BAP0fAQIAAAACfxBUfmD8FgvLWC+rhhI7HaW1ncmmlqpbSg8h3N/usyQAAAAAAP3///9Bihr4H2G1iZuygx3BHzwLSWfyqpoJAv/W0tpPJYLJAQAAAAAA/f///wUQJwAAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7sK0BAAAAAAAiUSCmCGnw288dxlnJzsuvgFATXqnozcSHBT8dxogJSdxoTL4KAAAAAAAAIlEgrfKcXTOb9Ymo27aSDRsOplytPApoV4riNE46JGW7F/AvJxUAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7AAAAAAAAAAAOagxyYXJlc2F0cy5uZXQAAAAAAAEBKxAnAAAAAAAAIlEgpghp8NvPHcZZyc7Lr4BQE16p6M3EhwU/HcaICUncaEwAAQErYOMWAAAAAAAiUSCoLymUTWW4aua15cx14pTq1sWTkaHtxeAW40mMZ/x7uwETQJva/GuDEmjt90kgar0keIovQZv62QXPHzpZgAH+Fjr29SIhlsTWs98306xkWYcBMCulKINXRtRZ1fPDLp3C5ScBFyCD3+haMVHSUXKQ2kYf4oFVke9p8rGKLOY/AWl6izExRQAAAAAAAA=="
}).json()
```

#### Response `200`

| Field | Type | Description |
| --- | --- | --- |
| `offer` | [Offer](https://raresats.net/docs/offer-object.md) |  |

```json title="Response"
{
  "offer": {
    "id": "dbf866a1e7e209e7",
    "kind": "item",
    "outpoint": "24b3eedfdc210f4a5baa96a6c99db5a51d3b1286ab2f58cb0b16fc607e54107f:0",
    "value": 10000,
    "seller_address": "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
    "price": 110000,
    "platform_fee": 2750,
    "fee_rate": 3,
    "network_fee": 963,
    "total": 113713,
    "vsize": 321,
    "unit_price": 100000,
    "row": "decasat_alpha",
    "count": 1,
    "rows": {"uncommon": 1, "alpha_uncommon": 1, "decasat_alpha": 1},
    "types": [
      {
        "types": ["uncommon", "decasat_alpha", "alpha"],
        "count": 1,
        "sats": [441000000000000]
      }
    ],
    "sats": 1,
    "ranges": [[441000000000000, 441000000000000]],
    "years": {"2010": 1},
    "bidder_payment_address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
    "bidder_ordinals_address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
    "status": "active",
    "created_at": 1790967012,
    "updated_at": 1790967012,
    "expires_at": 1791571812,
    "reason": null,
    "txid": null,
    "listing_id": "5e1cb930f43354c3",
    "acceptance_public": false
  }
}
```

#### Errors

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

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `bad_id` | `id` is not a prepared purchase or transfer id. |
| 400 | `bad_json` | The body is not valid JSON. |
| 400 | `bad_psbt` | Not a base64 PSBT, or one that does not parse. |
| 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 | `unknown_offer` | No such prepared offer (it expired, or the server restarted): prepare it again. |
| 405 | `method_not_allowed` | Wrong method for the route; the `allow` header lists the right ones. |
| 409 | `changed` | The UTXO's value is not what the PSBT says, or (wallet tools) a UTXO, its sats or the transaction being replaced changed since prepare: prepare again. |
| 409 | `expired` | The prepared purchase, offer or transfer expired, or the offer itself has: prepare it again. |
| 409 | `funding_spent` | A UTXO of the bidder's that funds the offer was spent (or is no longer plain cardinal): the offer is no longer valid. |
| 409 | `pending` | The UTXO's listing is being bought, or an offer of it accepted, right now. |
| 409 | `spent` | The UTXO (or a listing's UTXO) is spent or does not exist. |
| 413 | `too_large` | The body is over 256 KiB. |
| 415 | `not_json` | A POST without `content-type: application/json`. |
| 422 | `bad_sighash` | A signature uses the wrong sighash type (listings: 0x83; purchases: DEFAULT or ALL). |
| 422 | `missing_signature` | An input that must be signed is not. |
| 422 | `psbt_mismatch` | The signed PSBT is not the prepared transaction. |
| 422 | `unsupported_script` | The trade engine met a script type it does not support. |
| 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. |
| 500 | `invariant_violated` | A planned or finalized purchase or transfer broke an invariant (`violations`); nothing was broadcast. |
| 503 | `bitcoind_unavailable` | bitcoind is not available. |
| 503 | `engine_unavailable` | The trade engine is not available. |
| 503 | `fork_unavailable` | The rare-sat index is not available. |
| 503 | `market_unavailable` | The marketplace is closed right now (its backends disagree on the network, or are unreachable). |
| 503 | `oracle_unavailable` | The ord oracle is not available. |

```json title="Error (404)"
{"error": "no such prepared offer", "code": "unknown_offer"}
```

From now on the offer is `active`: the UTXO's holder can [accept](https://raresats.net/docs/accept-offer.md) it. Your funding UTXOs stay in your wallet; if you spend any of them, the offer becomes `invalid` (so do other offers you made with the same UTXOs once one is accepted).
