staging
CAPACITRCAPACITR

X402 // MPP // PAY PER CALL

Market intelligenceyour agent can buy.

One URL, two doors. No account, no API key, no invoice. Your agent pays each call from its own wallet — pathUSD on Tempo Moderato (testnet) over MPP, or USDC on Base Sepolia (testnet) over x402 — and either has a story analysed into the markets it moves and the trades that follow, or reads the latest Signals Capacitr has already produced — every trade with a live quote and where it has moved since the signal.

ENDPOINTS

POSThttps://markets-staging.capacitr.xyz/api/x402/signals

Submit a signal

Turn a URL, a social post, an article, or a free-text market question into the Feed Card the Capacitr app renders for it: the markets it moves and the ranked trades across Polymarket, Hyperliquid and Robinhood Chain, each with a live quote and, for a dated source, its price at signal. Ranks and confidences are model-derived and follow the markets, so the same input can rank differently over time.

input
string, REQUIRED. A URL, a post, or a question. `url` and `query` are accepted aliases for the same field; if more than one is sent, `input` wins, then `url`, then `query`. Sent as JSON in the request body.
mode
string, optional, one of "discover" | "hedge". Default "discover", which ranks trade ideas; "hedge" looks for offsetting positions. Any other value is read as "discover".
Response fields
recommendedTrades
The ranked trades as one list, best first (bestTrade is the first entry; otherTrades are the rest). Each carries marketType (bet | trade), marketSource, marketId, marketTitle, suggestedDirection, confidence, recommendationRank, recommendationReason, quoteSnapshot (a live quote for the market at response time), and matchedAt. signalPrice — the recommended side's price at the source's publication time — is present only when the source has a publication time (a post, an article) and the lookup succeeded; a free-text question has no publication time, so its trades carry no signalPrice. Empty, with bestTrade null, when nothing tradeable was found.
title / subtitle / narratives
What the source said and why each trade follows from it. narratives are the theses, each linked to the trades it supports.
categories / tags / publishedAt / url / sourceLabel
The story's own metadata, as the app shows it.
payment
What this call was charged: protocol (x402 | mpp), method (base | tempo), chain_id, currency, currency_address, amount, payer.
$0.2 USDC · Base Sepolia (testnet) · x402
$0.2 pathUSD · Tempo Moderato (testnet) · MPP
GEThttps://markets-staging.capacitr.xyz/api/x402/signals

Read the latest signals

The Signals Capacitr has already produced from its own monitored sources — each one the same complete Feed Card the submit route returns, with its narratives, its ranked trades across Polymarket and Hyperliquid, and a LIVE quote for every market fetched at response time. One payment returns one page; pass the returned cursor back for the next. An optional keyword filter narrows the page to a topic without changing the ordering, which is always newest first.

q
string, optional, up to 120 characters. A keyword filter — a ticker ("btc"), a category ("politics"), or free text ("electoral elections"). Matched against the markets each Signal is matched to, the Signal's categories, and its headline and summary. Omit it for the latest Signals overall. Sent in the query string, like every parameter on this route.
limit
integer, optional, 1 to 10. Signals per page. Default 10, which is also the maximum; a higher number is clamped to it, and a non-numeric one is a free 400.
cursor
string, optional. The `nextCursor` from a previous response, opaque — pass it back unchanged. Omit it for the first page; one that did not come from a response is a free 400.
venues
string, optional, comma-separated. The only value that changes anything is "agg": it adds aggregated-venue markets to the page. Anything else is ignored. Omit it for Polymarket and Hyperliquid only.
Response fields
signals
The page, newest first. Each entry is a complete Feed Card — trendingItemId, title, subtitle, categories, tags, publishedAt, source metadata, narratives, bestTrade and otherTrades — plus `recommendedTrades`, the card's trades as one ranked list, best first. Every trade carries a live `quoteSnapshot` fetched at response time, and `signalPrice` where the source has a publication time.
nextCursor
Pass back as `cursor` to buy the next page. `null` means the feed is exhausted; an empty `signals` array with a non-null cursor cannot happen.
payment
What this call was charged: protocol (x402 | mpp), method (base | tempo), chain_id, currency, currency_address, amount, payer.
$0.1 USDC · Base Sepolia (testnet) · x402
$0.1 pathUSD · Tempo Moderato (testnet) · MPP
TESTNET · Base Sepolia (testnet) · x402 · restricted preview
GEThttps://markets-staging.capacitr.xyz/api/x402/markets/narratives

Read one market's narratives

Every Narrative Capacitr has linked to one market — a Hyperliquid perp, a Robinhood Chain stock or a Polymarket market — newest first: what each source argued, which way it points, and the quote it came from. The same page the Capacitr app shows on that market's detail screen. One payment returns one page; pass the returned cursor back for the next.

marketType
string, REQUIRED, one of "trade" | "bet". It must agree with marketSource — the venue decides the type, so only three pairs exist: trade+hyperliquid, trade+robinhood, bet+polymarket. Any other pairing is a free 400. Sent in the query string, like every parameter on this route.
marketSource
string, REQUIRED, one of "hyperliquid" (perps, marketType trade) | "robinhood" (Robinhood Chain stock tokens, trade) | "polymarket" (prediction markets, bet). Capacitr's other two venues, "agg" and "basestocks", are not served here — the app shows no narratives on their detail screens either — and are a free 400.
marketId
string, REQUIRED, 1 to 256 characters. The market's id on its venue: the coin for Hyperliquid ("BTC"), the 20-byte token contract address for Robinhood Chain ("0x…", case-insensitive), the conditionId for Polymarket ("0x…") — the marketId a Signal's trades carry. An id of the wrong shape for its venue is a free 400; a well-formed id for a market that does not exist cannot be told apart before the run, so it returns an empty page and the payment is refunded.
facet
string, optional, default "all". A facet id from the response's own `facets`: "direction:bullish" | "direction:bearish" | "direction:neutral" on a perp or stock, "direction:yes" | "direction:no" or an outcome id on a Polymarket market. One this market has none of returns an empty page (refunded), not an error.
limit
integer, optional, 10 to 15. Narratives per page. Default 10; anything outside the range is a free 400 rather than a clamp.
cursor
string, optional. The `nextCursor` from a previous response, opaque — pass it back unchanged for the next page. One that did not come from a response is a free 400.
newerThan
string, optional. A `firstCursor` from an earlier response, opaque: the page then carries only narratives published since it, and `newerResultsAvailable` says whether any exist. How to poll one market without re-buying the page you already have.
includeSignalId
string, optional, one of "1" | "0" | "true" | "false". "1" or "true" adds `signalId` to each narrative — the Signal it came from, which `GET /api/x402/signals` returns as `trendingItemId`. Any other value is a free 400.
Response fields
narratives
The page, newest first. Each carries id, title, mode, direction (bullish | bearish | neutral), subject, rationale, sourceExcerpt, source (kind, label, type, author), publishedAt, sourceDestination (the original post or article URL), linkedMarket (marketType, marketSource, marketId, suggestedDirection, selectedOutcome), and signalId when asked for.
facets
Narrative counts for the whole market by direction: id, label, count — counted over the market, not the page.
nextCursor
Pass back as `cursor` to buy the next page. `null` means there are no older narratives.
firstCursor / newerResultsAvailable
The page's own newest position, and whether anything newer than the `newerThan` you sent exists. Send firstCursor back as `newerThan` to ask only for what has landed since.
payment
What this call was charged: protocol (x402 | mpp), method (base | tempo), chain_id, currency, currency_address, amount, payer.
$0.05 USDC · Base Sepolia (testnet) · x402
$0.05 pathUSD · Tempo Moderato (testnet) · MPP
GET · FREEhttps://markets-staging.capacitr.xyz/api/x402/signals/sample

Canned Signals page in the exact paid shape — sample: true markers, zeroed payment block. Build your parser against this before paying anything.

GET · FREEhttps://markets-staging.capacitr.xyz/api/x402/routes

The live price book — every paid route, its rails, amounts, limits and availability, read from the same source the 402 gate charges from.

HOW A CALL PAYS FOR ITSELF

01

CALL

POST a URL, post or question to have it analysed, or GET the latest Signals — a page of 10, newest first, with ?q= to narrow it. With no payment attached the answer is a 402 that carries both challenges. Minting one is free.

02

PAY

Sign the challenge locally on the rail your wallet holds — pathUSD on Tempo Moderato (testnet) over MPP, or USDC on Base Sepolia (testnet) over x402 — and send the same request again with the credential.

03

READ

The 200 carries the result and a Payment-Receipt header (PAYMENT-RESPONSE as well on x402). Money moves before the run. An empty result is flagged refund-due on the ledger and refunded out of band; a run that fails after charging re-runs on the same credential without a second charge.

MPP · pathUSD ON TEMPO MODERATO (TESTNET)

challenge
WWW-Authenticate: Payment
credential
Authorization: Payment
receipt
Payment-Receipt
token
0x20c0000000000000000000000000000000000000

X402 · USDC ON BASE SEPOLIA (TESTNET)

challenge
PAYMENT-REQUIRED
credential
PAYMENT-SIGNATURE
receipt
PAYMENT-RESPONSE
token
0x036CbD53842c5426634e7929541eC2318f3dCF7e

QUICKSTART — FOUR STEPS

1 · ASK (free — mints a 402 challenge)

curl -si 'https://markets-staging.capacitr.xyz/api/x402/signals' | grep -i payment-required

# decoded:
{ "accepts": [
    { "network": "eip155:8453",  "asset": "0x833589fC…2913", "amount": "50000",
      "payTo": "0x5500…C7c", "scheme": "exact", "extra": {"name":"USD Coin","version":"2"} },
    { "network": "eip155:84532", "asset": "0x036CbD…CF7e", "amount": "100000",
      "payTo": "0x5500…C7c", "scheme": "exact", "extra": {"name":"USDC","version":"2"} }
                                       //  ▲ TESTNET — restricted preview, faucet USDC
]}

2 · SIGN EIP-3009 TransferWithAuthorization

// viem — the EIP-712 domain comes from the accept itself
const domain = {
  name: accept.extra.name,     // "USDC" on sepolia, "USD Coin" on mainnet — differs!
  version: accept.extra.version,
  chainId: Number(accept.network.split(":")[1]),
  verifyingContract: accept.asset,
};
const authorization = {
  from: wallet.address, to: accept.payTo, value: BigInt(accept.amount),
  validAfter: 0n, validBefore: BigInt(now + accept.maxTimeoutSeconds),
  nonce: randomBytes32(),
};
const signature = await wallet.signTypedData({
  domain, primaryType: "TransferWithAuthorization",
  types: TRANSFER_WITH_AUTHORIZATION_TYPES, message: authorization,
});

3 · RESEND WITH THE CREDENTIAL

const paid = base64(JSON.stringify({
  x402Version: 2,
  accepted: accept,            // echo the accept object you chose, verbatim
  resource: requirements.resource,
  payload: { signature, authorization },
}));
const res = await fetch(url, { headers: { "PAYMENT-SIGNATURE": paid } });

4 · READ THE 200 + RECEIPT

{ "signals": [ { "trendingItemId": "…", "title": "…", "postText": "…",
               "pageUrl": "https://capacitr.xyz/feed/<trendingItemId>",
               "categories": ["…"], "publishedAt": "…",
               "bestTrade": { "marketSource": "…", "marketTitle": "…",
                              "suggestedDirection": "long", "confidence": 0.72 } }, … ],
  "nextCursor": "…",          // next page token — null at the cap
  "preview": true,            // test-rail pages only
  "payment": { "test_rail": true } }

Pick the accept by network, never by position — eip155:8453 is production data, eip155:84532 is the test rail. A 200 also carries a base64 PAYMENT-RESPONSE header — the on-chain receipt ({ network, payer, transaction, success }).

THE TEST RAIL — BASE SEPOLIA (TESTNET)

Mainnet (eip155:8453)Test rail (eip155:84532)
PriceLive route price (see above)Small test-USDC charge — read the accept
DataLive feedItems at least 48h old
Page sizeFull page5 cards
DepthPaginate freely2 pages max — nextCursor goes null
Markerspreview:true · payment.test_rail:true

The test rail exists to prove your payment plumbing end to end with faucet USDC — it is not a way to preview live data for free. Each page is its own payment carrying ?cursor=<nextCursor>; the cursor stops being minted after page 2, and a forged deeper cursor is clamped to the terminal page.

FAILURES & REPLAYS

402

Payment required / rejected

The body type names why; the merged payment-required challenge's error field carries the reason per rail. Fix the credential — never retry a spent nonce.

503

feed-unavailable

Our side is broken. Your payment is marked refund_due and refunded out of band. Retry after the Retry-After interval — you will not lose funds.

REPLAY

Settled credential re-sent

Returns the recorded response without charging again. Safe to retry after a network drop — same credential, same page.

POST · SUBMIT A SIGNAL

REQUEST

# 1. Ask for the challenge (free)
curl -i -X POST https://markets-staging.capacitr.xyz/api/x402/signals \
  -H 'Content-Type: application/json' \
  -d '{"input":"https://x.com/someone/status/123"}'
# → HTTP 402 with PAYMENT-REQUIRED and WWW-Authenticate: Payment ...

# 2. Repeat with the signed payment
curl -X POST https://markets-staging.capacitr.xyz/api/x402/signals \
  -H 'Content-Type: application/json' \
  -H 'PAYMENT-SIGNATURE: <signed x402 payload>' \
  -d '{"input":"https://x.com/someone/status/123"}'
# → HTTP 200 + PAYMENT-RESPONSE

RESPONSE (SHAPE)

{
  "input": "https://x.com/someone/status/123",
  "trendingItemId": "…",
  "pageUrl": "https://capacitr.xyz/feed/<trendingItemId>",   // the Capacitr page
  "title": "…", "subtitle": "…", "categories": [ … ], "tags": [ … ],
  "url": "…",   // the SOURCE the Signal was read off, not the Capacitr page
  "bestTrade": {
    "marketType": "bet", "marketSource": "polymarket",
    "marketId": "0x…", "marketTitle": "Will BTC reach $200k by Dec 31, 2026?",
    "suggestedDirection": "buy_yes", "confidence": 0.62,
    "recommendationRank": 0, "recommendationReason": "…",
    "quoteSnapshot": { "type": "bet", "yesPrice": 0.24, "noPrice": 0.76 },
    "signalPrice": 0.22   // only when the source has a publication time
  },
  "otherTrades": [ … ],
  "recommendedTrades": [ <bestTrade>, …otherTrades ],
  "narratives": [ … ],
  "payment": {
    "protocol": "x402", "method": "base", "chain_id": 84532,
    "currency": "USDC", "currency_address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "amount": "0.2", "payer": "did:pkh:eip155:84532:0x…"
  }
}

GET · READ THE LATEST SIGNALS

REQUEST

# 1. Ask for the challenge (free)
curl -i 'https://markets-staging.capacitr.xyz/api/x402/signals?q=btc'
# → HTTP 402 with PAYMENT-REQUIRED and WWW-Authenticate: Payment ...

# 2. Repeat with the signed payment
curl 'https://markets-staging.capacitr.xyz/api/x402/signals?q=btc' \
  -H 'PAYMENT-SIGNATURE: <signed x402 payload>'
# → HTTP 200 + PAYMENT-RESPONSE; pass nextCursor back as ?cursor= for the next page

RESPONSE (SHAPE)

{
  "signals": [
    {
      "trendingItemId": "…",
      "pageUrl": "https://capacitr.xyz/feed/<trendingItemId>",   // the Capacitr page
      "title": "…", "subtitle": "…", "categories": [ … ], "tags": [ … ],
      "publishedAt": "…", "source": { … },
      "url": "…",   // the SOURCE the Signal was read off, not the Capacitr page
      "bestTrade": { …, "quoteSnapshot": { … }, "signalPrice": 0.22 },
      "otherTrades": [ … ],
      "recommendedTrades": [ <bestTrade>, …otherTrades ],
      "narratives": [ … ]
    },
    … up to 20, newest first
  ],
  "nextCursor": "…",   // null when the feed is exhausted
  "payment": {
    "protocol": "x402", "method": "base", "chain_id": 84532,
    "currency": "USDC", "currency_address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "amount": "0.1", "payer": "did:pkh:eip155:84532:0x…"
  }
}

GET · READ ONE MARKET'S NARRATIVES

REQUEST

# 1. Ask for the challenge (free)
curl -i 'https://markets-staging.capacitr.xyz/api/x402/markets/narratives?marketType=trade&marketSource=hyperliquid&marketId=BTC'
# → HTTP 402 with PAYMENT-REQUIRED and WWW-Authenticate: Payment ...

# 2. Repeat with the signed payment
curl 'https://markets-staging.capacitr.xyz/api/x402/markets/narratives?marketType=trade&marketSource=hyperliquid&marketId=BTC' \
  -H 'PAYMENT-SIGNATURE: <signed x402 payload>'
# → HTTP 200 + PAYMENT-RESPONSE; pass nextCursor back as ?cursor= for older narratives

RESPONSE (SHAPE)

{
  "narratives": [
    {
      "id": "…", "title": "Retail Sentiment Shift", "mode": "idea",
      "direction": "bullish", "subject": "BTC",
      "rationale": "…", "sourceExcerpt": "…",
      "source": { "kind": "x", "label": null, "type": "x_account", "author": "…" },
      "publishedAt": "…", "sourceDestination": "https://x.com/…",
      "linkedMarket": { "marketType": "trade", "marketSource": "hyperliquid", "marketId": "BTC", "suggestedDirection": "long", "selectedOutcome": null }
    },
    … up to 15, newest first
  ],
  "facets": [ { "id": "all", "label": "All", "count": 1222 }, { "id": "direction:bullish", … } ],
  "nextCursor": "…",   // null when there are no older narratives
  "firstCursor": "…", "newerResultsAvailable": false,
  "payment": {
    "protocol": "x402", "method": "base", "chain_id": 84532,
    "currency": "USDC", "currency_address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "amount": "0.05", "payer": "did:pkh:eip155:84532:0x…"
  }
}

PASTE THIS INTO YOUR AGENT

# Capacitr Signals API — agent integration spec

Use Capacitr's paid API for market signals: SUBMIT a URL, post, article or question to have it analysed into a Feed Card of matched markets and ranked trades, or READ the latest Signals Capacitr has already produced. Both return the same card shape, each trade carrying a live quote and, for a dated source, its price at signal time.

Reading is the cheaper door: one payment returns one page of Signals plus a `nextCursor`; pass that cursor back and pay again for the next page. Add `?q=` to narrow a page to a ticker, a category, or free text — ordering stays newest-first either way.

For one market, READ its Narratives: every thesis Capacitr has linked to that Hyperliquid perp, Robinhood Chain stock or Polymarket market, with direction, rationale and source. Same one-payment-per-page cursor.

POST https://capacitr.xyz/api/x402/signals — Submit a signal
GET https://capacitr.xyz/api/x402/signals — Read the latest signals
GET https://capacitr.xyz/api/x402/markets/narratives — Read one market's narratives

Read GET https://capacitr.xyz/api/x402/routes for the current request fields, prices, payment rails, limits, and the response shape. The endpoint's 402 challenge carries the exact payment requirements.

Before any payment, collect from the user the input to analyze and a maximum spend per call. Then show the payment options the 402 challenge returns — currently USDC on Base Sepolia (testnet) over x402; pathUSD on Tempo Moderato (testnet) over MPP — ask the user which one to pay with, and wait for the answer. Use only an option returned by the challenge; never choose a chain automatically.

For every rail: validate the challenge's chain, token, recipient, and amount against the user's limit before signing, then verify the settlement receipt and confirm the on-chain transfer. Tempo may add a small network fee in pathUSD on top of the price unless the challenge marks the fee as sponsored, so check the transfer to the recipient against the price rather than the wallet's balance change.

Follow the official guide or SDK for the chosen rail:
- x402 on Base (Base Sepolia (testnet), USDC): https://docs.base.org/build-on-base/accept-payments/call-a-paid-service — install @x402/fetch and @x402/evm (the docs' @base-org/x402 name is not on npm). Register ExactEvmScheme with a viem account from the user's key, wrap fetch with wrapFetchWithPayment, and use onBeforePaymentCreation as the spend-limit gate.
- MPP on Tempo (Tempo Moderato (testnet), pathUSD): https://mpp.dev/quickstart/agent — with a user-supplied private key use the mppx client SDK: Mppx.create({ methods: [tempo({ account, expectedChainId: 42431 })], polyfill: false }). Use the Tempo CLI skill at https://tempo.xyz/SKILL.md only for an interactive browser login. MPP is accepted on Tempo only; the Base rail is x402.

After a network failure present the same credential again: it is never charged twice.

## Free sample — build your parser first

GET https://capacitr.xyz/api/x402/signals/sample returns a canned Signals page in the exact paid shape (sample:true markers, zeroed payment block). No payment, no account — use it to write and test your response parsing before spending anything.

## TESTNET rail — Base Sepolia (testnet) (eip155:84532)

https://capacitr.xyz/api/x402/signals also accepts a Base Sepolia USDC payment — TESTNET funds only, never real USDC — as a deliberately restricted preview with real settlement. Pick that accept by network (eip155:84532), never by position in accepts[].

Test-rail pages are capped: at most 5 cards per page, at most 2 pages per payment chain, and only items at least 48h old — the recency floor is the point, not a bug. The response marks itself: preview:true and payment.test_rail:true. A forged deeper cursor is clamped to the terminal page, never a third page.

## x402 on Base — signing detail

If you are hand-rolling the flow instead of using the SDK above: the 402's payment-required header decodes to { accepts[] }. Sign an EIP-3009 TransferWithAuthorization where the EIP-712 domain comes from the accept itself — name and version from accept.extra, chainId from accept.network, verifyingContract = accept.asset. The domain name differs per rail ("USD Coin" on mainnet, "USDC" on Sepolia); always read it from accept.extra. Resend the request with header PAYMENT-SIGNATURE = base64({ x402Version: 2, accepted: <the accept object verbatim>, resource: <the challenge's resource>, payload: { signature, authorization } }).

## Failure semantics

- A paid request answered 503 'feed-unavailable' with a Retry-After header means our side is broken, not your credential: the payment is recorded refund_due and refunded out of band. Retry after the interval.
- A 402 after a paid request is a payer rejection — read the body type AND the merged payment-required challenge's error field for the reason.
- Re-sending an already-settled credential replays the recorded response without charging again — safe after a network drop. Never reuse a nonce for a NEW payment.

Full guide for humans: https://capacitr.xyz/x402
Installable agent skill: https://capacitr.xyz/SKILL.md (references at https://capacitr.xyz/references/endpoints.md and https://capacitr.xyz/references/payment-protocol.md)

Generated from the registry on every load — the same text served at /api/x402/agent-instructions, so an agent that fetches it follows identical instructions to one whose user pasted it. For a harness that installs skills rather than prompts, the same API is packaged at /SKILL.md.