Payments Overview
Every order is paid the same way in outline. You confirm a quote, which finalizes an invoice, and then you settle that invoice. What differs is how the money moves: a person clicking a link on a Stripe-hosted page, an on-chain USDC transfer, or a card charge your agent authorizes in advance.Hosted invoice
The default. A person pays a Stripe-hosted invoice page.
x402 (exact)
Your agent signs a USDC transfer; a facilitator settles it on-chain.
MPP-SPT
Your agent mints a bounded Stripe token; Stripe charges the card.
Base URLs
The sandbox is a separate environment with its own accounts, tokens, and Stripe
objects. Every example in this section uses
$FOUNDRY_API_URL, so either works:
Quote:Update and Invoice:Pay
actions: Member, Billing, or Admin. Member is the usual role for an
agent, since it also creates experiments; Billing suits someone who only handles
quotes and invoices. Viewer has no access to quotes or invoices at all.
The two steps
Confirming a quote never moves money. It finalizes the invoice and hands back apayment block telling you where to pay:
kind is hosted_invoice for the default rail, carrying hosted_invoice_url
instead. kind is machine when the invoice settles programmatically, and
methods lists the rails this environment accepts.
POST /invoices/{invoice_id}/pay is the only endpoint that settles an invoice.
Call it twice: once without a credential to receive the 402 challenge, once
with the credential to settle. Repeating the settling call is safe — an invoice
that is already paid answers 200 with already_paid: true and charges
nothing.
Choosing a rail
One header selects the rail on every call:
Any other value is rejected with
400. Note the spelling: x402-exact, never
x402.
Both machine rails are server-side only. A browser cannot send the method or
credential headers, or read the challenge headers, so drive these flows from a
backend or an agent runtime.
At experiment creation you can select the rail in the request body instead:
400 (payment_method_selector_conflict).
A token issued with a machine payment policy is challenged without the
header:
POST /invoices/{id}/pay answers 402 offering every rail the
environment serves, and whichever credential you send back decides the rail.
A stock x402 or MPP client can then pay without knowing the method header
exists. Ask support@adaptyvbio.com for such
a token.What the rail pins
The rail you choose when you create the experiment binds the experiment’s quote and invoice — and the Stripe account they live on — for its whole lifetime. What that pin governs is the explicitX-Adaptyv-Payment-Method
header. Three consequences:
- An explicit header cannot switch rails. A header on
/paynaming a different machine rail than the pinned one returns409: an experiment created asx402-exactcannot be settled by asking formpp-spt, or the reverse. Retry with the pinned rail — the409names it. - Header-less discovery is the deliberate exception. With a machine-payment token and no header you are offered every machine rail this environment serves, and whichever credential you send back is the rail that settles — including a machine rail other than the pinned one. That is safe because every machine rail shares one Stripe account, so the pin has already done its job by the time you choose. Dropping the header is therefore not a way to reach the pinned rail; and without a machine-payment token it drops you onto the hosted invoice instead.
- Crossing between the hosted-invoice rail and a machine rail is rejected,
explicitly or by discovery. A quote created without a machine rail cannot be
settled with one, and a machine-rail experiment cannot fall back to the
hosted invoice. Both return
409. Create a fresh experiment instead.
Naming what settled
A settling200 carries settlement_reference when the payment can be named:
Status codes
POST /invoices/{invoice_id}/pay answers:
Read a
402 from the response headers and retry. A 402 that answers a
credential you sent is a rejection, not a new challenge: it carries a JSON
error body and no challenge header.
409 and 502 each cover several causes, and the cause decides the remedy.
Read the JSON error to tell them apart.
A 409 means one of:
- A credential arrived before you took the
402— retry without one. - The header names a different machine rail than the experiment pinned — retry with the pinned rail, which the error names.
- The rail crosses between the hosted-invoice and machine rails — create a fresh experiment.
- A settlement is already in flight — poll. If it has not cleared after several polls, contact support with the invoice id.
502 means one of:
- A card was declined. Declines arrive here, not as a
402. - A charge is still in flight — poll rather than mint a second token.
- An upstream outage — retry the request unchanged.