Submit a purchase
POST/api/market/purchase/submit
Step 3 of buying: the PSBTs from prepare, your inputs signed. The server checks they are exactly the prepared transactions, verifies your signatures, adds the sellers' signatures, rechecks every rule, then testmempoolaccept and broadcasts (setup first). The listings become pending. Resubmitting the same purchase is idempotent: use it to settle broadcast_unknown.
Request body #
content-type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | The prepared purchase id. |
setup_psbt | string | null | no | The signed setup PSBT, base64: required when prepare returned setup, absent otherwise. |
purchase_psbt | string | yes | The signed purchase PSBT, base64. Not finalized is fine; the transaction must be exactly the prepared one. |
Example request #
curl -X POST 'https://raresats.net/api/market/purchase/submit' \
-H 'content-type: application/json' \
-d '{
"id": "5c9e5c410688cd85",
"purchase_psbt": "cHNidP8BAP3lAQIAAAAEV/MhdPD/0sIHo8hX35p9GQud7geCW9jXa3hoAejEjgoAAAAAAP3///+3Sw6TNUSithUu7/zVH0UQDemfGc8HWcjxkuB/9nd1LAAAAAAA/f///6nTU92uYg6gtb+FEVnT5JxNFJ1H13OjJCln4+xg+7YvAAAAAAD9////VHDf42dMx3y1IrYZcBnC/YiD1iNsIqK5yRDtpRMrGUYAAAAAAP3///8JsAQAAAAAAAAXqRQ/tulYEuV7tGkfmkpiiGKmGk92m4ciAgAAAAAAACJRIKYIafDbzx3GWcnOy6+AUBNeqejNxIcFPx3GiAlJ3GhMguwAAAAAAAAWABTAzrzWw9PKjHXcXsYuvlUzDvkQ4vUCAAAAAAAAIlEgrfKcXTOb9Ymo27aSDRsOplytPApoV4riNE46JGW7F/D1AgAAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7WAIAAAAAAAAXqRQ/tulYEuV7tGkfmkpiiGKmGk92m4dYAgAAAAAAABepFD+26VgS5Xu0aR+aSmKIYqYaT3abh3hIAQAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHAAAAAAAAAAAOagxyYXJlc2F0cy5uZXQAAAAAAAEBIFgCAAAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHIgIDmztpS4/FteB/sGnHg8rHVPXTjD4IvtGWDjH9sd2jXCRIMEUCIQDqT5JFB8nk2LvYxjpKXk87G12Tn0Jt7fslQf2gSIvJjwIgbg+BAy20UrXd2niDIeZbPLkgyz+NcnSSkChVICw0gV4BAQQWABT5kGeayv4lwnYVNztAvyJEbST/RAABASBYAgAAAAAAABepFD+26VgS5Xu0aR+aSmKIYqYaT3abhyICA5s7aUuPxbXgf7Bpx4PKx1T104w+CL7Rlg4x/bHdo1wkRzBEAiAbu6SfmqbPok5pJyyLQoLTWtu6xHYFUxdkVqMdge1hPwIgXVp1PF1DvIaRLu2QeT0Qv9t+5Dfe2BUaY/Y2QBv58+ABAQQWABT5kGeayv4lwnYVNztAvyJEbST/RAABAR8iAgAAAAAAABYAFMDOvNbD08qMddxexi6+VTMO+RDiAAEBIPBJAgAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHIgIDmztpS4/FteB/sGnHg8rHVPXTjD4IvtGWDjH9sd2jXCRHMEQCIC8ykJLr2bz7gpup8hfRujOnTV4UdPVuIsfzFgJGmMuCAiARSDj1rK63S1wPEKMrRPqnZ7u+rJDysVPRn7ym3iNFpQEBBBYAFPmQZ5rK/iXCdhU3O0C/IkRtJP9EAAAAAAAAAAAAAA=="
}'const response = await fetch('https://raresats.net/api/market/purchase/submit', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
"id": "5c9e5c410688cd85",
"purchase_psbt": "cHNidP8BAP3lAQIAAAAEV/MhdPD/0sIHo8hX35p9GQud7geCW9jXa3hoAejEjgoAAAAAAP3///+3Sw6TNUSithUu7/zVH0UQDemfGc8HWcjxkuB/9nd1LAAAAAAA/f///6nTU92uYg6gtb+FEVnT5JxNFJ1H13OjJCln4+xg+7YvAAAAAAD9////VHDf42dMx3y1IrYZcBnC/YiD1iNsIqK5yRDtpRMrGUYAAAAAAP3///8JsAQAAAAAAAAXqRQ/tulYEuV7tGkfmkpiiGKmGk92m4ciAgAAAAAAACJRIKYIafDbzx3GWcnOy6+AUBNeqejNxIcFPx3GiAlJ3GhMguwAAAAAAAAWABTAzrzWw9PKjHXcXsYuvlUzDvkQ4vUCAAAAAAAAIlEgrfKcXTOb9Ymo27aSDRsOplytPApoV4riNE46JGW7F/D1AgAAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7WAIAAAAAAAAXqRQ/tulYEuV7tGkfmkpiiGKmGk92m4dYAgAAAAAAABepFD+26VgS5Xu0aR+aSmKIYqYaT3abh3hIAQAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHAAAAAAAAAAAOagxyYXJlc2F0cy5uZXQAAAAAAAEBIFgCAAAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHIgIDmztpS4/FteB/sGnHg8rHVPXTjD4IvtGWDjH9sd2jXCRIMEUCIQDqT5JFB8nk2LvYxjpKXk87G12Tn0Jt7fslQf2gSIvJjwIgbg+BAy20UrXd2niDIeZbPLkgyz+NcnSSkChVICw0gV4BAQQWABT5kGeayv4lwnYVNztAvyJEbST/RAABASBYAgAAAAAAABepFD+26VgS5Xu0aR+aSmKIYqYaT3abhyICA5s7aUuPxbXgf7Bpx4PKx1T104w+CL7Rlg4x/bHdo1wkRzBEAiAbu6SfmqbPok5pJyyLQoLTWtu6xHYFUxdkVqMdge1hPwIgXVp1PF1DvIaRLu2QeT0Qv9t+5Dfe2BUaY/Y2QBv58+ABAQQWABT5kGeayv4lwnYVNztAvyJEbST/RAABAR8iAgAAAAAAABYAFMDOvNbD08qMddxexi6+VTMO+RDiAAEBIPBJAgAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHIgIDmztpS4/FteB/sGnHg8rHVPXTjD4IvtGWDjH9sd2jXCRHMEQCIC8ykJLr2bz7gpup8hfRujOnTV4UdPVuIsfzFgJGmMuCAiARSDj1rK63S1wPEKMrRPqnZ7u+rJDysVPRn7ym3iNFpQEBBBYAFPmQZ5rK/iXCdhU3O0C/IkRtJP9EAAAAAAAAAAAAAA=="
})
});
const data = await response.json();import requests
data = requests.post("https://raresats.net/api/market/purchase/submit", json={
"id": "5c9e5c410688cd85",
"purchase_psbt": "cHNidP8BAP3lAQIAAAAEV/MhdPD/0sIHo8hX35p9GQud7geCW9jXa3hoAejEjgoAAAAAAP3///+3Sw6TNUSithUu7/zVH0UQDemfGc8HWcjxkuB/9nd1LAAAAAAA/f///6nTU92uYg6gtb+FEVnT5JxNFJ1H13OjJCln4+xg+7YvAAAAAAD9////VHDf42dMx3y1IrYZcBnC/YiD1iNsIqK5yRDtpRMrGUYAAAAAAP3///8JsAQAAAAAAAAXqRQ/tulYEuV7tGkfmkpiiGKmGk92m4ciAgAAAAAAACJRIKYIafDbzx3GWcnOy6+AUBNeqejNxIcFPx3GiAlJ3GhMguwAAAAAAAAWABTAzrzWw9PKjHXcXsYuvlUzDvkQ4vUCAAAAAAAAIlEgrfKcXTOb9Ymo27aSDRsOplytPApoV4riNE46JGW7F/D1AgAAAAAAACJRIKgvKZRNZbhq5rXlzHXilOrWxZORoe3F4BbjSYxn/Hu7WAIAAAAAAAAXqRQ/tulYEuV7tGkfmkpiiGKmGk92m4dYAgAAAAAAABepFD+26VgS5Xu0aR+aSmKIYqYaT3abh3hIAQAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHAAAAAAAAAAAOagxyYXJlc2F0cy5uZXQAAAAAAAEBIFgCAAAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHIgIDmztpS4/FteB/sGnHg8rHVPXTjD4IvtGWDjH9sd2jXCRIMEUCIQDqT5JFB8nk2LvYxjpKXk87G12Tn0Jt7fslQf2gSIvJjwIgbg+BAy20UrXd2niDIeZbPLkgyz+NcnSSkChVICw0gV4BAQQWABT5kGeayv4lwnYVNztAvyJEbST/RAABASBYAgAAAAAAABepFD+26VgS5Xu0aR+aSmKIYqYaT3abhyICA5s7aUuPxbXgf7Bpx4PKx1T104w+CL7Rlg4x/bHdo1wkRzBEAiAbu6SfmqbPok5pJyyLQoLTWtu6xHYFUxdkVqMdge1hPwIgXVp1PF1DvIaRLu2QeT0Qv9t+5Dfe2BUaY/Y2QBv58+ABAQQWABT5kGeayv4lwnYVNztAvyJEbST/RAABAR8iAgAAAAAAABYAFMDOvNbD08qMddxexi6+VTMO+RDiAAEBIPBJAgAAAAAAF6kUP7bpWBLle7RpH5pKYohiphpPdpuHIgIDmztpS4/FteB/sGnHg8rHVPXTjD4IvtGWDjH9sd2jXCRHMEQCIC8ykJLr2bz7gpup8hfRujOnTV4UdPVuIsfzFgJGmMuCAiARSDj1rK63S1wPEKMrRPqnZ7u+rJDysVPRn7ym3iNFpQEBBBYAFPmQZ5rK/iXCdhU3O0C/IkRtJP9EAAAAAAAAAAAAAA=="
}).json()Response 200 #
| Field | Type | Description |
|---|---|---|
txid | string | The purchase transaction, accepted by bitcoind and broadcast. |
setup_txid | string | The setup transaction (broadcast first), when there was one. |
{
"txid": "6897451c42f02de2594abfdd6a5d4fcf4794a62cbbd9fbec1ec983893ddc5df4"
}Errors #
{error, code}; see Errors 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_purchase | No such prepared purchase (it was dropped: your 11th prepared since, a busy server, or a restart): prepare it again. |
| 405 | method_not_allowed | Wrong method for the route; the allow header lists the right ones. |
| 409 | config_changed | The marketplace settings changed since this purchase or transfer was prepared (prepare it again), or since this offer was made (it is now invalid, reason config_changed). |
| 409 | conflict | A listing or one of your UTXOs was spent meanwhile (lost race), or bitcoind would not replace a transaction already spending it (insufficient fee), or a transfer's inputs are spent by another transaction. |
| 409 | expired | The prepared purchase, offer or transfer expired, or the offer itself has: prepare it again. |
| 409 | listing_changed | A listing's stored terms no longer match the plan: prepare it again. |
| 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 | 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 | rejected | bitcoind refused the transaction (reason). |
| 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 | bitcoind_unavailable | bitcoind is not available. |
| 503 | broadcast_unknown | The broadcast's outcome is unknown: submit the same purchase or transfer again to settle it. Carries txid. |
| 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. |
{
"error": "the prepared purchase expired: prepare it again",
"code": "expired"
}When the answer is lost #
A submit that times out, or answers 503 broadcast_unknown, may still have gone through. Submit the same body again: it is idempotent. If bitcoind has the transaction, the answer is the same {txid}; if it was refused, you get that error; while it cannot be known yet, broadcast_unknown again. Meanwhile the listings stay reserved (pending) for you, and return to sale if the transaction never reaches the mempool.