---
title: "Agent payments (x402 / MPP): not enabled yet"
description: "NOT ENABLED on api.thru.la (the /v1/facilitator/* endpoints return 404). Per-request HTTP 402 payments by AI agents: how the flow works, the @thru-payment/x402 middleware, chains and schemes"
source: https://docs.thru.la/facilitator-x402.md
html: https://docs.thru.la/facilitator-x402
index: https://docs.thru.la/llms.txt
---
# Agent payments (x402 / MPP)

> **Not enabled on api.thru.la.** Every `/v1/facilitator/*` endpoint currently returns `404`. This page is here so you can plan for it. Contact thru before building on it, and use `payments` or `checkout-sessions` for anything you need today.

## What it is

Your API charges per request. A caller, often an AI agent, requests a paid route without paying and gets `402 Payment Required`, with the price in a `PAYMENT-REQUIRED` header. The caller signs a stablecoin transfer authorization and repeats the request with it in a `PAYMENT-SIGNATURE` header (or `Authorization: Payment …` for MPP clients). Your server asks thru to verify and settle it, and then serves the response with a `PAYMENT-RESPONSE` header as the receipt.

The money moves from the payer directly to your settlement address (`payTo`), not through a payment address.

## The package

```bash
npm install @thru-payment/x402
```

The canonical example is `examples/express-server.ts` in the thru-sdk repository; it is type-checked in CI. The essentials:

```ts
import express from 'express';
import { createFacilitatorClient, resolveEvmRoute } from '@thru-payment/x402';
import { paymentMiddleware } from '@thru-payment/x402/express';

const facilitator = createFacilitatorClient({ apiKey: process.env.THRU_API_KEY! });

const routes = {
  'GET /reports/annual': await resolveEvmRoute(facilitator, {
    chain: 'robinhood',
    network: 'mainnet',
    asset: '0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168', // USDG, 6 decimals
    amountAtomic: 5_000_000n, // 5 USDG
    payTo: process.env.THRU_ROBINHOOD_SETTLEMENT_ADDRESS!,
    resource: '/reports/annual',
    maxTimeoutSeconds: 300,
  }),
};

const app = express();
app.use(paymentMiddleware(routes, facilitator));
```

- `paymentMiddleware(routes, client, options?)` takes **three positional arguments** and is imported from `@thru-payment/x402/express`, not from the package root.
- Route keys are `"METHOD /exact/path"`, matched exactly: no parameters, no wildcards.
- `asset` is a token contract address (a coin type on Sui), not a symbol.
- `amountAtomic` is a `bigint` in the token's smallest unit. USDC and USDT on BNB Chain have 18 decimals; most others have 6.
- `payTo` must be your settlement address for that chain and network (`GET /v1/settlement`).
- `resolveEvmRoute` and `resolveSuiRoute` choose the scheme for you from what the facilitator supports. Call them once at startup.
- `createFacilitatorClient` takes a `baseUrl` **without** `/v1` (default `https://api.thru.la`), unlike the other thru packages.
- For MPP clients, pass `{ mppSecret }` as the third argument. Your workspace's value comes from `GET /v1/facilitator/mpp-secret`, which returns `{ secret }`.

For other frameworks, `gateRequest(headers, route, client, options)` is exported from the package root.

## Chains and schemes

| `chain` | Schemes |
|---|---|
| `bnb`, `robinhood` | `eip3009_exact`: the token checks the payer's signature itself; no approval needed. `permit2_exact`: works with any ERC-20, but the payer first makes a one-time on-chain `approve` for the Permit2 contract and pays that gas. |
| `sui` | `sui_direct`: gasless, for eligible stablecoins. `sui_sponsored`: thru pays the gas. Native SUI cannot be paid this way. |

The facilitator also supports `arc` on the server, but the 0.2.0 SDK's `Chain` type lists only `bnb`, `robinhood` and `sui`. `GET /v1/facilitator/supported` (no key needed) returns the live chains, schemes and assets.

## Endpoints

| Endpoint | Auth | Does |
|---|---|---|
| `GET /v1/facilitator/supported` | none | Chains, schemes and assets available now |
| `POST /v1/facilitator/verify` | key | Checks a payment without broadcasting it |
| `POST /v1/facilitator/settle` | key | Broadcasts it |
| `GET /v1/facilitator/mpp-secret` | key | Your workspace's MPP binding secret |
| `GET /v1/facilitator/payments?limit=&cursor=` | key | Your agent-payment history |

The middleware calls `verify` and `settle` for you. Outcomes are also sent as the `facilitator.payment.settled` and `facilitator.payment.failed` webhook events.

## Failure reasons

A failed verify or settle carries a machine-readable `reason`. The ones that need action on your side:

| `reason` | What to do |
|---|---|
| `no_settlement_account` | Add a settlement address for that chain and network (Treasury → Settlement). |
| `payto_mismatch` | `payTo` is not your settlement address; read it from `GET /v1/settlement`. |
| `unsupported_asset`, `asset_not_gasless_eligible` | The scheme does not support this asset. Let `resolveEvmRoute` or `resolveSuiRoute` choose. |
| `deadline_expired`, `not_yet_valid`, `nonce_used` | The authorization is stale or already used. Answer with a fresh 402. |
| `settle_in_progress` | A settlement for this payment is already running. Do not settle again; check `GET /v1/facilitator/payments`. |

Other codes include `unknown_asset`, `unsupported_chain`, `amount_over_cap`, `daily_cap_exceeded`, `amount_mismatch`, `token_mismatch`, `invalid_signature`, `signer_mismatch`, `insufficient_balance`, `insufficient_allowance`, `transaction_failed`, `gas_tank_empty`, `deadline_too_far`, `wrong_payload_kind`, `simulation_did_not_run`, and, on Sui, `gas_budget_not_set`, `gas_price_not_set`, `gas_cost_exceeds_sponsor_limit`, `gas_owner_mismatch`, `sender_mismatch`, `not_gasless`, `missing_or_unbounded_expiration`, `malformed_transaction`, `invalid_sender_signature`, `payto_not_credited`, `underpayment`, `sponsor_non_gas_outflow` and `sui_native_not_supported_for_sponsored`. Treat any code you do not recognise as a failed payment.

---

Related: [sdks](https://docs.thru.la/sdks.md) · [refunds-and-settlement](https://docs.thru.la/refunds-and-settlement.md)
