Agent payments (x402 / MPP)
Not enabled on api.thru.la. Every
/v1/facilitator/*endpoint currently returns404. This page is here so you can plan for it. Contact thru before building on it, and usepaymentsorcheckout-sessionsfor 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
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:
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. assetis a token contract address (a coin type on Sui), not a symbol.amountAtomicis abigintin the token's smallest unit. USDC and USDT on BNB Chain have 18 decimals; most others have 6.payTomust be your settlement address for that chain and network (GET /v1/settlement).resolveEvmRouteandresolveSuiRoutechoose the scheme for you from what the facilitator supports. Call them once at startup.createFacilitatorClienttakes abaseUrlwithout/v1(defaulthttps://api.thru.la), unlike the other thru packages.- For MPP clients, pass
{ mppSecret }as the third argument. Your workspace's value comes fromGET /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.