Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.seismic-cards.systems/llms.txt

Use this file to discover all available pages before exploring further.

Seismic uses an OAuth 2.0 two-step authorization code grant. You first exchange your clientId for a short-lived authorization code, then exchange that code along with your clientSecret for an access token valid for 24 hours. Every subsequent API call includes that token in the x-access-token header.
1

Get your credentials

During onboarding, Seismic provisions a credential bundle for your program:
FieldVisibilityDescription
clientIdPublicIdentifies your program. Sent as a query parameter to /v1/oauth/authorize.
clientSecretServer-side onlySigns OAuth token requests. Treat like a password. Never embed in mobile apps or websites.
parentAccountIdServer-sideUUID of your top-level partner account. Required when creating user sub-accounts.
merchantIdServer-sideUUID of your master merchant account. Used to fund user budgets via inter-account transfers.
defaultBinIdServer-sideUUID of the BIN your program issues from. You may have more than one — see GET /v1/card/bins.
webhookSecretServer-sideUsed to verify HMAC-SHA256 signatures on incoming webhooks.
Storage best practices
  • Store secrets in a dedicated secret manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Doppler, etc.)
  • Rotate clientSecret and webhookSecret on a schedule — 90 days is recommended
  • Never log secrets, commit them to version control, or send them to a client SDK
2

Get an authorization code

Send a GET request to /v1/oauth/authorize with your clientId as a query parameter. This returns a single-use authorization code that expires in 10 minutes.
GET /v1/oauth/authorize?clientId=YOUR_CLIENT_ID
Host: sandbox-api.seismic.systems
Response (200):
{
  "code": "000000",
  "message": "success",
  "data": {
    "code": "8655cf76c744615b57e631216ee006df",
    "timestamp": 1758169263
  }
}
The data.code field is the authorization ticket you’ll use in the next step. The outer code field is the response status ("000000" means success).
The authorization code is single-use and expires in 10 minutes. If you don’t exchange it in time, run this step again to get a fresh code.
3

Exchange the code for an access token

Post your clientId, clientSecret, and the authorization code you just received to /v1/oauth/access-token. This returns an access token valid for 24 hours and a refresh token valid for 30 days.
POST /v1/oauth/access-token
Host: sandbox-api.seismic.systems
Content-Type: application/json

{
  "clientId":     "YOUR_CLIENT_ID",
  "clientSecret": "YOUR_CLIENT_SECRET",
  "code":         "8655cf76c744615b57e631216ee006df"
}
Response (200):
{
  "code": "000000",
  "message": "success",
  "data": {
    "accessToken":  "6da5bf8a64b34ea30cfb75a6a5e8a9a28a59b8a3",
    "refreshToken": "560d8accee67fb932688e568feca86c6b3866c4a858dce01b4cd66a73d7cc35a",
    "expiresIn":    86400,
    "timestamp":    1758169264
  }
}
FieldTypeDescription
accessTokenstringPass on every subsequent request as x-access-token: <accessToken>.
refreshTokenstringLong-lived (30 days). Reserved for a future refresh endpoint — for now, re-run steps 2 and 3 to get a new token.
expiresInintSeconds until the access token expires. Default is 86400 (24 hours).
timestampintServer timestamp in seconds.
Run this full two-step flow once and cache the access token in memory or Redis. Re-authenticate when the token is within 60 seconds of expiry.
4

Make your first authenticated call

With an accessToken in hand, list the BINs your program is authorized to issue from. Every program has at least one. The BIN’s id is what you’ll pass as binId in cardholder and card creation requests.
curl "https://sandbox-api.seismic.systems/v1/card/bins?accountId=YOUR_PARENT_ACCOUNT_ID" \
  -H "x-access-token: 6da5bf8a64b34ea30cfb75a6a5e8a9a28a59b8a3"
Response (200):
{
  "code": "000000",
  "message": "success",
  "data": {
    "list": [
      {
        "id":                  "1997881966041939972",
        "bin":                 "411111",
        "type":                0,
        "currencies":          ["USD"],
        "network":             "VISA",
        "supportPhysicalCard": false
      }
    ]
  }
}
FieldTypeDescription
idstringThe BIN UUID — pass as binId in card and cardholder creation requests.
binstringFirst 6 digits of the card number.
typeint0 = Budget BIN (use these for the standard issuance flow). 1 = Prepaid BIN.
currenciesstring[]Supported currencies. Currently ["USD"].
networkstringVISA or MASTERCARD.
supportPhysicalCardboolWhether physical cards are supported on this BIN.
If you received a 200 with "code": "000000", you’re authenticated and ready to issue cards. Continue to Issuing a Card.

Required headers

Include these headers on every API call:
HeaderWhen requiredValue
x-access-tokenAll authenticated callsThe access token from step 3.
Content-TypeAll requests with a bodyapplication/json
Idempotency-KeyMutating endpoints: POST /v1/cardholders, POST /v1/budget-card, POST /v1/budgets, POST /v1/budgets/{id}/transfer-in, POST /v1/budgets/{id}/transfer-out, POST /v1/business/transfer/externalA UUID v4 you generate per logical operation. Re-using the same key with the same body returns the same response — safe to retry on network errors.

Node.js / TypeScript reference implementation

This SeismicAuth class handles token caching and automatic re-authentication. Use it as a starting point for your own server-side integration.
import axios from "axios";

class SeismicAuth {
  private accessToken: string | null = null;
  private expiresAt = 0;

  constructor(
    private base: string,
    private clientId: string,
    private clientSecret: string,
  ) {}

  async getValidToken(): Promise<string> {
    if (this.accessToken && Date.now() < this.expiresAt - 60_000) {
      return this.accessToken;
    }

    // Step A — authorization code
    const { data: codeResp } = await axios.get(
      `${this.base}/v1/oauth/authorize`,
      { params: { clientId: this.clientId } },
    );

    // Step B — access token
    const { data: tokenResp } = await axios.post(
      `${this.base}/v1/oauth/access-token`,
      {
        clientId:     this.clientId,
        clientSecret: this.clientSecret,
        code:         codeResp.data.code,
      },
    );

    this.accessToken = tokenResp.data.accessToken;
    this.expiresAt   = Date.now() + tokenResp.data.expiresIn * 1000;
    return this.accessToken!;
  }
}

Common issues

SymptomCauseFix
40001 Invalid parameter on /v1/oauth/authorizeMissing or malformed clientId query parameterConfirm the credential string and that you’re hitting the correct environment URL.
40399 permission denied on any endpointAccess token expired, revoked, or used against the wrong environmentRe-run the full OAuth flow against the correct base URL.
Response has "code": "000000" but accessToken is emptyAuthorization code was already used or expired (> 10 minutes)Step 2 returns a single-use code. Run it again to get a fresh one.
All calls return 40399 after a long idle periodToken aged out after 24 hoursEnsure your token cache checks expiresIn. The reference implementation above handles this correctly.
For the full error code list, see Error Codes.