Error codes
Errors
All errors return the same shape:
{
"error": {
"code": "no_active_xpub",
"message": "Store has no active xpub for this environment...",
"request_id": "b9fc7e29-587f-4dda-b220-86d7144893fe"
}
}
Include the request_id when contacting support. It correlates to our server logs.
Errors POST /v1/invoices can return
The most-called endpoint. Plan for these.
401 — authentication
| Code | When |
|---|---|
api_key_invalid | Missing Authorization header, malformed, or the key doesn't exist. Check the header is Authorization: Bearer sl_live_… or sl_test_…. |
api_key_revoked | Key was revoked from the dashboard or by an admin. Mint a new one. |
api_key_wrong_env | Calling a live store with a test key, or vice versa. Use the key that matches the store's mode. |
403 — authenticated but blocked
| Code | When |
|---|---|
auth_account_suspended | Vendor account suspended by an admin. No invoice creation until reinstated. Contact support. |
404 — resource missing
| Code | When |
|---|---|
not_found (Store) | The store the API key belongs to was archived. Restore it or use a different store. |
409 — conflict / not configured
| Code | When | Resolution |
|---|---|---|
no_active_xpub | Store has no active xpub for this environment. Live invoices need a mainnet xpub; test invoices need any active xpub. | Add an xpub at app.satlane.com/stores/<id>/xpubs. |
gap_limit_exceeded | Wallet's gap limit is within 5 of being reached and we haven't seen recent funding. | Bump the gap limit in your Electrum wallet (recommend 100+) or rotate xpubs. Address recycling absorbs some of this for expired-unpaid invoices. |
idempotency_conflict | The same Idempotency-Key was reused with a different request body. | Either reuse the key with the original body (we'll return the cached response) or generate a new key. |
400 — validation
| Code | Cause |
|---|---|
validation_error | Zod rejected the body. Common: missing both (amount + currency) and amount_sats, providing both, expires_in_minutes outside [5, 120], invalid callback_url, metadata value > 255 chars. The message field names the offending field. |
validation_error | Idempotency-Key header > 255 chars or empty. |
invalid_currency | Currency code not in our supported list (only USD at MVP). |
invalid_amount | Sats amount ≤ 0, or fiat amount rounds to zero sats at the current rate. |
429 — rate limited
| Code | When | Resolution |
|---|---|---|
rate_limited | More than 100 invoice creations per minute on one API key. | Honor the Retry-After header (seconds). |
503 — temporary infrastructure issue (retry safe)
These mean the call would have succeeded if not for an infra condition. Retry with exponential backoff.
| Code | When |
|---|---|
chain_syncing | The Bitcoin node is in initial block download (verificationprogress < 0.999). We refuse new invoices to avoid issuing payable addresses against a stale tip. Usually only happens right after a fresh deploy. |
disk_full | The platform host is critically low on disk. We block writes to avoid losing webhook delivery state. Operator gets paged automatically; resolves itself. |
database_unavailable | Postgres unreachable. Rare. |
Recommended client retry policy
| HTTP | Action |
|---|---|
| 200 / 201 | Use the response. |
| 400, 401, 403, 404, 409 | Stop. These are caller bugs or configuration errors. Log and surface to the user. |
| 429 | Backoff using Retry-After, then retry. |
| 503 | Exponential backoff (e.g. 1s → 2s → 4s → 8s → 16s, max 5 tries). Show "Service temporarily unavailable" if exhausted. |
| Other 5xx | Treat as a bug on our side. Log request_id, escalate. |
Always send an Idempotency-Key when retrying. We cache the response for 24 hours per key, so retries after a transient network error return the same invoice instead of creating duplicates.
Errors from other endpoints
A non-exhaustive selection (see packages/shared/src/errors.ts for the full catalog):
auth_required(401) — session cookie missing on dashboard endpointsauth_totp_required(401) — 2FA-gated endpoint, prompt for code and call/v1/auth/totp/verifyauth_email_not_verified(403) — vendor email not yet verifiedinvoice_not_cancellable(409) — invoice already paid / late_paid / cancelled / expiredinvoice_expired(410) — payment flow hit a fully-expired invoiceinvoice_already_paid(409) — duplicate paid transition attemptnot_found— UUID doesn't match anything you owngone(410) — resource intentionally removed