Errors
Every non-2xx response uses the same JSON envelope:
{
"ok": false,
"error": {
"code": "AccountNotFound",
"message": "No account found for id acc_1a2b"
},
"request_id": "req_3f2a9c0d1b7e4f6a8c5d2e1b0a9f8e7d"
}ok: literalfalse, present only on errors. Success responses are flat payloads (e.g.{ "id": "f01e…", "status": "awaiting_funds", … }) with nookfield. Callers that want body-level dispatch can checkbody.ok === false. HTTP status is still authoritative. This mirrors the semantics offetch'sResponse.ok.code: stable PascalCase enum value. Safe to switch on programmatically. Will not change across minor releases. New codes may be added. Treat unknown codes as a generic failure at that HTTP status.message: human-readable detail with caller context. Surface to end users.request_id: reference for this request. Every response, success or error, also returns it as thex-request-idheader. Quote it when contacting support.
Status codes
The HTTP status categorises. The code field identifies.
| Status | Meaning | Typical causes |
|---|---|---|
| 400 | Bad request | Malformed body, invalid enum, rule violation (see codes below). |
| 401 | Unauthenticated | Missing / expired access token. Refresh and retry. |
| 403 | Not allowed | Authed, but you don't own the resource. |
| 404 | Not found | Unknown id, or scoped-away (also returned for "not yours"). |
| 409 | Conflict | Duplicate row, idempotency violation, or the resource's current state blocks the action (unverified customer, account still holding a balance). |
| 413 | Payload too large | Uploaded document exceeds the per-document size cap. |
| 422 | Unprocessable entity | Semantic failure: no eligible options, snapshot not ready, … |
| 429 | Rate limited | Too many requests. Back off and retry after the Retry-After header (seconds). |
| 5xx | Server-side | Service outage, DB issue. Safe to retry with exponential backoff. 503 carries a Retry-After header. |
Error codes
Codes group by surface. Not exhaustive: the server may add codes. Unknown codes should be handled as a generic failure at their HTTP status.
Orgs + KYB
code | Status | Meaning |
|---|---|---|
OrgNotFound | 404 | Unknown org id (or credential isn't scoped to it). |
NotOrgMember | 403 | Authed, but the user isn't a member of the requested org. |
LastOwner | 409 | Demote / remove would leave the org with zero owners. Promote someone first. |
KybProfileIncomplete | 422 | Required KYB fields are missing or malformed. See the missing (absent) and violations (present-but-malformed) arrays on the error body. |
KybNotApproved | 403 | Action requires the org's KYB verification to be approved — e.g. registering customers before the org has passed KYB. |
KybDocumentTooLarge | 413 | Uploaded document exceeds the size cap. See max_bytes / received_bytes on the error body. |
UnsupportedDocumentType | 400 | Uploaded file isn't PDF, JPEG, or PNG. Checked against both the multipart Content-Type and the filename extension. Applies to every KYB and KYC document upload. |
VerificationPaused | 409 | A compliance hold is in place. The profile stays editable, but submissions are refused until the hold clears. KYB and KYC. |
ResubmitUnchanged | 409 | Resubmission requested, but the saved profile is byte-identical to the last submission. Edit a field or replace a document first. KYB and KYC. |
Auth
code | Status | Meaning |
|---|---|---|
PasswordTooShort | 400 | Password below minimum length. |
EmailAlreadyExists | 409 | Signup email already registered. |
InvalidCredentials | 401 | Bad email/password on login. |
MfaChallengeInvalid | 401 | Wrong code, expired challenge, or clock skew > 30s. |
RefreshTokenInvalid | 401 | Refresh token expired or revoked. Send the user to login. |
PasswordResetTokenInvalid | 400 | Reset token doesn't exist, was already used, or expired. Request a new reset email. |
EmailVerificationTokenInvalid | 400 | Verification token doesn't exist, was already used, or expired. Request a new verification email. |
ComplianceEmailTokenInvalid | 400 | Compliance-email verification token doesn't exist, was already used, expired, or the org has since changed the target address. Request a new verification link. |
EmailNotVerified | 403 | Action requires a verified email. Verify first, then retry. |
SignupNotAllowed | 403 | Signups are restricted on this environment. Contact support. |
Note: LoginResponse separately reports status: "mfa_required" (not an error) when login succeeds but a TOTP challenge is still pending. That's the normal MFA-on-login flow, not the MfaRequired error code listed under Generic.
API keys
code | Status | Meaning |
|---|---|---|
BothCredsProvided | 400 | Request had both Api-Key: and Authorization: Bearer headers. Pick one. |
ApiKeyNotFound | 404 | Unknown key id (or belongs to a different org). |
ApiKeyRevoked | 401 | Key was valid but is now revoked. Mint a fresh one. |
AlreadyRevoked | 409 | Revoke called on a key that was already revoked. |
ApiKeyRevokeForbidden | 403 | You can see the key but can't revoke it. Only owners, admins, or the key's creator can. |
ApiKeyIpNotAllowed | 403 | Key authenticated fine, but the caller's IP isn't in the key's allow-list. See IP allow-listing. |
ApiKeyAccessDenied | 403 | Key authenticated fine, but its access level doesn't permit this request. access carries the key's level. See Access levels. |
Customers + accounts
code | Status | Meaning |
|---|---|---|
CustomerNotFound | 404 | Unknown id (or belongs to a different org). |
EndUserCustomerExists | 409 | This (org, reference_id) pair is already onboarded. |
KycProfileIncomplete | 422 | Required KYC fields are missing or malformed. See the missing (absent) and violations (present-but-malformed) arrays on the error body. |
CustomerNotVerified | 409 | Account creation requires the customer's verification to have reached approved. status on the error body carries the rolled-up value (draft, submitted, under_review, unknown) so you can tell what the customer still owes. Retry once verification completes. |
CustomerOnHold | 409 | The customer's verification carries a negative verdict, so no new accounts or quotes may be created for them. status on the error body is paused, rejected, or terminally_rejected — a clearable hold vs. a final decline. |
VirtualAccountNotApproved | 409 | The customer is verified, but isn't cleared to be issued a virtual account in this currency — a separate clearance, granted per currency. currency and status on the error body name the one asked for and where it stands (not_started, pending, action_required, rejected). GET /v0/customers/{id}/virtual-account-approvals lists every currency and its status. |
IdempotencyKeyConflict | 409 | The Idempotency-Key on a customer create was already used to create a customer of a different type. |
CustomerHasActiveOrders | 409 | The customer is referenced by an order that hasn't reached a terminal state, so it can't be deleted. |
AccountNotFound | 404 | Unknown account id. |
AccountHasActiveOrders | 409 | The account is the source or destination of an order that hasn't reached a terminal state, so it can't be deleted. |
AccountHasBalance | 409 | The account still holds a non-zero balance, so it can't be deleted. Withdraw the funds first. |
AccountIsLiquidationDestination | 409 | One or more virtual accounts auto-liquidate into this account, so it can't be deleted. related_ids names them — point each at another destination first. |
RailNotAllowed | 400 | Rail isn't settleable for the chosen currency, or the rail's inferred shape (evm, sol, …) doesn't match the chosen shape (e.g. wire on a crypto account, or solana on a currency whose only crypto shape is evm). |
MissingAccountFields | 400 | A required field is absent (e.g. details.account_number on fiat/external, address on crypto/external). |
InvalidAccountFields | 400 | A field is present but invalid: wrong details variant for the inferred shape, a crypto shape that does not match the rail, or a format failure (EVM address, IBAN structure, routing-number length, …). |
RailNotApprovedForOrg | 403 | Your org isn't approved to transact on this rail yet. See rail on the error body. Contact support to enable it. |
CurrencyNotApprovedForOrg | 403 | Your org isn't approved to transact in this currency yet. See currency on the error body. Contact support to enable it. |
FlowNotApprovedForOrg | 403 | Your org isn't approved for this account-flow family. See flow ("virtual" or "external") on the error body. Contact support to enable it. |
OrgSelfTransactingDisabled | 403 | The organization itself was named as the transacting party. Accounts and payments can only be created for end customers. Existing organization-owned accounts remain readable. |
Quotes
code | Status | Meaning |
|---|---|---|
CorridorUnsupported | 422 | This currency / country / rail combination isn't supported. Adjust the corridor or contact support for expansion. A payout to a US bank account with swift as its only rail returns this code with the message "Sending a SWIFT payment to a US bank is not supported. Please use ACH or Fedwire instead." |
NoNamedQuote | 422 | A quote created with ?named=1 has no named option. Two cases: from POST /quotes before anything is priced, no eligible route can be named and no snapshot is created; after pricing, no named route priced: with wait=1 the create request ended without a named option, and a GET or accept of a named=1 snapshot reports the same, since that snapshot won't change. Either way, create a new quote, without named to see every option. A named route that failed returns its own error instead. |
RateUnavailable | 422 | Both currencies are known, but no reference rate exists for the pair right now. |
QuoteNotFound | 404 | Snapshot id unknown (or not yours). |
QuoteNotReady | 422 | Snapshot is still pending. Poll GET /quotes/:id. |
QuoteExpired | 422 | The chosen quote's expires_at has passed. Re-create a snapshot. |
QuoteIdInvalid | 400 | quote_id in the accept body isn't in snapshot.quotes[]. |
AlreadyAccepted | 409 | Snapshot has already been turned into an order. |
OrderNotCancelable | 409 | The order can no longer be canceled. Only orders still awaiting_funds or pending are cancelable. See status on the error body. |
OrderNotAwaitingDocuments | 409 | A supporting document was submitted for an order that isn't waiting for one. Only an order whose status_reason is documents_required or documents_rejected takes one, so a retried or concurrent submission answers this once the first is filed. |
AmbiguousCurrency | 400 | Account supports multiple currencies. Pin one via { account, currency }. See currencies on the error body. |
CustomerOnHold | 409 | The end customer's verification carries a negative verdict. See status on the error body. Unlike account creation, quoting only requires the absence of a negative verdict — a customer still in review can be quoted. |
OrgSelfTransactingDisabled | 403 | The quote or accept names your organization as the end customer, or a leg references an organization-owned account. Payments can only be created for end customers. |
Generic
code | Status | Meaning |
|---|---|---|
InvalidRequest | 400 / 422 | Catch-all for malformed body / invalid enum (400) or a semantic failure (422). message has the detail. When Seismic can pinpoint the failing fields, the error body also carries a violations array. |
Unauthorized | 401 | Missing or invalid bearer token. |
Forbidden | 403 | Authed, but not permitted. |
MfaRequired | 403 | Authed, but the resource needs an MFA-active session. Send the user to enrol an authenticator, then sign back in. |
NotFound | 404 | Resource doesn't exist or isn't visible to your credential. Resource-specific 404s use their own codes (AccountNotFound, QuoteNotFound, …). |
NotImplemented | 501 | Endpoint exists in the API surface but isn't implemented yet. |
InternalError | 500 | Unexpected server failure. Safe to retry. |
BadGateway | 502 | A banking partner returned a server error on this request. Contact support rather than retrying in a loop. Distinguish from ServiceUnavailable, which is the same partner being unreachable and is worth retrying. |
ServiceUnavailable | 503 | A backend Seismic depends on is temporarily down. Retry after the Retry-After header (seconds), with exponential backoff. Without a Retry-After header, the resource itself is final (a quote that could not be fully priced): retrying it can't help, so create a new one as the message says. |
RateLimited | 429 | Request volume is being throttled. Slow down and retry after the Retry-After header (seconds). |
Unknown | N/A | Client-side fallback when the response body couldn't be parsed. |
Validation violations
When an InvalidRequest failure can be pinned to specific fields, the error body carries a violations array alongside message. Render the entries inline next to the offending inputs instead of parsing message.
{
"ok": false,
"error": {
"code": "InvalidRequest",
"message": "2 fields need attention",
"violations": [
{
"field": "account_holder.address.line1",
"reason": "missing",
"description": "should not be empty"
},
{
"field": "business_type",
"reason": "not_in_allowed_set",
"description": "must be one of the supported values",
"allowed": [
"b_corporation",
"c_corporation",
"limited_liability_company"
]
}
]
}
}field: dotted path into the request body you sent, with zero-based array indices (associated_persons.0.role). Omitted when the failure isn't tied to a single field.reason: stable snake_case slug, safe to switch on:missing,invalid_format,too_short,too_long,not_in_allowed_set,unknown_field,out_of_range,other. New slugs may be added. Treat unknown slugs likeother.description: human-readable constraint detail. English-only. Suitable for developer tooling and logs.allowed: accepted values forfield, present whenreasonisnot_in_allowed_set. Omitted for very large value sets (e.g. the industry catalog). Consult the field's API reference instead.
Not every InvalidRequest carries violations. Treat the array as an optional enrichment, with code + message as the baseline contract.
Retry guidance
Handling specific cases
A few codes are worth dispatching on rather than treating as a generic failure:
QuoteExpired: re-create the snapshot, show the new price, let the user re-confirm.AlreadyAccepted, treat as success: a prior request won the race.QuoteIdInvalid: UI sent an id that isn't in the snapshot. Log + report.CustomerNotVerifiedvsCustomerOnHold, both on account creation: the first means verification hasn't finished (retry when it does), the second means a verdict went against the customer (don't retry —statussays whether the hold is clearable).VirtualAccountNotApproved, on virtual bank account creation: the customer is verified, but not for this currency. Retrying won't help — this clearance is granted per currency, on its own timeline. ReadGET /v0/customers/{id}/virtual-account-approvalsto see which currencies are open to them today, and offer one of those instead.InvalidRequestwith afiles+missingviolation, on accept only: the destination corridor requires a compliance attachment (an invoice or similar payment-purpose document). Collect one and retry the accept withfilespopulated. See Document required.
