Skip to content
Seismic Orchestration

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: literal false, present only on errors. Success responses are flat payloads (e.g. { "id": "f01e…", "status": "awaiting_funds", … }) with no ok field. Callers that want body-level dispatch can check body.ok === false. HTTP status is still authoritative. This mirrors the semantics of fetch's Response.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 the x-request-id header. Quote it when contacting support.

Status codes

The HTTP status categorises. The code field identifies.

StatusMeaningTypical causes
400Bad requestMalformed body, invalid enum, rule violation (see codes below).
401UnauthenticatedMissing / expired access token. Refresh and retry.
403Not allowedAuthed, but you don't own the resource.
404Not foundUnknown id, or scoped-away (also returned for "not yours").
409ConflictDuplicate row, idempotency violation, or the resource's current state blocks the action (unverified customer, account still holding a balance).
413Payload too largeUploaded document exceeds the per-document size cap.
422Unprocessable entitySemantic failure: no eligible options, snapshot not ready, …
429Rate limitedToo many requests. Back off and retry after the Retry-After header (seconds).
5xxServer-sideService 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

codeStatusMeaning
OrgNotFound404Unknown org id (or credential isn't scoped to it).
NotOrgMember403Authed, but the user isn't a member of the requested org.
LastOwner409Demote / remove would leave the org with zero owners. Promote someone first.
KybProfileIncomplete422Required KYB fields are missing or malformed. See the missing (absent) and violations (present-but-malformed) arrays on the error body.
KybNotApproved403Action requires the org's KYB verification to be approved — e.g. registering customers before the org has passed KYB.
KybDocumentTooLarge413Uploaded document exceeds the size cap. See max_bytes / received_bytes on the error body.
UnsupportedDocumentType400Uploaded 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.
VerificationPaused409A compliance hold is in place. The profile stays editable, but submissions are refused until the hold clears. KYB and KYC.
ResubmitUnchanged409Resubmission requested, but the saved profile is byte-identical to the last submission. Edit a field or replace a document first. KYB and KYC.

Auth

codeStatusMeaning
PasswordTooShort400Password below minimum length.
EmailAlreadyExists409Signup email already registered.
InvalidCredentials401Bad email/password on login.
MfaChallengeInvalid401Wrong code, expired challenge, or clock skew > 30s.
RefreshTokenInvalid401Refresh token expired or revoked. Send the user to login.
PasswordResetTokenInvalid400Reset token doesn't exist, was already used, or expired. Request a new reset email.
EmailVerificationTokenInvalid400Verification token doesn't exist, was already used, or expired. Request a new verification email.
ComplianceEmailTokenInvalid400Compliance-email verification token doesn't exist, was already used, expired, or the org has since changed the target address. Request a new verification link.
EmailNotVerified403Action requires a verified email. Verify first, then retry.
SignupNotAllowed403Signups 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

codeStatusMeaning
BothCredsProvided400Request had both Api-Key: and Authorization: Bearer headers. Pick one.
ApiKeyNotFound404Unknown key id (or belongs to a different org).
ApiKeyRevoked401Key was valid but is now revoked. Mint a fresh one.
AlreadyRevoked409Revoke called on a key that was already revoked.
ApiKeyRevokeForbidden403You can see the key but can't revoke it. Only owners, admins, or the key's creator can.
ApiKeyIpNotAllowed403Key authenticated fine, but the caller's IP isn't in the key's allow-list. See IP allow-listing.
ApiKeyAccessDenied403Key authenticated fine, but its access level doesn't permit this request. access carries the key's level. See Access levels.

Customers + accounts

codeStatusMeaning
CustomerNotFound404Unknown id (or belongs to a different org).
EndUserCustomerExists409This (org, reference_id) pair is already onboarded.
KycProfileIncomplete422Required KYC fields are missing or malformed. See the missing (absent) and violations (present-but-malformed) arrays on the error body.
CustomerNotVerified409Account 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.
CustomerOnHold409The 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.
VirtualAccountNotApproved409The 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.
IdempotencyKeyConflict409The Idempotency-Key on a customer create was already used to create a customer of a different type.
CustomerHasActiveOrders409The customer is referenced by an order that hasn't reached a terminal state, so it can't be deleted.
AccountNotFound404Unknown account id.
AccountHasActiveOrders409The account is the source or destination of an order that hasn't reached a terminal state, so it can't be deleted.
AccountHasBalance409The account still holds a non-zero balance, so it can't be deleted. Withdraw the funds first.
AccountIsLiquidationDestination409One 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.
RailNotAllowed400Rail 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).
MissingAccountFields400A required field is absent (e.g. details.account_number on fiat/external, address on crypto/external).
InvalidAccountFields400A 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, …).
RailNotApprovedForOrg403Your org isn't approved to transact on this rail yet. See rail on the error body. Contact support to enable it.
CurrencyNotApprovedForOrg403Your org isn't approved to transact in this currency yet. See currency on the error body. Contact support to enable it.
FlowNotApprovedForOrg403Your org isn't approved for this account-flow family. See flow ("virtual" or "external") on the error body. Contact support to enable it.
OrgSelfTransactingDisabled403The 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

codeStatusMeaning
CorridorUnsupported422This 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."
NoNamedQuote422A 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.
RateUnavailable422Both currencies are known, but no reference rate exists for the pair right now.
QuoteNotFound404Snapshot id unknown (or not yours).
QuoteNotReady422Snapshot is still pending. Poll GET /quotes/:id.
QuoteExpired422The chosen quote's expires_at has passed. Re-create a snapshot.
QuoteIdInvalid400quote_id in the accept body isn't in snapshot.quotes[].
AlreadyAccepted409Snapshot has already been turned into an order.
OrderNotCancelable409The order can no longer be canceled. Only orders still awaiting_funds or pending are cancelable. See status on the error body.
OrderNotAwaitingDocuments409A 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.
AmbiguousCurrency400Account supports multiple currencies. Pin one via { account, currency }. See currencies on the error body.
CustomerOnHold409The 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.
OrgSelfTransactingDisabled403The 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

codeStatusMeaning
InvalidRequest400 / 422Catch-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.
Unauthorized401Missing or invalid bearer token.
Forbidden403Authed, but not permitted.
MfaRequired403Authed, but the resource needs an MFA-active session. Send the user to enrol an authenticator, then sign back in.
NotFound404Resource doesn't exist or isn't visible to your credential. Resource-specific 404s use their own codes (AccountNotFound, QuoteNotFound, …).
NotImplemented501Endpoint exists in the API surface but isn't implemented yet.
InternalError500Unexpected server failure. Safe to retry.
BadGateway502A 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.
ServiceUnavailable503A 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.
RateLimited429Request volume is being throttled. Slow down and retry after the Retry-After header (seconds).
UnknownN/AClient-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 like other.
  • description: human-readable constraint detail. English-only. Suitable for developer tooling and logs.
  • allowed: accepted values for field, present when reason is not_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.
  • CustomerNotVerified vs CustomerOnHold, 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 — status says 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. Read GET /v0/customers/{id}/virtual-account-approvals to see which currencies are open to them today, and offer one of those instead.
  • InvalidRequest with a files + missing violation, on accept only: the destination corridor requires a compliance attachment (an invoice or similar payment-purpose document). Collect one and retry the accept with files populated. See Document required.