CosignRefusals and retries
Refusals and retries
Read a 409, retry safely, and rebuild a refused batch.
Every co-sign refusal is HTTP 409. Nothing is signed, and refused purchases consume no budget. This page applies to every co-sign endpoint: POST /v1/cosign, POST /v1/evm/cosign and POST /v1/x402/cosign.
Alarm codes page Exchain. body_mismatch, payee_unpinned and clock_skew page Exchain's on-call engineer. An honest, correctly built request never triggers them.
Two kinds of 409
| Purchase-level | Batch-level | |
|---|---|---|
denied | "member_denied" | The code, for example "payee_unpinned" |
members[] | Each allowed or denied, with a code, reasonEnglish and numbers | All not_evaluated |
| Extra | rebuild object (Cardano) | alarm, optional detail |
An EVM request carries one intent, so its purchase-level refusal has one member: read the code in members[0].denied.
{
"decisionId": "dec_01J9XK3M4N5P6Q7R8S9T0V1W2X",
"txBodyHash": "blake2b_256:<64 hex>",
"requiredSigners": ["<agent key hash>", "<quorum key hash 1>", "<quorum key hash 2>", "<quorum key hash 3>"],
"denied": "member_denied",
"members": [
{ "purchaseId": "pur_1", "outputIndex": 0, "verdict": "allowed" },
{ "purchaseId": "pur_2", "outputIndex": 1, "verdict": "denied", "denied": "per_tx_cap",
"reasonEnglish": "A single payment of 250 tADA exceeds the per-payment cap of 200 tADA.",
"bound": "200000000", "used": "100000000", "attempted": "250000000" },
{ "purchaseId": "pur_3", "outputIndex": 2, "verdict": "allowed" }
],
"rebuild": { "keep": ["pur_1", "pur_3"], "batchId": "7f0c3d2e-9a4b-4c1d-8e2f-000000000002",
"heldUntil": "2026-11-05T10:02:01Z" },
"alarm": false,
"journalRef": "https://cosign.exchain.network/v1/decisions/dec_01J9XK3M4N5P6Q7R8S9T0V1W2X",
"asOf": "2026-11-05T10:00:01.412Z",
"schemaVersion": "1.1",
"chainTip": { "slot": 138150155, "blockHash": "<64 hex>" }
}Retries
Send Idempotency-Key equal to batchId on every request.
| You send | Exchain does |
|---|---|
Same batchId, same body | Returns the stored answer byte for byte. Nothing counts twice |
Same batchId, new body, after a 200 (Cardano) | Makes a new decision that counts again. The first budget returns only if its transaction never lands |
Same signed authorization, different walletId or intent (EVM) | 409 idempotency_conflict |
| Same nonce, different typed data (EVM) | 409 body_mismatch, detail: "nonce_reused" |
purchaseId is not a dedupe key.
When to retry, by code
| Code | Action |
|---|---|
daily_outflow, per_agent_cap | Retry at windowResetsAt or after retryAfterSec |
per_seller_cap, velocity_burst | Retry after retryAfterSec. A per_seller_cap refusal also allows paying another seller now |
reservation_conflict | Retry as soon as the hold clears (Cardano) |
utxo_unknown with stale_ref | Rebuild on the wallet's current UTxO (Cardano) |
idempotency_conflict | Do not retry. Sign a new authorization with a new nonce (EVM) |
per_tx_cap, envelope_exhausted, registry_gate, price_above_published, counterparty_blocked, asset_not_listed | Do not retry unchanged |
body_mismatch, payee_unpinned, clock_skew | Alarm. Investigate; do not retry |
Every code: Errors and codes.
Rebuild a Cardano batch
A Cardano batch can carry several purchases. When some are refused:
- Exchain judges purchases in submission order, each against state that includes the earlier admitted ones.
- Admitted purchases are held until
rebuild.heldUntil, 120 seconds after the refusal. - Rebuild the body with exactly
rebuild.keepand send it under the samebatchId. While the hold lasts, those purchases fit.
| During the hold | Exchain does |
|---|---|
Same batchId, new body | Supersedes the old decision and evaluates the new body |
| Another batch for the wallet | 409 reservation_conflict. Retry after the hold (at most 120 seconds) |
EVM answers
Besides 409, EVM co-sign requests can return:
| Answer | Meaning | Action |
|---|---|---|
401 agent_signature_invalid | The agent signature does not recover to the agent over the Safe message hash | Sign the Safe message hash, raw 65 bytes |
402 fee_due | Fees reached the settlement threshold. Nothing was decided | Settle fees, then retry |
400 batch_id_mismatch | batchId is not the authorization nonce | Set batchId to the nonce |
400 idempotency_key_mismatch | Idempotency-Key differs from batchId (every rail) | Send the same value in both |
409 body_mismatch, detail: "nonce_reused" | The nonce was already decided for another payment | Sign a new authorization with a new nonce |
409 body_mismatch, detail: "safe_nonce" | A SafeTx is not at the Safe's current nonce | Sign it again at the current nonce |
200 with relayError | Signed, but Exchain could not send it (direct only) | Send transaction from any account before it expires |
When Exchain cannot decide
| Answer | Meaning | Action |
|---|---|---|
503 quorum_unavailable | Fewer members of the attested quorum are reachable than the threshold; the body has reachable and threshold. Nothing consumed | Retry after Retry-After: 5 |
503 quorum_unavailable, detail: "stopped" | Exchain paused signing | Retry after 60 seconds |
503 quorum_unavailable, detail: "record" on POST /v1/cosign; 503 unavailable, detail: "record" on the EVM endpoints | The audit record could not be written | Retry after Retry-After: 5 |
429 rate_limited | Over your node's request rate, 120 a minute by default | Retry after retryAfterSec |
400 clock_rejected | Exchain's clock check failed | Contact Exchain |
A signed payment that never lands has its budget returned: After you submit (Cardano) and EVM settlement.