Skip to main content

Versioning

All endpoints are prefixed with /v2. The version is part of the URL path, not a request header.

Request Format

All request bodies use JSON (Content-Type: application/json), except file uploads which use multipart/form-data.

Response Format

All responses return JSON.
  • Dates are ISO 8601 strings in UTC (e.g., 2026-01-15T09:30:00.000Z).
  • Optional fields with no value are omitted from the response, never sent as null — a field like clientReferenceId is simply absent when none was supplied.
  • A few fields are nullable — when a field’s description says it can be null (for example the wire-receive bank details on a deposit, or payout.rail on webhook payloads), null means “no value yet / not applicable”, distinct from an omitted optional field. These fields are always present within their parent object — but the parent itself may be optional.
  • Empty collections are returned as [], never null.

IDs

Resources use prefixed IDs (e.g., cus_034A0gCCVsxdV2PjHLx9k1, app_034A0gCCVlvyhfAZssklTs). The prefix identifies the resource type:

Pagination

List endpoints use cursor-based pagination. Pass query parameters to control paging: All list responses use the {data, meta} envelope shown above. meta.mode is "cursor" or "offset" depending on the endpoint.
A cursor marks a place in list order. To walk forward, send meta.nextCursor as cursor with direction=forward. When nextCursor is null on a forward page, there are no more results. To walk back from a page you hold, send its meta.previousCursor as cursor with direction=backward. When previousCursor is null on a backward page, you reached the start of the list. A backward page always carries a nextCursor. Do not loop on nextCursor while direction=backward: it moves you one row, not one page. To filter on several values, repeat the key, for example status=a&status=b. Bracket and comma forms are not accepted. A filter that takes one value refuses a repeated key. The status filter on the webhook delivery list is one example. A cursor is tied to the sort order it was issued under. If you change the sort (sortBy/sortOrder) you must start again from the first page without a cursor — reusing an older cursor under a different sort returns 400 INVALID_CURSOR instead of a misleading page.
Cursors are opaque strings. Do not parse or construct them manually.

Errors

All error responses follow RFC 9457 (Problem Details for HTTP APIs):
Always match on the type field for error handling, not status or title. The type code is stable across API versions.
VALIDATION_ERROR (400), ONBOARDING_NOT_READY (422), and DOCUMENT_IDS_NOT_FOUND (400) include an errors array with one entry per offending field. Each entry has a pointer (JSON pointer to the invalid field), a detail (what is wrong; never echoes the rejected value, except that it can name a routing number), and an optional category (field, document, or individual) when the entry came from the requirements validator. category lets you group blockers without parsing pointers; it’s omitted on top-level schema rejections.
For a complete list of error codes, see the Error Codes Reference.

Idempotency

POST, PATCH, and PUT requests support idempotency via the idempotency-key header. Only successful responses are kept, and they are kept for 30 days:
  • Same key + same body after a success — returns the original response instead of creating a duplicate resource. Replayed responses carry an Idempotency-Replayed: true header.
  • Same key + different body, after a success or a 5xx — rejected with 409 IDEMPOTENCY_KEY_CONFLICT. The key is bound to its first request body for the full 30 days, so a modified payload never silently runs under another request’s key; send a genuinely new request with a fresh key.
  • Retry after a 4xx — client errors commit nothing, so retrying with the same key is re-evaluated against current state, and a corrected body is accepted on the same key (fix a validation error and retry — no new key needed). On order execution, for example, a 422 INSUFFICIENT_FUNDS clears on retry once the balance covers the request. Payouts are the exception: a payout that reached a terminal failed state stays bound to its key, so a same-key retry returns that original payout rather than re-evaluating — use a fresh key to submit a corrected payout.
Use a client-generated UUID or a deterministic key derived from the operation (e.g., onboard-{customer-external-id}).