Skip to main content

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:
Every endpoint authenticates with a bearer token, like the rest of the API. Confirming a quote and settling an invoice need a token whose role grants the 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 a payment 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:
Send one or the other. If both are present they must agree, or the request is rejected with 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 explicit X-Adaptyv-Payment-Method header. Three consequences:
  • An explicit header cannot switch rails. A header on /pay naming a different machine rail than the pinned one returns 409: an experiment created as x402-exact cannot be settled by asking for mpp-spt, or the reverse. Retry with the pinned rail — the 409 names 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 settling 200 carries settlement_reference when the payment can be named:
Both are independently checkable — in the Stripe dashboard and on Base respectively. Treat the field as optional even on a settling response: some settlements have no reference to report.

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.
A 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.