Errors
One body for every failure. Branch on its code; here is every code a key can meet, and what to do.
The 51 codes an API key can meet, from the API's error catalog and its SDK surface. Each endpoint lists the ones it can answer and links here.
The error body
An error body
{ "error": "Invalid request", "code": "validation_failed", "details": { "errors": [{"field":"name","message":"name must be at least 2 characters"}] }, "requestId": "0b9f6f3e-7c1a-4d8e-9a51-2f4c8e7d1a90"}error: A sentence, safe to show to a person. Never branch on it.code: The stable identifier to branch on, always present, with the HTTP status.details: Structured data for the codes that have some (below). Fields are named as you sent them (frequency.unitwhen nested), from the body, path or query, every failing one at once.requestId: The request's id, also theX-Request-Idheader. Log it; quote it to support.
errors-handling.sh
# A failure answers its HTTP status and { error, code, details, requestId }: branch on the status# and the code, quote requestId to support. An order nobody paid yet is not an error here: the# page has no item.curl "https://api.qbitflow.app/v2/payments?reference=order-1042" \ -H "X-API-Key: $QBITFLOW_API_KEY"# 200: a page of Payment ({ items, hasMore }), items empty when nobody paid that ordererrors-and-retries.ts
try { const payment = await client. payments. getByReference( 'order-1042'); console.log(`order-1042 paid: ${payment.amountUsd} USD`);} catch (err) { if (err instanceof NotFoundError) { console.log('no payment for order-1042 yet'); } else if (err instanceof ValidationError) { // Refused before sending (status undefined) or by the API (400). for (const f of err.fieldErrors) console.log(`${f.field}: ${f.message}`); } else if (err instanceof ApiError) { // Branch on status and code, never on the message. Quote requestId to support. console.log(`QBitFlow error ${err.status} ${err.code} (request ${err.requestId})`); if (isRetryable(err)) console.log('transient: try again later'); } else { throw err; }}errors_and_retries.py
try: payment = client. payments. get_by_reference( "order-1042") print("paid:", payment.uuid)except qbitflow.ValidationError as exc: # status None: refused before sending for field_error in exc.field_errors: print(f"{field_error.field}: {field_error.message}")except qbitflow.NotFoundError: print("no payment for order-1042 yet")except qbitflow.ApiError as exc: # the base of every error the SDK raises print(f"QBitFlow error {exc.status} {exc.code} (request {exc.request_id})") if qbitflow.is_retryable(exc): # network, 5xx, 429: worth trying again later print("transient: try again later")errors_and_retries/main.go
payment, err := client. Payments. GetByReference( ctx, "order-1042")var ( validation *qbitflow.ValidationError notFound *qbitflow.NotFoundError apiErr *qbitflow.APIError)switch {case err == nil: fmt.Println("order-1042 paid by", payment.UUID)case errors.As(err, &validation): // StatusCode 0: refused before sending; 400: refused by the API. for _, f := range validation.FieldErrors { fmt.Printf("%s: %s\n", f.Field, f.Message) }case errors.As(err, ¬Found): fmt.Println("no payment for order-1042")case errors.As(err, &apiErr): // Every SDK error embeds an *APIError: branch on Code, quote RequestID to support. fmt.Printf("QBitFlow error %d %s (request %s), retryable: %v\n", apiErr.StatusCode, apiErr.Code, apiErr.RequestID, qbitflow.IsRetryable(err))default: log.Fatal(err)}errors-and-retries.php
try { $payment = $client->payments->getByReference( 'order-1042'); echo "order-1042 was paid by {$payment->uuid}\n";} catch ( \QBitFlow\Exceptions\ValidationException $e) { foreach ($e->fieldErrors as $fieldError) { // refused before sending (status 0), or a 400 echo "{$fieldError->field}: {$fieldError->message}\n"; }} catch ( \QBitFlow\Exceptions\NotFoundException) { echo "No payment for order-1042 yet\n";} catch ( \QBitFlow\Exceptions\ApiException $e) { // Branch on apiCode, never on the message; quote the request id to support. printf("QBitFlow error %d %s (request %s), retryable: %s\n", $e->status, $e->apiCode, $e->requestId, $e->isRetryable() ? 'yes' : 'no');}Common codes
Any route may answer these, as its guards and inputs allow.
400 · Bad request
validation_HTTP 400failed path/query/body failed binding or validation, or a business rule about one input (a field named in
errors)What to do: Fix the inputs
details.errorslists: eachfieldis the name you sent (dotted when nested), and every failing input is reported at once.details:
errors: [{ field, message, reason? }]; with some,max/min(decimal strings for money)foreign_HTTP 400key_ violation a referenced resource does not exist
What to do:
details.fieldnames the reference that doesn't exist: check that id.details:
fieldinvalid_HTTP 400signature POST /: the signature doesn't match, or is older than 5 minutes; a send route: the transaction isn't the payer's signature over what the intent prepared (Payments and transactions § Sending a signed transaction)webhooks/ verify test_HTTP 400mode_ only something only test mode does asked in live mode (a test bill, a faucet, a test payment)
sender_HTTP 400is_ receiver a transfer whose paying wallet is the wallet it pays (a payment, a subscription, a refund, a release)
401 · Unauthorized
403 · Forbidden
forbiddenHTTP 403authenticated but not allowed (role, peer resource)
What to do: The key can't do this: the route needs an admin key, the
On-Behalf-Ofvalue isn't a member this key may act for, or a key wrote amembers.*policy (policies change in the dashboard).policy_HTTP 403disabled a member doing what their organization's policy doesn't allow them, reads of what the policy governs included
What to do: A member's key, while the organization's policy
details.policyis off for members: the organization turns it on in its settings.details.fieldnames the input it refuses (a checkout'ssuccessUrlorcancelUrl,members.): send it without. Organization keys never get it.checkoutRedirects details:
policy;fieldwhen the policy governs a request field (members., on by default: a member'scheckoutRedirects successUrl/cancelUrl,routes/)checkout-sessions. md processing_HTTP 403fee_ set_ by_ organization a member's checkout whose
fees.differs from their organization'sprocessingFee checkout.(Checkout sessions § Fees)customerPaysProcessingFee details:
field(fees.)processingFee
404 · Not found
not_HTTP 404found unknown or out-of-scope resource; also any route hidden by a guard
What to do: Check the id and the key's mode (test or live): a resource of another space answers the same, so existence is never revealed.
405
method_HTTP 405not_ allowed the path exists, not with this method; the
Allowheader lists its methods. Answered before any credential is read, like the404of a path no route has
409 · Conflict
unique_HTTP 409violation a unique value already exists (e.g. a duplicate
referenceoremail)What to do:
details.fieldnames the value already used in this space (the value itself is never echoed): pick another one.details:
field(never the value)conflictHTTP 409a state conflict no more specific code covers
tx_HTTP 409already_ sent a transaction sent already: running, or completed (Payments and transactions § Sending a signed transaction)
held_HTTP 409funds_ pending removing a member (
DELETE /,members/ :userUuid DELETE /for an admin who kept a personal space), or closing an organization, while the organization holds a member's live funds: release them first (Members and held funds); deleting the organization's live wallet of a chain (team/ :userUuid DELETE /) while it holds members' live funds in that chain's currencies, which their release is paid from (Wallets)wallets/ :uuid details:
DELETE /:wallets/ :uuid heldEntries(an integer: the held lines on that chain not settled yet)member_HTTP 409has_ no_ wallet trusting a member (
POST /) whose own wallets accept none of the organization's currencies: trusted, nobody could pay them (their checkouts would all answermembers/ :userUuid/ trust merchant_), and trust can't be undone. Trust them once they've added one (Members and held funds)not_ ready merchant_HTTP 409not_ ready opening a checkout nobody could pay: the space's wallets (the organization's, while it holds the member's funds) accept no currency, or no token for a subscription; a subscription for a member whose funds the organization holds (
fundsHeld: one-time payments only until trusted) (Checkout sessions); at a payer's intent (POST /,hosted/ payments/ intent POST /), a member's checkout whose organization takes a fee on it and has no wallet on the paid currency's chain anymore (hosted/ subscriptions/ intent organizationFeeNoWallet: a guard, the fee never dropped from a new payment; removing the organization's wallet of a chain already removes that chain's currencies from its members, so an open checkout meetscurrency_first; a subscription signed before keeps billing without the fee, Members and held funds)not_ accepted details:
reason:noWallet,organizationNoWallet,noTokenWallet,fundsHeld,organizationFeeNoWalletidempotency_HTTP 409key_ in_ use the
Idempotency-Key's first request is still running (idempotency): retry once it answeredWhat to do: Retry once the first request has answered, with the same key.
410 · Gone
merchant_HTTP 410closed a checkout of a removed member's or a closed organization's space: it takes no payment (Checkout sessions)
413 · Content too large
request_HTTP 413too_ large an
Idempotentroute's body over 1 MiB (idempotency)
422 · Unprocessable content
idempotency_HTTP 422key_ reused the
Idempotency-Keywas used for another request (idempotency): send a new keyWhat to do: Send a new key: one key names one operation.
429 · Too many requests
rate_HTTP 429limit_ exceeded rate limit hit (see below)
What to do: Wait
details.(also sent as theretryAfterSeconds Retry-Afterheader), then send the same request again, with the sameIdempotency-Key: the refused one did nothing.details:
limit,periodSeconds,retryAfterSeconds;limitedBy(and what it counts) on a route with several limits
500 · Internal server error
internalHTTP 500server error
What to do: Retry a read or an idempotent create; quote
requestIdwhen asking support.
Codes of particular areas
409 · Conflict
already_HTTP 409joined inviting someone already in the organization with that role and mode (Invitations)
subscription_HTTP 409already_ stopped_ or_ inactive cancelling a subscription, raising its allowance or its maximum, once it is
stopped(it ends with its paid period; customer-side routes), a merchant'sPOST /of a stopped one, or a merchant'ssubscriptions/ :uuid/ cancel?immediate=false POST /orsubscriptions/ :uuid/ cancel /test-billof a cancelled one
504 · Gateway timeout
timeoutHTTP 504the request ran past the server's deadline: retry (idempotent reads) or check the resource before retrying a write
What to do: Retry a read; check the resource before retrying a write.
Chain refusals
What a network refuses (a revert, a program's error, a node's refusal) is decoded the same way on
every chain. A key reads them as a checkout's failed attempt (lastAttempt.code on
GET /checkout-sessions/failureCode. details.reason names the source, for support only: never branch on it.
400 · Bad request
tx_HTTP 400mismatch The payer sent what QBitFlow didn't prepare, or too late (§ Sending a signed transaction).
tx_expiredalso ends an EVM token payment whose network fee fell too far since its intent, and a Solana transaction whose blockhash expired before it landed: create the intent againtx_HTTP 400expired The payer sent what QBitFlow didn't prepare, or too late (§ Sending a signed transaction).
tx_expiredalso ends an EVM token payment whose network fee fell too far since its intent, and a Solana transaction whose blockhash expired before it landed: create the intent againtx_HTTP 400replaced The payer the wallet replaced the transaction with another one (a cancel, other values): it never ran (a speed-up with the same values is followed)
409 · Conflict
subscription_HTTP 409not_ active The payer asked for what the subscription's state refuses
already_HTTP 409exists The payer asked for what the subscription's state refuses
payment_HTTP 409not_ due The payer asked for what the subscription's state refuses
cannot_HTTP 409cancel_ overdue The payer asked for what the subscription's state refuses
no_HTTP 409change The payer asked for what the subscription's state refuses
payment_HTTP 409in_ flight The payer sent a token payment or a subscription (both sent and paid for by QBitFlow's operator) while another one from the same wallet in the same token is still confirming, or failed under 2 minutes ago: retry in a couple of minutes. Every casing of an EVM address is the same wallet
422 · Unprocessable content
insufficient_HTTP 422funds The payer doesn't hold enough of the token (or SOL, for a native payment): top up
insufficient_HTTP 422native_ funds The payer can't pay the network fee (ETH, SOL…): top up the network's coin
insufficient_HTTP 422allowance The payer granted QBitFlow less than this amount: increase the allowance
allowance_HTTP 422expired The payer granted an allowance that expired: approve again
token_HTTP 422not_ approved The payer hasn't approved Permit2 for the token (EVM): approve it, then retry
signature_HTTP 422expired The payer signed too long ago: sign again (a free trial's message: ask a new intent)
max_HTTP 422amount_ exceeded The payer a bill above the subscription's maximum per period (the price moved): approve a higher maximum
invalid_HTTP 422amount The payer asked for an amount outside the subscription's limits (
allowance_, Solana only: a top-up of fewer periods than were spent since the last one granted, which its program refuses until its upgrade;too_ low details., the fewest periods it takes)minPeriods allowance_HTTP 422too_ low The payer asked for an amount outside the subscription's limits (
allowance_, Solana only: a top-up of fewer periods than were spent since the last one granted, which its program refuses until its upgrade;too_ low details., the fewest periods it takes)minPeriods max_HTTP 422increase_ exceeded The payer asked for an amount outside the subscription's limits (
allowance_, Solana only: a top-up of fewer periods than were spent since the last one granted, which its program refuses until its upgrade;too_ low details., the fewest periods it takes)minPeriods max_HTTP 422below_ current_ cycle The payer asked for an amount outside the subscription's limits (
allowance_, Solana only: a top-up of fewer periods than were spent since the last one granted, which its program refuses until its upgrade;too_ low details., the fewest periods it takes)minPeriods unsupported_HTTP 422token The payer uses a token the network doesn't support
tx_HTTP 422failed The payer can't act: the transaction failed for a reason no decoder recognized
transaction_HTTP 422refused The payer can't act: QBitFlow's side (reported, fixed by QBitFlow)
503 · Service unavailable
Codes only the dashboard meets
The other codes answer the dashboard's own routes (sign-in, the team, re-authentication, wallet signing): an API key never reaches them.
The 46 codes only the dashboard meets
token_expiredsession_endedrefresh_conflictplan_requiredinvalid_confirmation_tokeninvalid_unsubscribe_linkclient_closed_requestblockchain_errorinvalid_credentialsno_organizationstaff_view_read_onlystaff_view_endedinvalid_oidc_codeinvalid_onboarding_tokenidentity_already_linkedidentity_in_uselast_sign_in_methodupstream_errorreauth_requiredinvalid_passwordreauth_method_unavailableinvalid_invitationinvitation_email_mismatchwebhook_endpoint_not_failingintent_requiredtrial_not_cancellablecancellation_too_earlytrial_already_usedsubscription_not_pausablesubscription_not_pausedbill_in_progresssignature_already_usedcurrency_not_acceptedrefunds_disabledrefund_window_expiredrefund_already_answeredrefund_decided_by_organizationnothing_to_releaseheld_funds_owedheld_funds_changedheld_funds_release_in_flightrefund_in_flightheld_funds_releasedrefund_already_existsemail_refusedwallet_has_active_subscriptions