Create an account
Pick the endpoint matching the (kind, origin) of the account you're creating:
| Endpoint | When to use |
|---|---|
POST /customers/:id/accounts/fiat/virtual | Provision a new, on-platform bank account for the customer. |
POST /customers/:id/accounts/fiat/external | Register an existing bank account, the customer's own or a third party's. |
POST /customers/:id/accounts/crypto/virtual | Provision a new, on-platform wallet for the customer. |
POST /customers/:id/accounts/crypto/external | Register a wallet the customer already controls. |
Shared semantics
The four endpoints share a common response shape and a common idempotency story.
Idempotency
All four endpoints honor the Idempotency-Key HTTP header. Re-POSTing the same (org, key) returns the existing AccountCreationRequest in its current state. Seismic won't insert a duplicate or retry a previously failed registration.
curl -X POST ".../accounts/crypto/external" \
-H "Api-Key: $SEISMIC_ORCHESTRATION_API_KEY" \
-H "Idempotency-Key: my-attempt-001" \
-H "content-type: application/json" \
-d '{ ... }'The header is optional. Omit it and the server mints a key for the underlying request row.
Response shape
Every create endpoint returns the same envelope:
{
"request": { /* AccountCreationRequest */ },
"account": { /* Account | null */ }
}request: theAccountCreationRequest. Carries the lifecycle of the creation. Re-POSTing the sameIdempotency-Keyreturns this row in its current state.account:Account | null. When non-null it's the sameAccountshape you'd get back fromGET /accounts/:id.nullmeans the account row hasn't materialized yet.externalendpoints populateaccountsynchronously (caller supplied the details).virtualendpoints populateaccountas soon as Seismic finishes provisioning: inline when provisioning completes during the request, otherwise on a later poll ofGET /customers/:id/accounts/requests/:request_id(request.status === "completed"⇒request.account_idis set).
Currency / rail references
Where a body field accepts a currency or a rail — currency, the external fiat rails, a crypto account's rail — both forms are accepted:
- By name (case-insensitive):
"USDC","ethereum","USD". - By id (the stable numeric id from the seed JSON):
5,7.
Pass whichever is convenient. Unknown names return 404.
Common errors
- 400
MissingAccountFields/InvalidAccountFields: caller posted a body that doesn't match the chosen endpoint's required fields, or a field failed format validation (EVM address, IBAN structure, routing-number length, …). - 403: caller isn't a member of the customer's owning org.
- 403
FlowNotApprovedForOrg/RailNotApprovedForOrg/CurrencyNotApprovedForOrg: your org isn't approved for this account flow, rail, or currency yet. - 404: unknown customer id, unknown currency or rail.
- 409
CustomerNotVerified: the customer's verification hasn't reachedapproved. See Verification is required first. - 409
CustomerOnHold: the customer's verification carries a negative verdict.
Verification is required first
All four endpoints require the customer's rolled-up verification status to be approved. A customer still in draft, submitted, or under_review is rejected with CustomerNotVerified (409); the error body's status carries that rolled-up value, so you can tell what the customer still owes. Once a negative verdict lands (paused, rejected, terminally_rejected) the code is CustomerOnHold instead — the two are the difference between "not finished" and "declined", and are worth branching on.
Quoting is looser: it refuses only a negative verdict, so a customer still in review can be quoted but cannot yet hold an account.
Authoring
Pick one endpoint per account. Each has its own page with the full body, response, and curl examples:
- Provision a virtual bank account
- Register an external bank account
- Provision a virtual crypto wallet
- Register an external crypto wallet
