---
title: "Pay invoice"
method: POST
path: "/api/v1/invoices/{invoice_id}/pay"
tags: ["invoices"]
---

# Pay invoice

`POST /api/v1/invoices/{invoice_id}/pay`

## Request

- Base URL: `https://devs.adaptyvbio.com`
- URL: `https://devs.adaptyvbio.com/api/v1/invoices/{invoice_id}/pay`
- Auth: HTTP bearer

Settles the given invoice. How it settles is chosen by the
`X-Adaptyv-Payment-Method` request header:

| Header value | Behaviour |
|---|---|
| _(absent)_ | Returns the current invoice state; pay via the hosted invoice URL. If the quote was pinned to a crypto rail at experiment creation, returns `409` — retry with the `X-Adaptyv-Payment-Method` header used at creation. |
| `mpp-spt` | Settles from a Stripe shared payment token |
| `x402-exact` | Settles on-chain with an x402 `exact` USDC transfer |

The payment rail is pinned when the experiment is created, and an *explicit*
`X-Adaptyv-Payment-Method` cannot switch it: a header naming a rail other
than the pinned one is rejected with `409` — retry with the pinned rail.
Header-less discovery is the deliberate exception: a caller with a machine
payment policy is offered every machine rail this deployment serves and
commits by sending one rail's credential, which may be a machine rail other
than the pinned one (they share a Stripe account, so the pin has already
decided everything it decides). Omitting the header is therefore not a way
to reach the pinned rail, and a caller with no machine policy falls through
to the hosted invoice — which on a crypto-pinned quote is the absent-header
`409` in the table above.

This is the only endpoint that settles an invoice. The flow is two steps:
`POST /quotes/{quote_id}/confirm` issues the invoice and returns a `payment`
pointer to this route, then `POST /invoices/{invoice_id}/pay` settles it.

Send the first call with the method header and no credential. It answers
`402`, and **the challenge is carried in the response headers, not the
body** — `PAYMENT-REQUIRED` for `x402-exact`, `WWW-Authenticate` for
`mpp-spt`.

That challenge is the whole handshake: it carries every parameter needed to
produce the credential — for `mpp-spt`, the amount, currency, seller-network
id, expiry, and accepted payment-method types to mint the shared payment
token against. There is no separate endpoint to fetch them from, and there
cannot usefully be one: the credential must echo the challenge it answers,
so parameters obtained without taking a `402` cannot be settled here.

Both `mpp-spt` and `x402-exact` settle over the API directly with these
headers; the MCP server's `pay_invoice` tool is one client of this route,
not the only way to reach the rail.

Repeat the call with the signed credential to settle — `PAYMENT-SIGNATURE`
for `x402-exact`, `Authorization` for `mpp-spt` — and the response is `200`
with the paid invoice. Repeating it again is safe: an invoice that is
already paid answers `200` with `already_paid: true` and settles nothing.

When the settlement can be named, the response carries
`settlement_reference`. Treat it as optional even on a settling response.

The `payment.methods` array on the confirm response lists the methods this
environment accepts; a value outside that list is rejected with `400`.

## Path parameters

- `invoice_id` string, required

## Headers

- `X-Adaptyv-Payment-Method` string, nullable

## Response `200`

Invoice paid (or already paid)

- PayInvoiceResponse: Response returned by `POST /invoices/{invoice_id}/pay`. Reports the invoice after the call, so no follow-up `GET` is needed. It is returned for three outcomes, which `status` and `already_paid` tell apart: a settlement this request performed, an invoice that was already paid, and — when no payment-method header was sent — the current state of an invoice that is still unpaid and that this call settled nothing on. A `200` therefore does **not** by itself mean the invoice is paid. Read `status`. The `402` challenge is not this body.
  - `already_paid` boolean, required: `true` when the call was a no-op because the invoice was already paid before this request. `false` otherwise — which covers two different outcomes: this request settled the invoice, or no payment method was selected and the body reports the invoice's current state without settling. Read `status` to tell those apart; `false` alone does not mean the invoice is paid.
  - `hosted_invoice_url` string, nullable: Stripe-hosted invoice URL, when known.
  - `id` string, required: Stripe invoice ID (`in_xxx`) that was paid.
  - `settlement_reference` union: Rail-specific reference to the event that settled the invoice. Read `type` to tell the two apart. `x402_transaction` carries the on-chain transaction that moved the funds; `stripe_payment_intent` carries the Stripe PaymentIntent credited to the invoice. Either identifies this payment uniquely, so it can be quoted back to Adaptyv support to resolve a billing question.
    - object: Settled on-chain: an x402 `exact` transfer broadcast by the facilitator. The hash is the reconciliation key for this payment and is checkable against the network without any Adaptyv involvement.
      - `tx_hash` string, required: On-chain transaction hash of the settling transfer.
      - `type` 'x402_transaction', required
    - object: Settled through Stripe: the PaymentIntent attached to the invoice.
      - `payment_intent_id` string, required: Stripe PaymentIntent ID (`pi_xxx`) credited to the invoice.
      - `type` 'stripe_payment_intent', required
  - `status` 'draft' | 'open' | 'paid' | 'void' | 'uncollectible', required: Invoice status from Stripe's API.

## Other responses

- `400`: Unrecognised payment method, or a malformed credential
- `401`: Invalid or missing authentication token
- `403`: Insufficient permissions to pay invoices
- `404`: Invoice not found or not accessible
- `409`: The `X-Adaptyv-Payment-Method` header names a rail other than the one pinned at experiment creation (retry with the pinned rail — it is named in the error), a settlement for this invoice is already in flight, or the session is closed
- `422`: Invoice is not in a payable state
- `500`: Internal server error
- `501`: This environment does not provide the requested payment method

---

[API](https://skmtc.dev/adaptyvbio/apis/adaptyv-foundry-api.md) - [All operations](https://skmtc.dev/adaptyvbio/apis/adaptyv-foundry-api/llms.txt) - [OpenAPI document](https://skmtc.dev/adaptyvbio/apis/adaptyv-foundry-api/revisions/5a2228f4bd59?raw)
