Errors, pending operations, and retries
Treat HTTP acceptance, on-chain confirmation, invoice state, share-link availability, and email delivery as separate outcomes. 202 is pending, not failure. A connection timeout can occur after a transaction was submitted.
Error response shapes#
Current routes are not uniform. Handle { error, message }, { error, code }, and validation { error, details } responses. Preserve the HTTP status and any returned userOpHash, txHash, or invoice ID. A machine-readable code may be in code or in error; error strings are not uniformly cased.
| HTTP result | Examples and next step |
|---|---|
400 | Invalid JSON fields, malformed HMAC headers, invalid invoice status, incomplete profile, or transaction failure. Fix the specific cause; do not loop blindly. |
401 | Missing/invalid/revoked/expired credentials, bad signature, excessive clock skew. Check key ID, decoded secret, exact signed bytes, and clock. |
403 | Scope or ownership failure, disabled agent, inactive write access. Have the owner inspect access configuration. |
404 | Resource missing or not accessible to your studio. A recently issued invoice may also be waiting for indexing. |
409 | Payload conflict, another request in progress, duplicate customer, or invalid state transition. Handle the specific code. |
429 | Request-rate or operation-quota limit. Honor Retry-After if present and inspect quota/reset information. |
500 | Server failure; some invoice finalization errors occur after on-chain creation. Inspect the body before retrying. |
502 / 503 | Paymaster, bundler, indexer, RPC, or service availability issue. A write outcome may still need reconciliation. |
Common issuance codes include PROFILE_INCOMPLETE, no_active_signer, FORBIDDEN, QUOTA_EXCEEDED, TRANSACTION_FAILED, FINALIZATION_FAILED, IDEMPOTENCY_CONFLICT, and IDEMPOTENCY_LEASE_CONTENTION. Withdrawal setup can return NO_WITHDRAWAL_ADDRESS or ADDRESS_MISMATCH. Check both code and status instead of matching a prose error message alone.
Invoice idempotency#
Invoice creation supports the Idempotency-Key header, including server-signed POST /v1/invoices/issue and the client-signed prepare/submit workflow. Use a unique stable key for one intended invoice, 1 through 128 visible ASCII characters with no spaces. Keep it with your business operation record before sending the first request. Without it, repeated creation requests can create distinct invoices.
Deployments can require this header; it is required by default outside production. Missing or invalid headers then return IDEMPOTENCY_KEY_REQUIRED or IDEMPOTENCY_KEY_INVALID. Always send it rather than depending on a deployment accepting unkeyed creation.
For /issue, persist the customer UUID, raw amount, fixed dueAt or relative dueDays, exact description, and email choice. Supply exactly one due-date field. Relative terms are anchored to the original durable operation record, so their effective date does not move on retries. Keep the same studio and chain. Reuse the same key and payload on retries, but generate fresh HMAC headers. Whitespace changes in a description can change the fingerprint. A new key means a new operation, not a way to recover the old one.
- A confirmed result can be replayed with
idempotencymetadata, includingkey,replayed, andstate. IDEMPOTENCY_CONFLICTmeans the key was used with a different payload. Restore the original payload or deliberately start a different business operation.IDEMPOTENCY_LEASE_CONTENTIONmeans another request for the same operation is processing. Wait briefly, then retry with the same key and payload.IDEMPOTENCY_SUBMISSION_UNCERTAINmeans submission began but no usable receipt hash is recorded. Let the original request finish. If it crashed, reconcile the original operation with support; lease expiry does not prove it was never accepted. The server refuses to rebuild this ambiguous operation automatically.- A pending operation can be repolled by repeating that same request. This is still a POST to the existing idempotent operation, not a generic status endpoint.
FINALIZATION_FAILEDmay include an invoice ID and transaction hash because the invoice already exists on-chain. Retry with the original idempotency key to repair finalization, not with a new key to issue again.
Do not generalize invoice creation idempotency to customer writes, cancellations, token creation, withdrawals, key rotation, or Safe lifecycle operations. Those routes have different state handling and are not covered by this promise.
Server-signed cancellation has its own internal per-invoice tracking and repoll behavior. It does not consume the caller's issuance idempotency key, and the pre-check can reject an already-canceled invoice before replaying a prior result.
Pending and incomplete successful results#
A typical pending issuance response is:
{
"status": "pending",
"userOpHash": "0x...",
"message": "Invoice submitted but confirmation timed out. Check status later."
}The hash above is illustrative, not a usable transaction hash. Store the real returned value. Do not credit a customer ledger or share a fabricated payment link from this response.
Server-signed recovery requires a real invoice ID, finalizes the encryption mapping, and restores a tokenized share link before returning a ready-to-share result. Missing invoice events stay pending; missing metadata or tokens return a finalization error. Recovery preserves the original amount and date context, and optional email delivery uses the existing delivery record. Legacy pending operations created before recovery context was stored may still require manual reconciliation. Retain the original operation and contact support rather than issue another invoice.
When the numeric ID is known, GET /v1/invoices/:id?mode=strong helps reconcile state. Lists may lag the chain. Inspect returned metadata; absence from a single list response is not proof the transaction failed. Payment confirmation is Paid, not issuance Issued or submission confirmed.
Retry policy for clients#
For reads, use bounded exponential backoff with jitter on transient failures. Respect rate limits, keep a deadline, and surface persistent failures. For writes, first determine whether an operation was submitted. Retry invoice creation only within the idempotency contract above; for other ambiguous writes, reconcile state/receipts before deciding whether a new request is necessary.
The MCP send tool sends stable relative due terms. Reuse its idempotency_key with the same customer, amount, description, due_days, and send_email. On ambiguous failures it retains the key and returns copyable retry inputs. An invocation without a key deliberately generates a new operation; it is not a retry of a previous invoice.
For support, provide the endpoint, HTTP status, error code, approximate UTC time, numeric invoice ID, and transaction/UserOp hashes when available. Do not send HMAC secrets, JWTs, signatures, full UserOps, or tokenized share URLs. See troubleshooting.