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

# Pay invoice

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



## OpenAPI

````yaml https://devs.adaptyvbio.com/api/v1/openapi.json post /api/v1/invoices/{invoice_id}/pay
openapi: 3.1.0
info:
  title: Adaptyv Foundry API
  description: >

    The Foundry API enables programmatic access to Adaptyv Bio's protein
    characterization services.


    ## Getting Started


    1. **Obtain an API token** from the [Adaptyv
    Portal](https://foundry.adaptyvbio.com) or by
       contacting Adaptyv directly
    2. **Authenticate requests** using Bearer token in the Authorization header

    3. **Create experiments** to submit protein sequences for characterization

    4. **Monitor status** via webhooks or polling the experiment detail endpoint


    ## Authentication


    All requests require a Bearer token:


    ```

    Authorization: Bearer <your-token>

    ```


    Tokens can be attenuated (restricted) to specific organizations or
    capabilities via the `/tokens` endpoint.


    ## Pagination


    List endpoints support offset-based pagination:


    | Parameter | Description | Default |

    |-----------|-------------|---------|

    | `limit` | Maximum items per page | 50 |

    | `offset` | Number of items to skip | 0 |


    ## Filtering


    Most list endpoints support filtering via query parameters using comparison
    operators:


    - `equ(field)=value` — equals

    - `geq(field)=value` — greater than or equal

    - `gtr(field)=value` — greater than

    - `leq(field)=value` — less than or equal

    - `lss(field)=value` — less than


    Example: `GET /experiments?geq(created_at)=2025-01-01&status=draft`


    ## Environments


    | Environment | Base URL |

    |-------------|----------|

    | Production | `https://devs.adaptyvbio.com` |


    ## Support


    - **Email**: support@adaptyvbio.com

    - **Documentation**: [docs.adaptyvbio.com](https://docs.adaptyvbio.com)
  version: 0.0.2
servers:
  - url: https://devs.adaptyvbio.com/
    description: Production API (Public)
security:
  - adaptyv_biscuits_v0: []
tags:
  - name: experiments
    description: >

      Experiments are the core resource for submitting protein sequences to
      Adaptyv's characterization services.


      ## Lifecycle


      Experiments progress through these stages:


      1. **Draft** — Initial state for designing your experiment

      2. **In Review** — Submitted for validation and quote generation

      3. **Quote Sent** — Adaptyv has sent a pricing quote

      4. **Waiting for Confirmation** — Quote ready, awaiting acceptance

      5. **In Queue** — Confirmed and queued for lab scheduling

      6. **Waiting for Materials** — Confirmed, waiting for samples

      7. **In Production** — Lab work in progress

      8. **Data Analysis** — Characterization complete, processing results

      9. **Done** — Results available for download


      **Canceled** is a terminal state reachable from Draft, In Review, Waiting
      for Confirmation, or In Queue.


      ## Experiment Types


      | Type | Description | Target Required |

      |------|-------------|-----------------|

      | `screening` | High-throughput binding assessment | Yes |

      | `affinity` | Kinetic characterization (KD, kon, koff) | Yes |

      | `thermostability` | Thermal stability measurement (Tm) | No |

      | `expression` | Expression yield assessment | No |

      | `fluorescence` | Fluorescence-based characterization | No |


      ## Typical Workflow


      ```

      POST /experiments                    # Create experiment

      POST /experiments/cost-estimate      # Preview pricing

      POST /experiments/{id}/submit        # Submit for review

      GET /experiments/{id}/quote          # Retrieve quote when ready

      POST /experiments/{id}/quote/confirm # Accept quote

      GET /experiments/{id}                # Monitor progress

      GET /results?experiment_id={id}      # Retrieve results when done

      ```


      ## Related Resources


      - [Results](#tag/results) — Characterization data from completed
      experiments

      - [Sequences](#tag/sequences) — Individual sequences within experiments

      - [Targets](#tag/targets) — Target proteins for binding experiments
  - name: feedback
    description: >-
      Use this endpoint to give us feedback, report bugs that are not already
      caught by our observability or request features (or have your agents do
      it).
  - name: info
    description: >-
      Programmatic discovery and health probes. Anonymous-eligible liveness and
      database connectivity probes; admin-only assay-type catalog (internal
      builds).
  - name: invoices
    description: >-
      Invoice listing for organization billing history. Read-only access to
      invoices generated from accepted Stripe quotes, mirrored locally with org
      context, currency, and status.
  - name: quotes
    description: >-
      Stripe quote lifecycle: list, retrieve, confirm, and reject quotes for
      experiment pricing.
  - name: results
    description: >

      Results contain the characterization data from completed experiments.


      ## Result Availability


      Results appear when an experiment reaches the **Done** status. Some
      experiment types provide partial results during **Data Analysis**.


      ## Result Types by Experiment


      | Experiment Type | Result Data |

      |-----------------|-------------|

      | Affinity | KD, kon, koff, sensorgrams |

      | Screening | Binding yes/no, response units |

      | Thermostability | Tm values, melting curves |

      | Expression | Yield measurements |


      ## Downloading Results


      Use the result `id` to fetch detailed data. Result downloads may include:


      - Raw sensorgram data

      - Fitted kinetic parameters

      - Quality metrics

      - Summary reports


      ## Related Resources


      - [Experiments](#tag/experiments) — Parent resource that produces results
  - name: sequences
    description: >

      Sequences provide read-only access to protein sequences submitted across
      experiments.


      ## Sequence Format


      Sequences are amino acid strings in standard single-letter IUPAC format
      (e.g., `MKTLLLTLLV...`).


      ## Sequence Properties


      | Field | Description |

      |-------|-------------|

      | `aa_string` | The amino acid sequence |

      | `name` | Optional human-readable identifier |

      | `control` | Whether this is a control sequence |

      | `metadata` | Structural annotations (tag location, antibody type) |


      ## Querying Sequences


      Filter sequences by experiment or other criteria:


      ```

      GET /sequences?experiment_id={id}

      GET /sequences?name=mAb-001

      ```


      ## Related Resources


      - [Experiments](#tag/experiments) — Parent resource containing sequences
  - name: targets
    description: >

      Targets represent the molecules your samples will be tested against in
      binding experiments (affinity and screening).


      ## Target Catalog


      Adaptyv maintains a catalog of pre-validated target proteins with
      established pricing. Use `selfservice_only=true` to filter for targets
      with immediate pricing availability.


      ## Self-Service vs Custom Targets


      | Category | Description | Pricing |

      |----------|-------------|---------|

      | Self-service | Pre-validated catalog targets | Instant quote via cost
      estimate |

      | Custom | User-supplied or special request | Requires manual quote |


      ## Usage


      1. Browse targets with `GET /targets?selfservice_only=true`

      2. Use the target's `id` when creating an experiment

      3. For custom targets, provide a `requested_target` object in the
      experiment request


      ## Related Resources


      - [Experiments](#tag/experiments) — Create experiments using targets

      - [Cost Estimate](#operation/cost_estimate) — Preview pricing for
      self-service targets
  - name: tokens
    description: >

      Create restricted versions of your API token for delegation to team
      members or automated systems.


      ## Token Attenuation


      Attenuation **reduces** the permissions of your token—it cannot grant
      additional access. Attenuated tokens inherit a subset of the parent
      token's capabilities.


      ## Restriction Types


      | Restriction | Effect |

      |-------------|--------|

      | Organization | Limit access to specific organizations |

      | Resource | Limit to specific resource types (experiments, results) |

      | Action | Limit to specific actions (read, create, update) |

      | Expiry | Set a shorter expiration time |


      ## Security Best Practices


      - Create narrowly-scoped tokens for automated systems

      - Use short expiration times for temporary access

      - Revoke tokens promptly when no longer needed
  - name: updates
    description: >-
      Real-time feed of experiment status changes, progress notifications, and
      operational alerts. Supports cursor-based pagination for efficient
      polling.
  - name: webhooks
    description: >-
      Register an HTTPS endpoint that receives signed push notifications for the
      experiments in your organization that have not registered a webhook of
      their own. Each delivery carries an X-Adaptyv-Signature header
      (HMAC-SHA256, hex) computed over the raw request body. Set a secret here
      to verify deliveries with a key only your organization holds; a
      per-experiment webhook_secret overrides it.
  - name: whoami
    description: >-
      Identify yourself: the organizations your token can access (with the
      active one marked), your user id, the permissions your token grants, and
      its expiry.
paths:
  /api/v1/invoices/{invoice_id}/pay:
    post:
      tags:
        - invoices
      summary: Pay invoice
      description: >-
        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`.
      operationId: pay_invoice
      parameters:
        - name: invoice_id
          in: path
          description: Foundry invoice UUID or Stripe invoice ID (`in_xxx`)
          required: true
          schema:
            type: string
        - name: X-Adaptyv-Payment-Method
          in: header
          description: >-
            Settlement method: `mpp-spt` or `x402-exact`. Must name the rail
            pinned at experiment creation; naming a different one is a `409`.
            Omit to read the current invoice state and pay via the hosted URL
            instead — but a quote pinned to a crypto rail at experiment creation
            returns `409` when the header is absent, so retry with the value
            used at creation. The methods an environment accepts are listed in
            `payment.methods` on the confirm response.
          required: false
          schema:
            type:
              - string
              - 'null'
      responses:
        '200':
          description: Invoice paid (or already paid)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayInvoiceResponse'
        '400':
          description: Unrecognised payment method, or a malformed credential
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Invalid or missing authentication token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Insufficient permissions to pay invoices
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Invoice not found or not accessible
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            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
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Invoice is not in a payable state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '501':
          description: This environment does not provide the requested payment method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    PayInvoiceResponse:
      type: object
      description: >-
        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.
      required:
        - id
        - status
        - already_paid
      properties:
        already_paid:
          type: boolean
          description: >-
            `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:
          type:
            - string
            - 'null'
          description: Stripe-hosted invoice URL, when known.
          example: https://invoice.stripe.com/i/acct_1234/test_5678
        id:
          type: string
          description: Stripe invoice ID (`in_xxx`) that was paid.
          example: in_1OqLk2LkdIwHu7ix6OboRpXl
        settlement_reference:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/SettlementReference'
              description: >-
                Names what settled this invoice — the on-chain transaction for

                `x402-exact`, or the Stripe PaymentIntent for `mpp-spt`.


                Only a response that performed the settlement can carry it, so
                it is

                absent when `already_paid` is `true` and absent from the state
                read of

                an unpaid invoice. Treat it as optional even on a settling
                response:

                when the rail reports nothing this API can name honestly, the
                field is

                omitted rather than guessed at.


                Record it when you receive it. Repeating the call afterwards
                answers

                `already_paid` without the reference, and it is not available
                from any

                other endpoint.
        status:
          $ref: '#/components/schemas/StripeInvoiceStatus'
          description: Current invoice status after the call. `paid` on success.
    ErrorResponse:
      type: object
      description: >-
        Error response body returned by all endpoints on 4xx/5xx failures.


        Every error response contains a human-readable message and the request
        ID

        for support correlation. The `request_id` is also returned in the

        `x-request-id` response header.
      required:
        - error
        - request_id
      properties:
        error:
          type: string
          description: Human-readable error description.
          example: experiment not found
        request_id:
          type: string
          description: >-
            Request identifier for support correlation (also in `x-request-id`
            header).
          example: req_019462a4-b1c2-7def-8901-23456789abcd
    SettlementReference:
      oneOf:
        - type: object
          description: >-
            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.
          required:
            - tx_hash
            - type
          properties:
            tx_hash:
              type: string
              description: On-chain transaction hash of the settling transfer.
              example: >-
                0x9c2f1a7e5b0d4c3a8f6e2d1b0a9c8f7e6d5c4b3a29180f7e6d5c4b3a29180f7e
            type:
              type: string
              enum:
                - x402_transaction
        - type: object
          description: 'Settled through Stripe: the PaymentIntent attached to the invoice.'
          required:
            - payment_intent_id
            - type
          properties:
            payment_intent_id:
              type: string
              description: Stripe PaymentIntent ID (`pi_xxx`) credited to the invoice.
              example: pi_3OqLk2LkdIwHu7ix6OboRpXl
            type:
              type: string
              enum:
                - stripe_payment_intent
      description: >-
        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.
    StripeInvoiceStatus:
      type: string
      description: Invoice status from Stripe's API.
      enum:
        - draft
        - open
        - paid
        - void
        - uncollectible
  securitySchemes:
    adaptyv_biscuits_v0:
      type: http
      scheme: bearer
      bearerFormat: Adaptyv Biscuits v0
      description: >-
        Biscuit-based bearer token. Obtain tokens from the Adaptyv Portal or via
        the `/tokens` endpoint. Tokens encode organization membership and
        role-based capabilities; the API verifies the token's cryptographic
        signature and authorization claims before processing requests. Use
        `/tokens/attenuate` to create restricted tokens for delegation.

````