> For the complete documentation index, see [llms.txt](https://docs.trezalabs.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.trezalabs.com/api/mcp-server/x402-payments.md).

# x402 Payments

Treza integrates the [x402 protocol](https://docs.cdp.coinbase.com/x402/welcome) to enable instant, autonomous stablecoin micropayments for API access. AI agents and developers can pay for Treza services programmatically using USDC on Base — no API keys, subscriptions, or manual billing required.

Two endpoints are payable:

* [**Pay-per-video**](#pay-per-video): send a prompt and a payment, get a finished clip back. No Treza account required.
* [**Credit top-up**](#funding-an-account-without-a-human): add to the prepaid balance that pipeline runs draw from.

### What is x402?

x402 is an open payment protocol built by [Coinbase](https://docs.cdp.coinbase.com/x402/welcome) that uses the HTTP `402 Payment Required` status code. When a client requests a paid resource:

1. The server responds with `402` and payment instructions (price, network, recipient)
2. The client signs a USDC payment
3. The client retries the request with a `Payment-Signature` header
4. The server verifies the payment, returns the data, and settles on-chain

Payments are instant, permissionless, and settled in USDC on Base (L2).

### How Treza Uses x402

#### Paid Endpoints

| Endpoint                    | Method | Price      | Description                                                               |
| --------------------------- | ------ | ---------- | ------------------------------------------------------------------------- |
| `/api/x402/video`           | POST   | from $1.64 | Generate a video from a prompt and return it. Priced per second of output |
| `/api/billing/credits/x402` | POST   | $5.00      | Top up a prepaid credit balance                                           |

### Pay-Per-Video

`POST /api/x402/video` generates a video from a text prompt and returns it. Pay once per clip. No Treza account, API key, or signup is required. The payment is the only credential.

#### Price

Price is quoted per request and scales with clip length. Read it off the 402 challenge rather than hard-coding it.

| Clip length | Price |
| ----------- | ----- |
| 5 seconds   | $1.64 |
| 10 seconds  | $3.27 |
| 15 seconds  | $4.90 |

`16:9` and `9:16` cost the same.

#### Request

```json
{
  "prompt": "a manta ray gliding over a sunlit coral reef, slow cinematic drift",
  "seconds": 5,
  "aspectRatio": "16:9"
}
```

| Field         | Required | Default | Notes                                           |
| ------------- | -------- | ------- | ----------------------------------------------- |
| `prompt`      | Yes      | —       | What the video should show. Max 2000 characters |
| `seconds`     | No       | `5`     | One of `5`, `10`, `15`. Drives the price        |
| `aspectRatio` | No       | `16:9`  | `16:9` or `9:16`                                |

A request asking for a size that is not on sale is refused with a `400` **before the payment settles**, so a malformed call costs nothing.

#### Flow

Renders take a few minutes, so a paid call returns `202` immediately with a run id and a `statusUrl` containing a signed token. Poll that URL for the result.

```bash
# 1. Ask. With no payment you get the challenge back.
curl -X POST https://www.trezalabs.com/api/x402/video \
  -H 'content-type: application/json' \
  -d '{"prompt":"a manta ray gliding over a sunlit coral reef","seconds":5}'

# HTTP/2 402
# Payment-Required: <base64 challenge carrying price, network and payTo>
```

Any x402 client signs and retries for you. On success:

```json
{
  "runId": "2026-08-23T18:01:22.045Z#9df91f98",
  "status": "running",
  "statusUrl": "https://www.trezalabs.com/api/x402/video?runId=...&token=...",
  "pollAfterMs": 15000,
  "paidUsd": 1.64,
  "seconds": 5,
  "aspectRatio": "16:9",
  "network": "eip155:8453",
  "transaction": "0x6d9154...",
  "payer": "0x0ac0ab..."
}
```

Poll `statusUrl` until `status` is no longer `running`. The token in the URL is the only credential it needs.

```json
{
  "runId": "2026-08-23T18:01:22.045Z#9df91f98",
  "status": "success",
  "durationMs": 260672,
  "video": "https://www.trezalabs.com/api/media/generated/x402_video/....mp4?s=...",
  "outputs": { "video": "https://..." }
}
```

#### Billing

Your payment credits an account keyed to your wallet address, and the render is charged against that balance. Anything left over stays as credit for your next call. The same wallet always maps to the same account.

A failed render is not charged, so the payment remains available for a retry.

#### Full example

```typescript
import { x402Client } from '@x402/core/client';
import { registerExactEvmScheme } from '@x402/evm/exact/client';
import { safeBase64Encode, safeBase64Decode } from '@x402/core/utils';
import { privateKeyToAccount } from 'viem/accounts';

const ENDPOINT = 'https://www.trezalabs.com/api/x402/video';
const body = JSON.stringify({ prompt: 'a manta ray gliding over a coral reef', seconds: 5 });

const post = (paymentHeader?: string) =>
  fetch(ENDPOINT, {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
      ...(paymentHeader ? { 'PAYMENT-SIGNATURE': paymentHeader } : {}),
    },
    body,
  });

// 1. Unpaid request returns the challenge, priced for this clip length.
const challenge = await post();
const paymentRequired = JSON.parse(safeBase64Decode(challenge.headers.get('payment-required')!));

// 2. Sign a payment for it.
const client = new x402Client();
registerExactEvmScheme(client, { signer: privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) });
const payload = await client.createPaymentPayload(paymentRequired);

// 3. Pay, then poll the ticket you get back.
const paid = await post(safeBase64Encode(JSON.stringify(payload)));
const { statusUrl, pollAfterMs } = await paid.json();

while (true) {
  await new Promise((r) => setTimeout(r, pollAfterMs));
  const run = await (await fetch(statusUrl)).json();
  if (run.status !== 'running') {
    console.log(run.video);
    break;
  }
}
```

A worked example is at [trezalabs.com/x402](https://www.trezalabs.com/x402).

### Funding an Account Without a Human

This is the one most agents want. Pipeline runs draw a prepaid credit balance, and an agent holding a wallet can refill that balance itself, with nobody signing in to a billing page.

Ask what the balance is and how to add to it, using [`get_credit_balance`](/api/mcp-server.md) over MCP or `GET /api/account/balance` with an API key:

```json
{
  "balanceUsd": 1.13,
  "typicalVideoChargeUsd": 1.06,
  "topUpUrl": "https://www.trezalabs.com/platform/settings",
  "x402": {
    "url": "https://www.trezalabs.com/api/billing/credits/x402",
    "topUpPerCallUsd": 5,
    "network": "eip155:8453"
  }
}
```

An `x402` block means agent-native top-up is available on this deployment. `topUpUrl` is the human path; the rest of this page is the other one.

**Paying the endpoint**

`PAYMENT-SIGNATURE` carries the payment and is the only required header.

`Authorization` is optional and selects which account is credited. Send a Treza credential (API key, OAuth token, or session) to credit that account; send none and the credits go to an account keyed to the paying wallet address.

```typescript
import { x402Client } from '@x402/core/client';
import { registerExactEvmScheme } from '@x402/evm/exact/client';
import { safeBase64Encode, safeBase64Decode } from '@x402/core/utils';
import { privateKeyToAccount } from 'viem/accounts';

const ENDPOINT = 'https://www.trezalabs.com/api/billing/credits/x402';
const headers = { authorization: `Bearer ${process.env.TREZA_API_KEY}` };

// 1. Ask, and get the terms back.
const challenge = await fetch(ENDPOINT, { method: 'POST', headers });
const paymentRequired = JSON.parse(
  safeBase64Decode(challenge.headers.get('payment-required')!)
);

// 2. Sign a payment for exactly those terms.
const client = new x402Client();
registerExactEvmScheme(client, { signer: privateKeyToAccount(process.env.WALLET_KEY) });
const payload = await client.createPaymentPayload(paymentRequired);

// 3. Ask again, paying.
const paid = await fetch(ENDPOINT, {
  method: 'POST',
  headers: { ...headers, 'PAYMENT-SIGNATURE': safeBase64Encode(JSON.stringify(payload)) },
});
console.log(await paid.json());
```

A successful call returns the grant and the transaction that paid for it:

```json
{
  "grantedUsd": 5,
  "duplicate": false,
  "balanceUsd": 16.13,
  "network": "eip155:8453",
  "transaction": "0x3e08158c2933cf4174d4f6b7429cdebd2df23b91f135c5a59eaef11a6cbd6450",
  "payer": "0x0Ac0AB6C04C3A92C929e35b39Be88E445360c199"
}
```

Call it again to add another $5. The credits are spendable immediately.

{% hint style="info" %}
The wallet needs USDC on Base mainnet (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`). The facilitator broadcasts the transfer, so the wallet does not need ETH for gas.
{% endhint %}

**Paying twice for the same thing is not possible**

The ledger entry is keyed to the settled transaction hash, so a repeated request credits once. A replayed payment does not even reach that check: the authorization nonce is already spent on-chain, and the facilitator rejects it.

```json
{ "error": "invalid_payload: authorization nonce already submitted; transaction already on-chain" }
```

**When a payment is refused**

The response says why, rather than repeating the challenge:

| Message                                                  | Meaning                                                    |
| -------------------------------------------------------- | ---------------------------------------------------------- |
| `invalid_payload: ... execution reverted`                | The wallet cannot cover the payment                        |
| `invalid_payload: authorization nonce already submitted` | This payment was already spent                             |
| `Invalid or expired credentials`                         | The bearer is wrong, or the key lacks a `pipelines:` scope |
| `x402 payments are not configured on this deployment`    | Agent-native top-up is off here; use `topUpUrl`            |

Authorization is checked before the payment settles, so a request refused for a bad bearer costs nothing.

#### Payment Flow

```
Client                          Treza API                     Facilitator         Base L2
  │                                │                              │                  │
  │── POST /api/x402/video ───────▶│                              │                  │
  │                                │                              │                  │
  │◀── 402 Payment Required ───────│                              │                  │
  │    price: $1.64                │                              │                  │
  │    network: eip155:8453        │                              │                  │
  │    payTo: 0xTreza...           │                              │                  │
  │                                │                              │                  │
  │── Sign USDC payment ──┐        │                              │                  │
  │◀── Payment-Signature ─┘        │                              │                  │
  │                                │                              │                  │
  │── POST /api/x402/video ───────▶│                              │                  │
  │    + Payment-Signature         │                              │                  │
  │                                │── Verify payment ───────────▶│                  │
  │                                │◀── Valid ─────────────────── │                  │
  │                                │                              │                  │
  │◀── 202 runId + statusUrl ──────│                              │                  │
  │                                │── Settle payment ───────────▶│── USDC tx ──────▶│
  │                                │                              │                  │
```

### Client-Side: Paying for Treza Services

#### Using the Treza SDK (Enclave as Wallet)

A Treza Enclave can act as the payment wallet itself, keeping the private key inside the hardware-isolated TEE. This is optional; any viem-compatible account works just as well.

**Install Dependencies**

```bash
npm install @treza/sdk @x402/core @x402/evm @x402/fetch
```

**Auto-Paying Fetch**

The simplest approach — `createEnclaveFetch` returns a `fetch` function that automatically handles 402 responses:

```typescript
import { TrezaClient, createEnclaveFetch } from '@treza/sdk';

const treza = new TrezaClient({
  baseUrl: 'https://trezalabs.com',
});

const paidFetch = await createEnclaveFetch(treza, {
  enclaveId: 'enc_abc123',
  verifyAttestation: true,  // verify enclave integrity before each payment
});

// Payments are handled automatically
const response = await paidFetch('https://www.trezalabs.com/api/x402/video', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ prompt: 'a manta ray gliding over a coral reef', seconds: 5 }),
});
const { statusUrl } = await response.json();
```

**x402 Client (Advanced)**

For more control, use the x402 client directly:

```typescript
import { TrezaClient, createEnclaveX402Client } from '@treza/sdk';

const treza = new TrezaClient({
  baseUrl: 'https://trezalabs.com',
});

const { x402, account } = await createEnclaveX402Client(treza, {
  enclaveId: 'enc_abc123',
  verifyAttestation: true,
});

// account.address → the enclave's signing address (fund this with USDC)
console.log('Payment wallet:', account.address);
```

**Enclave Account (Low-Level)**

Create a viem-compatible account backed by the enclave for use with any x402 library:

```typescript
import { TrezaClient, createEnclaveAccount } from '@treza/sdk';

const treza = new TrezaClient({
  baseUrl: 'https://trezalabs.com',
});

const account = await createEnclaveAccount(treza, {
  enclaveId: 'enc_abc123',
  verifyAttestation: true,
});

// Use with x402 directly
import { x402Client } from '@x402/core/client';
import { registerExactEvmScheme } from '@x402/evm/exact/client';

const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
```

#### Using Any x402 Client (External Wallet)

You don't need the Treza SDK to pay. Any x402-compatible wallet works:

```typescript
import { wrapFetchWithPayment } from '@x402/fetch';
import { x402Client } from '@x402/core/client';
import { registerExactEvmScheme } from '@x402/evm/exact/client';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0xYourPrivateKey...');

const client = new x402Client();
registerExactEvmScheme(client, { signer: account });

const paidFetch = wrapFetchWithPayment(fetch, client);

const response = await paidFetch('https://www.trezalabs.com/api/x402/video', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ prompt: 'a manta ray gliding over a coral reef', seconds: 5 }),
});
```

#### Using curl (Manual)

To inspect the 402 response without paying:

```bash
curl -i -X POST https://www.trezalabs.com/api/x402/video \
  -d '{"prompt":"a manta ray gliding over a coral reef","seconds":5}'

# HTTP/2 402
# Payment-Required: <base64 challenge>
#
# Decoded, the challenge carries the price for the clip length you asked for:
# {"accepts":[{"scheme":"exact","amount":"1640000","network":"eip155:8453","payTo":"0x..."}],...}
```

Amounts are in token atomic units, so `1640000` is 1.64 USDC.

### Server-Side: Adding x402 to Your Own Endpoints

If you're building on the Treza platform and want to monetize your own API endpoints, use the `withX402Payment` wrapper.

#### Basic Usage

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { withX402Payment } from '@/lib/x402';

async function handleGET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
): Promise<NextResponse> {
  // Your endpoint logic here
  return NextResponse.json({ data: 'your response' });
}

export const GET = withX402Payment(handleGET, {
  price: '0.001',       // USDC price per request
  description: 'Get resource data',
});
```

#### With Bazaar Discovery

Make your endpoint discoverable by AI agents on the [x402 Bazaar](https://docs.cdp.coinbase.com/x402/bazaar):

```typescript
export const POST = withX402Payment(handlePOST, {
  // A fixed price, or a function of the request body for per-request pricing.
  price: (body) => `$${priceFor(body).toFixed(2)}`,
  // Name the route. Without this it registers as `*`, which the Bazaar
  // publishes under a generated template like `:var1`.
  routePattern: 'POST /api/x402/video',
  serviceName: 'Treza',
  tags: ['video', 'text-to-video', 'ai'],
  description: 'Generate a video from a text prompt and get the file back.',
  discovery: {
    method: 'POST',
    bodyType: 'json',
    input: { prompt: 'a manta ray gliding over a coral reef', seconds: 5 },
    inputSchema: {
      type: 'object',
      properties: {
        prompt: { type: 'string' },
        seconds: { type: 'number', enum: [5, 10, 15] },
      },
      required: ['prompt'],
    },
    output: {
      example: { runId: '...', status: 'running', statusUrl: 'https://...' },
    },
  },
});
```

Declare `method` and `bodyType` for any route that takes a request body. Without them the listing describes the route as query-parameterized, and an agent will send its arguments in the URL of a request that only reads JSON.

#### Environment Variables

| Variable               | Required          | Default                                         | Description                                                  |
| ---------------------- | ----------------- | ----------------------------------------------- | ------------------------------------------------------------ |
| `X402_ENABLED`         | No                | `false`                                         | Set to `true` to enable x402 payment gating                  |
| `TREZA_X402_PAY_TO`    | Yes (if enabled)  | none                                            | Wallet address that receives USDC payments                   |
| `X402_NETWORK`         | No                | `eip155:8453`                                   | Network identifier (Base mainnet)                            |
| `X402_FACILITATOR_URL` | No                | `https://api.cdp.coinbase.com/platform/v2/x402` | Payment verification facilitator                             |
| `X402_CDP_API_KEY`     | Yes (CDP mainnet) | none                                            | CDP API key id; the CDP facilitator authenticates every call |
| `X402_CDP_API_SECRET`  | Yes (CDP mainnet) | none                                            | CDP API key secret                                           |
| `X402_TOPUP_USD`       | No                | `5`                                             | USD credited per successful top-up call                      |

{% hint style="warning" %}
Mainnet settlement runs through Coinbase's facilitator, which authenticates every verify and settle call. Without `X402_CDP_API_KEY` and `X402_CDP_API_SECRET` the endpoint fails closed with a 503 and stops advertising itself, rather than answering payments it cannot settle.
{% endhint %}

**Testnet Configuration**

```env
X402_ENABLED=true
TREZA_X402_PAY_TO=0xYourTestnetAddress
X402_NETWORK=eip155:84532
X402_FACILITATOR_URL=https://www.x402.org/facilitator
```

**Mainnet Configuration**

```env
X402_ENABLED=true
TREZA_X402_PAY_TO=0xYourWalletAddress
X402_NETWORK=eip155:8453
X402_FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402

# Pay-per-video. Each clip size on sale is its own published pipeline, because
# duration lives in a video-gen node's saved config. Unset leaves it off.
X402_VIDEO_PIPELINES={"5s-16x9":"...","5s-9x16":"...","10s-16x9":"..."}
# Provider cost per generated second. Price is this times the credits markup,
# so it is the one number to change when the model's price moves.
X402_VIDEO_PER_SECOND_USD=0.233
```

### Discovering Payable Services

AI agents can discover Treza's x402-payable endpoints through the Bazaar:

```typescript
import { discoverPayableServices } from '@treza/sdk';

const services = await discoverPayableServices({
  maxPrice: '0.01',          // filter by max price per call
  network: 'eip155:8453',   // filter by network
});

services.forEach(service => {
  console.log(service.url, service.description, service.accepts);
});
```

### Security Model

| Layer                        | Protection                                                                                                                |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **TEE Signing**              | Payment signatures are created inside the hardware-isolated enclave — private keys never leave the Nitro Enclave boundary |
| **Attestation Verification** | Optional pre-signing attestation check ensures the enclave is untampered before authorizing payments                      |
| **Facilitator Verification** | Payment signatures are verified by the Coinbase facilitator before the server returns data                                |
| **On-Chain Settlement**      | Payments settle as real USDC transfers on Base, providing an immutable audit trail                                        |
| **Replay Protection**        | Each payment signature is bound to a specific request and cannot be reused                                                |

### Architecture

```
┌─────────────────────────────────────────────────────────┐
│  AI Agent / Client                                      │
│  ┌───────────────┐  ┌──────────────┐                    │
│  │  @treza/sdk   │  │  @x402/fetch │                    │
│  │  createEnclave│──│  auto-pay    │                    │
│  │  Fetch()      │  │  on 402      │                    │
│  └───────┬───────┘  └──────────────┘                    │
│          │                                              │
│  ┌───────▼───────┐                                      │
│  │ EnclaveAccount│  Signs payments inside TEE           │
│  └───────┬───────┘                                      │
└──────────│──────────────────────────────────────────────┘
           │
           ▼
┌──────────────────┐     ┌──────────────────┐     ┌──────┐
│  Treza Platform  │────▶│  Facilitator     │────▶│ Base │
│  withX402Payment │     │  (Coinbase/x402) │     │  L2  │
│  402 → verify →  │     │  verify + settle │     │ USDC │
│  respond + settle│     │                  │     │      │
└──────────────────┘     └──────────────────┘     └──────┘
```

### FAQ

**Do I need USDC to use Treza?** Only if x402 is enabled on the endpoint you're calling. During development and on testnet, x402 is disabled by default.

**Which network are payments on?** Base (Coinbase L2). Testnet uses Base Sepolia, mainnet uses Base mainnet. Both settle in USDC.

**Can I use a regular wallet instead of an enclave?** Yes. Any viem-compatible account or x402 client works. The enclave-as-wallet pattern is optional — it just adds the security of TEE-based key management.

**How much do API calls cost?** Video generation is priced per second of output, from $1.64 for a five-second clip; the 402 challenge quotes the exact figure for what you asked for. Credit top-up is $5.00 per call.

**Do I need a Treza account?** No, for either x402 endpoint. Video generation needs only the payment, and credit top-up without a bearer credits an account keyed to your wallet address. An account is required for the pipeline API.

**What if the render fails?** A failed render is not charged. The payment stays as credit on your wallet's account for a retry.

**Is there a minimum balance?** No minimum. You just need enough USDC in your wallet to cover the call price.

### Related

* [x402 Protocol Documentation](https://docs.cdp.coinbase.com/x402/welcome)
* [x402 Bazaar](https://docs.cdp.coinbase.com/x402/bazaar)
* [Coinbase Payments MCP](https://docs.cdp.coinbase.com/payments-mcp/welcome)
* [Pay-per-video walkthrough](https://www.trezalabs.com/x402)
* [Treza SDK README](https://github.com/treza-labs/treza-sdk)
* [Agent Commerce Guide](https://github.com/treza-labs/treza-sdk/blob/main/docs/AGENT_COMMERCE_GUIDE.md)
