API v3
v3 is where the BlockWyre API is going. It is served under /v3, next to /api/v2, and replaces it resource by resource. A v2 page that has a v3 replacement says so at the top and links to it. Both keep working during the transition (see Versioning).
| Base URL | https://api.blockwyre.com/v3 (sandbox: https://api-sandbox.blockwyre.xyz/v3). See Environments. |
| Authentication | Authorization: APIKey <signed-jwt>, the same header as /api/v2. See Authentication. |
| Content type | application/json |
| Rate limit | 100 requests per minute per client IP, shared by every key used from that IP. See Rate limits. |
What is in v3
| Section | Covers | Replaces in v2 |
|---|---|---|
| Balance | Balances per account and bank account | Balance |
| Customer | Creating, listing and reading the workspace's accounts | Account: create, list, get |
| Individual verification (KYC) | A person's verification record, its documents, and the hand-off to the verifier | Individual verification |
| Business verification (KYB) | A business's verification record, its documents and its beneficial owners | Business verification |
| Bank account | An account's bank accounts, their deposit instructions, movements and totals | Deposit Instructions, now per bank account; the rest is new |
| ACH pull | Funding an account's USD bank account by debiting the holder's own account at another bank | — (new) |
| Reports | Volume, fee and transaction reports over a date range | — (new) |
| Crypto (beta) | Crypto wallets, counterparties and withdrawals; beta, enabled per workspace. Until BlockWyre enables it, creating a wallet, saving, renaming or deleting a counterparty, and previewing or creating a withdrawal answer 404 feature_not_enabled | Crypto: wallets, beneficiaries, withdrawals |
What is different from v2
IDs are TypeIDs
Every id is a TypeID: a prefix naming the kind of thing, an underscore, and the UUID you know from v2 encoded in base32.
| Prefix | Is |
|---|---|
cst_ | an account (v2's accountId) |
bnk_ | a bank account |
bpa_ | a pull account: the holder's account at another bank that an ACH pull debits |
ach_ | an ACH pull |
kyc_ | a person's verification record, including a business's beneficial owner |
kyb_ | a business's verification record |
doc_ | a verification document |
wlt_ | a crypto wallet, or a crypto counterparty (an address you send to) |
wtx_ | a crypto withdrawal |
tnt_ | a workspace |
apk_ | an API key |
cst_01h455vb4pex5vsknk084sn02q and the UUID 01890a5d-ac96-774b-bcce-b302099a8057 are the same account. v3 paths and query parameters take the TypeID form only, and a raw UUID is refused with 400. Any TypeID library converts between the two.
Field names are snake_case
account_id, bank_account_id, pending_credits, never accountId. This applies to query parameters too.
Money is an object
{ "amount": "1250.75", "currency": "USD" }
amount is a decimal string, never a JSON number, so no digit is lost on the way.
Crypto withdrawals (beta) are the exception: their amount, fee and total are plain decimal strings next to the coin, not money objects. See Crypto v3.
Errors
An error answers with this body, 4xx or 5xx:
{ "code": 404, "message": "bank_account_not_found" }
| Field | Is |
|---|---|
code | The HTTP status, repeated. |
message | A stable snake_case code to branch on (insufficient_balance, bank_account_not_found). Each endpoint lists its own. |
details | Only when there is more to say, for example the fields a 422 validation failure names. |
Some errors also carry errorCode, the HTTP status text ("Bad Request"); branch on message, not on it.
Three message values are not snake_case codes:
"failed to parse request"(400): the body is not valid JSON, or a value has the wrong type (for example, a malformed id)."Validation failed"(422), withdetails: a field is missing or out of range."rate limit exceeded"(429): too many requests from your IP address; waitRetry-Afterseconds. See Rate limits.
{
"code": 422,
"errorCode": "Unprocessable Entity",
"message": "Validation failed",
"details": [{ "field": "body.email", "message": "email is a required field" }]
}
Two answers have no JSON body at all: a path that does not exist answers 404 with the plain text 404 page not found, and a method the path does not take answers 405 with an empty body.
401 unauthorized means the key, the signature or a claim failed, or the endpoint does not take this kind of key; 403 forbidden means the key authenticated but may not make this call. See 401 or 403 and Error Handling.
Lists
Paginated lists take page (from 1) and limit (default 25, at most 100), and answer with this envelope. A limit over 100 is read as 100, not refused, and the response's page and limit are the ones actually used, so total_pages is computed from them. next_cursor and prev_cursor are reserved for cursor pagination and are not returned yet.
{
"data": [],
"page": 1,
"limit": 25,
"total_items": 0,
"total_pages": 0
}
An endpoint that returns a whole set at once (for example, balances) says so on its page. Only List Customers takes a sort parameter; see Pagination and Sorting.
Which account a request acts for
- An account-scoped key acts for its own account only. It does not need to name the account; naming a different one is refused (
403), and another account's resources read as404. - A workspace-scoped key acts for the whole workspace. Endpoints that act on one customer's money take an
account_idquery parameter, and refuse the request without it (400 account_id_required). Each endpoint says whether it takes one. - The workspace's own operations (listing, reading and creating customers, running their KYC and KYB) take a workspace-scoped key or a dashboard session. An account-scoped key is refused with
401.
Every key, workspace-scoped or account-scoped, holds the same fixed permission set, the API Key role, from the moment it is created: there is no role to assign. It covers everything this reference documents. Members, roles, API keys, workspace settings and account lifecycle actions (block, freeze, unfreeze) stay in the dashboard, and their endpoints refuse any key. See Key types and permissions.
Safe retries: Idempotency-Key
The writes listed below take an Idempotency-Key header: one key per intended operation, sent again unchanged on every retry of that same operation. A key is any printable-ASCII string of 1 to 255 characters; a UUID is recommended. An empty Idempotency-Key counts as missing.
| Endpoint | Without the header |
|---|---|
POST /v3/customers | Refused: 400 idempotency_key_required. |
POST /v3/banking/pull-accounts | Refused: 400 idempotency_key_required. |
POST /v3/banking/ach-pulls | Refused: 400 idempotency_key_required. |
POST /v3/bank-accounts | Grace period for API keys until 2027-01-18: the request runs without the protection, and its response carries a Warning header. Dashboard sessions are refused already. |
POST /v3/crypto/wallets | Same grace period as POST /v3/bank-accounts. |
POST /v3/crypto/withdrawals | Same grace period as POST /v3/bank-accounts. |
The grace-period warning reads:
Warning: 299 - "Idempotency-Key will be required on this endpoint from 2027-01-18"
| What happens | Answer |
|---|---|
| A retry with the same key and the same request, within 24 hours | The first response again, with Idempotency-Replayed: true. The operation is not run twice. |
| The same key more than 24 hours after the first request | The key has been forgotten: the request runs as a new one. |
| The same key on the same endpoint with a different request (other path values, query string or body) | 422 idempotency_key_conflict |
| The same key while the first request is still running | 409 idempotency_in_progress, with Retry-After |
| A key longer than 255 characters, or with a character outside printable ASCII | 400 idempotency_key_invalid |
| A keyed request whose body is over 1 MiB | 413 body_too_large: a request signed with an API key is capped at 1 MiB before its Idempotency-Key is read. (413 idempotency_body_too_large is only reachable from a dashboard session.) |
| The key store is unavailable | 503 idempotency_unavailable. Nothing ran: retry later with the same key. |
- What is replayed.
2xxand4xxanswers are stored and replayed byte for byte. A5xxis not stored, so a retry after one runs the request again. - Headers. The response of a run or of a replay echoes
Idempotency-Keyand carriesIdempotency-Replayed: false(it ran now) ortrue(a replay). - Scope. A key belongs to one caller (one API key, or one dashboard user) and one endpoint (method and route): two callers, or two endpoints, can use the same key without colliding.
- Same request means the same path, query string and exact body bytes. Re-serializing the body differently on a retry makes it a different request.
Each endpoint's page says whether it takes the header.
Support and Resources
If you need assistance or have any questions, our support team is here to help. You can contact our support team at support@blockwyre.com.
Stay Updated
Stay up-to-date with the latest news, updates, and features from BlockWyre by following us on social media:
- Instagram: BlockWyre Instagram
- Twitter: BlockWyre Twitter
- Facebook: BlockWyre Facebook
- LinkedIn: BlockWyre LinkedIn
We are excited to have you on board and look forward to seeing how you leverage BlockWyre's powerful tools to enhance your financial operations. Happy integrating!