Find it, pay it,
call it.
13 paid routes. No signup or API key: each call is paid on its own in USDC on Base. Give your agent the skill file, or plug in the MCP server and set a spend cap.
One 402, one signature, one retry.
The same flow on every route. Errors are not charged, apart from the exceptions noted on a route in the docs.
Call
Send the request with no payment header. Paid routes answer HTTP 402.
Read the challenge
Base64-decode the PAYMENT-REQUIRED response header (x402 v2 JSON). The 402 body is empty JSON {}.
Check it
Pay only if scheme is exact, network is eip155:8453 (Base), asset is USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, payTo is 0x5b32c973596078a967562ca652761404f19be0e9 and amount matches the listed price.
Sign
Sign an EIP-3009 transferWithAuthorization for that USDC amount to payTo with an x402 client library. No gas, no on-chain transaction from you.
Retry
Send the identical request again with the PAYMENT-SIGNATURE header (x402 v2). X-PAYMENT is the legacy v1 header; do not rely on it.
Use the result
200 returns the JSON plus a PAYMENT-RESPONSE receipt header. Settlement happens only after the route succeeds; error responses are not charged.
# 1. Unpaid call: HTTP 402, challenge in the PAYMENT-REQUIRED header curl -si https://horizonpulse.dev/api/pulse | grep -i '^payment-required' | cut -d' ' -f2 | base64 -d # 2. Check scheme exact, network eip155:8453, USDC, payTo and amount # 3. Sign EIP-3009 with an x402 client, retry with PAYMENT-SIGNATURE # 4. 200 + JSON, receipt in the PAYMENT-RESPONSE header
// npm i @x402/core@2.27.0 @x402/evm@2.27.0 viem@2.37.5
import { x402Client, x402HTTPClient } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
const PAY_TO = "0x5b32c973596078a967562ca652761404f19be0e9"; // the only payTo to accept
const account = privateKeyToAccount(process.env.PRIVATE_KEY); // dedicated low-balance wallet
const http = new x402HTTPClient(
x402Client.fromConfig({
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
policies: [(v, reqs) => reqs.filter((r) => r.payTo.toLowerCase() === PAY_TO)],
spendControls: { maxAmountPerPayment: "$0.05" }, // per-payment cap
}),
);
const url = "https://horizonpulse.dev/api/pulse";
const r1 = await fetch(url); // 402 + PAYMENT-REQUIRED
const req = http.getPaymentRequiredResponse((h) => r1.headers.get(h), await r1.json());
const payload = await http.createPaymentPayload(req); // throws if payTo or cap don't match
const r2 = await fetch(url, { headers: http.encodePaymentSignatureHeader(payload) });
console.log(r2.status, (await r2.json()).assets.BTC.priceUsd); // 200, $0.005 USDC on BaseA skill file your agent can follow.
/skill.md is plain markdown: when to use each route, exact prices, the pay flow step by step, error and billing rules, and safety checks. It is generated from /openapi.json, so prices match the 402s.
curl https://horizonpulse.dev/skill.md
Every route as a tool, paid with a cap.
The local MCP server (stdio, Node) exposes each route as a tool plus free catalog, quote and demo tools, and pays the x402 challenge with the official x402 client from a wallet you configure.
Guards
0x5b32c973596078a967562ca652761404f19be0e9AssetUSDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 on BasePrice checkRefuses any amount above the price in /openapi.jsonCapsHP_MAX_USD_PER_CALL (default $0.05), HP_MAX_USD_TOTAL per session (default $1)No keyQuote only, never paysWallet
Use a dedicated buyer wallet with a small USDC balance on Base. The key stays in your MCP config on your machine; it is never sent to Horizon Pulse, only the signed payment is. No ETH or gas is needed.
git clone https://github.com/horizon-pulse/horizon-pulse.git cd horizon-pulse/mcp npm install # installs pinned deps (@x402/core + @x402/evm 2.27.0) and builds dist/
{
"mcpServers": {
"horizon-pulse": {
"command": "node",
"args": ["/absolute/path/to/horizon-pulse/mcp/dist/index.js"],
"env": {
"HP_PRIVATE_KEY": "0x...key of a dedicated, low-balance buyer wallet",
"HP_MAX_USD_PER_CALL": "0.05",
"HP_MAX_USD_TOTAL": "1"
}
}
}
}{
"mcpServers": {
"horizon-pulse": { "url": "https://horizonpulse.dev/mcp" }
}
}From a domain name to a payment, without paying.
mcp/examples/demo.mjs starts with only horizonpulse.dev, finds the routes through /.well-known/x402, llms.txt and skill.md, then drives the local MCP server: catalog, quote, then a paid tool. It runs with HP_DRY_RUN=1 and no private key, so it never signs and never pays. It prints the real 402 requirements and the exact authorization a client would sign, with the payTo, price and cap checks.
git clone https://github.com/horizon-pulse/horizon-pulse.git cd horizon-pulse/mcp npm install node examples/demo.mjs horizonpulse.dev # dry run: never signs, never pays
DRY RUN: this demo never signs and never pays. No private key is loaded.
Start: only the domain "horizonpulse.dev" (2026-10-08T03:48:50.090Z)
=== 1. Discover routes from the domain ===
GET https://horizonpulse.dev/.well-known/x402 → 15 paid operations
… [trimmed: the 15 operations, GET/POST /api/…]
GET https://horizonpulse.dev/llms.txt → published payTo 0x5b32c973596078a967562ca652761404f19be0e9
POST `/mcp` — hosted MCP (streamable HTTP, stateless); `initialize` and `tools/list` free, `tools/call` returns the same
GET https://horizonpulse.dev/skill.md → HTTP 200, 127 lines (pay flow + safety checks)
[PASS] llms.txt payTo equals the MCP server's hardcoded payTo 0x5b32c973596078a967562ca652761404f19be0e9
=== 2. Local MCP server (stdio), HP_DRY_RUN=1, no HP_PRIVATE_KEY ===
tools/list → 16 tools: catalog, quote, demo, extract, fetch, funding, gas, http, pdf, portfolio, pulse, screenshot, search, signals, x402_check, yield
catalog → 13 routes from https://horizonpulse.dev/openapi.json; mode: dry-run (never pays); payTo 0x5b32c973596078a967562ca652761404f19be0e9; caps $0.05/call, $1.00/session
… [trimmed: 13 tools with method, path and price]
quote pulse → HTTP 402, result "quote", $0.005, wouldPayWithCurrentConfig=false
tools/call pulse → result "quote" (not paid). Server says: HP_DRY_RUN is set: not paying. This call costs $0.005 USDC on Base to 0x5b32c973596078a967562ca652761404f19be0e9.
session spend after the dry run: $0.00 of $1.00
=== 3. Real HTTP 402 from GET /api/pulse (unpaid) ===
HTTP 402; PAYMENT-REQUIRED header: 3040 chars base64, decoded:
{
"x402Version": 2,
"resource": "https://horizonpulse.dev/api/pulse",
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "5000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x5b32c973596078a967562ca652761404f19be0e9",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USD Coin",
"version": "2"
}
}
]
}
=== 4. Checks before signing ===
[PASS] scheme "exact" is exact
[PASS] network eip155:8453 is Base mainnet
[PASS] asset 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 is Base USDC
[PASS] payTo 0x5b32c973596078a967562ca652761404f19be0e9 equals the pinned payTo
[PASS] amount 5000 ($0.005) ≤ advertised price 5000 in /openapi.json
[PASS] amount $0.005 ≤ per-call cap $0.05
[PASS] amount $0.005 ≤ remaining session budget $1.00
=== 5. What WOULD be signed (EIP-712 TransferWithAuthorization), NOT SIGNED ===
{
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 8453,
"verifyingContract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
},
"types": { … [trimmed: TransferWithAuthorization(from, to, value, validAfter, validBefore, nonce)] },
"primaryType": "TransferWithAuthorization",
"message": {
"from": "<your buyer wallet address>",
"to": "0x5b32c973596078a967562ca652761404f19be0e9",
"value": "5000",
"validAfter": "0",
"validBefore": "1791431633",
"nonce": "0xfbdff919058d838a9ac7733f272baee4e13265cce7c25d76b6a45962415af9d6"
}
}
With a key, the client would sign this, retry GET /api/pulse with a PAYMENT-SIGNATURE header, and pay $0.005 USDC on Base only if the route succeeds.
DRY RUN complete: nothing was signed, nothing was sent, no payment happened.Want your own API found by agents?
Agent promotion gets your API found and used by AI agents: a description pack (llms.txt, skill.md, an OpenAPI summary and a Coinbase Bazaar description), directory submissions or ready-to-submit packs, and a monthly agent-traffic report. See Agent promotion →
Tools and prices, straight from the API.
Tool names are the same in the skill file and both MCP servers. Inputs marked * are required. Full schemas and examples are in the docs.
| Tool | Route | Inputs | USDC per call |
|---|---|---|---|
| pulse | GET /api/pulseBTC, ETH and SOL spot prices with 24h momentum (Coinbase Exchange, CoinGecko fallback) | none | $0.005 |
| signals | GET /api/signalsRSI, MACD and Bollinger bands plus perpetual funding (CoinGecko, OKX) | none | $0.015 |
| yield | GET /api/yieldRanked DeFi yields, pools with TVL of $10M or more (DefiLlama) | none | $0.02 |
| portfolio | GET /api/portfolioBase and Ethereum balances, rule-based risk score and rebalance flags (not financial advice) | address* | $0.04 |
| gas | GET /api/gasBase and Ethereum fees with a suggested max fee (public RPC) | none | $0.01 |
| funding | GET /api/fundingBTC, ETH and SOL perpetual funding with a crowding hint (OKX) | none | $0.01 |
| fetch | GET /api/fetchAny public URL as clean text or markdown | url* | $0.02 |
| http | GET/POST /api/httpUniversal HTTP proxy: your method, headers and body | url*, method, headers, body | $0.01 |
| extract | GET/POST /api/extractPage to structured fields, or your own CSS selectors | url*, fields, html | $0.015 |
| x402_check | GET /api/x402-checkAudit any x402 endpoint without paying it | url*, method, body | $0.01 |
| screenshot | GET /api/screenshotHeadless Chromium render to PNG or JPEG | url*, width, height, fullPage, format, delayMs | $0.02 |
| search | GET /api/searchWeb search with the top pages as clean text and sources | q*, n | $0.03 |
GET /api/pdfPDF URL to text per page plus metadata | url*, pages | $0.02 |