# Card Hedge Agent API Guide Pay-per-call trading card data for autonomous agents. No account, no API key, no balance to top up — your agent pays directly from its own wallet each time it calls an endpoint. Base URL: `https://api.cardhedger.com` Agent surface: `/v1/agent/*` --- ## What It Is Card Hedge provides pricing, search, and market data for 2.7M+ sports and trading cards across 38+ categories. The agent surface exposes 24 endpoints: 23 priced in USD and payable on-demand via **x402** (the active payment rail — agents pay USDC on Base, settled through Stripe), plus 1 free discovery endpoint (set-search). There is no subscription tier and no per-agent account — the payment protocol carries identity and funds. *(A second rail, MPP over Tempo, is built but not currently enabled — see the note below.)* --- ## How You Pay (No Signup, No Key) Your agent does NOT create an account or obtain an API key. Call an endpoint directly. If payment is required, the server responds with **HTTP 402** carrying an **x402** payment challenge (the `PAYMENT-REQUIRED` header); your x402 client pays from its wallet and retries the original request with the payment proof. ### Payment Flow ``` Agent → POST /v1/agent/cards/card-search ← 402 Payment Required PAYMENT-REQUIRED: Agent pays (x402): Agent → POST /v1/agent/cards/card-search X-PAYMENT: ← 200 OK PAYMENT-RESPONSE: ``` **x402** settles via **Stripe** (USDC on Base mainnet) — a failed request is not charged, since settlement only completes after a successful 2xx response. > **MPP (planned — not currently enabled).** A second rail using the Machine Payments Protocol over Tempo is built but held off pending clarity on Stripe-side MPP settlement. When enabled, the 402 will additionally carry a `WWW-Authenticate: Payment` challenge and accept `Authorization: Payment` (receipt in `Payment-Receipt`). Until then, **x402 is the only active rail** — always check `GET /v1/agent/pricing` (`payment.protocols`) for the live list. ### x402 in Brief x402 is an HTTP-native payment protocol. Your x402-capable client: 1. Receives the 402 with a `PAYMENT-REQUIRED` header containing a JSON challenge (amount, recipient address, network, nonce). 2. Signs a payment transaction from your wallet (USDC on Base). 3. Retries the request with `X-PAYMENT: `. 4. Receives the data plus `PAYMENT-RESPONSE: `. ### MPP (planned, not enabled) When enabled, MPP clients will pay via standard HTTP headers — challenge `WWW-Authenticate: Payment`, retry `Authorization: Payment`, receipt `Payment-Receipt` — settled on Tempo. It is currently **off**; **x402 is the only active rail.** Watch `GET /v1/agent/pricing` (`payment.protocols`) for when it goes live. --- ## Endpoints + Prices 24 agent endpoints total: 23 paid + 1 free. Batch endpoints (`details-by-certs`, `card-fmv-batch`, `90day-prices-by-grade`) are **not** on the agent surface. Price-estimate endpoints (superseded by FMV) and write/workflow endpoints (`card-request`, `issues`, `subscribe-price-updates`) are intentionally not on the agent surface. | Path | Method | Price (USD) | Description | |------|--------|-------------|-------------| | `/v1/agent/cards/set-search` | POST | **FREE** | Search and browse card sets by name or category — no payment required | | `/v1/agent/cards/top-movers` | GET | $0.010 | Cards with the biggest recent price gains; filter by category | | `/v1/agent/cards/card-search` | POST | $0.010 | Flexible text search across 3.5M+ cards | | `/v1/agent/cards/card-details` | POST | $0.010 | Full card details by card ID | | `/v1/agent/cards/card-match` | POST | $0.010 | AI card match — identify a card from a text description (LLM per call) | | `/v1/agent/cards/image-search` | POST | $0.010 | Visual image similarity search | | `/v1/agent/cards/image-match` | POST | $0.010 | Identify the single best-matching card from an image (vision-LLM disambiguation) | | `/v1/agent/cards/prices-by-cert` | POST | $0.010 | Price lookup by grader certificate number (PSA, BGS, SGC, CGC) | | `/v1/agent/cards/prices-by-cert-ocr` | POST | $0.010 | Prices by grader cert photo (OCR — LLM on every call) | | `/v1/agent/cards/details-by-cert-ocr` | POST | $0.010 | Details by grader cert photo (OCR — LLM on every call) | | `/v1/agent/cards/sales-stats-by-player` | POST | $0.010 | Sales stats (bucketed by day/week/month) for a player | | `/v1/agent/cards/total-sales-by-player` | POST | $0.010 | Total sales count for a player over a date window | | `/v1/agent/cards/player-search` | POST | $0.010 | Search players by name: canonical `player_id`, categories, merged aliases, player-index availability | | `/v1/agent/cards/player-index` | POST | $0.010 | Cached player index (market-cap-weighted price of a player's top-10 cards) with 1d–365d changes and constituents; by `player_id` or `name` (+`category`) | | `/v1/agent/cards/player-index-history` | POST | $0.020 | Player index series (daily / weekly / monthly) over a date window (history tier) | | `/v1/agent/cards/search-cards-wsort` | POST | $0.010 | Card search with custom sort (gain, sales, price, or alphabetical) | | `/v1/agent/cards/all-prices-by-card` | POST | $0.010 | Latest prices across all grades for a card | | `/v1/agent/cards/price-updates` | POST | $0.010 | Price updates since a given timestamp (delta polling) | | `/v1/agent/cards/additions-summary` | POST | $0.010 | Daily card addition counts by category, set, and subset | | `/v1/agent/cards/card-fmv` | POST | $0.010 | Fair Market Value for a card at a given grade (with confidence grade and explanation) | | `/v1/agent/cards/fmv-by-cert` | POST | $0.010 | FMV by grader certificate number (chains cert lookup into FMV; falls back to AI card-match) | | `/v1/agent/cards/prices-by-card` | POST | $0.020 | Price history for a specific card ID and grade (history tier) | | `/v1/agent/cards/comps` | POST | $0.020 | Comparable sale prices with anomaly filtering, high/low values, optional time-weighting | | `/v1/agent/cards/sales-by-search` | POST | $0.020 | Newest clean sales by text card identity and grade; no card ID required, up to 20 results | Prices are in USD, charged once per successful call. The machine-readable manifest (always up to date) is at `GET /v1/agent/pricing`. --- ## Resolving a card_id Five endpoints take a `card_id` — **`card-details`**, **`prices-by-card`**, **`all-prices-by-card`**, **`comps`**, and **`card-fmv`**. You don't guess it; you get one from a discovery endpoint first: | To find a card by… | Call | You get back | |---|---|---| | text / filters (player, set, year) | `card-search` or `search-cards-wsort` | matching cards, each with a `card_id` | | a free-text description | `card-match` | the single best match + `card_id` | | a photo | `image-search` / `image-match` | matching card(s) + `card_id` | Then pass that `card_id` to the endpoints above. Example: `card-search {"player":"Mike Trout","set":"2011 Topps Update"}` → take `cards[0].card_id` → `card-fmv {"card_id":"…","grade":"PSA 10"}`. **No `card_id`?** Call **`sales-by-search`** with a text card identity and grade to get up to 20 filtered sales directly. If you have a grading certificate, **`prices-by-cert`** (price history) and **`fmv-by-cert`** (value) resolve the card for you. `top-movers`, `additions-summary`, and the `*-by-player` endpoints also need no resolution step. For `sales-by-search`, `last_n` defaults to 10 (maximum 20), and `category` is optional. Text retrieval overlaps optional, validated catalog enrichment. This endpoint is not a general free-text search. Use card identifiers to specify one card: player/character, set/product, card number and variant where applicable. Put the grade in the separate `grade` field. A catalog `card_id` is not required. Sports queries must include a four-digit release year or season (for example, `2025 Bowman Leo De Vries #43` or `2023-24 Prizm Victor Wembanyama #136`). A missing sports year returns HTTP 422 before retrieval or model calls. Non-sports queries, such as `Pokemon Charizard Base Set #4`, do not require a year. Supply `category` when the text does not clearly identify sports versus non-sports; without it, clear sports category/product words trigger the rule, while ambiguous text remains allowed. A card number or serial is not a release year. Consecutive seasons such as `1996-97`, `1996/97`, `1996-1997` and `1996/1997` match the starting year `1996`, including spaced and Unicode-dash spellings. They do not also match `1997`. Non-consecutive set ranges such as `1909-11` retain their identity; abbreviated `96-97` does not supply a four-digit year. TCG searches require a card number but no year: for example, `Pokemon Charizard Base Set #4`, `Pokemon Charizard 4/102`, or `One Piece Luffy OP01-001`. Missing TCG numbers also return HTTP 422 before retrieval or model calls. Years, grades, set-only codes and explicitly labeled serials do not satisfy this requirement. This number requirement applies to trading card games, not every entertainment category grouped under non-sports. The [non-sports category list](sales-search-category-rules.md) defines recognized names and spelling variants. Matching is case-insensitive; `Pokemon`/`Pokémon`, `Lorcana`/`Disney Lorcana`, and `Yu-Gi-Oh`/`Yu-gi-oh`/`Yu Gi Oh`/`Yugioh` apply the same number requirement whether supplied in `category` or the query. Validation errors identify the missing requirement on `query`, explain the card identity contract, and include a corrected example. For example, a TCG query without its card number returns HTTP 422 with error type `sales_search_card_number_required` and location `["body", "query"]`; provide `query="Pokemon Charizard Base Set #4"` and `grade="Raw"`. A sports query without its year returns `sales_search_year_required`; provide `query="2025 Bowman Leo De Vries #43"` and `grade="Raw"`. These errors stop before sales retrieval or model calls. A query that cleans down to only a grade, year, serial or filler still returns HTTP 400 with the same card-identity guidance. Inspect `results.coverage_complete`, `partial_reasons`, `deadline_reached`, `unresolved_candidates`, and `identity_ambiguous`: a filled result count or exhausted index (`complete`) does not guarantee resolved coverage. The default whole-request budget is 12 seconds and the optional enrichment budget is two seconds. Partial/uncertain responses are not cached. Paid Agent and MCP calls use the same handler and coverage contract as the commercial API. Listing titles must contain every word of the index query, so prefer the wording sellers use. If your exact wording finds too few sales, one broader query (the subject and card number) also runs; every sale is still checked against your full query. Send the full printed card number when you have it (`#BST-4`, not `#4`). A bare number is matched to a prefixed card only when the catalog holds exactly one such card for that subject; otherwise the response reports `ambiguous_identity`. In the pricing manifest, each endpoint is tagged `requires: ["card_id"]` or `produces: ["card_id"]` so agents can wire this up automatically (see `payment`/`workflow` notes there). --- ## Resolving a player Player endpoints identify players by the canonical Card Hedge `player_id`. Get one from `player-search` (it folds duplicate player records into one ID and lists the merged `aliases`). `player-index` and `player-index-history` also accept `name` (+ one optional `category`): if the name matches several players you get **409 `ambiguous_player`** with `candidates` — pick a `player_id` and retry. A `withheld` index (`value: null`) means 25% or more of its weight comes from other players' cards (`integrity.foreign_weight_pct`); `integrity.reason` names whose. --- ## Discovery There are four ways to discover the agent surface: ### 1. Pricing Manifest (machine-readable, unauthenticated) ``` GET https://api.cardhedger.com/v1/agent/pricing ``` Returns a JSON object listing all 24 endpoints (23 paid + 1 free), their prices, and payment protocol details. No auth, no payment required. ### 2. OpenAPI Spec ``` GET https://api.cardhedger.com/openapi.json ``` The full OpenAPI 3.0 spec. Agent endpoints appear under the `agent` tag. Each operation's description includes the price and the active payment rail (x402). ### 3. MCP Tool Server (agent-scoped, native x402 payments) ``` https://api.cardhedger.com/mcp/agent/ ``` A native MCP (Model Context Protocol) streamable-HTTP server scoped to the 23 paid agent endpoints plus 2 free tools (`agent_set_search` and `agent_pricing_manifest`). It speaks the **x402 v2 MCP transport binding** — payment happens *inside* the MCP tool call, not as a separate HTTP 402. No API key is needed to connect; discovery (`tools/list`), `agent_set_search`, and `agent_pricing_manifest` are all free. How a paid tool call works: 1. **Unpaid call** → the server returns a NORMAL tool result with `isError: true` carrying a `PaymentRequired` envelope (`x402Version`, `resource` = `mcp://tool/`, and `accepts[]` with `scheme`/`network`/`asset`/`amount`/`payTo`). The envelope is in the result's `structuredContent` AND as JSON-stringified text in `content[0]`. The `payTo` is a fresh deposit address minted for that challenge. 2. **Pay & retry** → your client signs the payment (USDC on Base) and retries the SAME `tools/call`, putting the signed `PaymentPayload` in the request's `_meta["x402/payment"]`. 3. **Success** → the server verifies, executes the tool, settles, and returns the result with the settlement receipt in `_meta["x402/payment-response"]`. If settlement fails *after* execution, the server withholds the tool content (you are not served data you didn't pay for). **Auto-paying MCP clients** handle this loop for you — e.g. Cloudflare's `withX402Client`, Vercel's `withPayment()`, or [MCPay](https://mcpay.tech). With one of those wrappers, you just call the tool and the payment round-trip is transparent. > Why native (not the keyed `/mcp`)? The keyed `/mcp` server is the stateless Streamable HTTP surface for subscription customers and requires `X-API-Key` on every request. `/mcp/agent/` is a separate, key-less, pay-per-call server; an HTTP proxy can't carry a payment binding inside MCP, so this mount is a native MCP server. ### 4. In-Band 402 Simply call any `/v1/agent/*` endpoint. The 402 response is self-describing: it includes the price, the payment protocols accepted, and the network/address details needed to pay. --- ## Examples ### Step 1: See the 402 (no payment yet) ```bash curl -i -X POST https://api.cardhedger.com/v1/agent/cards/card-search \ -H "Content-Type: application/json" \ -d '{"query": "2011 Topps Update Mike Trout"}' ``` Expected response: ``` HTTP/1.1 402 Payment Required PAYMENT-REQUIRED: {"version":"1","scheme":"exact","network":"base","maxAmountRequired":"10000","resource":"https://api.cardhedger.com/v1/agent/cards/card-search","description":"Card search — $0.010 USD","mimeType":"application/json","payTo":"0x...","maxTimeoutSeconds":300,"asset":"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913","extra":{"name":"USDC","version":"2"}} WWW-Authenticate: Payment realm="Card Hedge Agent API", scheme="tempo", token="..." content-type: application/json ``` The response body carries both challenge headers simultaneously. Your client picks one. ### Step 2: Pay and Retry (x402 pseudo-code) ```python import httpx from x402.client import X402Client # hypothetical x402 library client = X402Client(wallet_private_key=YOUR_KEY) response = client.post( "https://api.cardhedger.com/v1/agent/cards/card-search", json={"query": "2011 Topps Update Mike Trout"}, ) # X402Client handles the 402 → sign → retry cycle automatically print(response.json()) ``` ### MCP Client Setup (agent-scoped, no key needed) For Claude Code, add the remote Streamable HTTP server with: ```bash claude mcp add --transport http card-hedge-agent \ "https://api.cardhedger.com/mcp/agent/" ``` For another MCP client, add `https://api.cardhedger.com/mcp/agent/` as a remote **Streamable HTTP** server with no API-key header. Keep the trailing slash: `/mcp/agent` redirects, and some MCP clients do not follow redirects for protocol POSTs. `tools/list` and the free `agent_pricing_manifest` and `agent_set_search` tools work immediately. A standard MCP client can discover the paid tools, but it will not pay automatically: a paid call returns a `PaymentRequired` result (`isError: true`) until the client signs the challenge and retries the same call with the signed payload in request `_meta["x402/payment"]`; the receipt comes back in result `_meta["x402/payment-response"]`. An **x402-aware MCP client wrapper handles this loop automatically** — e.g. Cloudflare `withX402Client`, Vercel `withPayment()`, or MCPay — so you can just call the tool. --- ## Errors | Code | Meaning | |------|---------| | `402 Payment Required` | Payment is required. Response carries the `PAYMENT-REQUIRED` (x402) challenge header (plus `WWW-Authenticate: Payment` only if MPP is enabled). Pay per the challenge and retry. | | `402` (invalid payment) | Payment signature was malformed, expired, or the amount was insufficient. The response body explains the specific failure. | | `404` | Endpoint does not exist on the agent surface, or, on the player endpoints, `detail.code` is `player_not_found` (no indexed player has that name) or `index_not_found` (player has no cached index). Check `/v1/agent/pricing` for the full list. | | `409` | On the player endpoints, `detail.code` is `ambiguous_player` (name matches several players; `detail.candidates` lists them in search-result shape — pick a `player_id` and retry). | | `422` | Request body validation failed. The response body lists the missing or invalid fields. On `player-index` and `player-index-history`, `detail` is structured: `{"code": "invalid_request", "message": …}`, plus `valid_categories` for an unknown category (`player-search` answers an unknown category the same way). | | `429 Too Many Requests` | Velocity cap hit (see **Rate limits** below). The response carries a `Retry-After` header (seconds) and a body of `{"error": "rate limited", "retry_after_seconds": }`. Back off for that long and retry. Capped requests are never charged and never mint a payment challenge. | | `502` | On the player endpoints, `detail.code` is `upstream_error` (player index data is temporarily unavailable). | | `503` | On the player endpoints, `detail.code` is `identity_unavailable` (player catalog has not loaded yet; retry shortly). On a paid call, `detail.error` is `payment service temporarily unavailable`: the payment facilitator could not be reached or answered with an error, so your payment was neither verified nor settled and you were **not charged**. The response carries `Retry-After`; retry the call (a fresh 402 → pay → retry cycle is fine). Any other 503 means the service is temporarily unavailable. | | `5xx` | Server error. A failed call is not charged — for x402, settlement only completes after a successful 2xx response. | A non-2xx response is never charged — for x402, settlement only completes after a successful 2xx response. A request whose connection closes before the response can be delivered is not charged either: the server checks that the client is still connected before settling. ### Rate limits Paying volume is welcome — these caps exist only to protect shared upstreams from hot-loop hammering, not as a usage quota. They are keyed off the **paying wallet** (not an account, since there is none). Two independent tiers apply: **Cheap endpoints** — all endpoints except the heavy ones below: - **300 requests/minute per payer.** - **16 concurrent in-flight calls per payer.** **Heavy (AI/upstream) endpoints** — these can burn external AI/upstream capacity per call (image endpoints: Ximilar + vision LLM; cert endpoints: the cert-resolution chain with its OCR/card-match LLM fallback; card-match and sales-by-search: card/listing LLM work; OCR cert endpoints: OCR LLM on every call) and have tighter caps: - `/v1/agent/cards/image-search` - `/v1/agent/cards/image-match` - `/v1/agent/cards/prices-by-cert` - `/v1/agent/cards/fmv-by-cert` - `/v1/agent/cards/card-match` - `/v1/agent/cards/sales-by-search` - `/v1/agent/cards/prices-by-cert-ocr` - `/v1/agent/cards/details-by-cert-ocr` - **60 requests/minute per payer.** - **6 concurrent in-flight calls per payer.** The two tiers are **independent per payer**: exhausting the heavy cap does not affect your cheap budget, and vice versa. **Unpaid path:** - **10 challenge mints/minute per IP** on the pre-payment 402 path. Exceeding any cap returns **429** with a `Retry-After` header. The checks run *before* payment is verified, so a capped call never moves funds and a capped unpaid flood never mints a payment challenge. The current numbers are published in the `limits` block of `GET /v1/agent/pricing`. --- ## Acceptable Use Card Hedge provides per-call data access for agent use. Data is provided as-is; no warranty is made as to accuracy or completeness. Do not abuse the service (e.g., high-frequency hammering without throttling) — access may be rate-limited or blocked. A full Terms of Service will be published at `https://api.cardhedger.com/terms`. Questions or issues: support@cardhedger.com