---
title: "Pay with x402 · Scrape.ly Docs"
description: "Let AI agents pay per request with USDC on Base instead of an API token."
url: https://scrape.ly/docs/x402
markdown: https://scrape.ly/docs/x402.md
---

# Pay with x402

> Let AI agents pay per request with USDC on Base instead of an API token.

AI agents can pay for Scrape.ly API calls with USDC on Base using the [x402 protocol](https://x402.org) instead of an API token. The agent needs a wallet with USDC. It does not need a Scrape.ly account: one is created for the wallet on its first payment.

## How it works

1. The client sends a request with the header `x-scrapely-payment-protocol: x402` and no payment.
2. The API answers `402 Payment Required`. The `payment-required` response header holds the price, network, and receiving address as base64 JSON.
3. The client signs a USDC transfer for that amount and sends the same request again with the signature in the `PAYMENT-SIGNATURE` header.
4. The API checks the signature and handles the request as the wallet's account.

Payments top up credit. The API collects a payment on-chain and adds it to the wallet's account as credit only when the account needs it: when its credit is low, or after a run was refused for insufficient credit. While the account has enough credit, a signed payment only identifies the wallet and no funds move. Actor runs are charged from that credit, and set their estimated cost aside before they start. Credit that is not used stays on the account for later requests.

Each signed payment works for one request. Signing is free, so sign a new one for every request, or use the [session token](#session-token) for follow-up requests.

## Payment requirements

```bash
curl -i -H "x-scrapely-payment-protocol: x402" "https://api.scrape.ly/v2/actors/"
```

Decoded `payment-required` header:

```json
{
  "x402Version": 2,
  "error": "X402 payment required",
  "resource": { "url": "https://api.scrape.ly" },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "amount": "1000000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0x…",
      "maxTimeoutSeconds": 60,
      "extra": { "name": "USD Coin", "version": "2", "displaySymbol": "USDC" }
    }
  ]
}
```

| Field | Description |
| --- | --- |
| `network` | CAIP-2 network id: `eip155:8453` for Base, `eip155:84532` for Base Sepolia. |
| `amount` | Top-up amount in USDC base units (6 decimals). `1000000` is 1 USDC. |
| `asset` | USDC contract on that network. |
| `payTo` | Scrape.ly's receiving wallet. |
| `extra.name`, `extra.version` | The USDC contract's EIP-712 domain. The signature must use these values: `USD Coin` on Base, `USDC` on Base Sepolia. |

Read these values from the response instead of hard-coding them. The same requirements apply to every endpoint.

## Sign and send a payment

The signature is an EIP-3009 `TransferWithAuthorization` for the USDC contract, sent as base64 JSON. The official x402 client libraries build it for you:

```bash
npm install @x402/core @x402/evm viem
```

```js
import { x402Client } from '@x402/core/client'
import { registerExactEvmScheme } from '@x402/evm/exact/client'
import { toClientEvmSigner } from '@x402/evm'
import { privateKeyToAccount } from 'viem/accounts'
import { createPublicClient, http } from 'viem'
import { base } from 'viem/chains'

const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY)
const client = registerExactEvmScheme(new x402Client(), {
  signer: toClientEvmSigner(account, createPublicClient({ chain: base, transport: http() })),
})

const url = 'https://api.scrape.ly/v2/actors/'
const headers = { 'x-scrapely-payment-protocol': 'x402' }

const unpaid = await fetch(url, { headers })
const required = JSON.parse(atob(unpaid.headers.get('payment-required')))
const payment = await client.createPaymentPayload(required)

const res = await fetch(url, {
  headers: { ...headers, 'PAYMENT-SIGNATURE': btoa(JSON.stringify(payment)) },
})
```

The wallet pays no gas: the payment is collected through an x402 facilitator.

The signed authorization must have a `nonce` of `0x` followed by 64 hex characters, and a `validBefore` that is a whole number of seconds at most `maxTimeoutSeconds` plus 5 minutes from now. The x402 client libraries do both by default. Other payments are refused before they are checked.

## Session token

Every response to an authenticated x402 request has an `x-scrapely-x402-session` header. Send it back with `x-scrapely-payment-protocol: x402` instead of `PAYMENT-SIGNATURE`, and the request runs as the same wallet account for the next 10 minutes without a new payment:

```js
const session = res.headers.get('x-scrapely-x402-session')
await fetch('https://api.scrape.ly/v2/actor-runs/RUN_ID', {
  headers: { ...headers, 'x-scrapely-x402-session': session },
})
```

Treat the token like an API key: anyone who has it can use the account's credit until it expires.

## The wallet's account

The first payment from a wallet creates a Scrape.ly account for it and is always collected. Later requests from the same wallet use the same account, so its runs, datasets, and other storage stay together. Collected payments are added to the account as pay-as-you-go credit and are not refunded.

If a run cannot set aside enough credit, the API answers `402 payment-required` with the `payment-required` header. Sign a new payment and retry: once the account has less than one top-up of credit left, that payment is collected as a top-up. Retry again if one top-up is not enough for the run. While the account still has more credit than one top-up, a refused run collects nothing.

x402 payments act for the account but cannot use account endpoints: `/v2/users/me/*`, API keys, earnings, payouts, monetization, conversations, schedule changes, and the other endpoints that need a user API key. These return `403 insufficient-permissions` before the payment is checked, so the signature is not spent and can still pay for another request. Session tokens get the same `403`. See [Users](https://scrape.ly/docs/api-users.md).

Actor owners can stop AI agents from running their Actor with `allowsAgenticUsers` (see [Update Actor](https://scrape.ly/docs/api-actors.md#update-actor)). Such an Actor returns `403 agentic-users-not-allowed` to x402 payments and to the wallet's account on every start path: runs, saved tasks, schedules, automations, Standby, metamorph, and resurrect.

## MCP server

Connect to `https://api.scrape.ly/mcp?payment=x402` without a token. Tools that call the API are marked with `_meta.x402` and the payment requirements. Pass the payment in the tool call's `_meta["x402/payment"]`, as the payment object or its base64 string. A call without a payment returns the requirements as an error result, so the client can sign and retry. One payment covers the whole tool call, including `call-actor` waiting for the run and reading its results. See [MCP server](https://scrape.ly/docs/mcp.md).

## Errors

Each row except the last returns `402` with error type `payment-required` and the `payment-required` header.

| Message | Cause |
| --- | --- |
| `Payment required. Provide a valid x402 payment in the PAYMENT-SIGNATURE header.` | No `PAYMENT-SIGNATURE` header. |
| `The PAYMENT-SIGNATURE header is not valid base64-encoded x402 payment data.` | The header is not base64 JSON. |
| `The provided x402 payment is invalid, already used, or does not match the payment requirements.` | Wrong signature, amount, receiver, network, or token name, not enough USDC, or the authorization expired. |
| `This x402 payment has already been used.` | The signature already paid for a request. |
| `The x402 payment must carry one signed authorization with a from address and nonce.` | The authorization has no `from`, its `nonce` is not `0x` plus 64 hex characters, or the payment carries two different authorizations. |
| `The x402 authorization validBefore may be at most 60 seconds ahead.` | `validBefore` is more than `maxTimeoutSeconds` plus 5 minutes away, or is not a whole number of seconds. |
| `Payment settlement failed: …` | A top-up was needed but could not be confirmed. Usually no funds moved. If they did, resend the same payment once to add it as credit. Then sign a new payment for the request. |
| `This x402 payment was credited to the wallet account but cannot authenticate a request. Send a new x402 payment.` | A resent payment whose earlier collection had not been credited. It is now credit; sign a new payment for the request. |
| `The x402 session has expired or is not valid. Send a new x402 payment.` | The session token is older than 10 minutes or unknown. |
| `Insufficient funds. Estimated cost: …` | A run could not set aside enough credit. Retry with a new payment to top up. |
| `403 insufficient-permissions` | The endpoint needs a user API key. The payment is not collected. |

---

- Previous: [MCP server](https://scrape.ly/docs/mcp.md)
- Next: [Account & billing](https://scrape.ly/docs/account.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
