Skip to main content

Create a Checkout Session

Create a session on your server, then redirect the buyer to the checkout URL. The sequenced path through the whole integration is Build your integration.

When to create the session​

Mint the session when the buyer signals they are ready to pay — they open the cart or click Checkout — not on every page view: each session has a short TTL (default 30 minutes, expiresIn), so a session minted on page load usually expires unpaid.

Endpoint​

POST /v1/sessions
Authorization: Bearer vp_sk_live_xxx
Content-Type: application/json
Idempotency-Key: <unique-key> (optional, recommended)

Headers​

HeaderRequiredDescription
AuthorizationYesBearer token (vp_sk_live_xxx or vp_sk_test_xxx). The header must use the Bearer scheme; a missing or malformed token returns 401.
Content-TypeNo (recommended)Send application/json. The body is parsed as JSON regardless of this header; a body that isn't valid JSON surfaces as a 500.
Idempotency-KeyNoUnique key to prevent duplicate session creation. If you retry a request with the same key, the original session is returned instead of creating a new one. Recommended for all production integrations. See Idempotency below for retention behavior.

Request Body​

FieldTypeRequiredDescription
amountintegerConditionalAmount in minor units (cents). 1499 = $14.99. Integer in the range 0–99,999,999; required and ≥ 1 for mode: "payment" (the default).
currencystring (exactly 3)YesThree-character currency code, uppercased server-side (USD, EUR, GBP). The field enforces exactly 3 characters; it does not validate ISO 4217 membership, so an unsupported but well-formed 3-letter code is accepted at this layer.
countrystring (exactly 2)NoISO 3166-1 alpha-2 country code, exactly 2 characters, uppercased (US, CA, GB)
successUrlstring (≤ 2048)NoHTTPS URL to redirect buyer after payment
cancelUrlstring (≤ 2048)NoHTTPS URL to redirect buyer on cancel
modestring: payment | setupNoEnum "payment" | "setup", default "payment" — a one-time charge, the only mode available on hosted checkout. A mode: "setup" request returns 501 endpoint_not_implemented. There is no subscription mode; for recurring flows use Payment Intents.
integrationModestring: embed | elementsNoSelects how the embedded payment surface is composed for this session — enum "embed" | "elements", default "embed". You choose it per session; there is no account-level lock, so the same account can create one session as "embed" and the next as "elements". "embed" renders the whole payment surface (card, billing details, payment-method picker, and any wallet buttons) together in a single combined card iframe — the simplest integration. "elements" renders the pieces (card, email, address, cardholder, save-for-future-use, payment-method picker) as separately placed elements you lay out yourself, so you control where each appears. Discrete "elements" rendering applies only where your account supports it; where it isn't available the session transparently falls back to "embed" (never an error), which is why the response echoes the effective mode. In "embed" the card is always one combined box; in "elements" you can keep the combined box or split it into the discrete card-number / card-expiry / card-cvc fields. See Elements.
chargeAtSubmitbooleanNoCharge the buyer when the embedded fields are submitted, instead of only vaulting a card. Honoured only on an elements session in payment mode — on any other session it is silently set to false rather than refused. Read the chargeAtSubmit value echoed on the create response to confirm which behaviour you got.
captureMethodstring: automatic | manualNoTake the money now (automatic, the default) or hold it and take it later (manual). On manual the payment is authorised only: the funds are held on the buyer's card and nothing moves until you capture the payment intent. Read on the card submit of a charge-at-submit session (integrationMode: "elements" with chargeAtSubmit: true); a wallet tap holds the money the same way (chargeStatus: "succeeded", charged: false, a paymentIntentId, no token, since a wallet card vaults at capture); a card the bank challenges is held once the buyer completes the challenge and reads authorized server-side. The hosted checkout page holds the money too. The embed card form on your own page stores and echoes the value but captures the payment. Secret key only: sent with a publishable key the request is refused with 403 auth_key_type_forbidden. See Authorise now, capture later. Track a hold by its paymentIntentId, never by session status: a session expires 30 minutes after creation while a hold lasts days, and capture works on an authorized intent whatever the session says.
storedCredentialUsestring: recurring | installment | unscheduledNoDeclare what a stored card will be used for — enum "recurring" | "installment" | "unscheduled". Card networks require the payment that stores a card to say what the card is for; your later merchant-initiated charge is anchored to it. Send the same value you will later send as mit.reason on POST /v1/payment_intents — recurring for a fixed-interval subscription, installment for a known number of scheduled payments for one purchase, unscheduled for a card kept on file and charged with no fixed schedule. Omit it for a one-off payment. If you omit it, nothing is declared to the card networks and the payment is sent exactly as it was before this field existed — even when the buyer consents to saving their card. There is no inferred default, and a subscription must declare recurring explicitly: buyer consent looks identical for a subscription and a card on file, so no value is guessed on your behalf. You also need buyer consent (setup_for_future_use: "off_session") on the payment that saves the card — a declaration sent without that consent is discarded, with no error. Echoed back on session retrieve so you can confirm it landed; null means none was recorded. See Recurring & saved cards.
setupForFutureUsestring: on_sessionNoRecord that the card this session saves may be charged again while the buyer is present, for example a one-click upsell on your next page, without showing a "save my card" checkbox. on_session is the only value. It does not allow charging when the buyer is away: that needs the buyer's own consent, and a buyer who ticks "save my card" records off_session, which wins. Applies when an Embedded Fields charge at submit card payment or an Apple Pay or Google Pay payment saves the card, which needs a buyer on the session. Charge the saved card with no mit block. Secret key only: sent with a publishable key the request is refused with 403 auth_key_type_forbidden.
descriptionstring (≤ 500)NoHuman-readable description of the payment (max 500 chars). This is not the bank-statement descriptor — statement text is configured per-merchant server-side via a separate statement-descriptor field.
localestring (≤ 10)NoCheckout page language, max 10 chars (e.g. "en", "fr")
expiresInintegerNoSession TTL in seconds (300–604800, default 1800). Minimum 5 minutes, maximum 7 days.
buyerIdstring (≤ 200)NoYour stable, unique-per-user account ID (max 200 chars) — the same value every visit, not a per-visit token. See Buyer identification. Drives saved payment methods in both directions. Save: for the embedded charge-and-save flow a buyer on the session is the vaulting driver — with a buyer, submit() charges the card AND returns a reusable vp_pmt_* in one step; a guest / no-buyer session charges once and saves nothing (no token). Reuse: when a returning buyer is on the session, the embed lists that buyer's previously-saved cards inside the iframe for one-click reuse — only cards they vaulted in a prior session appear (a first-time buyer has none); see the note under Buyer identification. See Tokenization — charge-and-save for the buyer-vaulting model. Secret key only: any buyer field (buyerId, buyerEmail, buyerName, buyer) sent with a publishable key is refused with 403 auth_key_type_forbidden, because a buyer identity is what a saved card is stored under.
buyerNamestring (≤ 200)NoBuyer's name, max 200 chars (pre-fills billing form, encrypted at rest) Secret key only (see buyerId).
buyerEmailstring (≤ 254)NoBuyer's email — must be a valid email, max 254 chars (encrypted at rest). When provided, it links the buyer across sessions by email (a fallback identifier when buyerId varies or isn't set). On its own it does not surface saved cards — see the note under Buyer identification. Secret key only (see buyerId).
buyerobjectNoNested buyer profile (camelCase on this surface): externalId, email, name, phone, company, address (addressLine1, addressLine2, city, state, postalCode, country), metadata. Upserts the same buyer record as POST /v1/buyers and links the session to it — the richer alternative to the flat buyerId / buyerName / buyerEmail fields above, which remain supported. Must carry an identity (buyer.externalId or buyer.email) directly or via a flat field. Sending a flat field AND its nested counterpart with equal values is fine; different identity values return 400 validation_error. Echoed back on the create response (present only when sent; buyer.metadata echoed post-scrub). See Buyers. Secret key only (see buyerId).
buyerContactobjectNoSecret key only. The buyer's contact details when you already have them, for example a sale your staff keys in: phone, shippingAddress, billingAddress (each optional; an address needs firstName, lastName, address, city, state, zip and country). Stored where the hosted checkout stores what a buyer types, so it counts toward the fields your account requires. Echoed on the 201; never returned by GET /v1/sessions/{id}.
lineItemsarray ≤ 100NoOrder items to display on the checkout page (max 100 items). This field drives the buyer's checkout page only. Mirroring the order into a connected store? Send order.lineItems instead — one list that feeds both the checkout page and the store order. See Describing the order.
shippingstring: none | auto | requiredNoShipping-address collection mode — "none" | "auto" | "required". Omit it to inherit your account setting (none unless you change it in the dashboard). The captured address is returned on the session object as shippingAddress. See Asking the buyer for more than a card.
phonestring: none | auto | requiredNoPhone collection mode — "none" | "auto" | "required". Omit it to inherit your account setting (none unless you change it). A phone the buyer types does reach a mirrored store order, filling the order's shipping-address phone and its customer record — but only where you left that phone unset; one you supplied at session-create is never overwritten. ⚠ A typed phone must also pass a format check or the buyer cannot pay. See Asking the buyer for more than a card.
billingAddressstring: none | auto | requiredNoBilling-address collection mode — "none" | "auto" | "required". Omit it to inherit your account setting (auto unless you change it) — note the different starting point: billing is shown-but-optional out of the box, while shipping and phone are hidden. See Asking the buyer for more than a card.
collectEmailbooleanNoWhen true, the hosted checkout shows an email input even if a buyer email was already provided on the session. An email input is shown automatically whenever no buyer email is on file, regardless of this flag. Defaults to false. Still a boolean — deliberately not folded into the three-way vocabulary above, because the auto-show behaviour means "none" could not be honoured.
metadataobject of stringNoKey-value string pairs (each value max 500 chars). Stored on the session for your own buyer/app context (e.g. device_id, app_user_id). Note: session metadata is persisted on the session record but is not currently echoed into webhook event payloads. Do not put a mirror block here: metadata values are strings, and a stringified mirror block is never parsed, so the session is created with no mirror attached and no store order is ever made. Use the top-level mirror field below.
orderobjectNoWhat the buyer is purchasing — lineItems, optional coupon and metadata, plus shipping on Next Commerce. Sent with mirrorTo, it drives both the checkout page and the connected-store order, so the purchase is described once. What you describe must add up to amount or the request is refused with 400 order_total_mismatch before any charge. On Shopify send shipping as a line item — the shipping field is refused there. See Describing the order.
mirrorToobjectNoWhere to record the order — platform ("shopify" or "nextcommerce") and store. The customer and addresses are inherited from buyer unless you set them here. Sent with order. The store order is created asynchronously after the charge, so it is not in the create response: poll GET /v1/public/sessions/{sessionId}/mirror-order or subscribe to the mirror.order.created webhook. See Describing the order.
mirrorobjectNoThe older connected-store block, still accepted — order / mirrorTo compiles into it. It carries its own line_items, separate from the top-level lineItems above, so on this path state the items in both places. Send this or order / mirrorTo, never both: a request carrying both is refused with 400 order_mirror_conflict. For the block's fields, the per-platform differences, and the error codes, see Connected platforms.

Line Item Object​

FieldTypeRequiredDescription
namestring (≤ 200)YesItem name (1–200 chars)
quantityintegerYesQuantity (1-9999)
unitAmountintegerYesUnit price in minor units (integer ≥ 0)
imageUrlstring (≤ 2048)NoProduct image URL (HTTPS)

Asking the buyer for more than a card​

Three fields on the hosted checkout share one vocabulary:

ValueWhat the buyer sees
noneThe field is not shown.
autoShown, optional — the buyer may leave it blank.
requiredShown, and Pay stays disabled until it's filled in.
{
"amount": 4999,
"currency": "USD",
"shipping": "required",
"phone": "auto",
"billingAddress": "required"
}

Omitting a field is not the same as setting it to none. Omit it and the session inherits your account-level setting for that field (dashboard-configurable; out of the box none for phone and shipping, auto for billingAddress). A value on the request applies to that one payment only.

Sent with a publishable key, a value can only make collection stricter. A value weaker than your account setting is raised to that setting; a stricter one is honoured. The floor is your account setting read at create time — if that read fails, shipping and phone fall back to none and billingAddress to auto — so if a field genuinely must be collected, enforce it server-side too.

The 201 response returns shipping, phone and billingAddress carrying the mode actually applied; read them rather than assuming your request was taken verbatim. A later GET /v1/sessions/{id} returns the same three values.

What required guarantees​

The page will not enable Pay without the field, and our server re-checks immediately before the charge, so a page that misbehaves cannot let a payment through with it empty. It is not a defence against a deliberately modified client.

A typed phone is checked for shape, in every mode​

A phone the buyer types is checked for shape even under phone: "auto", and a value that fails hides the payment surface until they fix it — leaving an optional phone empty blocks nothing. Allowed characters: digits, +, spaces, (, ), ., -; between 7 and 15 digits. (555) 123-1234, +44 20 7946 0958 and 555.123.1234 all pass. The number travels to your connected store as the courier's contact.

An address counts only when it's complete​

For shipping and billingAddress, required means a usable address, judged per country — addresses and states, the same rule the form renders against.

collectShipping and collectPhone are refused, not ignored​

Sending either returns 400 validation_unknown_field and no session is created. The replacement:

OldNew
"collectShipping": true"shipping": "auto"
"collectShipping": falseomit, or "shipping": "none"
"collectPhone": true"phone": "auto"
"collectPhone": falseomit, or "phone": "none"

true maps to auto, not required: the old booleans only ever showed a field. Use "required" to enforce one.

Buyer identification​

Pass these fields on every session so Von Payments can link a buyer's transactions across sessions, consolidate them, and help you troubleshoot (duplicate charges, disputes, "is this the same buyer?"). They are secret key only: a buyer identity is what a saved card is stored under, so it may only be asserted from your server. Any buyer field (buyerId, buyerEmail, buyerName, or the nested buyer object) sent with a publishable key is refused with 403 auth_key_type_forbidden, never silently dropped.

  • buyerId — your stable, unique-per-user account ID. Send the same value every time this buyer pays, across visits, devices, and cards (e.g. user_8f3a2b, a persistent ID from your users table). Do not send a per-visit, per-page-load, session, cart, or random value: a buyerId that changes per visit makes a returning buyer look brand-new and breaks duplicate-charge detection.
  • buyerEmail — the buyer's email. When provided, Von Payments links the buyer across sessions by email, a fallback identifier when buyerId varies or isn't set.
  • metadata — optional extra context as string key/values, e.g. { "app_user_id": "8f3a2b", "device_id": "d-1029" }.

Minimum for clean linking: send buyerId (stable) and buyerEmail together. If you're generating this call from an SDK or an AI agent, source buyerId from the authenticated user's persistent record ID — never a request-scoped or random token.

To manage the buyer record directly — richer profile fields (name, phone, company, address), lookup by your reference or email, and sparse updates — see the Buyers API. The nested buyer object on this call writes the same record.

Setting a buyer is what makes the embed show that buyer's saved cards — a card appears only if the buyer vaulted one in a prior completed session with a buyer on it. You don't pre-register buyers; the record is created the first time you reference one. When contacting support about a transaction, include the buyerId, the buyer's email and the X-Request-Id from the API response.

Authorise now, capture later​

By default a charge takes the money. Set captureMethod: "manual" on the session and the charge authorises only: the funds are held on the buyer's card and nothing moves until you capture. Use it when you should not take money at checkout, most often when goods ship later or stock has to be confirmed first. It is the same setting, with the same two values, as capture_method on POST /v1/payment_intents.

Where it applies. The value is read on the hosted checkout page (checkoutUrl), on Apple Pay and Google Pay charges, and on the card submit of a charge-at-submit session (integrationMode: "elements" with chargeAtSubmit: true). A card the bank challenges is held the same way once the buyer completes the challenge: the submit result you saw was requires_action, and after the buyer returns the payment intent reads authorized on a server-side read, ready to capture as below. A wallet tap on a manual session holds the money the same way: the buyer's sheet closes as a success and the result reads chargeStatus: "succeeded" with charged: false and a paymentIntentId, with no token, because a wallet card vaults at capture rather than at authorisation. Some accounts' payment connections cannot hold a payment at all; there a session asking for manual is refused with 422 capability_not_supported instead of being accepted and captured. The embed card form mounted on your own page accepts and echoes the value but captures the payment, so do not send manual on a session you render as embed.

On the hosted page, find the hold on the session. The buyer leaves your site, so you never see a submit result. Read GET /v1/sessions/{id}: once paymentStatus reads held, paymentIntentId names the payment intent to capture or void. If you confirm completion with POST /v1/public/sessions/{id}/complete, a hold answers 200 with status: "held", charged: false, vp_tx_id: null and the payment_intent_id to capture. Branch on status, never on the HTTP code alone, and do not mark the order paid on held. paymentIntentId is also set on an ordinary automatic payment, where the money is already taken, so decide from paymentStatus, never from the id being present.

Track a hold by its payment intent, not by the session's status. The two have different lifetimes on purpose: a checkout session expires 30 minutes after you create it unless you set expiresIn, while a hold lasts until you capture it, you void it, or the issuer's window lapses days later. So store the paymentIntentId and reconcile from GET /v1/payment_intents/{id}. A session whose status reads expired tells you nothing about whether money is held, since capture works on an authorized intent whatever the session says, and treating it as "the buyer did not pay" is how a held card gets charged twice.

Reading the session back does answer the money question, on a different field: GET /v1/sessions/{id} returns paymentStatus beside status. status says whether the buyer finished; paymentStatus says whether the money moved, and on a manual session a completed buyer reads status: "succeeded" with paymentStatus: "held". Fulfil on paid, never on succeeded alone.

Without charged: true, nothing has been taken. On a manual session a successful submit resolves with chargeStatus: "succeeded" and charged: false: the authorisation worked, nothing failed, and no money has moved. false is not a decline; chargeStatus says where the charge is and charged says whether money moved, and only charged === true means paid. Treat an absent charged as unknown, never as paid or failed: the SDK omits it when the platform gave it no boolean. A result carrying recovered: true (an outcome resolved by polling after a lost response) is the one case where you should not act on charged at all: a recovered result carries no paymentIntentId to capture against, only transactionId. Confirm the payment server-side first, quoting the transactionId to support if you cannot find it. Fulfilling on chargeStatus alone means shipping goods against money you have not taken, and if you never capture, you never will. The submit result is your signal for the hold, not a webhook. Capture with the paymentIntentId from the result via POST /v1/payment_intents/{id}/capture, in full or in part, and take the outcome from the capture response (status: "succeeded"; the same status is readable on GET /v1/payment_intents/{id}), not from a webhook. To let a hold go, void it rather than refunding it. A hold is not indefinite, and the window is shorter than most integrations assume: roughly seven days on Mastercard, American Express and Discover, about five days on a Visa merchant-initiated payment, and as little as two days for a card-present payment. Treat the shortest of those as your deadline unless you know the card brand. When a hold lapses the money is released to the buyer, the sale is lost, and nothing notifies you.

Secret key only. The field decides whether a buyer's money is taken or merely held, so it may only be set server to server. Sent with a publishable key the request is refused with 403 auth_key_type_forbidden rather than ignored.

Confirm it landed. The create response echoes captureMethod, read back from the stored session. Sessions created without the field read automatic. If you sent manual and read automatic, your setting did not land: every payment on that session will take the money immediately.

Response​

{
"id": "vp_cs_live_k7x9m2n4p3q8r5t1",
"checkoutUrl": "https://checkout.vonpay.com/checkout?session=vp_cs_live_k7x9m2n4p3q8r5t1",
"expiresAt": "2026-03-31T15:30:00.000Z",
"status": "pending",
"paymentStatus": "unpaid",
"integrationMode": "embed",
"captureMethod": "automatic",
"threeDSecure": { "mode": "redirect" }
}

The response carries the session id, the checkoutUrl to redirect the buyer to, expiresAt, its current status and paymentStatus (see Idempotency), the effective captureMethod (see above), and the effective integrationMode: the value you requested, or "embed" if an "elements" request fell back because your account does not support discrete rendering (it never errors; read the value back). Processor selection is server-side and not exposed (VORA).

threeDSecure.mode tells your server, before anything is charged, how this session will authenticate the card if the bank asks for it. redirect means the shopper goes to the bank's own page: hosted checkout does that for you, and on an Embedded Fields or Payment Intents charge you send the shopper to the URL the charge returns (by path). It reports what we will do, not proof that a challenge ran. New values may be added, so don't fail on one you don't recognise.

The session id has the form vp_cs_{live|test}_{16-character id} — for example vp_cs_live_k7x9m2n4p3q8r5t1.

The checkoutUrl host depends on the session. Test-mode sessions (and live-mode sessions without a configured merchant slug) use the platform host https://checkout.vonpay.com/checkout?session=<sessionId>. Live-mode sessions with a merchant slug use the merchant subdomain https://<slug>.vonpay.com/checkout?session=<sessionId>. Always redirect the buyer to the exact checkoutUrl returned in the response rather than constructing it yourself.

Session Expiry​

Sessions expire after the expiresIn window (default 30 minutes, up to a max of 7 days). If the buyer hasn't completed payment by then, the session status becomes expired and the checkout page shows an error.

Idempotency​

Idempotency-Key is bound to the session record it created — a replayed key returns the same session for the life of that record. Key it to the operation, not the attempt (order_123_create, not order_123_attempt_1), and reuse it on every retry; a key that changes per attempt misses the duplicate check and creates a second session — and on POST /v1/payment_intents a second real charge. A replay with a different body returns 422 idempotency_replay_incompatible.

A replay returns the original session as it is now, and the create response carries its status and paymentStatus, so you can tell whether it already finished. A new session reads pending / unpaid. On failed (for example, a charge on it was declined), start a new attempt with a new key. On succeeded, do not charge again. paymentStatus: "held" means authorised but not captured: capture or void it, and do not fulfil on it. Only paid means captured.

successUrl / cancelUrl​

successUrl and cancelUrl are validated for shape only — HTTPS (with localhost exempt for test-mode keys), max 2048 characters; any HTTPS URL is accepted, there is no allowlist. Terminate the redirect at a server-side endpoint you control and read the payment status from the API before trusting the buyer's browser state — Handle the return.

Amount Format​

Amounts are always in minor units (the smallest currency unit):

AmountCurrencyValue
1499USD$14.99
1000EUR10.00 EUR
999GBP9.99 GBP
100000JPY100,000 JPY (JPY has no minor unit)

Example: With Line Items​

curl -X POST https://checkout.vonpay.com/v1/sessions \
-H "Authorization: Bearer vp_sk_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_456_create" \
-d '{
"amount": 3298,
"currency": "USD",
"country": "US",
"successUrl": "https://mystore.com/order/456/confirm",
"cancelUrl": "https://mystore.com/cart",
"description": "Order #456",
"locale": "en",
"buyerId": "user_8f3a2b",
"buyerName": "Jane Doe",
"buyerEmail": "jane@example.com",
"lineItems": [
{ "name": "Wireless Headphones", "quantity": 1, "unitAmount": 2499 },
{ "name": "USB-C Cable", "quantity": 1, "unitAmount": 799 }
],
"metadata": { "orderId": "order_456" }
}'

Example: Simple Payment (No Items)​

curl -X POST https://checkout.vonpay.com/v1/sessions \
-H "Authorization: Bearer vp_sk_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: simple_pay_001" \
-d '{
"amount": 5000,
"currency": "USD",
"country": "US",
"successUrl": "https://mystore.com/thank-you"
}'

If no lineItems are provided, the checkout page shows the total amount without an itemized breakdown.

Also recording the order in a connected store? Add order and mirrorTo to either request above and the captured payment creates the matching already-paid order in your Shopify or Next Commerce store. order.lineItems describes the purchase once and drives both the checkout page and the store order, so there is no second list to keep in step. See Describing the order for the fields and a complete request.

Validation Rules​

Every constraint is in the request table. Two that are easy to trip: currency is uppercased but not checked against an ISO 4217 allowlist, and the schema is strict — unknown fields, snake_case keys and the retired collectShipping / collectPhone all return 400 validation_unknown_field with field suggestions.

Errors​

StatusErrorCause
400Validation error messageInvalid request body
401Authentication required / Authentication failedMissing or wrong Bearer token (codes auth_missing_bearer / auth_invalid_key)
403Sandbox merchants cannot create live payment sessionsSandbox key used to create a live session (auth_key_type_forbidden)
422Idempotency-Key replay body does not match the original request.Replayed Idempotency-Key with a changed body (idempotency_replay_incompatible)
429Too many requestsRate limited: rate_limit_exceeded_per_key at 30/min per key, or rate_limit_exceeded per IP (300/min with a secret key, vp_sk_* or a legacy vp_key_*; 10/min with a publishable key or none). Wait Retry-After (1 to 60 s)
500Internal errorServer error (internal_error)
501Card-on-file setup sessions are not yet available.mode: "setup" (endpoint_not_implemented)