DevelopersBuild safely

Errors

The v1 error body, request ids, the codes you will meet most, and how the SDK turns errors into classes and retries.

4 min read

Every v1 error has the same JSON body. Branch on code, show or log message, and quote request_id when you write to support@fianto.xyz.

The error body

{
  "statusCode": 409,
  "error": "Conflict",
  "code": "order_already_paid",
  "message": "That order_id is already paid. Use a new order_id to charge again.",
  "request_id": "req_…",
  "field": "order_id"
}
FieldWhat it is
statusCodeThe HTTP status
errorThe HTTP reason phrase, such as Conflict
codeA stable, machine-readable code. Branch on this
messageWhat went wrong and what to do, for people. Always a string
request_idThe request's id, also sent as the X-Request-Id header
fieldThe request field at fault, when there is one
detailsEvery problem, when one request had several

An unexpected server error always reads internal_error with the message "Something went wrong on our side. Quote the request_id when you contact support."

Request ids

Every response carries an X-Request-Id header: req_ followed by 24 characters. If you send your own X-Request-Id of 8 to 64 letters, digits, _ or -, fianto keeps it, so your logs and fianto's share one id.

Codes

The list of codes is open-ended: fianto can add codes, so treat any code you do not know by its HTTP status. When a response has no specific code, fianto uses a general one for its status, such as bad_request, unauthorized, forbidden, conflict, unprocessable or rate_limited.

The codes you are most likely to meet:

StatusCodeMeaning
400validation_failedA field failed validation, or the body has an unknown field. field names the first one, message the first problem and details every problem
401invalid_api_credentialsWrong or disabled credentials, or an account that is not approved and active. See Authentication
400idempotency_key_required, idempotency_key_invalidThe Idempotency-Key header is missing or malformed. See Idempotency
422idempotency_key_reusedThe key was used with a different request
409idempotency_request_in_progressThe first request with this key is still running
409idempotency_response_unreadableThe request already completed; fetch the resource
400amount_or_price_requiredSend price_id, or amount with description
400amount_out_of_rangeThe amount is outside 0.01 to 1,000,000 USDC
400url_insecuresuccess_url or cancel_url is not https
400expires_at_out_of_rangeexpires_at is not 5 minutes to 24 hours away
404price_not_foundNo price with that id belongs to you
409order_already_paidThis order_id is paid; use a new one
409payment_in_progressA payment for this session is being processed, or the last transaction prepared for it could still land
409session_not_reissuableThe session is not open, or has under 120 seconds left
409session_not_cancelableThe session is no longer open
409checkout_unavailablefianto could not reach or check Solana in time. Retry after Retry-After (5 seconds)
422merchant_token_account_missingYour receiving wallet has no USDC token account
400price_id_required, amount_not_allowedA subscription session needs price_id and no amount or description
422price_not_recurringA subscription session needs a recurring price
429plan_limit_reachedYour account created 50 new subscription plans in the last 24 hours
409subscription_already_endedThe subscription has already ended
409webhook_endpoint_not_activeNo verified webhook URL to send a test event to
429rate_limitedToo many requests. Wait for Retry-After. See rate limits
500internal_errorSomething failed on fianto's side. Quote the request_id

In the SDK

The SDK throws one class per status. Every class extends FiantoError.

ClassWhen
APIErrorAny error response. Has status, code, message, field, details, requestId, headers
AuthenticationError401
PermissionDeniedError403
InvalidRequestError400 and 422
NotFoundError404
ConflictError409
RateLimitError429, with retryAfterSeconds
ServiceUnavailableError503
InternalServerErrorOther 5xx
ConnectionErrorNo response arrived
TimeoutErrorThe request took longer than timeoutMs

The status classes extend APIError. Check a code with isFiantoError(err, ErrorCode.X):

import { APIError, ErrorCode, Fianto, isFiantoError } from '@fianto/sdk';

const fianto = new Fianto();

export async function createCheckout(orderId: string) {
  try {
    return await fianto.checkoutSessions.create({
      mode: 'payment',
      order_id: orderId,
      amount: '12.50',
      description: 'Coffee beans, 1 kg',
      success_url: 'https://shop.example/thank-you',
      cancel_url: 'https://shop.example/cart',
    });
  } catch (err) {
    if (isFiantoError(err, ErrorCode.OrderAlreadyPaid)) {
      return null; // this order is paid; do not charge it again
    }
    if (err instanceof APIError) {
      console.error(err.status, err.code, err.requestId); // quote requestId to support
    }
    throw err;
  }
}

What the SDK retries

By default the SDK retries a call up to 2 times (maxRetries, 0 to 10). It retries network errors, timeouts, 408, 429, every 5xx, and a 409 only when the code is idempotency_request_in_progress or checkout_unavailable. It never retries any other 4xx, idempotency_key_reused included.

Between attempts it waits for Retry-After when the response sends one, and otherwise backs off from 500 ms, doubling each time up to 8 s, with ±25 % jitter. If Retry-After asks for more than 10 seconds, the SDK does not wait: it throws the error at once. Every retry of a POST reuses the same idempotency key.

Route helpers pass only some codes to the browser

The SDK's checkout route helpers (Checkout(), checkout(), createCheckoutHandler) relay only an allowlist of codes to the browser, such as payment_in_progress, url_insecure, price_id_required and plan_limit_reached. Every other error, including 401, 403, 5xx and merchant_token_account_missing, reaches the browser as 500 internal_error. Your onError always receives the original error, so log it there.

instanceof can fail when one app loads the SDK twice, once as ESM and once as CommonJS. isFiantoError works either way.

See also

Was this page helpful? Tell us

On this page