Envelope
GET /v3/payments/:id/receipt.pdf, which returns the PDF bytes.
Pagination
List endpoints are cursor-paginated. Pass?limit= (1–100, default 20) and ?cursor= (the previous nextCursor). When hasMore is false, nextCursor is null.
Status codes
Universal codes
INTERNAL_SERVER_ERROR, NOT_FOUND, BAD_REQUEST, CONFLICT, RATE_LIMITED,
UNPROCESSABLE_ENTITY, UNAUTHORIZED, FORBIDDEN, TOKEN_EXPIRED,
TOKEN_INVALID, MFA_REQUIRED, ORG_NOT_FOUND, MEMBERSHIP_NOT_FOUND,
INSUFFICIENT_ROLE, ENVIRONMENT_MISMATCH, SANDBOX_ONLY, LIVE_ONLY,
IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_CONFLICT, VALIDATION_ERROR,
MISSING_FIELD, INVALID_ID.
Domain codes
Payments
Payments
PAYMENT_NOT_FOUND, PAYMENT_NOT_REFUNDABLE, PRODUCT_NOT_FOUND,
CUSTOMER_NOT_FOUND, PAYMENT_METHOD_NOT_FOUND,
CRYPTO_ONLY_ORG, CARD_PAYMENT_BELOW_MIN, PROVIDER_NOT_CONFIGURED, KYB_NOT_APPROVED,
INVALID_PAYMENT_STATUS_TRANSITION, CRYPTO_ASSET_NOT_ACCEPTED,
CRYPTO_PAYOUT_ADDRESS_MISSING, CRYPTO_PAYMENT_BELOW_MIN,
CRYPTO_PRICE_FEED_UNAVAILABLE, CRYPTO_CURRENCY_NOT_SUPPORTED,
BTC_NOT_AWAITING_APPROVAL, SOL_PAYER_ADDRESS_REQUIRED,
SOL_RPC_UNAVAILABLE, EVM_PAYER_ADDRESS_REQUIRED, EVM_RPC_UNAVAILABLE,
EVM_CONTRACT_NOT_DEPLOYED, CRYPTO_AUTOSWAP_TARGET_MISSING,
CRYPTO_VAULT_NOT_PROVISIONED, CRYPTO_STABLECOIN_DISABLED,
CRYPTO_VOLATILE_DISABLED, CRYPTO_AUTOSWAP_MODE_NOT_SUPPORTED.Crypto charges live on /v3/crypto/charges and carry their own codes ·
see the Crypto section.CUSTOMER_NOT_FOUND (404) · no customer with that id belongs to the
account the key authenticates. Same answer as an id that exists under another
account — confirming that someone else’s customer is real would itself be a
disclosure — so read it as “not yours”, not as “not anywhere”.This endpoint does not raise CUSTOMER_NAME_REQUIRED. A buyer name is
needed when the payer is present and it travels to the rail with the card they
are typing; an off-session debit sends the stored instrument and the acquirer’s
own customer ref, and no identity at all. A customer you only know by email is
chargeable here.CARD_PAYMENT_BELOW_MIN (422) · the card would be charged less than
1.00 in the payment currency, counted after the discount and with VAT
included. Nothing is created. Crypto has its own per-chain minimum
(CRYPTO_PAYMENT_BELOW_MIN).CHARGING_PAUSED_FOR_DEBT (402) · card charging is suspended until an
outstanding balance is covered, then resumes automatically. Crypto is
unaffected. Not the same as CARD_LIVE_NOT_ACTIVATED (never enabled).Subscriptions
Subscriptions
SUBSCRIPTION_NOT_FOUND, SUBSCRIPTION_CUSTOMER_NOT_FOUND,
SUBSCRIPTION_PRODUCT_NOT_FOUND, PRODUCT_NOT_RECURRING,
PRODUCT_PRICE_MISSING, CUSTOMER_NAME_REQUIRED, FIRST_PAYMENT_DECLINED,
SUBSCRIPTION_NOT_CANCELABLE, CRYPTO_ONLY_ORG.A subscription is opened by a checkout session, so FIRST_PAYMENT_DECLINED
surfaces on that page rather than as an API error.Plan change: SUBSCRIPTION_NOT_MODIFIABLE, TARGET_PRODUCT_NOT_FOUND,
TARGET_PRODUCT_NOT_RECURRING, SAME_PLAN, PLAN_CHANGE_CURRENCY_MISMATCH,
UPGRADE_REQUIRES_CARD, PLAN_CHANGE_DECLINED,
NO_SCHEDULED_PLAN_CHANGE, PLAN_CHANGE_NOT_CANCELABLE.SAME_PLAN (409) is not the way to undo a queued change · it fires
precisely because the subscription is already on that plan. Call
POST /v3/subscriptions/{id}/cancel-plan-change instead.NO_SCHEDULED_PLAN_CHANGE (404) · nothing is queued on this
subscription, or the scheduledChangeId you passed does not name what is.
Re-read scheduledChange from GET /v3/subscriptions/{id}.PLAN_CHANGE_NOT_CANCELABLE (409) · the change is an immediate upgrade
whose charge is in flight or settled, so dropping the swap would leave the
customer paying for a plan they never get. Refund the charge and change the
plan back.Expiration date: SUBSCRIPTION_NOT_MODIFIABLE, PERIOD_END_NOT_IN_FUTURE,
PERIOD_END_SHORTENING_UNCONFIRMED.PERIOD_END_SHORTENING_UNCONFIRMED (409) · the new date is earlier than the
current one, which takes back days the customer already paid for. data.daysRemoved
says how many. Send the same request with allowShorten: true to confirm.Products
Products
PRODUCT_NOT_FOUND, PRODUCT_OUT_OF_STOCK, PRODUCT_ARCHIVED,
RECURRING_REQUIRES_INTERVAL.PRODUCT_BILLING_INVALID (422) · the billing fields contradict each
other: a pay_as_you_go product carrying a price, a subscription with no
recurringInterval, an interval on a onetime product, or an ACTIVE product
carrying no price. Raised on create and on PATCH (against the row merged
with the patch); the message names the rule.A priceCents under 100 (1.00) is refused as a validation error
(VALIDATION_ERROR, 422) on create and on any PATCH that sends one. A
product priced lower before this rule stays editable as long as the PATCH
leaves priceCents out.PRODUCT_BILLING_MODE_LOCKED (422) · a PATCH tried to move a product
into or out of billingMode: subscription. The cadence drives running plans
and is not editable, and changing the mode is that same edit by another name.
Create a second product and move subscribers with a plan change.PRODUCT_PRICED_PER_USE (422) · a pay_as_you_go product reached a
surface that prices products by itself — a charging checkout session, a bundle
line, a subscription cycle. Those read an amount off the catalog with no
caller in the loop, and this product has none to read. Sell it with a
mode=setup session (the payer authorises the card) and charge each use with
POST /v3/payments/off-session and an explicit priceCents.PRODUCT_NOT_PRICED_PER_USE (422) · the mirror of the one above — an
off-session debit (POST /v3/payments/off-session) named a product the
catalog already prices, or named none at all. That call carries its own amount
because only a per-use product leaves one undecided: debiting a onetime
product this way bills a customer again for something they authorised once, and
a subscription product for a cycle Suby’s own clock already bills. Both would
succeed at the acquirer. Send productId of a pay_as_you_go product together
with priceCents.PRODUCT_AMOUNT_REQUIRED (422) · a pay_as_you_go charge named no
amount, so nobody decided the price.PRODUCT_CURRENCY_MISMATCH (422) · a pay_as_you_go charge named a
currency the product does not bill in. Refused rather than converted — the
two readings differ by an exchange rate, and one of them debits a real
customer the wrong amount.PRODUCT_PRICE_MISSING (422) · a catalog-priced product that was never
priced reached a charge. Price it, or declare it pay_as_you_go.PRODUCT_PER_USE_UNAVAILABLE (422) · billingMode: pay_as_you_go on an
account that cannot serve it. Per-use bills after the fact, against a credential
stored at checkout, and two things have to hold.First, a method that leaves such a credential: a card, Apple Pay or Google Pay. A
redirect rail authorises one amount on its own site and a crypto deposit is a
transfer, not a mandate — enable one of the three.Second, an account whose card setup authenticates the card as it stores it.
That authentication is what anchors the chain every later usage charge replays;
without it the card is stored, authorises fine, and is refused on the first
charge — days later, with the product already shared. Accounts on a rail that has
no such step cannot create per-use products at all today, and there is nothing to
enable: it opens for them when the rail gains one.Raised on create and on a PATCH that moves a product onto per-use.PRODUCT_METHOD_NOT_PER_USE (422) · a pay_as_you_go product’s
paymentMethods whitelist named a method its checkout can never offer. That
session only shows card, Apple Pay and Google Pay, so naming a redirect rail
states a choice no payer will see — and a whitelist made only of those resolves
to an empty method list, which is a checkout with nothing on it. The message
names the offending categories.Customers & payment methods
Customers & payment methods
CUSTOMER_NOT_FOUND, CUSTOMER_ALREADY_EXISTS,
CUSTOMER_PAYMENT_METHOD_NOT_FOUND, CUSTOMER_PAYMENT_METHOD_REVOKED.Checkout sessions
Checkout sessions
CHECKOUT_SESSION_NOT_FOUND, CHECKOUT_SESSION_EXPIRED,
CHECKOUT_SESSION_INVALID_SIGNATURE, CHECKOUT_PRODUCT_INACTIVE,
CHECKOUT_SUBSCRIPTION_REQUIRES_RECURRING_PRODUCT,
CHECKOUT_METHOD_NOT_AVAILABLE, CHECKOUT_QUOTE_UNAVAILABLE,
CHECKOUT_ASSET_NOT_ACCEPTED, CHECKOUT_ALREADY_COMPLETED,
CHECKOUT_EMAIL_REQUIRED, CHECKOUT_BILLING_ADDRESS_REQUIRED,
CHECKOUT_PAYMENT_FAILED, CHECKOUT_CAPTCHA_REQUIRED.CHECKOUT_SETUP_REQUIRES_PER_USE_PRODUCT (422) · mode=setup named a
product that carries its own price. A setup session stores a card and charges
nothing, so pointing it at a priced product gives the payer a page they
complete and the merchant a saved card where they expected a sale. The mode is
for the product priced per charge (billingMode: pay_as_you_go); anything
else is sold with mode=payment.Discount codes
Discount codes
Writing a code.
POST and PATCH /v3/discount-codes refuse a code that could
never price:A
FIXED amount is not converted: "200" with currency: "EUR" takes €2 off and
only ever €2. Scoping it to a $ product is refused here rather than at the
checkout, because this is the moment you can still fix it — a code accepted now and
refused later reaches your buyer, not you. Scope it to products priced in the same
currency, mint one code per currency, or use PERCENTAGE, which carries no currency
at all.A code with no productIds covers your whole catalogue and cannot be checked at write
time; it is refused at redemption with DISCOUNT_CODE_CURRENCY_MISMATCH when it meets
an order in the other currency.Redeeming a code. A submitted code either prices or is refused with a reason ·
never dropped in silence.POST /v3/checkout/sessions returns 422. The hosted apply-code call returns
200 with { "applied": false, "reason": "…" } · a payer’s typo isn’t an API
error. The hosted pay call returns 422 rather than billing full price.Licences
Licences
LICENSE_NOT_FOUND, LICENSE_NOT_ACTIVE,
LICENSE_ACTIVATION_LIMIT_REACHED, LICENSE_INSTANCE_NOT_FOUND./v3/licenses/* is authenticated with your API key, like every other
endpoint, and the lookup is scoped to your account · you can only ask about
licences you issued. That is what stops the endpoint being an enumeration
oracle: grinding candidate keys through your own credential only ever finds
your own. The consequence is deliberate · your customer’s software cannot
call this directly, because that would mean shipping sk_live inside it.
Your backend relays the check.LICENSE_NOT_FOUND (404) · no licence with that key on your account.
The only 4xx validate raises: an expired or revoked licence answers 200
with its status, because a 4xx there pushes callers into treating an
expiry as an outage.LICENSE_NOT_ACTIVE (409, on activate) · the licence is expired,
disabled, or was never claimed by the buyer. One code rather than three ·
what the caller does next is the same in all three cases.LICENSE_ACTIVATION_LIMIT_REACHED (409) · every seat is taken. Free
one with POST /v3/licenses/deactivate. The limit is a commercial
guard-rail, not DRM.LICENSE_INSTANCE_NOT_FOUND (404, on deactivate) · no instance with
that id on this licence. Deactivating one already deactivated succeeds.Crypto
Crypto
The headless rail (
/v3/crypto/*) refuses before it prices, so a charge
never reaches a payer it cannot settle.CRYPTO_AUTOSWAP_MODE_NOT_SUPPORTED (422) · the account converges
incoming crypto to a stablecoin on another chain, and wallet_connect
cannot do that: the connected wallet settles on the source chain in a single
transaction, leaving nothing in between to convert. Retry with
mode: "qr_deposit". Nothing is wrong with the account’s configuration.
