API docs · OpenAPI 3.1.0

Every route.
One payment flow.

13 paid routes and 15 operations, paid per call in USDC on Base with x402. No signup or API key. This page is rendered from /openapi.json, so prices and inputs always match what the API charges.

Quickstart

Call, see the price, pay, retry.

You only pay for a successful response. Errors are not charged, apart from the exceptions noted on a route.

The 402 flow with curl
# 1. Free sample of the real output, no payment
curl https://horizonpulse.dev/api/demo/pulse

# 2. Unpaid call: HTTP 402 with the price in PAYMENT-REQUIRED (base64 JSON)
curl -si https://horizonpulse.dev/api/pulse | grep -i '^payment-required' | cut -d' ' -f2 | base64 -d

# 3. Pay: an x402 client signs a USDC transfer on Base and retries with
#    PAYMENT-SIGNATURE. You get 200 plus a PAYMENT-RESPONSE receipt.
agent.mjs
// 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 account = privateKeyToAccount(process.env.PRIVATE_KEY);
const http = new x402HTTPClient(
  new x402Client().register("eip155:8453", new ExactEvmScheme(account)),
);

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);
const r2 = await fetch(url, { headers: http.encodePaymentSignatureHeader(payload) });
console.log(r2.status, (await r2.json()).assets.BTC.priceUsd); // 200, $0.005 USDC on Base

Network

Base mainnet eip155:8453, USDC, x402 v2 exact scheme.

payTo

0x5b32c973596078a967562ca652761404f19be0e9

Facilitator

Coinbase CDP verifies the payment first and settles it only after the route succeeds.

Reference

Crypto market data

Crypto market data on Base and Ethereum.

GET

/api/pulse

$0.005USDC per call · 5000 atomic

Crypto market data. Use when you need current BTC, ETH and SOL spot prices in USD. Call GET with no parameters. Returns JSON: assets.BTC/ETH/SOL with priceUsd, change24hPct and momentum (bullish/bearish/neutral), plus overall momentum, sentiment (risk-on/risk-off/neutral), signal (buy/sell/hold) and avgChange24hPct, and asOf. Prices from Coinbase Exchange, CoinGecko fallback (source field). Labels are fixed rules on 24h change, not advice. Errors are not charged.

Inputs

No inputs. Call it as is.

Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/pulse'
200 response example (trimmed)
{
  "ok": true,
  "source": "coinbase",
  "asOf": "2026-10-01T14:36:42.113Z",
  "assets": {
    "BTC": {
      "id": "bitcoin",
      "priceUsd": 84092.26,
      "change24hPct": 0.40332180519101707,
      "momentum": "neutral"
    },
    "ETH": {
      "id": "ethereum",
      "priceUsd": 2695,
      "change24hPct": 0.6776596460031482,
      "momentum": "neutral"
    },
    "SOL": {
      "id": "solana",
      "priceUsd": 117.79,
      "change24hPct": -0.8084210526315737,
      "momentum": "neutral"
    }
  },
  "overall": {
    "momentum": "neutral",
    "sentiment": "neutral",
    "signal": "hold",
    "avgChange24hPct": 0.09085346618753054
  }
}
Errors
502Upstream data source failed. Not charged
GET

/api/signals

$0.015USDC per call · 15000 atomic

Crypto technical analysis. Use when you need indicators for BTC, ETH and SOL rather than just prices. Call GET with no parameters. Returns JSON per asset: rsi14, macd {macd, signal, histogram} (null when there are too few candles), bollinger {upper, middle, lower, period}, lastClose and OKX perpetual funding {fundingRate, fundingTime}. Computed from CoinGecko OHLC closes (Coinbase candles fallback); methodology included. Descriptive only, not advice. Errors are not charged.

Inputs

No inputs. Call it as is.

Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/signals'
200 response example (trimmed)
{
  "ok": true,
  "asOf": "2026-10-01T14:36:42.828Z",
  "assets": {
    "BTC": {
      "rsi14": 74.6303843022904,
      "macd": null,
      "bollinger": {
        "upper": 88132.02721674947,
        "middle": 71862.85,
        "lower": 55593.672783250535,
        "period": 20
      },
      "lastClose": 84442,
      "funding": {
        "instId": "BTC-USDT-SWAP",
        "fundingRate": 0.0000850403758528,
        "fundingTime": "1790870400000",
        "venue": "okx"
      }
    },
    "ETH": {
      "rsi14": 78.943937124236,
      "macd": null,
      "bollinger": {
        "upper": 2868.5199493803807,
        "middle": 2201.9800000000005,
        "lower": 1535.4400506196203,
        "period": 20
      },
      "lastClose": 2686.91,
      "funding": {
        "instId": "ETH-USDT-SWAP",
        "fundingRate": 0.0000642085539106,
        "fundingTime": "1790870400000",
        "venue": "okx"
      }
    }
  }
}
Errors
502Upstream data source failed. Not charged
GET

/api/yield

$0.02USDC per call · 20000 atomic

DeFi yields. Use when you need to compare current DeFi pool yields, especially stablecoin or single-asset pools. Call GET with no parameters. Returns JSON: up to 25 pools ranked by preference tier then APY, each with project, chain, symbol, tvlUsd, apy, apyBase, apyReward, apyMean30d, stablecoin, exposure, ilRisk and poolId. Filters: TVL >= $10M, non-outlier APY. Figures passed through from DefiLlama at fetch time, not advice. Errors are not charged.

Inputs

No inputs. Call it as is.

Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/yield'
200 response example (trimmed)
{
  "ok": true,
  "source": "defillama",
  "asOf": "2026-10-01T14:36:44.086Z",
  "meta": {
    "scanned": 17001,
    "afterTvlFilter": 816,
    "afterPreferenceFilter": 686,
    "returned": 25
  },
  "pools": [
    {
      "rank": 1,
      "poolId": "edf44260-d78f-5dab-853a-f89c4f523169",
      "chain": "Ethereum",
      "project": "axis",
      "symbol": "SUSDX",
      "tvlUsd": 37488444,
      "apy": 25.85643,
      "apyBase": 25.85643,
      "apyReward": null,
      "apyMean30d": 23.14246,
      "stablecoin": true,
      "exposure": "single",
      "ilRisk": "no",
      "poolMeta": null,
      "preferenceTier": 2,
      "preferenceReason": "stablecoin + single-asset"
    },
    {
      "rank": 2,
      "poolId": "9fe33fd6-d3f3-4dbe-9187-7bff012e79f5",
      "chain": "Ethereum",
      "project": "pendle-v2",
      "symbol": "APYUSD",
      "tvlUsd": 19815029,
      "apy": 15.60566,
      "apyBase": 15.60566,
      "apyReward": null,
      "apyMean30d": 14.44087,
      "stablecoin": true,
      "exposure": "single",
      "ilRisk": "no",
      "poolMeta": "For buying PT-apyUSD-05NOV2026",
      "preferenceTier": 2,
      "preferenceReason": "stablecoin + single-asset"
    }
  ]
}
Errors
502Upstream data source failed. Not charged
GET

/api/portfolio

$0.04USDC per call · 40000 atomic

Crypto wallet analysis. Use when you need the token holdings and USD value of one EVM address. Call GET with required address=0x... (40 hex). Reads Base and Ethereum via RPC: native ETH, USDC, WETH, WBTC/cbBTC, DAI. Returns JSON: holdings (balance, priceUsd, valueUsd, weight), totals (valueUsd, stablecoinShare, max asset/chain weight), risk {score 0-100, band} and rule-based rebalance suggestions. Read-only; not advice. Invalid addresses return 400 and are not charged.

Inputs
address*stringEVM address to read on Base + Ethereum (required): 0x followed by 40 hex characters.
Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/portfolio?address=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
200 response example (trimmed)
{
  "ok": true,
  "address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
  "asOf": "2026-10-01T14:36:45.419Z",
  "holdings": [
    {
      "network": "base",
      "chainId": 8453,
      "symbol": "USDC",
      "kind": "erc20",
      "contract": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "decimals": 6,
      "balanceAtomic": "43286078",
      "balance": "43.286078",
      "stablecoin": true,
      "priceUsd": 1,
      "valueUsd": 43.286078,
      "weight": 0.0015226151828024019
    },
    {
      "network": "ethereum",
      "chainId": 1,
      "symbol": "ETH",
      "kind": "native",
      "contract": null,
      "decimals": 18,
      "balanceAtomic": "5715987139328012679",
      "balance": "5.715987139328012679",
      "stablecoin": false,
      "priceUsd": 2696.21,
      "valueUsd": 15411.50168492758,
      "weight": 0.5421093233546252
    }
  ],
  "totals": {
    "valueUsd": 28428.77076815375,
    "stablecoinShare": 0.003485316759255355,
    "maxAssetWeight": 0.83884957651361,
    "maxAssetSymbol": "ETH",
    "maxChainWeight": 0.682574246168484,
    "maxChain": "ethereum"
  },
  "risk": {
    "score": 85,
    "band": "high"
  },
  "suggestions": [
    {
      "priority": "high",
      "code": "concentration_high",
      "message": "ETH is 83.9% of portfolio USD. Rule: trim toward ≤40% single-asset weight (swap a slice to USDC/DAI or another asset)."
    }
  ]
}
Errors
400Invalid input. Not charged502Upstream data source failed. Not charged
GET

/api/gas

$0.01USDC per call · 10000 atomic

Blockchain gas fees. Use before sending a transaction on Base or Ethereum to choose fees and timing. Call GET with no parameters. Returns JSON per network: baseFeeGwei, priorityFeeGwei (p50, with p10/p90), suggestedMaxFeeGwei (2x base + priority), timingHint (cheap/normal/expensive vs the last 20 blocks) and simpleTransfer cost in ETH and USD, plus ethUsd. From eth_feeHistory with eth_gasPrice fallback. Descriptive, not a forecast. Errors are not charged.

Inputs

No inputs. Call it as is.

Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/gas'
200 response example (trimmed)
{
  "ok": true,
  "asOf": "2026-10-01T14:36:44.639Z",
  "ethUsd": 2699.12,
  "networks": [
    {
      "network": "base",
      "chainId": 8453,
      "ok": true,
      "baseFeeGwei": "0.005",
      "priorityFeeGwei": "0.001",
      "priorityFeePercentilesGwei": {
        "p10": "0.000000079",
        "p50": "0.001",
        "p90": "0.01"
      },
      "suggestedMaxFeeGwei": "0.011",
      "timingHint": "normal",
      "simpleTransfer": {
        "gasLimit": 21000,
        "costWei": "231000000000",
        "costEth": "0.000000231",
        "costUsd": 0.000623
      }
    },
    {
      "network": "ethereum",
      "chainId": 1,
      "ok": true,
      "baseFeeGwei": "0.47866594",
      "priorityFeeGwei": "0.244726498",
      "priorityFeePercentilesGwei": {
        "p10": "0.001003273",
        "p50": "0.244726498",
        "p90": "2"
      },
      "suggestedMaxFeeGwei": "1.202058378",
      "timingHint": "expensive",
      "simpleTransfer": {
        "gasLimit": 21000,
        "costWei": "25243225938000",
        "costEth": "0.000025243225938",
        "costUsd": 0.068134
      }
    }
  ]
}
Errors
502Upstream data source failed. Not charged
GET

/api/funding

$0.01USDC per call · 10000 atomic

Crypto derivatives data. Use when you need current perpetual futures funding rates for BTC, ETH and SOL. Call GET with no parameters. Returns JSON per asset from OKX USDT swaps: instId, fundingRate, fundingTime and a rule-based crowding hint {side: longs/shorts/neutral, level: quiet/mild/elevated/extreme} with the thresholds used. OKX public data only. The hint is not a forecast or advice. Errors are not charged.

Inputs

No inputs. Call it as is.

Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/funding'
200 response example (trimmed)
{
  "ok": true,
  "source": "okx",
  "asOf": "2026-10-01T14:36:45.068Z",
  "assets": {
    "BTC": {
      "instId": "BTC-USDT-SWAP",
      "fundingRate": 0.0000850403758528,
      "fundingTime": "1790870400000",
      "crowding": {
        "side": "longs",
        "level": "mild"
      }
    },
    "SOL": {
      "instId": "SOL-USDT-SWAP",
      "fundingRate": -0.0000439448031955,
      "fundingTime": "1790870400000",
      "crowding": {
        "side": "neutral",
        "level": "quiet"
      }
    }
  }
}
Errors
502Upstream data source failed. Not charged
Reference

Web and documents

Read, call and render the public web.

GET

/api/fetch

$0.02USDC per call · 20000 atomic

Web page to text. Use when you need to read a public web page as clean text or markdown for an LLM. Call GET with required url (absolute http/https). Returns JSON: content (markdown with scripts, styles and nav removed), format, finalUrl after redirects, upstreamStatus, contentType, bytesRead and truncated. Caps: 200KB, 8s, 3 redirects; private and localhost targets are blocked. For raw status, headers and body use /api/http. Errors are not charged.

Inputs
url*stringAbsolute http(s) URL to fetch (required). Private/localhost blocked; ~200KB / 8s caps.
Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/fetch?url=https%3A%2F%2Fexample.com'
200 response example (trimmed)
{
  "ok": true,
  "requestedUrl": "https://example.com",
  "finalUrl": "https://example.com/",
  "upstreamStatus": 200,
  "contentType": "text/html; charset=utf-8",
  "format": "markdown",
  "truncated": false,
  "bytesRead": 713,
  "content": "# Example Domain\n\nThis domain is for use in documentation examples without needing permission. This is not a service, avoid relying on it fo…"
}
Errors
400Bad or blocked URL. Not charged413Upstream body too large415Unsupported Content-Type502Upstream error504Upstream timeout
GET

/api/http

$0.01USDC per call · 10000 atomic

HTTP proxy. Use when you need to call a public URL or API and get the raw response. GET with required url (optional method, headers as JSON string), or POST a JSON body {url, method, headers, body}. Methods: GET, POST, HEAD, PUT, PATCH, DELETE. Returns JSON: status, filtered headers, body (text or base64), contentType, finalUrl, elapsedMs. Caps: 384KB response, 64KB body, 12s, 3 redirects; private targets blocked. Upstream 4xx/5xx come back as data (charged); proxy errors are not.

Inputs
url*stringAbsolute http(s) URL (required). Private/localhost blocked.
methodGET | POST | HEAD | PUT | PATCH | DELETEUpstream method: GET (default) | POST | HEAD | PUT | PATCH | DELETE
headersstringOptional allowlisted outbound headers (no Cookie / hop-by-hop). On GET pass as JSON string query param.
Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/http?url=https%3A%2F%2Fexample.com&method=GET'
200 response example (trimmed)
{
  "ok": true,
  "status": 200,
  "headers": {
    "content-type": "text/html; charset=utf-8",
    "last-modified": "Mon, 28 Sep 2026 16:19:32 GMT",
    "server": "cloudflare"
  },
  "body": "<!doctype html><html lang=en><head><meta charset=utf-8><link rel=icon href=data:,><meta name=viewport content=\"width=dev…",
  "bodyEncoding": "text",
  "contentType": "text/html; charset=utf-8",
  "finalUrl": "https://example.com/",
  "truncated": false,
  "bytesRead": 713,
  "elapsedMs": 10
}
Errors
400Bad or blocked URL / method / headers. Not charged413Request or upstream body too large502Upstream error504Upstream timeout
POST

/api/http

$0.01USDC per call · 10000 atomic

HTTP proxy. Use when you need to call a public URL or API and get the raw response. GET with required url (optional method, headers as JSON string), or POST a JSON body {url, method, headers, body}. Methods: GET, POST, HEAD, PUT, PATCH, DELETE. Returns JSON: status, filtered headers, body (text or base64), contentType, finalUrl, elapsedMs. Caps: 384KB response, 64KB body, 12s, 3 redirects; private targets blocked. Upstream 4xx/5xx come back as data (charged); proxy errors are not.

Inputs
url*stringAbsolute http(s) URL (required). Private/localhost blocked.
methodGET | POST | HEAD | PUT | PATCH | DELETEUpstream method: GET (default) | POST | HEAD | PUT | PATCH | DELETE
headersobjectOptional allowlisted outbound headers as a JSON object of string values (no Cookie / Host / hop-by-hop).
bodyanyOptional upstream request body for POST/PUT/PATCH: a string, or a JSON value (sent JSON-encoded). Size-capped (64KB).
Unpaid call (returns 402 with the price)
curl -i -X POST https://horizonpulse.dev/api/http \
  -H 'content-type: application/json' \
  -d '{"url":"https://example.com","method":"GET"}'
200 response example (trimmed)
{
  "ok": true,
  "status": 200,
  "headers": {
    "content-type": "text/html; charset=utf-8",
    "last-modified": "Mon, 28 Sep 2026 16:19:32 GMT",
    "server": "cloudflare"
  },
  "body": "<!doctype html><html lang=en><head><meta charset=utf-8><link rel=icon href=data:,><meta name=viewport content=\"width=dev…",
  "bodyEncoding": "text",
  "contentType": "text/html; charset=utf-8",
  "finalUrl": "https://example.com/",
  "truncated": false,
  "bytesRead": 713,
  "elapsedMs": 10
}
Errors
400Bad input / blocked target. Not charged413Body too large502Upstream error504Upstream timeout
GET

/api/extract

$0.015USDC per call · 15000 atomic

Web page to structured fields. Use when you need specific data from a page, not its full text. GET with required url, or POST JSON {url or html, fields}. Returns JSON: title, description, canonical, language, links, images, headings, jsonLd, textSample, and with fields (map of name to CSS selector, max 20) the matched values with fieldErrors. Caps: 200KB HTML, 8s; private targets blocked. If no requested field matches it returns 422 and is not charged. Errors are not charged.

Inputs
url*stringAbsolute http(s) URL to fetch and extract (required on GET; on POST send url or html in the JSON body). Private/localhost blocked.
fieldsstringOptional CSS-selector fields (max 20). Map of name to selector string or {selector, attr?: "text"|"html"|<attribute>, all?: boolean, limit?: 1-50}. GET: URL-encoded JSON. Missing fields come back null with fieldErrors; if none match, 422 no_fields_matched and no charge.
Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/extract?url=https%3A%2F%2Fhorizonpulse.dev'
200 response example (trimmed)
{
  "ok": true,
  "url": "https://horizonpulse.dev/",
  "finalUrl": "https://horizonpulse.dev/",
  "title": "Horizon Pulse",
  "description": "Pay-per-call APIs for AI agents via x402 on Base: web fetch, HTTP proxy, page extract, and crypto market data.",
  "language": "en",
  "links": [
    {
      "href": "https://horizonpulse.dev/",
      "text": "Horizon Pulse"
    },
    {
      "href": "https://horizonpulse.dev/#catalog",
      "text": "Catalog"
    }
  ],
  "headings": [
    {
      "level": 1,
      "text": "Pay-per-call APIs built for agents."
    },
    {
      "level": 2,
      "text": "Thirteen routes. One protocol."
    }
  ],
  "textSample": "# Horizon Pulse\n\nLive on Base · x402 v2\n# Pay-per-call APIs\n built for agents.\n\n13 routes for market data, the web and d…",
  "fields": {
    "heading": "Pay-per-call APIsbuilt for agents.",
    "links": [
      "https://horizonpulse.dev/",
      "https://horizonpulse.dev/#catalog",
      "… 1 more"
    ]
  },
  "fieldErrors": {},
  "matchedFields": 2,
  "requestedFields": 2,
  "elapsedMs": 120
}
Errors
400Missing input / bad HTML Includes bad_fields for an invalid fields spec. Not charged413HTML or body too large415Upstream Content-Type is not HTML422No requested field matched (no_fields_matched); body includes fieldErrors. Not charged502Upstream error504Upstream timeout
POST

/api/extract

$0.015USDC per call · 15000 atomic

Web page to structured fields. Use when you need specific data from a page, not its full text. GET with required url, or POST JSON {url or html, fields}. Returns JSON: title, description, canonical, language, links, images, headings, jsonLd, textSample, and with fields (map of name to CSS selector, max 20) the matched values with fieldErrors. Caps: 200KB HTML, 8s; private targets blocked. If no requested field matches it returns 422 and is not charged. Errors are not charged.

Inputs
urlstringAbsolute http(s) URL to fetch and extract. Send url or html (at least one). Private/localhost blocked.
htmlstringRaw HTML to parse instead of fetching (size-capped, 200KB). Send url or html (at least one); if both are sent, html is parsed and url is echoed.
fieldsobjectOptional CSS-selector fields (max 20). Map of name to selector string or {selector, attr?: "text"|"html"|<attribute>, all?: boolean, limit?: 1-50}. Missing fields come back null with fieldErrors; if none match, 422 no_fields_matched and no charge. POST: a JSON object.
Unpaid call (returns 402 with the price)
curl -i -X POST https://horizonpulse.dev/api/extract \
  -H 'content-type: application/json' \
  -d '{"url":"https://horizonpulse.dev","fields":{"heading":"h1","links":{"selector":"a","attr":"href","all":true,"limit":5}}}'
200 response example (trimmed)
{
  "ok": true,
  "url": "https://horizonpulse.dev/",
  "finalUrl": "https://horizonpulse.dev/",
  "title": "Horizon Pulse",
  "description": "Pay-per-call APIs for AI agents via x402 on Base: web fetch, HTTP proxy, page extract, and crypto market data.",
  "language": "en",
  "links": [
    {
      "href": "https://horizonpulse.dev/",
      "text": "Horizon Pulse"
    },
    {
      "href": "https://horizonpulse.dev/#catalog",
      "text": "Catalog"
    }
  ],
  "headings": [
    {
      "level": 1,
      "text": "Pay-per-call APIs built for agents."
    },
    {
      "level": 2,
      "text": "Thirteen routes. One protocol."
    }
  ],
  "textSample": "# Horizon Pulse\n\nLive on Base · x402 v2\n# Pay-per-call APIs\n built for agents.\n\n13 routes for market data, the web and d…",
  "fields": {
    "heading": "Pay-per-call APIsbuilt for agents.",
    "links": [
      "https://horizonpulse.dev/",
      "https://horizonpulse.dev/#catalog",
      "… 1 more"
    ]
  },
  "fieldErrors": {},
  "matchedFields": 2,
  "requestedFields": 2,
  "elapsedMs": 120
}
Errors
400Missing input / bad HTML Includes bad_fields for an invalid fields spec. Not charged413Body or HTML too large422No requested field matched (no_fields_matched); body includes fieldErrors. Not charged502Upstream error504Upstream timeout
GET

/api/screenshot

$0.02USDC per call · 20000 atomic

Web page screenshot. Use when you need to see how a public page renders. GET with required url; optional width (320-1920), height (240-2000), fullPage=true (clipped at 4000px), format png/jpeg, delayMs (0-3000). Renders in headless Chromium and returns JSON: imageBase64, mimeType, width, height, bytes, finalUrl, pageStatus, title and blockedRequests. Every sub-request is checked; private targets blocked. Over the 25s budget returns 504. Errors are not charged.

Inputs
url*stringAbsolute http(s) URL to render (required). Private/localhost blocked.
widthinteger 320–1920Viewport width 320-1920 (default 1280).
heightinteger 240–2000Viewport height 240-2000 (default 800).
fullPagebooleantrue to capture the full page (clipped at 4000px).
formatpng | jpegpng (default) or jpeg.
delayMsinteger 0–3000Extra wait after load, 0-3000 ms.
Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/screenshot?url=https%3A%2F%2Fhorizonpulse.dev'
200 response example (trimmed)
{
  "ok": true,
  "requestedUrl": "https://horizonpulse.dev",
  "finalUrl": "https://horizonpulse.dev/",
  "pageStatus": 200,
  "title": "Horizon Pulse",
  "mimeType": "image/png",
  "width": 1280,
  "height": 800,
  "fullPage": false,
  "bytes": 133636,
  "imageBase64": "iVBORw0KGgoAAAAN…",
  "blockedRequests": 0,
  "elapsedMs": 5818
}
Errors
400Missing/bad/blocked URL or params (not charged). Not charged413Image over cap (not charged)502Navigation or render failed (not charged)504Navigation timeout (not charged)
GET

/api/search

$0.03USDC per call · 30000 atomic

Web search with page contents. Use when you need current information from the web with sources to cite. GET with required q (max 300 chars) and optional n (1-5 pages, default 3). Searches Google results via Serper, then fetches each top result as clean markdown. Returns JSON: results with rank, url, title, snippet, per-page ok/status and content (up to 12K chars each), plus resultCount and fetchedOk. Billed when at least one result returns; zero results or provider errors are not charged.

Inputs
q*stringSearch query (required, <=300 chars).
ninteger 1–5Number of result pages to fetch, integer 1-5 (default 3).
Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/search?q=x402%20payment%20protocol&n=2'
200 response example (trimmed)
{
  "ok": true,
  "query": "x402 payment protocol",
  "provider": "serper (Google results)",
  "n": 2,
  "resultCount": 2,
  "fetchedOk": 2,
  "results": [
    {
      "rank": 1,
      "url": "https://x402.org/",
      "title": "x402",
      "snippet": "x402 enables instant, low-cost payments for digital services. It's designed for API monetization, agentic commerce, paywalled content, and a…",
      "ok": true,
      "status": 200,
      "format": "markdown",
      "truncated": false,
      "content": "# x402\n\nClose Search\n\n# x402\n\nx402 is an open, neutral standard for internet-native payments. It absolves the Internet’s…"
    }
  ],
  "elapsedMs": 1860
}
Errors
400Missing/too-long query or bad n (not charged). Not charged404No search results (not charged)503Search provider unavailable (not charged)
GET

/api/pdf

$0.02USDC per call · 20000 atomic

PDF to text. Use when you need the text of a public PDF document. GET with required url (http/https, max 10MB) and optional pages (1-50, default 50). Returns JSON: pages [{page, text}], totalPages, pagesReturned, truncated, bytes, finalUrl and meta (title, author, subject, creator, producer, creationDate). Reads the PDF text layer with pdf.js; no OCR, so scanned image-only PDFs return 422. Max 100K chars. Not a PDF, encrypted, too large or failed downloads are not charged.

Inputs
url*stringAbsolute http(s) URL of a public PDF (required, max 10MB).
pagesinteger 1–50Max pages to return, integer 1-50 (default 50).
Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/pdf?url=https%3A%2F%2Fhorizonpulse.dev%2Fsample.pdf'
200 response example (trimmed)
{
  "ok": true,
  "requestedUrl": "https://horizonpulse.dev/sample.pdf",
  "finalUrl": "https://horizonpulse.dev/sample.pdf",
  "bytes": 858,
  "totalPages": 1,
  "pagesReturned": 1,
  "truncated": false,
  "meta": {
    "title": "Horizon Pulse sample PDF",
    "author": "Horizon Pulse"
  },
  "pages": [
    {
      "page": 1,
      "text": "Horizon Pulse sample PDF\nPay-per-call APIs for AI agents over x402 (USDC on Base).\nThis file is the fixed input for the free /api/demo/pdf s…"
    }
  ]
}
Errors
400Missing/bad url, blocked host, or bad pages (not charged). Not charged413File larger than 10MB (not charged)415Not a PDF (not charged)422Encrypted, corrupt, or image-only PDF with no text layer (not charged)502Upstream download failed (not charged)504Download or parse timed out before any text (not charged)
Reference

Agent utilities

Tools for agents working with x402.

GET

/api/x402-check

$0.01USDC per call · 10000 atomic

x402 developer tool. Use before paying an unknown x402 endpoint, or to debug your own. GET with required url (optional method GET/POST, body JSON for POST probes). Sends one unpaid request and never pays. Returns JSON: httpStatus, isX402, x402Version, decoded accepts (network, asset label, amount in USD, payTo and whether payTo is an EOA or contract), discovery metadata presence, and pass/warn/fail checks with a summary. Private targets are blocked. Errors are not charged.

Inputs
url*stringAbsolute http(s) URL of the x402 endpoint to audit (required). Private/localhost blocked.
methodGET | POSTHTTP method for the unpaid probe: GET (default; one POST retry on 405) or POST.
bodystringOptional JSON body (<=8KB, URL-encoded) sent with a POST probe, for endpoints that validate input before returning 402. Implies POST.
Unpaid call (returns 402 with the price)
curl -i 'https://horizonpulse.dev/api/x402-check?url=https%3A%2F%2Fhorizonpulse.dev%2Fapi%2Fpulse&method=GET'
200 response example (trimmed)
{
  "ok": true,
  "target": "https://horizonpulse.dev/api/pulse",
  "method": "GET",
  "httpStatus": 402,
  "isX402": true,
  "x402Version": 2,
  "challengeSource": "header",
  "accepts": [
    {
      "network": "eip155:8453",
      "assetLabel": "USDC (Base)",
      "amountUsd": "$0.005",
      "payTo": "0x5b32c973596078a967562ca652761404f19be0e9",
      "payToType": "eoa"
    }
  ],
  "discovery": {
    "present": true,
    "method": "GET",
    "hasInputSchema": true,
    "hasOutputExample": true
  },
  "checks": [
    {
      "id": "status_402",
      "level": "pass"
    },
    {
      "id": "challenge",
      "level": "pass"
    },
    "… 3 more"
  ],
  "summary": {
    "pass": 4,
    "warn": 0,
    "fail": 0
  },
  "elapsedMs": 201
}
Errors
400Missing, bad, or blocked URL / method (not charged). Not charged502Target unreachable or too many redirects (not charged)504Target timeout (not charged)