> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adaptyvbio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Payments Overview

> How an order gets paid: confirm the quote to issue an invoice, then settle it through the hosted invoice page, x402, or a Stripe Shared Payment Token.

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

<CardGroup cols={3}>
  <Card title="Hosted invoice" icon="link" href="/api-reference/payments/pay-hosted-invoice">
    The default. A person pays a Stripe-hosted invoice page.
  </Card>

  <Card title="x402 (exact)" icon="coins" href="/api-reference/payments/pay-x402-exact">
    Your agent signs a USDC transfer; a facilitator settles it on-chain.
  </Card>

  <Card title="MPP-SPT" icon="credit-card" href="/api-reference/payments/pay-mpp-spt">
    Your agent mints a bounded Stripe token; Stripe charges the card.
  </Card>
</CardGroup>

## Base URLs

| Environment       | Base URL                                     |
| ----------------- | -------------------------------------------- |
| Production        | `https://devs.adaptyvbio.com/api/v1`         |
| Testing (sandbox) | `https://devs.testing.adaptyvbio.com/api/v1` |

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:

```bash theme={null}
export FOUNDRY_API_URL="https://devs.adaptyvbio.com/api/v1"
curl "$FOUNDRY_API_URL/invoices?limit=3" \
  -H "Authorization: Bearer $FOUNDRY_API_TOKEN"
```

Every endpoint authenticates with a bearer token, like the rest of the
[API](/api-reference/api-introduction). 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:

```json theme={null}
{
  "id": "qt_1OqLk2...",
  "status": "accepted",
  "invoice_id": "in_1OqLk2LkdIwHu7ix6OboRpXl",
  "payment": {
    "kind": "machine",
    "pay_url": "/api/v1/invoices/in_1OqLk2LkdIwHu7ix6OboRpXl/pay",
    "methods": ["x402-exact", "mpp-spt"]
  }
}
```

`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:

| `X-Adaptyv-Payment-Method`    | Rail                                                         | Settlement authority                              |
| ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------- |
| *(absent)* or `async_invoice` | [Hosted invoice](/api-reference/payments/pay-hosted-invoice) | Stripe, when the hosted page is paid              |
| `x402-exact`                  | [x402 (`exact`)](/api-reference/payments/pay-x402-exact)     | Facilitator settles an EIP-3009 transfer on-chain |
| `mpp-spt`                     | [MPP-SPT](/api-reference/payments/pay-mpp-spt)               | Stripe redeems a Shared Payment Token             |

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:

```json theme={null}
{ "payment": { "method": "x402-exact" } }
```

Send one or the other. If both are present they must agree, or the request is
rejected with `400` (`payment_method_selector_conflict`).

<Note>
  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](mailto:support@adaptyvbio.com) for such
  a token.
</Note>

## 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:

```json theme={null}
{ "type": "stripe_payment_intent", "payment_intent_id": "pi_3Tw3Ah..." }
```

```json theme={null}
{ "type": "x402_transaction", "tx_hash": "0xabc123deadbeef" }
```

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:

| Status | Meaning                                                                                                        |
| ------ | -------------------------------------------------------------------------------------------------------------- |
| `200`  | Settled, or already paid (`already_paid: true`)                                                                |
| `402`  | Challenge issued, or the credential you sent was rejected                                                      |
| `400`  | Unrecognized rail, or a malformed credential                                                                   |
| `403`  | The token does not grant `Invoice:Pay`                                                                         |
| `404`  | Invoice not found, or not yours                                                                                |
| `409`  | The header names a rail other than the pinned one, a settlement is already in flight, or the session is closed |
| `422`  | The invoice is not in a payable state                                                                          |
| `501`  | This environment does not serve the requested rail                                                             |
| `502`  | The gateway or facilitator refused, failed, or has not finished                                                |

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.
