Generate your Agent API Key
Free users can generate a read-only Agent API Key for MCP tools with signals:read and 200 MCP requests/day. Pro adds real-trade REST/MCP scopes; Elite adds full agent write access.
PolyTrackers API (External + AI Agent Guide)
Agent authentication discovery: /auth.md describes human-issued Agent API
Keys. A rejected POST /api/mcp returns its existing JSON-RPC 401 error with
WWW-Authenticate: Bearer realm="PolyTrackers API", resource_metadata="https://polytrackers.com/.well-known/oauth-protected-resource/api/mcp".
The metadata binds to the challenged resource. This is API-key discovery, not
OAuth delegation or anonymous agent registration.
This is the production-facing API guide for external developers and AI agents.
- OpenAPI JSON:
/openapi.json - Agent instructions:
/llms.txtand/skill.md - MCP endpoint:
https://polytrackers.com/api/mcp
Scope
This guide/spec covers the full AI agent surface:
- Auth:
/api/auth/refresh - Account/Tier:
/api/account,/api/account/stats,/api/account/streak,/api/account/risk-profile - Discovery:
/api/markets,/api/markets/end-dates,/api/market-signals,/api/leaderboard,/api/leaderboard/internal,/api/leaderboard/internal/me,/api/clob/price,/api/clob/prices,/api/clob/price-history,/api/clob/fee-info,/api/trader-lookup - Anomalies:
/api/anomalies,/api/anomalies/{id},/api/anomalies/by-market,/api/anomalies/performance,/api/receipts/recent,/api/scan/trigger,/api/scan/run - Backtesting:
/api/backtest - Recommendations, activity & feedback:
/api/recommendations,/api/bets/sync,/api/feedback - Agent API Keys:
/api/keys,/api/keys/generate, plus public feeds - Trading:
/api/trades,/api/trade/execute,/api/trade/history,/api/trade/ledger,/api/trade/money-summary,/api/trade/orders* - Alerts & webhooks:
/api/alerts/preferences,/api/webhooks* - Whale service + ingest webhooks: wallets/roster/whale control +
/api/whale/webhook* - Health:
/api/health(anonymous, unmetered liveness + database probe)
Excluded intentionally:
/api/mock/*internal/MCP-only simulation helpers (use the registeredpt_mock_*tools)/api/cron/*scheduled internal jobs/api/admin/*,/api/stripe/*,/api/telegram/*,/api/onboarding/*and other UI/operator-only routes
The MCP-only pt_whale_forward_returns_get read accepts 1–50 arbitrary wallet
entries with exact per-wallet since timestamps and a shared as_of cutoff.
It returns one request-ordered row per entry with bounded fill coverage,
gross/post-cost capital-accounting ROI, unresolved capital, resolution
provenance, and isolated ok|partial|error state. Its Data API and Gamma calls
use the low-priority backfill request budget and are counted in _budget.
Zero resolved capital produces null ROI; compute cohort medians client-side
over non-null rows. Re-runs at the same cutoff may incorporate later
authoritative Gamma outcome/closedTime evidence as corrected upstream data.
The Elite, read-only pt_copy_replication_aggregates_get tool is MCP-only,
operator-allowlisted through ADMIN_EMAILS, and has no public REST or browser
route. Ordinary customer keys fail closed. It requires one exact whale, fixes
the service-role Copy Receipt V1 RPC limit at one, and uses index-backed lookup
with a caller-side five-second deadline that aborts the PostgREST request.
Scored metrics stay null below 20
distinct settled conditions and high confidence still requires 40+ conditions
with at least 75% complete receipts. Do not present it as a public copy score
or recommendation.
GET /api/recommendations includes the stored botLikeness aggregate. A
high tier adds a copy-fill-lag warning and demotes matchScore; the response
never includes raw Polymarket trade history.
Rows also include tradingStyle, a deduped BUY entry-price
distribution with sample/window metadata. Its badge classification is withheld
below 20 fills; sufficiently sampled distributions also contribute to
deterministic risk-fit scoring.
Recommendation rows also expose name_source (tracked_alias, leaderboard,
or address_fallback), name_as_of, and Polymarket's rename-stable
pseudonym when available. Address-prefixed legacy aliases are treated as
absent; wallet address remains the identity key.
GET /api/leaderboard and GET /api/recommendations include upstream
metadata for the shared Polymarket leaderboard fetch. Check its per-timeframe
outcomes plus degraded and stale before interpreting an empty candidate
pool. A stale response is explicitly labeled, retains the original fetchedAt
timestamp, and is bounded to the latest fully healthy snapshot from the prior
24 hours. staleExpiresAt is the absolute cutoff; public, REST, and MCP
response layers do not cache leaderboard payloads beyond the durable
10-minute Redis cadence, and each response revalidates after downstream
enrichment so an embedded fallback is removed at that cutoff. Candidate
actionability also distinguishes known dormancy from missing ingestion
coverage: zero local activity inside the warning window for an untracked,
newly tracked, or recently resumed wallet returns warning-only
ACTIVITY_COVERAGE_UNKNOWN plus
recent_activity.source/coverage metadata, never DORMANT_WHALE. Discovery
rows expose bankroll ROI, arithmetic average per-trade return, entered/used
counts, source truncation, and first/last loaded signal as separately named
capital-accounting fields. Truncated or fewer-than-20-entry canonical runs are
reporting-only insufficient_coverage; a shorter complete diagnostic does not
clear the 90- or 180-day admission gate. MCP capability revisions are
whale-coverage-honesty-v11 for the leaderboard,
whale-coverage-honesty-v9 for recommendations,
copy-digest-entry-fill-v18 for the copy-trading digest, and
whale-coverage-honesty-v4 for the backtest tool. Wallet-shaped suggestion/trader rows use the canonical
recent-flow majority predicate, filter majority-ephemeral and unknown-coverage
rows under durable_only, retain dormant wallets, and expose bounded
durability_profile provenance.
Recommendation and digest-suggestion rows expose
previously_removed_by_caller by normalized wallet address. It is null or
contains { count, last_removed_at, last_reason }, where count covers
currently inactive assignments across the caller's mock wallets rather than
lifetime removal cycles. These candidates remain visible with a warning by
default. REST accepts exclude_previously_removed=true|false; direct MCP
accepts the boolean exclude_previously_removed and binds it to continuation
cursors. Filtered counts appear as omitted_by_prior_removal_filter.
PATCH /api/roster/{address} treats fromWalletId as the source-assignment
selector. If one address has active assignments on multiple visible wallets,
an address-only write fails with HTTP 409, code AMBIGUOUS_ASSIGNMENT, and
candidate_wallet_ids rather than changing an arbitrary row.
Cookie-authenticated PATCH requests and POST /api/whale/roster-assign
requests return HTTP 403 when browser origin headers indicate a cross-site
write. The WHALE_SERVICE_SHARED_SECRET path for PATCH is unaffected.
POST /api/roster accepts any exact Polymarket whale wallet address; the whale
does not need to appear in leaderboard or recommendation results. The
walletId may select a caller-owned paper roster or dedicated real-money copy
roster returned by GET /api/wallets. name is optional, and
copyEnabled:false stages the assignment watch-only. When starting from a
username, resolve and review the exact address with GET /api/trader-lookup
before creating the assignment. Username resolution uses Polymarket's public
profile search, then fetches and validates trades for only that canonical
wallet; trade pages are never used to infer username ownership.
Roster rows also expose maxMirrorsPerDay (and max_mirrors_per_day).
POST /api/roster, POST /api/whale/roster-assign, and
PATCH /api/roster/{address} accept an optional integer from 0 to 100000;
0 is the default and means unlimited. The cap is scoped to one wallet and
whale, counts original copy BUY entries in the current UTC day, and does not
count partial SELL activity. A positive mock cap is enforced by the atomic
trade insert; real copy reads use real_trades.whale_address for advisory
evaluation. Set fromWalletId when patching an address assigned to multiple
wallets.
Auth modes
1) User auth (session cookie or bearer)
Most user-scoped agent endpoints accept authenticated user context via:
- Supabase session cookie (
sb-*managed cookies) Authorization: Bearer <supabase-jwt-access-token>
2) JWT-only bearer
POST /api/keys/generate strictly verifies Supabase JWT bearer identity.
3) Agent API Key bearer
Agent API Keys are prefixed ptk_ and are passed as Authorization: Bearer <agent-api-key>. The same key works for PolyTrackers MCP server access and direct REST/API requests when it has the required scopes:
signals:read— read-only data, including caller-owned private account/trade receipts on supported MCP tools (not a public-data-only scope)agent:scan— scan trigger where allowedtrade:execute— real trade execution only, after readiness and preflight gates passagent:full— full agent/write/trade access (currently Elite-only for Agent API Keys)
Endpoints that accept Agent API Keys:
| Endpoint | Required scope | Direct REST tier |
|---|---|---|
GET /api/anomalies | signals:read | Pro+ |
GET /api/trades | signals:read | Pro+ |
GET /api/leaderboard | signals:read | Pro+ |
POST /api/backtest | signals:read | Free+ (fixed demo config on Free) |
GET /api/anomalies/performance | signals:read | Pro+ |
GET /api/public/whale-signals | signals:read | Elite |
GET /api/public/copy-signals | signals:read | Elite |
POST /api/trade/execute | trade:execute | Pro+ |
GET /api/trade/history | agent:full | Pro+ (Elite for new full keys) |
GET /api/trade/ledger | agent:full | Pro+ (Elite for new full keys) |
GET /api/trade/money-summary | agent:full | Pro+ (Elite for new full keys) |
GET /api/trade/orders, GET /api/trade/orders/{id} | agent:full | Elite |
DELETE /api/trade/orders/{id} | agent:full | Pro+ |
POST /api/feedback | agent:full | Elite |
The direct REST tier applies only when the bearer is a ptk_ Agent API Key;
session-authenticated callers follow the tier behavior below. Free Agent API
Keys work on POST /api/backtest, but only with the fixed demo configuration,
and the returned trade list is capped at 5 rows. Every other row requires
current Pro or Elite access: the key validator rejects a Free key before the
handler runs, so GET /api/trades answers a Free key with a plain 401
({ "error": "Unauthorized" }), not TIER_UPGRADE_REQUIRED. Free-tier
agents read mock and real trade history through MCP pt_trades_list
(signals:read, Free) instead of GET /api/trades. New agent:full keys are
normally issued only to Elite accounts; the Pro+ cancellation tier allows an
existing agent:full key to keep reducing exposure after an account downgrade.
GET /api/trade/history keeps its existing REST Agent API Key gate:
agent:full plus the validator's current Pro/Elite access check. Browser/Supabase
JWT users can read their own receipts at every tier without an Agent API Key.
For least-privilege agent reads, use MCP pt_trade_history_get with
signals:read at any tier; do not add write scopes to a key just to read history.
Both interfaces fence the same real_trades projection to the authenticated
user, regardless of extra owner/wallet query parameters. See the
trade-history permission contract
for the fields, compatibility rationale and authorization tests.
4) Service auth
For whale-service control endpoints:
x-whale-service-secret: <secret>orAuthorization: Bearer <WHALE_SERVICE_SHARED_SECRET>
5) Scan secret
POST /api/scan/run also accepts x-scan-secret: <SCAN_SECRET> (bypasses the tier gate; used by cron/internal automation).
6) Webhook signature auth
POST /api/whale/webhook requires:
x-alchemy-signature = HMAC_SHA256(raw_body, ALCHEMY_WEBHOOK_SIGNING_KEY)
When a registration has a secret, outbound deliveries from /api/webhooks*
include X-Webhook-Signature with the value
sha256=<HMAC_SHA256(raw_body, secret)>. Compute the HMAC over the raw request
body before parsing it.
Tier gating (important)
Effective tier resolution: a user on free with an active trial (trial_ends_at > now) is treated as pro. Tier failures return TIER_UPGRADE_REQUIRED with the required tier and an upgradeUrl.
Free
GET /api/trades: 7-day mock history plus full-depth own real-trade receipts.GET /api/anomalies: max 5 results, 7-day base retention window — each activated referral adds 7 days, up to 28 days total — with a 1-hour visibility delay (detections newer than 1 hour are hidden).POST /api/scan/trigger: not allowed (403)./api/keys+POST /api/keys/generate: allowed; generated keys always get exactlysignals:read(requested write/automation scopes are stripped). Limit: 1 active Agent API Key per user; rotate by revoking the old key, then generating a replacement. MCP usage is capped at 10 requests/min per tool and 200 requests/day total.POST /api/trade/execute: session callers can execute only when manual trading-readiness checks pass. Agent API Key/MCP execution is unavailable because Free keys never carrytrade:execute.- Session callers can read their own
/api/trade/history,/api/trade/ledger, and/api/trade/money-summary; direct Agent API Key calls still requireagent:full. Order listing/detail remain unavailable.DELETE /api/trade/orders/{id}may cancel the signed-in user's own resting order because cancellation reduces exposure. /api/webhooks(create): not allowed (403)./api/public/whale-signals,/api/public/copy-signals: not allowed (403).
Pro
GET /api/trades: 90-day mock history plus full-depth own real-trade receipts.GET /api/anomalies: max 100 results, 90-day retention window, no delay.POST /api/scan/trigger: allowed, rate limited to 2/hour./api/keys+POST /api/keys/generate: allowed; scopes default tosignals:read, with optionaltrade:executefor real trade execution andagent:scanfor scan automation where allowed. Limit: 1 active Agent API Key per user; rotate by revoking the old key, then generating a replacement./api/webhooks(create): up to 5 active webhooks./api/public/whale-signals,/api/public/copy-signals: not allowed (Elite only).POST /api/trade/execute: session callers can execute only when manual trading-readiness checks pass. Agent API Key/MCP execution requirestrade:executescope.- Session callers can read their own
/api/trade/history,/api/trade/ledger, and/api/trade/money-summary; direct Agent API Key calls still requireagent:full. Order listing/detail remain unavailable.DELETE /api/trade/orders/{id}may cancel the signed-in user's own resting order.
Elite
GET /api/tradesreturns full-depth mock and own real-trade history.GET /api/anomalies: max 200 results, full retention window.POST /api/scan/trigger: allowed, rate limited to 10/hour./api/keys+POST /api/keys/generate: scopes may includesignals:read,trade:execute,agent:scan, and/oragent:full. Limit: 1 active Agent API Key per user; rotate by revoking the old key, then generating a replacement./api/trade/execute,/api/trade/history,/api/trade/ledger,/api/trade/money-summary, and/api/trade/orders*: allowed where the route-specific readiness checks pass (trade:executeoragent:fullfor execute API-key auth). Browser/session order cancellation is allowed for the signed-in user's own resting order regardless of tier./api/public/whale-signals,/api/public/copy-signals: allowed./api/webhooks(create): up to 50 active webhooks.
Rate limits
Agent API Key REST limits use the scope shown below; MCP separately applies per-identity/per-tool and aggregate request buckets (see MCP rate limits). Session requests are limited per user, while the public discovery and refresh limits are per IP.
| Endpoint | Limit | Scope |
|---|---|---|
POST /api/auth/refresh | 30/minute | IP |
| Anonymous discovery GETs | shared 60/minute anonymous budget | IP |
POST /api/scan/trigger | Pro: 2/hour, Elite: 10/hour | user |
POST /api/keys/generate | 5/hour per user | user |
POST /api/trade/execute | 10/minute per user + 20/minute per API key | user + key |
GET /api/public/whale-signals | 120/minute per API key | API key |
GET /api/public/copy-signals | 120/minute per API key | API key |
The shared anonymous discovery budget covers GET /api/markets,
GET /api/events, GET /api/home-snapshot, and
GET /api/market-signals. One anonymous caller consumes the same IP-scoped
60-request bucket across all four routes.
Rate limit response headers
Every rate-limited response — a success as well as a 429 — carries the RFC
RateLimit-* quota view for the bucket that governed it. Read it and pace
yourself; do not discover the ceiling by being refused.
| Header | Meaning |
|---|---|
RateLimit-Limit | Effective ceiling for this caller, after tier multipliers (Pro x2, Elite x5). |
RateLimit-Remaining | Requests left in the current window. |
RateLimit-Reset | Delta-seconds until the quota refills. Never a unix timestamp. |
RateLimit-Policy | "bucket";q=<limit>;w=<window seconds> — endpoints sharing a bucket share one budget. |
curl -si https://polytrackers.com/api/markets | grep -i '^ratelimit-'RateLimit-Limit: 60
RateLimit-Remaining: 59
RateLimit-Reset: 60
RateLimit-Policy: "public-market-read";q=60;w=60
Rules a client can rely on:
- On a
429,Retry-Afterstays the authoritative back-off signal, andRateLimit-Resetis never smaller than it. Sleeping forRateLimit-Resetis therefore always safe. RateLimit-Resetrounds up, so it never expires early.- Paths with no limiter behind them omit the headers entirely rather than
advertise a quota nobody enforces:
/api/health,/api/cron/*, the signed provider webhooks, and the cachedGET /api/mcpcapability probe. - A few in-route limiters signal with
Retry-Afterand a structured body but publish no quota view: thePOST /api/trade/executeper-API-key limiter,POST /api/auth/refresh(whose429can be proxied from upstream), theGET /api/clob/price-historyper-IP limiter, and theGET /api/markets/end-datesper-user limiter (which sends legacyX-RateLimit-Limit/X-RateLimit-Remainingheaders instead). Treat a missing quota view as "unknown budget", never as "unlimited". - The MCP per-user/per-tool buckets are metered in-route and report
retry_after_secondsinside the JSON-RPC error payload; theRateLimit-*headers on/api/mcpdescribe the per-IP transport cap only.
MCP rate limits
MCP (POST /api/mcp) is metered separately from the REST limits above, in
three per-user layers plus one transport cap. All apply at the same time; the
tightest one binds.
| Bucket | Free | Pro | Elite | Scope |
|---|---|---|---|---|
Per-tool tools/call | 10/minute | 60/minute | 300/minute | user, per tool name |
Daily tools/call quota (all tools) | 200/day | no daily cap | no daily cap | user |
| All JSON-RPC requests (setup + listings) | 120/minute | 600/minute | 1800/minute | user |
Transport caps on /api/mcp apply per IP before authentication, regardless
of tier: 300/minute for authenticated traffic and 60/minute for the
anonymous health probe and handshakes.
- The per-tool bucket is keyed by tool name, so a burst on one tool does not
consume another tool's budget. Blocked calls return
RATE_LIMITEDwithretry_after_seconds. - The daily quota is consumed only after the per-minute bucket allows the call, so minute-throttled calls do not burn the daily budget.
- The request bucket charges every authenticated POST one setup unit, plus one
extra unit for catalog listings and resource/prompt reads.
tools/call,initialize,ping, andnotifications/*pay only the setup unit. - The transport IP caps are enforced before JSON-RPC handling and may return
a non-JSON 429; treat any raw 429 as
RATE_LIMITEDand back off.
The same figures are published machine-readably in the
polytrackers://mcp/capabilities resource (rate_limit_per_minute,
rate_limit_per_day, transport_rate_limits.streamable_http).
404 response shape (unknown paths)
Any /api/** path that matches no endpoint returns HTTP 404 with a JSON body.
It is never the HTML app shell, so a client can branch on the payload:
{
"error": "Not Found",
"code": "NOT_FOUND",
"message": "No PolyTrackers API endpoint matches GET /api/nope. The OpenAPI contract lists every documented path.",
"documentation": {
"openapi": "https://polytrackers.com/openapi.json",
"apiDocs": "https://polytrackers.com/docs/api",
"llmsTxt": "https://polytrackers.com/llms.txt",
"agentSkill": "https://polytrackers.com/skill.md",
"mcp": "https://polytrackers.com/api/mcp"
}
}Send Accept: text/markdown for the same recovery links as a short markdown
document. Both variants set Vary: Accept. NOT_FOUND is not retryable —
re-read the OpenAPI contract instead of backing off.
Page (non-/api) paths keep serving the regular HTML 404 page.
Markdown content negotiation (public pages)
A curated set of public pages answers Accept: text/markdown with a short
machine-readable summary instead of HTML — the page title, its description, and
the discovery links to read next. The set is every static public page — the
homepage, the documentation hub and API reference, the guides, the comparison
pages, and the policy pages — reachable either by sending the header or by
appending .md to the path (/docs -> /docs.md). Live-data pages (the
leaderboard, anomalies, wallet tracker, analytics, search, and copy trading)
are excluded on purpose: a summary of a continuously refreshed page would carry
none of its data. public/llms.txt publishes the live list.
curl -s -H 'Accept: text/markdown' https://polytrackers.com/docs/apiRules:
- The markdown body is a summary, not a transcription. Fetch the canonical HTML URL it names when you need the full document.
- Both representations set
Vary: Accept, so a shared cache cannot mix them. - Ranking honors q-values.
text/markdownmust outranktext/html; a tie serves HTML, because a client that accepts both can render the page. - An
Acceptthat matches neither representation and carries no wildcard gets406 Not Acceptable. A missing or wildcardAcceptgets HTML. - Every other path ignores the header and serves HTML exactly as before.
429 response shape
Every 429 body includes "error" (string). The Retry-After header (seconds)
is the authoritative back-off signal — it is present on all rate-limited
responses (the one exception: 429s proxied from the upstream auth provider on
POST /api/auth/refresh may omit it). Other body fields vary by endpoint, so
treat them as optional:
| Endpoint / limiter | 429 body |
|---|---|
GET /api/public/whale-signals | { error } |
GET /api/public/copy-signals | { error } |
POST /api/trade/execute (per API key) | { error, code: "API_KEY_RATE_LIMITED", retryAfter, docsUrl } |
POST /api/trade/execute (per user) | { error, code: "RATE_LIMITED" } |
POST /api/keys/generate | { error, code: "RATE_LIMITED" } |
POST /api/auth/refresh | { error, code: "RATE_LIMITED" } |
POST /api/scan/trigger | { error } |
The fullest shape — returned by the /api/trade/execute per-API-key limiter:
{
"error": "This API key has exceeded its trade execution rate limit. …",
"code": "API_KEY_RATE_LIMITED",
"retryAfter": 7,
"docsUrl": "https://polytrackers.com/docs/api"
}Idempotency for /api/trade/execute
Session-authenticated trade execution enforces the configured site origin:
Sec-Fetch-Site: cross-site or a mismatching Origin returns HTTP 403 with
code TRADE_EXECUTE_CROSS_ORIGIN, before body parsing, idempotency locking,
or trade execution. Requests without either browser origin header retain
existing behavior. Agent API Key requests are exempt from this session check.
Trade execution is idempotent when you supply an Idempotency-Key header. For
Agent API Key requests the header is required (400 IDEMPOTENCY_KEY_REQUIRED
otherwise); session requests may still pass idempotencyKey in the body.
Every /api/trade/execute body must also include regionCertification: true
to certify the caller is permitted to trade in their jurisdiction and is not in
a restricted or prohibited Polymarket trading region.
-
Accepted formats: UUIDv4 (preferred) or any 8–256 printable-ASCII token.
-
Retrying the same key replays the original trade result (
200withidempotencyReused: true) instead of re-executing. The replay window is indefinite — keys never expire. Generate one fresh key for each new trade intent. Reuse that same key for every retry or reconciliation attempt for that intent, including after an uncertain timeout or aTRADE_IN_FLIGHTresponse; never reuse the key for a different trade intent. -
Two concurrent requests with the same key return
409 TRADE_IN_FLIGHTfor the loser:json{ "error": "trade_in_flight", "code": "TRADE_IN_FLIGHT", "idempotencyKey": "agent-trade-2026-04-20-001", "retryAfter": 5 }
Lifecycle contradictions and order-book indicators
Agent API and MCP clients must treat Gamma lifecycle fields as conservative
write gates. When pt_mock_price_get or a composed market-intel/copy-desk
workflow reports PAST_END_DATE_BUT_MARKET_APPEARS_LIVE, that is contradiction
evidence only; it is not permission to place, mirror, or override by default.
Live CLOB/order-book indicators, bid/ask/spread rows, recent trades, or
enableOrderBook signals do not override MARKET_PAST_END_DATE,
MARKET_CLOSED, auth, tier, anti-IDOR, preflight/idempotency, wallet-readiness,
region, or real-money execution safeguards.
Resolved markets are separately readable for settlement. When Gamma has one
unique terminal 0/1 outcome, pt_mock_price_get adds an optional resolution
block with winning_outcome, winning_token_id, ordered outcome/token/payout
rows, and resolved_at. When the live CLOB book is unavailable but its market
metadata marks one winning token, pt_clob_price_get returns
status:"resolved" with that winner instead of a generic unavailable result.
This is settlement evidence only: priceable and all entry/write gates remain
unchanged, and open or ambiguous markets expose no winner.
Marking an existing position is not a write gate, and one narrow case is
handled separately. Daily markets — the weather book's "highest temperature in
<city> on <date>" questions — carry an end_date at the START of the
observation day, up to ~24h before they resolve, and keep trading the whole
time. pt_mock_price_get therefore prices a past-end-date market when it still
looks like a same-day market mid-observation on every axis Gamma reports:
inside a 36h grace window, not closed, not archived, active, accepting orders,
order book enabled, a row updated within the hour, and coherent two-sided
outcome prices. Missing or contradicting any one of those keeps the
conservative non-priceable answer, so a long-dead market whose Gamma flags
still read live stays unpriceable. lifecycle_reason reports
MARKET_PAST_END_DATE either way; manual entry gates retain this conservative
valuation contract. Copy execution uses the separate provider lifecycle
contract below.
A future manual Agent API/MCP write exception must prove the Gamma lifecycle field is stale, require explicit user/operator approval for the exact action, write an audit log, and must not broaden default Agent API/MCP write privileges.
Copy execution (paper and real) does not infer closure from Gamma's scheduled
end date. For a past-date market, explicit closed: false and
acceptingOrders: true, with no resolved/archived/inactive state, permits the
ordinary copy risk path. Lifecycle evidence records end_date_stale: true;
closed or non-accepting markets still emit MIRROR_SKIPPED_MARKET_PAST_END.
Missing flags remain lifecycle-unknown. Existing priceability, CLOB order,
authorization and wallet risk checks still apply.
Activity metadata includes bounded marketLifecycle evidence: reason,
source, checkedAt, endDate, end_date_stale, active, closed,
archived, acceptingOrders, and umaResolutionStatus. Digest lifecycle
summaries preserve the checked time and stale-date marker and do not infer a
past-end verdict from the schedule when that stored observation confirms an
open market. Cursor/byte-compacted rows retain marketLifecycle. This is
historical decision evidence, not a current quote or permission to place a
new trade; legacy rows and explicit priceability/closure warnings keep their
existing conservative interpretation.
Bot challenge (403)
Session traffic that BotID flags as automated receives:
{
"error": "bot_challenge_failed",
"reason": "automated_traffic_detected",
"suggestedAction": "authenticate_with_api_key",
"docsUrl": "https://polytrackers.com/docs/api"
}Valid Authorization: Bearer ptk_… requests bypass BotID entirely — see
llms.txt.
Quickstart (agent flow)
1) Sign in and obtain a session
PolyTrackers auth is passwordless. Create an account or sign in through the web
app at /register or
/login using Google OAuth or an emailed
magic link (Terms of Service and Privacy Policy acceptance is captured during
sign-up). This yields a Supabase session — a session cookie plus an
access_token / refresh_token pair you can pass as a bearer token.
Exchange a refresh token for a fresh session pair when the access token nears expiry:
curl -X POST "https://polytrackers.com/api/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refresh_token":"'"$REFRESH_TOKEN"'"}'Response includes:
access_tokenrefresh_tokentoken_typeexpires_in
2) Read account + tier
curl "https://polytrackers.com/api/account" \
-H "Authorization: Bearer $ACCESS_TOKEN"Returns tier (effective tier, honoring active trial), trialEndsAt, apiKeysCount, walletsCount, onboarding + wallet-link status.
3) Discover markets
curl "https://polytrackers.com/api/markets?query=btc&status=open&sort=volume&limit=25"To decorate public market cards with coarse recent signal evidence, pass up to 100 comma-separated condition IDs. The response contains only 24-hour whale and anomaly booleans—no wallet, trade-size, price, or execution data:
curl "https://polytrackers.com/api/market-signals?conditionIds=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"For one market or event, the anonymous detail endpoints return more than the
list rows do. GET /api/markets/{slug} includes the full resolution-rules
description plus a heuristic resolutionRisk score; GET /api/events/{slug}
returns the event's complete outcome ladder (every priced leg, not just the
legs on a paginated list page); GET /api/events/featured returns the current
featured multi-outcome event, or {"event": null} when none qualifies:
curl "https://polytrackers.com/api/markets/some-market-slug"
curl "https://polytrackers.com/api/events/some-event-slug"
curl "https://polytrackers.com/api/events/featured"Anonymous requests to /api/markets, /api/events, /api/home-snapshot, and
/api/market-signals share one 60 requests/minute per IP discovery budget.
Pace discovery loops across the group and honor Retry-After on 429.
The {slug} detail endpoints are metered separately: each has its own
60 requests/minute per IP bucket, and both can return 503 with
reason: "upstream_request_budget_deferred" when the shared upstream request
budget defers the fetch — honor retryAfterMs before retrying.
4) Trigger anomaly scan (Pro/Elite)
curl -X POST "https://polytrackers.com/api/scan/trigger" \
-H "Authorization: Bearer $ACCESS_TOKEN"5) Read anomalies
Session, or a Pro+ Agent API Key (signals:read):
curl "https://polytrackers.com/api/anomalies?limit=50&severity=high" \
-H "Authorization: Bearer $ACCESS_TOKEN"Free-tier session callers see at most 5 rows, within their retention window (7-day base, up to 28 days with activated referral rewards), and only those detected more than 1 hour ago. Direct REST access with an Agent API Key requires Pro+.
6) Read anomaly detector performance
Aggregate win-rate, average return, and P&L for historical anomaly detections. Backed by the anomaly_performance_daily view.
curl "https://polytrackers.com/api/anomalies/performance?since=2026-01-01T00:00:00Z&until=2026-04-01T00:00:00Z&group_by=month&anomaly_type=SHARP_MOVE" \
-H "Authorization: Bearer $ACCESS_TOKEN"Query parameters:
since,until— ISO-8601 datetimes. Defaults to the last 30 days; the window is capped at 365 days.group_by— one ofday(default),month,anomaly_type,severity,detector_version,category,all.anomaly_type,severity— optional filters passed straight through to the view.detector_version— optional legacy detector label (for examplev2). When omitted, Anomaly 2.0 (v3) receipts are excluded from the blended population; passingv3returns400because that cohort is not available on this public endpoint.category— optional market category:sports,oil-energy,politics-elections,crypto,economics, orother.
Response body:
window—{ since, until }echoing the resolved window (ISO-8601).totals— aggregate{ flagged, resolved, wins, losses, voids, winRate, avgReturnPct, totalPnlUsd, equalDollarPnlPer100 }across the window.series— same shape astotalsbut keyed bycohort(the group_by bucket), sorted ascending.coverage—resolved / flaggedfor the window (0–1).coverageThreshold— the coverage at which the cohort is considered mature enough to surface headline numbers (currently0.5).minDecidedForHeadline— the absolute number of decided outcomes (wins + losses) that is, on its own, enough to surface headline numbers (currently20).headlineReliable—coverage >= coverageThreshold || (wins + losses) >= minDecidedForHeadline. Because markets resolve long after detection, a window can have low coverage while still holding a large, meaningful sample of decided outcomes; either condition makes the win rate reliable.
equalDollarPnlPer100 is a simulation: $100 flat-staked on every decided anomaly in the cohort, expressed as total dollars of P&L.
Accepts session auth (cookie/JWT) or a Pro+ Agent API Key with signals:read
scope. Responses are cached privately with s-maxage=60, stale-while-revalidate=300.
7) Read unified trade history
curl "https://polytrackers.com/api/trades?type=all&status=all&sort=newest&limit=25&offset=0" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response body includes tier and historyDepthDays; the latter is the mock
history window (7 for Free, 90 for Pro, null for Elite). Own real-trade
receipts are full-depth at every tier. For real rows, status describes the
position: a filled BUY remains open until resolution or exit accounting is
recorded. rawStatus keeps the order lifecycle, and closedAt is only an
actual resolution/settlement time—never a bookkeeping updated_at value.
Real rows also carry failureReason and failureDetail explaining why a
failed/cancelled attempt terminated; both are null on healthy rows.
fillAmount, requestedShares, and price expose the durable execution facts;
isDustFill is derived when filled shares are below the applicable market
share minimum, executed notional is below $1, or the fill is under 10% of
requested shares. Execution reuses live market/book metadata; bounded list and
metric reads use the centralized conservative fallback and make no per-row
provider calls. Dust remains real P&L but is excluded from win-rate and
mock↔real parity grading.
payoutReconciliationStatus carries the receipt-backed payout state, while
modeledPayout is the deterministic binary/SPLIT estimate used during
reconciliation and must not be presented as observed spendable cash.
A type=real&status=executed response also includes realWalletStats. That
object is aggregated server-side over every matching executed real-trade row,
independent of limit/offset, and reports realized P&L, all-time decided-BUY
cost basis/ROI inputs, open positions, and resolved rows still awaiting P&L
attribution. The trades array remains paginated.
conditionId narrows the feed to one exact Polymarket condition ID. Offset
pagination is reliable only before offset 1000 because PostgREST caps each
source window. A page that reaches that boundary returns its reachable prefix
as an explicit terminal page with pagination.depthLimited: true,
depthLimit: 1000, and
depthLimitReason: "postgrest_max_rows"; narrow by type, status, wallet, or
condition instead of continuing.
When the wallet-scope integrity tripwire drops a foreign raw row, the returned
trade rows remain verified against the requested wallet but count metadata
cannot be proven from the same suspect result set. The response therefore adds
pagination.degraded: true,
degradedReason: "wallet_scope_invariant_violated",
degradedFields: ["total", "hasMore"], and droppedRows. Treat total and
hasMore as untrusted for that incident response; normal-path pagination is
unchanged.
Direct REST access with an Agent API Key requires signals:read plus current
Pro or Elite access; a Free key gets 401. The 7-day Free history depth
applies to session callers. Free-tier agents use MCP pt_trades_list
(signals:read, Free) for the same feed.
8) Run a historical backtest
Free Agent API Keys can run the fixed demo configuration with signals:read;
the returned trade list is capped at 5 rows. Pro and Elite keys can customize
the configuration within their tier's window limits. costModel: "leader"
(default) uses the request's flat slippageBps and feeBps. For a whale-copy
admission run, use costModel: "copy": the server applies the daily
copy-receipt slippage bucket for each entry and charges the measured entry-fee
rate on every trade. The response labels the calibration source and timestamp;
missing receipt evidence falls back to documented non-zero conservative costs.
curl -X POST "https://polytrackers.com/api/backtest" \
-H "Authorization: Bearer $AGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source":{"kind":"anomalies","minSeverity":"HIGH"},
"stake":{"mode":"flat","amountUsd":100},
"startingBankrollUsd":1000,
"windowDays":30,
"costModel":"leader",
"slippageBps":50,
"feeBps":0
}'The copy-actionability gate recommends the same shape with a whale source,
windowDays 90 or 180, and costModel: "copy". Its old leader-cost runs do
not satisfy the gate. A canonical whale run returns
outOfSampleValidation: the requested window is split into a pre-holdout
selection slice and an independently replayed trailing 30-day holdout. Both
slices must enter at least one trade, avoid bankroll bust, and have positive
copy-cost capital-accounting ROI. The full requested-window result remains
reporting evidence, but it cannot clear admission when either disjoint slice
fails; leaderboard and recommendation actionability then expose
OUT_OF_SAMPLE_VALIDATION_FAILED.
Every REST result includes strategy.stats_by_entry_price_bucket. It always
contains the five copy-calibration entry-odds buckets (<0.10, 0.10-0.35,
0.35-0.65, 0.65-0.90, >=0.90), including explicit zero-trade rows. Each
row reports trades, wins, losses, splits, entryCostUsd, totalPnlUsd,
capital-weighted roiPct, and arithmetic avgReturnPct. The aggregation is
computed from the existing settled trade log and adds no query or stored data.
9) Readiness-gated real trade execution
Session auth requires manual trading readiness: trading wallet/signing setup, wallet funding, wallet risk config, circuit-breaker state, idempotency, reservations, region certification, and audit logging.
Session auth:
curl -X POST "https://polytrackers.com/api/trade/execute" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"conditionId":"0xconditionid",
"side":"YES",
"amount":20,
"limitPrice":0.5,
"idempotencyKey":"agent-trade-2026-04-16-001",
"regionCertification":true
}'Agent API Key with trade:execute scope:
curl -X POST "https://polytrackers.com/api/trade/execute" \
-H "Authorization: Bearer $AGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c2b7f9e-47b8-4ad2-95b5-8f8d5f2f6a3c" \
-d '{"conditionId":"0x...","side":"YES","amount":20,"regionCertification":true}'CDP delegated-wallet browser users must submit explicit limit orders until the SDK adapter supports cap-priced immediate-or-cancel market orders. Non-CDP market buys retain the server-side slippage-cap path.
10) Cancel an own resting real order
Session callers can cancel their own resting order even when their tier cannot list all CLOB orders:
curl -X DELETE "https://polytrackers.com/api/trade/orders/$ORDER_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"The two programmatic paths have different gates: direct REST bearer calls to
GET /api/trade/history, GET /api/trade/ledger,
GET /api/trade/money-summary, and
GET /api/trade/orders* require agent:full. MCP
pt_trade_history_get requires signals:read at every tier, while
pt_trade_orders_list and pt_trade_orders_get require signals:read plus
the Elite tier.
Agent API Key lifecycle
Generate Agent API Key (JWT bearer only)
Free callers can generate read-only Agent API Keys for MCP access; requested scopes are reduced to signals:read and MCP usage is capped at 200 requests/day. Pro callers may request signals:read, the narrow trade:execute scope, and the narrow agent:scan scope; Elite callers may additionally request agent:full. Accounts keep 1 active Agent API Key; rotate by revoking the old key, then generating a replacement and updating clients and agents (409 API_KEY_LIMIT_REACHED when the cap is reached).
curl -X POST "https://polytrackers.com/api/keys/generate" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"nickname":"Agent API Key","expiresInDays":90,"scopes":["signals:read"]}'expiresInDays: 1–365, defaults to 90.scopes: defaults to["signals:read"]. Free keys are always reduced tosignals:read; paid-tier validation still applies outside the free MCP/read surface. Addtrade:executeonly when intentionally creating a real-trading key. Pro-tier requests foragent:fullare stripped, whiletrade:executeandagent:scanare allowed where their automation surfaces are enabled.- The raw
api_keyis returned once in the response — store it securely.
List keys
curl "https://polytrackers.com/api/keys" \
-H "Authorization: Bearer $ACCESS_TOKEN"Use Agent API Key for public whale feed (Elite)
curl "https://polytrackers.com/api/public/whale-signals?limit=25" \
-H "Authorization: Bearer $AGENT_API_KEY"Outbound webhooks (Pro/Elite)
Create, list, delete, and test delivery endpoints live under /api/webhooks*:
GET /api/webhooks— list your registrations.POST /api/webhooks— register a new URL with optional filters (minScore,severity[],anomalyTypes[],marketCategories[]) and a secret. Capped at 5 (Pro) / 50 (Elite); over-limit returnsWEBHOOK_LIMIT_REACHED.DELETE /api/webhooks/{id}— soft-delete a registration. The row is retained for audit (withdeleted_atstamped andstatus=paused), it will stop receiving future deliveries, and historicaldelivery_logentries remain queryable.GET /api/webhooks/{id}/deliveries— recent delivery logs.POST /api/webhooks/{id}/test— fire a test payload.
Webhook delivery contract
Matching anomaly alerts are delivered as anomaly.detected JSON:
{
"event": "anomaly.detected",
"timestamp": "2026-07-20T14:30:00.000Z",
"anomaly": {
"id": "anomaly-id",
"score": 82,
"market_title": "Will BTC reach $150k in 2026?",
"market_slug": "will-btc-reach-150k-in-2026",
"market_url": "https://polymarket.com/event/will-btc-reach-150k-in-2026",
"current_price": 0.64,
"anomaly_type": "WHALE_ANOMALY",
"severity": "HIGH",
"side": "BUY",
"outcome": "YES",
"bet_recommendation": {
"recommendedBet": 100
},
"whale_flag": true
}
}- Delivery targets must be public HTTPS URLs. Redirects are treated as failures and are not followed.
- Any
2xxresponse completes delivery. A failed initial attempt is retried after 30 seconds, then 5 minutes, then 30 minutes: 4 total delivery attempts. - Each registration is capped at 100 delivery attempts per rolling hour, including retries.
- When the registration has a secret, verify
X-Webhook-Signatureagainstsha256=<HMAC_SHA256(raw_body, secret)>before parsing the JSON.
X-Webhook-Signature signs outbound user deliveries. It is distinct from the
inbound x-alchemy-signature required by /api/whale/webhook.
Mock-wallet unrealized P&L freshness
GET /api/wallets exposes mock-wallet unrealizedPnl as a persisted
point-in-time snapshot, not a continuously priced equity value.
unrealizedPnlMarkedAt records the pricing-pass timestamp; null means no
trustworthy mark timestamp exists. The companion unrealizedPnlIsSnapshot,
unrealizedPnlCaveat, and unrealizedPnlAuthority fields keep this contract
machine-readable.
For current equity decisions, use
pt_mock_analytics_get.wallet_mark_to_market.preferred_unrealized_pnl and
inspect live_pricing_status. A complete analytics pricing pass refreshes the
persisted snapshot after the read returns. Partial or unavailable pricing never
overwrites the last complete mark. A partial pass covering at least 90% of open
positions nevertheless becomes the preferred live value, with unpriced legs
held at cost basis; lower coverage falls back to the timestamped snapshot.
The reserved real-money-copy row is never accepted by mock analytics and
never receives its write-behind. In normalized /api/wallets and
GET|POST /api/trade/real-wallet responses, its mark timestamp is null and its
provenance instead points to real_trades for position P&L and
/api/trade/wallet-funds for spendable funds.
GET /api/wallets derives current-day realized P&L from
get_wallet_daily_copy_stats. dailyPnl and the backward-compatible
daily_pnl alias always carry the same UTC-day value. If the projection fails,
both are null and dailyPnlDegraded:true / dailyPnlStatus:"degraded" make
the partial response explicit; the route never substitutes the denormalized
mock-wallet counter. Reserved real-wallet legacy performance aliases are
projected from real_trades. A reserved real wallet whose risk configuration
is missing or does not select the real lane makes the daily projection fail
closed instead of reading mock_trades.
GET /api/trade/wallet-funds also returns equityIdentity for the authenticated
real wallet. Its complete identity is:
supportedStablecoinValue + openMarkedValue + pendingRedemptionValue - netDeposits = lifetimePnl
supportedStablecoinValue sums the selected funding wallet's pUSD, native USDC
and USDC.e at the ledger's nominal $1-per-token basis. spendablePusd remains
pUSD only; unconvertedStablecoinValue is USDC plus USDC.e, which require
conversion before trading. The sum already includes spendable pUSD: do not add
it again. Token conversions are not new capital or profit. These three token
reads bypass caches and share one Polygon block. Invalid, missing, cached,
wrong-wallet or older-than-60-second token evidence returns a degraded identity
with null stablecoin totals and lifetime P&L. A failed chain snapshot returns
502, not a verified zero. This endpoint uses nominal parity, not market depeg
quotes. Account ROI is lifetime P&L / positive net deposits, distinct from
cumulative trade-efficiency ROI.
Capital still comes from the existing user-wide ledger and holdings from the selected funding address; historical multi-wallet capital allocation remains unresolved. The token snapshot does not synchronize the venue positions or ledger, so in-flight transfers/redemption indexing can temporarily differ.
pendingRedemptionValue comes from the bounded Polymarket Data API positions
read and includes only positions marked redeemable. The separate
modeledPendingRedemptionValue is the unresolved-payout-row projection for
operations diagnostics only: it never enters lifetimePnl, and its absence
alone does not degrade the identity. netDeposits is the signed sum of deposit
and withdrawal ledger entries, while openMarkedValue excludes redeemable
venue positions so pending winnings are not double-counted. If the positions
read or another required component is unavailable, status: "degraded" and
lifetimePnl: null fail closed; callers must not reconstruct or display a
partial lifetime P&L as complete.
Deletion & audit retention
PolyTrackers favors soft deletion for user-owned resources so that reporting, billing, copy-trading analytics, and security audits remain reliable. The behavior per-resource is:
| Resource | Endpoint(s) | Behavior |
|---|---|---|
| Agent API Keys | DELETE /api/keys?id=… | Soft-revoke: is_active=false, revoked_at=now(). Authentication is rejected immediately; row is retained for audit. |
| Mock wallets | DELETE /api/wallets, DELETE /api/mock/wallet/{id} | Soft-delete: is_active=false, deleted_at=now(). Whale assignments are detached (active=false, removed_at=now()). mock_trades, whale_activity, and whale_decision_traces are preserved. Wallet names can be reused. |
| Outbound webhook registrations | DELETE /api/webhooks/{id} | Soft-delete: deleted_at=now(), status=paused. No future deliveries fire; historical delivery_log remains queryable via GET /api/webhooks/{id}/deliveries. |
| Alert preferences | DELETE /api/alerts/preferences | Hard reset of the preferences row. |
| Roster (whale) assignments | DELETE /api/roster/{address} | Detach: row retained, active=false, removed_at=now(). |
| Trade orders | DELETE /api/trade/orders/{id} | Cancels the live order at the CLOB; hard-removes the queued row. |
Mock trades are not user-deletable by design once placed — open, closed, and
resolved rows feed the leaderboard, copy-trading scoring, and tier-history
analytics. To exit an open mock position, close it through
POST /api/mock/resolve; this records the terminal state and preserves the
trade row for history.
Whale service + inbound webhooks
Existing whale-service endpoints remain supported and documented in OpenAPI:
- Wallet/roster management (
/api/wallets,/api/roster*) - Service status/config/control (
/api/whale/*) - Webhook ingest/events/replay (
/api/whale/webhook*)
For replay automation, use dryRun: true first, then replay live once validated.
Discovery endpoints
Anonymous, CDN-cached, and safe to fetch before you have a key. Every one of them describes a surface documented in this file:
| Path | Media type | What it is |
|---|---|---|
/openapi.json | application/json | The OpenAPI 3.1 contract for the REST API. /api/openapi.json is a 301 alias. |
/.well-known/api-catalog | application/linkset+json | RFC 9727 API catalog: a linkset naming the REST, MCP, and documentation MCP entry points, each with its service-desc (machine-readable description), service-doc (human documentation), and service-meta (metadata) links. The one URL to fetch when you want the developer resources and nothing else. |
/.well-known/mcp | application/json | MCP handshake manifest (alias /.well-known/mcp.json). |
/llms.txt | text/plain | Agent instructions: when to use PolyTrackers, automation-safe lanes, error shapes, and the crawl policy. |
/skill.md | text/markdown | The official agent skill — tool catalog, scopes, and workflows. |
MCP server
PolyTrackers exposes a stateless MCP endpoint for agent clients:
GET /api/mcp— unauthenticated health/capability probe.GET /.well-known/mcp(aliasGET /.well-known/mcp.json) — unauthenticated manifest describing this server: transport URL, advertised protocol versions, capabilities, auth scheme, and the stdio bridge package. Generated from the same constants the server itself uses, so it cannot drift.POST /api/mcp— JSON-RPC 2.0 over Streamable HTTP. SendAuthorization: Bearer <agent-api-key>— use the sameptk_...key as your REST/API examples.GET /api/mcp/events— optional authenticated SSE notifications foranomalies,whale_signals,copy_signals, andscan_progress, with: keepaliveevery 15s. SendAuthorization: Bearer ptk_.... Thewhale_signalsandcopy_signalstopics are Elite-only; requests for them from Free or Pro keys are silently dropped while the200stream remains live for permitted topics. Optionalseverity(comma-separated, case-insensitive) andmarket_slug(exact match) query parameters narrow delivery to events whose payload carries a matching field; events without that field are dropped.GET/POST /api/mcp/docs— the anonymous documentation MCP server: a separate, read-only Streamable HTTP server that takes no API key and reads noAuthorizationheader.GETis a CDN-cacheable health probe (it reportsauthentication: "none");POSTis stateless JSON-RPC serving three read-only tools —docs_list,docs_search,docs_get— plus the same documents as MCP resources, over the published.mdpage twins and sectionllms.txtindexes. It serves no live market, whale, or account data. Both operations are in the OpenAPI contract at/openapi.json; the pre-connection card is/.well-known/mcp/docs-server-card.json.
SSE connections last at most 800 seconds (~13 minutes), so reconnect when the
server closes the stream. There is no Last-Event-ID resumption: each reconnect
starts at the current Redis position ($), so events published while
disconnected are permanently missed.
When the client supports Streamable HTTP, point it at
https://polytrackers.com/api/mcp with Authorization: Bearer <agent-api-key>.
For stdio-only MCP hosts, run the published bridge with
npx -y @polytrackers/mcp-stdio; it relays stdio JSON-RPC to that endpoint. It
reads POLYTRACKERS_API_KEY (required),
POLYTRACKERS_MCP_URL (optional, defaults to the endpoint above),
POLYTRACKERS_MCP_ALLOWED_HOSTS (optional host allowlist), and
POLYTRACKERS_MCP_TIMEOUT_MS (optional). Full hosted and stdio setup examples
are published in /skill.md.
To test strategies programmatically with no real money — the mock (paper) trading tool flow, free-tier reads, and going-live fees — see the Polymarket Mock Trading API guide.
Agent skill distribution
Install the PolyTrackers agent skill (which bundles this MCP setup) with one
command: npx skills add polytrackers/polymarket-copy-trading-skill. Official
distribution channels:
| Channel | Location |
|---|---|
| GitHub | polytrackers/polymarket-copy-trading-skill |
| skills.sh | polytrackers/polymarket-copy-trading-skill |
| npm | @polytrackers/mcp-stdio (stdio bridge) |
Scopes:
signals:read— read-only tool/resource catalog for Free+; free keys are shaped by 7-day base anomaly history (up to 28 days with activated referral rewards), top-5 delayed anomalies, and the 200 requests/day MCP quota.trade:execute— permitspt_trade_executefor Pro+ afterpt_trade_preflightapproval.agent:scan— permitspt_scan_triggerfor Pro+ without granting broader writes.agent:full— current Elite write/execution superset.
Versioning & changes
-
2026-10-14: Paper and real-copy wallet risk config now exposes
smallOrderPolicy(scale-upby default, orskip) throughPUT /api/whale/configand both guarded MCP config writers, and new wallets defaultminTradeSizeto $1. The minimum copy size is the larger of $1, the enabledminTradeSize, and (real wallets) the signedminPerTrade; it applies after fixed or percentage sizing and before final risk validation and reservation. Inscale-upmode a positive copy below the minimum is raised to it when every risk, authorization, balance, fee, and venue gate allows, which can exceed the strategy-sized cost; otherwise it is skipped with a reason. Real scaling still needs a signedminPerTrade: null/0 authorizations keep skip behavior until the owner changes them, and stored custom minimums are retained.skipnever enlarges a copy, and SELL quantities and actual partial fills are never enlarged. This supersedes the 2026-08-10minPerTradenote below. -
2026-10-01: Real-wallet copy controls now reflect mandatory authorization pauses. Expired signing or inactive copy authorization returns
fully_paused, an actionableauthorizationPause, and the preservedsavedMode. Resume attempts return409until signing and copy authorization are restored. Paper wallets and whale assignments are unchanged; an existing manual pause still applies after reauthorization.
The current REST/OpenAPI contract version is 2.0.0, sourced from the
canonical OpenAPI document and served at /openapi.json. The hosted MCP server
version is 0.2.0, sourced from the server package version and returned by
the MCP initialize response and each tool response's _meta.server_version.
Additive endpoints and tools may ship without a version bump.
Version header
The REST contract is versioned in a response header, not in the URL path.
Every /api/* response carries:
| Header | Meaning |
|---|---|
API-Version | Contract version that produced this response, e.g. 2.0.0. Matches info.version in the OpenAPI document. |
Assert on it at startup and fail loudly on an unexpected major, rather than
discovering a change through a parse error later. The header is response-only:
no request header changes how a route behaves, so there is nothing here that
can be pinned to and later withdrawn. Paths outside the proxy's matcher
(/api/cron/*, /api/whale/webhook, /api/goldsky/shadow) are
machine-to-machine and omit it.
The OpenAPI document declares this header on every response it describes, as
$ref: "#/components/headers/ApiVersion" — so a generated client sees it
without reading this page, and a spec parser can detect the versioning
strategy. The four POST /api/whale/webhook responses deliberately carry no
such declaration, because that path is outside the matcher. A contract test
holds both halves in place against the matcher in proxy.ts.
How the number moves:
- Patch/minor — additive only. New endpoints, new optional request fields, and new response fields ship without a major bump, so clients must ignore response fields they do not recognise.
- Major — anything that could break a correct client: a removed or renamed field, a narrowed type, a changed status code, or a retired endpoint. A major is always preceded by the deprecation signal below.
Deprecation and sunset headers
A retired endpoint announces itself on the wire, not only in this document:
| Header | Meaning |
|---|---|
Deprecation | RFC 9745 structured-field date — @<epoch seconds> — for when the deprecation was announced. |
Sunset | RFC 8594 HTTP-date for when the endpoint stops serving. A date in the past means it is already gone. |
Link | rel="successor-version" pointing at the replacement, plus rel="deprecation" pointing at this page. |
POST /api/whale/trade is the current example: retired on 2026-02-27, it
answers 410 Gone and carries all three headers pointing at
POST /api/whale/webhook. Poll Deprecation on the endpoints you depend on —
it is the earliest machine-readable warning available, and it is set long
before the Sunset date arrives.
Compatibility and deprecation policy:
- Breaking REST contract changes are recorded in this section when they ship.
- MCP tool additions, removals, and renames are recorded in the
polytrackers://changelogresource. Its current catalog count and fingerprint are checked against the registered catalog in CI. - Breaking MCP changes bump the server minor version. A renamed tool remains
available for one minor version with
deprecated: trueand a replacement in its description before removal. - The MCP HTTP transport does not emit a
Deprecationresponse header for tool changes; the REST headers above cover REST endpoints only. For MCP, consult this section andpolytrackers://changelog.
Change log:
-
2026-09-11: Agent alert-preference updates now redact stored Discord webhook tokens from their response.
pt_mock_experiment_runno longer accepts the ineffectivefund_amountinput and uses the caller tier's standard balance when it creates a wallet. Webhook registration rejects URL credentials and custom HTTPS ports, whilePOST /api/walletsbounds its body to 64 KiB and enforces the established 100-character name and 20-character color limits. -
2026-10-12: Wallet-risk REST and mock/real MCP config expose
excludeShortHorizonMarkets(strict boolean, default false). When enabled, BUYs within 24 hours are skipped across all categories withMIRROR_SKIPPED_SHORT_HORIZON; sports use game/event start and non-sports use market resolution time. Recognized short-window crypto/daily-price markets with unknown timing also skip. SELLs are unaffected. Paper atomic execution repeats the check; config import to a real wallet preserves the policy. -
2026-09-28: Wallet-risk config, REST, and mock/real MCP tools expose an optional inclusive copied-BUY entry band (
minEntryPrice0 by default,maxEntryPrice1 by default) andexcludeSameDaySports(false by default). The latter skips sports-like markets ending within 24 hours or with an unknown end date. Mock fills check final VWAP atomically; real BUYs check the observed executable price before reservation because final fill VWAP is unavailable there. SELL exits remain unchanged. -
2026-09-08: Wallet-risk updates now apply the existing $10,000,000 absolute ceiling to
hourlyLossLimitanddailyLossLimitacross the dashboard, REST endpoint, and both guarded MCP config writers. UnexpectedPUT /api/whale/configfailures retain full server-side logs but return a fixed message instead of exposing raw infrastructure details. -
2026-09-07: The Elite read-only
pt_real_wallet_copy_config_getprojection now includes effective signed copy-tradingauthorizationLimits.maxPerTradeandauthorizationLimits.dailyLossLimit. Null means a ceiling is unavailable; both remain distinct from same-named wallet-risk fields underconfig, and no write path or signing material was added. -
2026-08-23: The OpenAPI document now declares
API-Versionon every response it describes, instead of only defining the header component and describing it in prose. Documentation only — the header itself has shipped on the wire since earlier the same day, and no endpoint's status, body, or media type changed. -
2026-08-23: Every
/api/*response now carriesAPI-Version, and the retiredPOST /api/whale/tradeadditionally carriesDeprecation,Sunset, and a successorLink. Additive headers only — no status code, body, or cache directive changed, and page routes are untouched. Every OpenAPI operation also now has a description; a contract test fails the build if one ships without. -
2026-08-14: Wallet copy pause is now a three-state REST/MCP contract:
copying,buys_paused, orfully_paused. Buys-only mode blocks new BUY mirroring withWALLET_COPY_BUYS_PAUSEDwhile paper and real SELL exits keep flowing. The v2 MCP setter prefersmodeand retains legacycopyPaused:true|false; every live transition remains atomically audited, with explicit confirmation and a best-effort security email for real wallets. -
2026-08-14: Added the operator-allowlisted, Elite, read-only, MCP-only
pt_copy_replication_aggregates_getinternal evidence tool over the service-role Copy Receipt V1 aggregate RPC. It requires one exact whale, fixes the RPC limit at one, and adds index-backed lookup plus a caller-side five-second PostgREST request deadline. Database-owned 20/40-condition confidence floors and below-floor null metrics are unchanged; ordinary customer keys fail closed and no REST or browser surface was added. -
2026-08-18: Paper and dedicated real-copy wallets now expose
fixedStakeUsd. Zero preserves conviction/percentage sizing; a positive value of at least $1 requests the same USD amount for every copied BUY and bypasses the copy multiplier. Kelly, position, max-bet, balance, authorization, breaker, reservation, and venue gates remain authoritative. The dashboard plus both guarded MCP config writers can read, preview, and update the wallet-scoped field; existing wallets default to off. -
2026-09-07: Paper and real-copy risk config now exposes default-on
nettingProtectionandopposingSideProtectiontoggles. Owners may opt out per wallet. Disabled netting activatesmaxConditionExposureUsd, which caps total open BUY cost across both outcomes plus the proposed copy, defaults to three timesmaxPerTrade, stays at or above that per-trade limit, and is bounded to $10,000,000. Real protection opt-outs remain atomically audited and emitcopy_protection_disabled. -
2026-08-11: Added the free read-only MCP tool
pt_whale_forward_returns_getfor bounded forward-return counterfactuals across arbitrary wallet cohorts. It preserves one row per request entry, enforces exact decision/cutoff windows, and exposes unresolved, truncation, fee, resolution-source, and vendor-call-budget evidence. -
2026-08-10: Dedicated real-copy wallets now persist a guarded 0–10%
realCopySlippageTolerance, exposed through the real-wallet MCP config tools. Where the venue ceiling permits, BUY FAK caps retain at least one market tick of headroom above the observed ask, and deterministic signed-cap rejections are no longer replayed through Goldsky promotion recovery. -
2026-08-10: Recommendation and digest-suggestion rows now expose display-name source/time plus Polymarket's stable
pseudonym. Legacy aliases beginning with a wallet address no longer override the current leaderboard name, and compact/byte-capped/cursor-replayed shapes retain the provenance. -
2026-08-07: Rollout-eligible dedicated real-copy wallets can now set a 1–2x owner-only sizing multiplier through the authenticated dashboard. The server gate fails closed to 1x, all existing caps remain authoritative, and MCP real-wallet writes continue to exclude the field.
-
2026-08-10: Real-wallet dashboard and Elite MCP config surfaces now read, preview, and write authorization-scoped
minPerTrade. Null/0 disables the floor; positive values are bounded to $1 through the signed copy-trading authorization'smaxPerTrade, restore only percentage-reduced dust, and never exceed the strategy-sized cost. The floor applies only whenfixedStakeUsdis off and is explicitly distinct from risk-rulemaxPerTradeandminTradeSize. Superseded on 2026-10-14: the floor now applies after fixed or percentage sizing and can exceed the strategy-sized cost whensmallOrderPolicyisscale-up(see that entry above). -
2026-08-07: Session-authenticated
PUT /api/whale/confignow routes real-wallet risk changes through the atomic config-plus-audit RPC and sends the owner security email. Real-wallet requests rejectexecutionMode; paper-wallet behavior is unchanged. -
2026-08-07: Whale-edge loads are now batch-independent under deterministic per-wallet caps.
coverageTruncatedis wallet-specific, SPLIT/non-directional evidence is exposed throughnonDirectionalSampleCount*, androi_provenanceincludes the capital used by each ROI window. -
2026-08-07: Leaderboard, recommendation, and digest-suggestion rows now share canonical ROI/sample projections and a
roi_provenanceblock across full, compact, and replayed snapshot shapes. BaresampleCountmeans the directional edge-row count; recommendation admission evidence is separately named. Explicit edge-load fallback uses labeled 365-day receipts-only counts with null ROI. -
2026-08-04:
pt_recommendations_getnow returns snapshot-pinned opaque continuation cursors plus explicit total/returned/has-more counts. Continue with the same projection and settled-sample filter when the byte cap trims a page;min_settled_samplesnarrows the population and is not continuation. -
2026-08-03: Added Elite
agent:fullpt_real_wallet_copy_config_get/pt_real_wallet_copy_config_updateandpt_real_wallet_breaker_resume/pt_real_wallet_breaker_kill. Every real-money preview or write requiresconfirm_real_money:true; live changes append an audit event and send a best-effort security email. The strict config writer excludesexecutionModeandcopySizingMultiplier. -
2026-07-31: Added Elite
agent:fullpt_mock_wallet_copy_config_get/pt_mock_wallet_copy_config_updatefor full caller-owned paper-wallet risk reads and guarded dry-run/live updates. The strict write schema excludesexecutionMode, real-wallet rows are unreachable, and zero-value warnings are returned with the preview. -
2026-07-26: Added
GET /api/trade/money-summary(user-scoped real-money totals/counts aggregate; session oragent:full, same auth semantics asGET /api/trade/ledger). -
2026-07-21: Added the paper-only wallet copy-sizing multiplier to the wallet risk-config contract and
pt_mock_wallet_copy_sizing_update; the MCP catalog baseline was then 65 tools and real-copy sizing was 1x. The 2026-08-07 owner-dashboard 1–2x rollout supersedes that historical behavior. -
2026-07-20: Published the version/deprecation policy and recorded the 2.0.0 OpenAPI contract plus the then-current 64-tool MCP catalog baseline.
Reliability notes
- Handle
429on all rate-limited endpoints and respectRetry-After. - Treat trade execution as idempotent when you provide
idempotencyKey(idempotent replays return200withidempotencyReused: true). POST /api/keys/generatereturns the raw key once — store it securely. Each account keeps one active Agent API Key; rotate by revoking the old key, then generating a replacement and updating clients/agents./api/anomalies/{id}may return404when a record falls outside your tier window.TIER_UPGRADE_REQUIREDresponses includerequiredTier,currentTier, andupgradeUrl— surface these to the user/agent verbatim.
Historical sports durability compares trade time with event timing, independently
of query age. Missing/conflicting timing stays unknown. Discovery actionability
recent_activity.durability_evidence and digest wallet
durability_profile.durability_evidence retain timing-basis counts,
known-notional coverage and shares, majority basis, and the capped loaded window
through compact and cursor responses. Per-trade activity_profile retains timing
basis and uncertainty. See historical flow durability for the 24-hour
horizon, conservative slug calendar envelope, 90% notional-coverage rule,
1,000-row disclosure, and denominator semantics. No new execution veto is added.
Leaderboard backtest population and market-specific discovery
backtestable_only vets usable coverage within the returned Polymarket
leaderboard population. It narrows discovery; missing or unavailable coverage
is not a negative profitability or category verdict. A new or untracked wallet
can lack evidence even when it has useful trades. This filter is separate from
the canonical capital-accounting backtest admission gate.
Resolved-position scope enrolls the top 50 leaderboard wallets selected by
whale edge plus active wallet_whale_assignments roster wallets. Ingestion
requires copyable classification; high-fill-rate market makers are excluded
from that lane. Bounded Data API backfills add historical fills for scoped
wallets, and coverage/backtests union those positions with legacy
resolved_receipts joined by anomaly_logs.wallet, deduplicating conditions.
Missing resolved-position coverage retains receipt-based diagnostics; inspect
per-row backtestCoverage, roi_provenance, truncation and fallback fields.
Enrollment alone guarantees neither complete history nor current freshness.
REST /api/leaderboard and MCP pt_leaderboard_get return the explanation in
omission_metadata.backtestable_scope with either filter setting and in full,
compact and continued responses. Existing omission counts and continuation
semantics remain unchanged. Unknown coverage does not become proof of poor
performance or an absence of durable-category traders.
For market-specific discovery, obtain a condition id with pt_markets_search,
then call Pro+ pt_market_trades_get to find recent public trading wallets.
That tool reads at most 100 source rows, has no cursor, and reports omissions;
it does not enroll wallets or create backtest evidence. Follow candidates with
pt_trader_profile_get for coverage and pt_whale_activity_get for
ingestion/timing evidence, then pt_backtest_run for modeled copy-cost
validation. One market sample guarantees neither durable flow nor profitable
whales, and a passing backtest remains screening evidence rather than proof of
live copy profitability.
Roster session POST/PATCH/DELETE requests enforce the configured site origin
and reject cross-site browser writes; adding an arbitrary bearer header cannot
bypass this check. Verified whale-service secrets keep their programmatic
access, and scoped Agent API Keys keep the MCP roster tools. REST roster-add
name accepts a string of at most 100 UTF-16 code units before normal display
name sanitization, matching MCP's length contract. Unexpected roster database
errors return fixed operation-specific messages while server logs retain the
underlying failure.
Session writes to PATCH /api/whale/copy-pause, PUT /api/whale/config, and
POST/PATCH /api/trade/copy-trading enforce the configured site origin
before authentication, body parsing, or mutation. Cross-site fetch metadata
or a mismatching Origin returns HTTP 403 with
{ "error": "Cross-origin requests are not allowed." }. Adding a bearer header
does not bypass the origin guard. Headerless clients retain existing
authentication requirements, and scoped Agent API Key MCP tools are unchanged.
Staged Data API v2 bet sync adds optional historyComplete to
POST /api/bets/sync and its bets_sync_trigger MCP projection. false means
successful sync of a bounded recent sample, not complete wallet history. A
failed source is an error, not an empty/up-to-date result. Selection uses the
existing server-only history rollout cohort; this contract does not activate it.