AnjomanBeta
9 min readUpdated October 27, 2026

Paying with x402

x402 is an HTTP-native payment protocol. Your client makes a request, gets a payment challenge back if it hasn't paid, completes the payment, and retries. The node verifies the payment before calling the miner. The whole exchange happens inside a normal HTTP request cycle — no separate checkout, no account, no API key.

This page covers the payment mechanics. For what to actually send and get back, see Engine Inference.

Paying more than occasionally? The same endpoints accept a second scheme, escrow: fund once, then skip the facilitator round trip and the per-request on-chain settlement entirely. Same header, same envelope, one different string. See Paying from Escrow.

What You Need

  • A USDC balance on Base Sepolia (0x036CbD53842c5426634e7929541eC2318f3dCF7e) or Solana Devnet.
  • An x402 client library to sign the payment — the @x402/* packages (@x402/fetch, @x402/core, @x402/evm, and @x402/svm for Solana) are what the Telegraph MCP server uses.
  • Node.js 20 or newer if you're using those packages. They sign with the WebCrypto API, which Node 18 doesn't expose — on Node 18 payments fail with Failed to create payment payload: Crypto API not available.
  • The URL of a Telegraph node.

Live testnet node: https://devnode.telegraphprotocol.com

Step 1: Discover Available Miners

Before sending a request, check which miners are live and what their schemas look like:

GET /api/miners

This returns a JSON array of every registered miner with its endpoints, input/output schemas, supported Intents, and minimum prices. The set of live miners changes as operators register and deregister on-chain, so treat this endpoint as the source of truth rather than any list written down elsewhere.

Filter server-side instead of fetching the whole catalog: GET /api/miners?intent=WEATHER_CHECK_TEMPERATURE returns only miners that support that Intent. status and limit are also supported query params.

You can also browse the live miner set and their output on the Intelligence Terminal.

Each miner has a numeric id — that's the value you put in the request path. The bittensor- prefix on some slugs is historical — every provider is a miner, whether it's a Bittensor subnet, a hosted model, or a private API.

Step 2: Make a Request — Receive the 402

Call a miner through the Engine. Without a payment header you get an HTTP 402 back:

POST /engine/v1/ask/18
Content-Type: application/json

{
  "method": "GET",
  "endpoint": "/predict",
  "payload": { "lat": 25.2, "lon": 55.3, "variable": "hourly" }
}

Response (402 Payment Required):

HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6Mi...  ← base64-encoded challenge
Content-Type: application/json

Decode the PAYMENT-REQUIRED header (base64 → JSON) to see the payment options:

json
{
  "x402Version": 2,
  "error": "Payment required",
  "resource": {
    "url": "http://devnode.telegraphprotocol.com/v1/ask/18",
    "description": "Payment required for direct subnet inference.",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:84532",
      "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      "amount": "10000",
      "payTo": "0x...",
      "maxTimeoutSeconds": 60
    },
    {
      "scheme": "exact",
      "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
      "asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
      "amount": "10000",
      "payTo": "...",
      "maxTimeoutSeconds": 60
    }
  ]
}

The amount field is in 6-decimal USDC units — 10000 = $0.01. Different miners charge different amounts.

Each entry also carries an extra object holding what the signature needs — {"name": "USDC", "version": "2"} on Base Sepolia, and a feePayer on Solana. The SDK reads these for you.

The response body carries a shorter summary of the same options, meant for humans reading a failed call:

json
{
  "error": "payment required",
  "accepts": [
    {
      "scheme": "exact",
      "price": "$0.01",
      "network": "eip155:84532",
      "payTo": "0x5a2324aA18613FAD4e44bDF0d6c73Ec1f6D87ff8"
    },
    {
      "scheme": "exact",
      "price": "$0.01",
      "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
      "payTo": "G53EbeTZSNsAn7bj6iMFUQnq3zpDdEbHhKkPRywo8bix"
    }
  ]
}

It quotes price as a dollar string and omits asset, amount and maxTimeoutSeconds, so build your payment from the decoded header — the body alone doesn't carry enough to sign with.

Always read payTo from the challenge you received. Never hardcode a receiving address — it is set per node and changes.

Note that resource.url reports the path inside the engine (/v1/ask/18), without the /engine prefix you called. Keep using the URL you requested; don't rebuild it from this field.

Step 3: Complete the Payment

Hand the decoded challenge and your wallet to an x402 client. It picks a network from accepts[], signs a transfer of amount of the asset token to payTo, and returns the base64 payload for the retry header.

js
import { wrapFetchWithPayment } from "@x402/fetch";
import { createSigner } from "@x402/evm";

const signer = createSigner(process.env.EVM_PRIVATE_KEY);
const fetchWithPayment = wrapFetchWithPayment(fetch, signer);

// Handles the 402, signs, and retries — you just make the call.
const res = await fetchWithPayment("https://devnode.telegraphprotocol.com/engine/v1/ask", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query: "What is the current price of Bitcoin?" }),
});

Use a library rather than hand-rolling the payload. The signature is EIP-712 typed data over the token's own domain, not a plain transfer call, and the node rejects a malformed payload with a bare 402 — indistinguishable from having sent no payment at all. The quickest working setup is the MCP server, which bundles this client and pays for you.

Step 4: Retry with Payment Header

Retry the exact same request with the PAYMENT-SIGNATURE header containing the base64-encoded payment proof:

POST /engine/v1/ask/18
Content-Type: application/json
PAYMENT-SIGNATURE: <base64-encoded-payment-payload>

{
  "method": "GET",
  "endpoint": "/predict",
  "payload": { "lat": 25.2, "lon": 55.3, "variable": "hourly" }
}

The node forwards the payment proof to the PayAI facilitator (https://facilitator.payai.network) for verification. On success, the miner is called.

Step 5: Receive the Response

On the direct path the miner's raw output comes back inside result (with result_shape: "raw"), alongside the routing and cost metadata. On the auto-routed POST /engine/v1/ask, result is instead read into the Intent's shape; see Engine Inference.

json
{
  "miner_id": "18",
  "miner_name": "zeus",
  "result": {
    "hourly": {
      "time": ["2026-06-26T00:00", "2026-06-26T01:00"],
      "temperature_2m": [38.2, 37.9],
      "wind_speed_10m": [12.1, 11.8]
    },
    "latitude": 25.2,
    "longitude": 55.3
  },
  "result_shape": "raw",
  "cost_usd": 0.01,
  "duration_ms": 412,
  "signal_hash": "0x7a44569d..."
}

Keep the signal_hash. It's how you look the call up afterwards — see Verifying a call.

The response also carries a settlement header:

PAYMENT-RESPONSE: <settlement-proof>
X-TELEGRAPH-RECEIPT: <base64 JSON — see below>

Keep this if you need to audit or dispute the payment later.

You Only Pay for Answers

Payment settles only when the miner actually answers. If the request fails — a bad payload, an unregistered miner, an upstream error — the node returns an error status and no payment is taken. You are never charged for a failed call.

Verifying a call

Every paid call is recorded as a signal — a hash committing to the request and the response together. Look one up by its hash:

GET /engine/v1/signal/{signal_hash}

The response includes the signal, the result behind it, and the payload the hash was computed over, so you can re-derive the hash yourself rather than taking the node's word for it.

Your receipt

Alongside x402's own PAYMENT-RESPONSE, a served request returns X-TELEGRAPH-RECEIPT (base64 JSON). The two answer different questions: PAYMENT-RESPONSE says your payment settled; the Telegraph receipt says what that payment bought.

json
{
  "receipt_hash": "0x3f1c…",
  "rail": "x402",
  "payer": "0xyourwallet…",
  "miner": "377",
  "intent": "STOCK_PRICE_US_EQUITY",
  "amount_uusdc": "10000",
  "epoch_id": 314,
  "settlement": "onchain",
  "chain": "eip155:84532",
  "tx_hash": "0x6bb27730…",
  "timestamp": 1789466181
}

receipt_hash identifies your specific call, permanently. tx_hash does not, and the difference matters more here than on any other rail: the facilitator batches concurrent payments from one wallet into a single settlement transaction — measured on the live network, 40 payments shared 25 transactions, one covering four separate /v1/ask calls 0.3 seconds apart. If you are reconciling calls to charges, key on receipt_hash.

It is the same object, field for field, that the escrow and WebSocket rails return, so you can handle all three identically.

Only a delivered 200 carries one. A payment that settled but whose request then failed is recorded as a payment but bills you no consumption, so there is nothing to receipt.

Paying on Solana? You get a receipt too, with epoch_id: 0. A Solana payment goes to the node's Solana receiving address rather than the Diamond, so it never enters Base epoch settlement. The receipt is the same object with the same fields, and its epoch_id is 0: epoch numbering starts at 1, so 0 means "settled on its own chain, not in a Telegraph epoch" — there is no epoch close to wait for and nothing still pending. tx_hash names the Solana transaction and chain the Solana network, so you can verify it on Solana directly. Only a Base Sepolia receipt reconciles against a Telegraph epoch.

Payment Networks

NetworkCAIP-2 IdentifierUSDC Contract
Base Sepolia (testnet)eip155:845320x036CbD53842c5426634e7929541eC2318f3dCF7e
Solana Devnetsolana:EtWTRABZaYq6iMfeYKouRu166VU2xqa14zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU

Choose based on where you hold USDC. The amount is the same either way. The accepts[] array in the challenge is authoritative — a node may offer more or fewer networks than this table.

Dynamic Pricing

The price per request isn't fixed. It's the miner's declared floor price multiplied by a demand multiplier based on 24-hour request volume for that Intent. A miner with a 0.01 floor seeing 2,000 requests per day charges0.015 at the 1.5× tier. See the full demand multiplier tiers.

A miner's floor price is the min_price_usdc field of its /api/miners entry, in the same 6-decimal units as the challenge — 10000 means $0.01. The amount actually charged — floor × current multiplier — is the amount field of the 402 challenge.

Endpoints That Don't Require Payment (Discovery)

Inference is paid. Discovery and health endpoints are always open:

EndpointPurpose
GET /api/minersThe live miner catalog with schemas and prices. Supports ?intent=, ?status=, &limit= filters.
GET /miner-dispatcher/openapi.jsonMachine-readable OpenAPI spec of all miner endpoints.
GET /miner-dispatcher/openapi.yamlThe same spec in YAML.

Use these to explore the network and build your request before paying for a single call.

Was this helpful?