Foresight

Developer reference · API + MCP

Build forecasting into your agent.

Eight MCP tools, a full HTTP surface, zero auth in Phase 0. Every endpoint mirrors the same data the web UI renders — no second source of truth to keep in sync. Copy any command below and run it locally.

01 · Quickstart

Pick your surface.

AI agents speak MCP — one install gives them tool-calling access to the marketplace. Server-side code or bash scripts read the HTTP endpoints directly. Same data either way.

Use from any MCP-aware agent

Claude Desktop · Claude Code · custom SDK clients

# Add to your agent's MCP config (e.g., claude_desktop_config.json)
{
  "mcpServers": {
    "foresight": {
      "command": "npx",
      "args": ["-y", "foresight-mcp@latest"]
    }
  }
}

Use from any HTTP client

curl · fetch · requests · server-to-server

curl https://foresight.cuvetsmo.com/api/markets \
  -H "Accept: application/json" | jq '.markets[0]'

02 · MCP server

Eight tools, one server.

Standalone Node package. Reads from the public /api/markets endpoint with a 5-minute in-process cache, falls back to a bundled seed if the network is down. Override the base URL with FORESIGHT_API_BASE.

View on GitHub

Install

# Add to your MCP-aware agent's config
{
  "mcpServers": {
    "foresight": {
      "command": "npx",
      "args": ["-y", "foresight-mcp@latest"]
    }
  }
}

# Then in your conversation:
> List the top 5 Thai politics markets closing this quarter.
> Dry-run the resolver on 'anthropic-1m-ctx-2027-q1' as of next March.

foresight_list_markets

Browse the live forecasting marketplace.

tool

Filter by category and status, sort by volume, open interest, closing date, or newest. Read-only, free. The agent uses this to discover what is listable before any other tool.

Input

{
  "category"?: "thai-politics" | "thai-climate" | "thai-vet" | "sea-elections" |
              "crypto" | "global-tech" | "global-sports" | "ai-research",
  "status"?: "open" | "closing-soon" | "resolved",
  "sortBy"?: "volume" | "open-interest" | "closing-soonest" | "newest",  // default volume
  "limit"?: number  // 1-50, default 10
}

Output

{
  "count": 10,
  "totalAvailable": 17,
  "filters": { "category": "...", "status": "...", "sortBy": "..." },
  "source": "live" | "bundled-seed",
  "markets": [/* market summary objects with id, slug, question, probabilities, url */]
}

foresight_get_market

Full detail for one market.

tool

Pass an id or slug — either works. Returns probabilities, volume, open interest, deadline countdown, resolution criterion, named primary sources, tags, and the canonical web URL.

Input

{
  "identifier": "string"  // id like 'th-elec-2027' or slug
}

Output

{
  "id": "string",
  "slug": "string",
  "question": "string",
  "category": "...",
  "status": "open | closing-soon | resolved",
  "probabilities": { "yes": 0.42, "no": 0.58 },
  "volume": { "usd": 0, "openInterestUsd": 0 },
  "timing": { "closesAt": "ISO", "daysLeft": 240 },
  "resolution": { "criteria": "string", "sources": ["url"] },
  "tags": ["..."],
  "url": "https://foresight.cuvetsmo.com/markets/<slug>"
}

foresight_propose_market

Submit a new market proposal to the review queue.

tool

Any MCP-aware agent can propose. Proposals land in the public review queue at /admin/proposals — they are not auto-listed. Iron-Rule-0 schema: requires a machine-verifiable resolution criterion plus at least one named primary source URL.

Input

{
  "question": "string  // 10-280 chars, yes/no phrasing",
  "questionEn"?: "string  // optional English translation",
  "category": "thai-politics" | "thai-climate" | "thai-vet" |
              "sea-elections" | "crypto" | "global-tech" |
              "global-sports" | "ai-research",
  "closesAt": "ISO datetime  // must be in the future",
  "resolutionCriteria": "string  // 40-1000 chars, machine-verifiable",
  "resolutionSources": ["url"],  // 1-5 named primary sources
  "tags"?: ["string"]  // 0-8 discovery tags, ≤40 chars each
}

Output

{
  "status": "pending_review",
  "draftId": "draft-<timestamp>-<rand>",
  "proposal": { /* the validated input echoed back */ },
  "preview": {
    "url": "https://foresight.cuvetsmo.com/propose?draft=<id>",
    "reviewSlaHours": 48
  },
  "message": "string  // human-readable acceptance"
}

foresight_resolve_check

Dry-run the multi-source verifier on a market.

tool

Identical resolver as POST /api/resolve, but scoped to a known market. Read-only — never mutates state. Useful for agents that want to preview a resolution before proposing an appeal.

Input

{
  "identifier": "string",  // market id or slug
  "asOf"?: "ISO datetime"  // simulate 'resolve as of' — defaults to now
}

Output

{
  "market": { "id": "string", "slug": "string", "question": "string" },
  "asOf": "ISO",
  "status": "verifiable | pending | ambiguous | refused",
  "proposedOutcome"?: "yes | no",
  "confidence"?: 0.92,
  "reasoning": "string",
  "citedSources": [{ "source": "url", "checked": true, "note": "string" }],
  "appealAvailable": true
}

foresight_stream_events

Subscribe to market lifecycle events.

tool

Returns the recent event tail for one market or for all markets matching a filter. Phase 0 returns a synthetic tail; Phase 1 wires to Supabase realtime. Use for live dashboards, calibration trackers, or alert bots.

Input

{
  "market"?: "string",  // id or slug — omit for global stream
  "since"?: "ISO datetime"  // cursor — pass back the previous response's cursor
}

Output

{
  "events": [
    {
      "cursor": "ISO",  // use as 'since' on next call
      "type": "price-tick | trade | market-open | market-close | resolution",
      "marketId": "string",
      "marketSlug": "string",
      "marketUrl": "https://foresight.cuvetsmo.com/markets/<slug>",
      "data": { /* event-specific payload */ },
      "ts": "ISO"
    }
  ],
  "count": 4,
  "cursor": "ISO",  // next-page cursor (last event's ts)
  "pollIntervalSec": 30,
  "note": "string"
}

foresight_cross_venue

Compare Polymarket, Kalshi, and Manifold pricing.

tool

Look up how the major venues price a question — by Foresight market id/slug (auto-derives search terms) or by free-text query. Returns each venue's matching markets with YES probability, volume, liquidity. When all three return nothing, exclusiveToForesight is true: the topic is priced here and nowhere else — the niche the giants overlook. Read-only; data public + cached 1h. (v0.3.0+)

Input

{
  "identifier"?: "string",  // market id or slug — auto-derives terms
  "query"?: "string",       // free-text (when identifier omitted)
  "terms"?: ["string"]      // optional AND-group keyword sets, e.g. ["bank,thailand","rate,cut"]
}

Output

{
  "query": "string",
  "exclusiveToForesight": false,
  "totalMatches": 3,
  "venues": {
    "polymarket": [{ "question": "...", "yesProbability": 0.18, "volumeUsd": 0, "url": "..." }],
    "kalshi": [/* same shape */],
    "manifold": [/* same shape */]
  },
  "interpretation": "string  // plain-English read of the result",
  "fetchedMs": 350,
  "attribution": "string"
}

foresight_wikidata_entity

Ground a named entity to a stable Wikidata Q-id.

tool

Resolve a name (person, org, place, party, event) to a stable Wikidata Q-id + label + description. Lets a resolution criterion cite a Q-id so the verifier and appeal panel can't disagree on WHICH entity is meant — stable IDs survive renames and translations. Wikidata is CC0. Public, no auth. (v0.4.0+)

Input

{
  "query": "string",  // e.g. "Pheu Thai Party", "Prabowo Subianto", "Chiang Mai"
  "language"?: "string",  // "en" or "th" most useful, default "en"
  "limit"?: number  // 1-10, default 5
}

Output

{
  "query": "string",
  "language": "en",
  "count": 3,
  "entities": [{
    "qid": "Q116758847",
    "label": "Anthropic",
    "description": "American artificial intelligence corporation",
    "url": "https://www.wikidata.org/wiki/Q116758847",
    "conceptUri": "http://www.wikidata.org/entity/Q116758847"
  }],
  "note": "string", "license": "Wikidata content is CC0 (public domain)."
}

03 · HTTP endpoints

The full REST surface, no API key.

All endpoints are public in Phase 0. No signup, no rate limit gating — fair-use only. CORS open on read endpoints; write endpoints accept JSON only.

GET

/api/markets

List every market — single source of truth.

Returns the full market catalogue in the same shape rendered by the web UI. The MCP server, downstream agents, and external tools all read from here. Headers: max-age=30, s-maxage=60, stale-while-revalidate=300. CORS open.

Example

curl https://foresight.cuvetsmo.com/api/markets | jq '.markets[0]'

Response shape

{
  "version": 1,
  "count": 17,
  "generatedAt": "2026-05-27T12:00:00.000Z",
  "markets": [
    {
      "id": "string",
      "slug": "string",
      "question": "string",
      "questionEn": "string?",
      "category": "thai-politics | thai-climate | thai-vet | sea-elections | crypto | global-tech | global-sports | ai-research",
      "status": "open | closing-soon | resolved",
      "yesProbability": 0.42,
      "volumeUsd": 0,
      "openInterestUsd": 0,
      "closesAt": "2027-01-01T00:00:00.000Z",
      "resolutionCriteria": "string",
      "resolutionSources": ["string"],
      "tags": ["string"],
      "isSample": true
    }
  ]
}
  • Phase 0 markets all carry isSample=true — they are curated demonstrations of the resolution-criteria pattern, not live order-book contracts.
  • Volume + openInterest are zero by design until real trading turns on. Do not interpret them as activity signals.
POST

/api/resolve

Dry-run the multi-source verifier.

Two input modes. Pass an existing market identifier to resolve a known market, or pass an ad-hoc question + criterion + sources for any forecast. Always returns the same ResolveResult shape. Status of pending, ambiguous, or refused is a feature — the verifier will say it doesn't know rather than fabricate.

Example

curl -X POST https://foresight.cuvetsmo.com/api/resolve \
  -H "Content-Type: application/json" \
  -d '{
    "question": "Will the Bank of Thailand cut the policy rate at the December 2026 MPC meeting?",
    "resolutionCriteria": "YES if the BoT MPC December 2026 statement records a policy rate decrease vs the previous meeting. NO otherwise.",
    "resolutionSources": ["https://www.bot.or.th/en/our-roles/monetary-policy/mpc-meeting.html"],
    "closesAt": "2026-12-31T23:59:59Z"
  }'

Response shape

{
  "market": { "identifier": "string" } | null,
  "asOf": "ISO",
  "status": "verifiable | pending | ambiguous | refused",
  "proposedOutcome"?: "yes | no | void",
  "confidence"?: 0.92,
  "reasoning": "string",
  "citedSources": [{ "source": "url", "checked": true, "note": "string" }],
  "appealAvailable": true,
  "providerUsed"?: "groq | cerebras | sambanova | openrouter | mistral",
  "refusalReason"?: "string",
  "crossVenueReference"?: { /* known-market only — what other venues price this at */ }
}
  • Confidence below 0.85 auto-downgrades status to ambiguous. The verifier never commits a low-confidence outcome.
  • 5-provider fallback chain (Groq → Cerebras → SambaNova → OpenRouter → Mistral). When none are configured the route returns status=pending with a transparent note; providerUsed is omitted in that case.
  • 12s abort timeout across the chain. proposedOutcome can also be 'void' when the resolver determines the market is unresolvable as-stated.
  • Identifier mode attaches crossVenueReference (Polymarket/Kalshi/Manifold prices for the same question) as a sanity signal — it does NOT influence the verdict; the criterion + named sources decide.
GET

/api/resolve/status

Probe which verifier providers are wired.

Returns which LLM providers are configured WITHOUT leaking key values, prefixes, or any partial form. Used by the /resolver page footer to show live vs Phase 0 fallback honestly.

Example

curl https://foresight.cuvetsmo.com/api/resolve/status

Response shape

{
  "mode": "live | fallback",
  "providersConfigured": ["groq"],
  "providersAvailable": ["groq", "cerebras", "sambanova", "openrouter", "mistral"],
  "note": "string"
}
GET

/api/cross-venue

Find matching markets on Polymarket and Kalshi.

By-slug mode resolves a Foresight market then pulls term-derived matches from Polymarket Gamma + Kalshi + Manifold Markets public APIs. Free-text mode lets you pass an arbitrary query + AND-group keyword sets. Returns exclusiveToForesight: true when no venue carries the topic — that is a feature, not a bug.

Example

# by Foresight slug
curl 'https://foresight.cuvetsmo.com/api/cross-venue?slug=anthropic-1m-ctx-2027-q1'

# free-text + AND-groups
curl 'https://foresight.cuvetsmo.com/api/cross-venue?q=BoT%20rate%20cut&terms=bank+thailand&terms=rate+cut'

Response shape

{
  "query": "string",
  "polymarket": [{ "question": "string", "url": "string", "yesProbability": 0.18, "volumeUsd": 0 }],
  "kalshi": [{ "question": "string", "url": "string", "yesProbability": 0.22 }],
  "manifold": [{ "question": "string", "url": "string", "yesProbability": 0.63, "volumeUsd": 53 }],
  "exclusiveToForesight": false,
  "fetchedMs": 412,
  "note": "string?"
}
  • Cached at the route level for 1h. Polymarket pagination walks offset 0/100/200; Kalshi handles their 404-with-body quirk; Manifold uses per-query search-markets (higher recall for SEA/AI/climate/vet).
  • exclusiveToForesight=true is positive social proof — it means the topic is on our venue and no global aggregator lists it.
  • Manifold is MIT-licensed; Polymarket + Kalshi are public APIs without official partnership.
POST

/api/waitlist

Capture trading-intent for a market.

Records a measurable demand signal — email, optional market, side, and intended size. Phase 0 sink is stdout plus an audit-log row when Supabase service-role is wired. Phase 1 swaps to a dedicated waitlist table.

Example

curl -X POST https://foresight.cuvetsmo.com/api/waitlist \
  -H "Content-Type: application/json" \
  -d '{
    "email": "trader@example.com",
    "marketId": "anthropic-1m-ctx-2027-q1",
    "side": "yes",
    "sizeUsd": 50,
    "source": "docs"
  }'

Response shape

{ "status": "ok", "saved": true }
  • Email is the only required field. Side, marketId, sizeUsd, and source are optional context.
  • No confirmation email yet — Phase 1 adds a double-opt-in flow.
GET

/api/health

Build identity + deployment URL.

Linked from the footer Status link. Phase 1 will extend to report Supabase reachability, MCP server status, and the deploy commit SHA.

Example

curl https://foresight.cuvetsmo.com/api/health

Response shape

{
  "name": "Foresight",
  "status": "ok",
  "phase": "0",
  "baseUrl": "https://foresight.cuvetsmo.com",
  "educationalBeta": true,
  "timestamp": "ISO"
}
GET

/api/proposals

Read the market proposal queue.

Mirrors the UI at /admin/proposals — single source of truth, no second list. Phase 0 queue is intentionally empty. Approve/reject actions are NOT exposed here (auth-gated, Phase 2).

Example

curl https://foresight.cuvetsmo.com/api/proposals | jq '.byStatus'

Response shape

{
  "version": 1,
  "count": 0,
  "byStatus": { "pending": 0, "revisionsRequested": 0, "approved": 0, "rejected": 0 },
  "generatedAt": "ISO",
  "queueUrl": "/admin/proposals",
  "submitVia": { "mcpTool": "foresight_propose_market", "mcpPackage": "foresight-mcp" },
  "reviewSlaHours": 48,
  "proposals": [/* MarketProposal[] */]
}
  • Empty array is the honest empty-state — no fabricated proposals to look busy.
  • Read endpoint. To submit, POST to this same path (below) or use the foresight_propose_market MCP tool — same Iron Rule 0 contract.
POST

/api/proposals

Submit a market proposal (web form + agent share this).

Browser equivalent of foresight_propose_market — the /propose page posts here. Identical Iron Rule 0 contract: 10-280 char question, machine-verifiable criterion (40-1000 chars) with a temporal anchor, 1-5 named sources, future close, no distress markets. Lands in the public queue — never auto-listed.

Example

curl -X POST https://foresight.cuvetsmo.com/api/proposals \
  -H "Content-Type: application/json" \
  -d '{
    "question": "Will the BoT cut its policy rate at the December 2026 MPC meeting?",
    "category": "thai-politics",
    "closesAt": "2026-12-31T23:59:59Z",
    "resolutionCriteria": "Resolves YES if the BoT MPC December 2026 statement records a policy rate decrease vs the previous meeting, per bot.or.th.",
    "resolutionSources": ["https://www.bot.or.th/"]
  }'

Response shape

{
  "status": "pending_review",
  "draftId": "draft-<ts>-<rand>",
  "queueUrl": "/admin/proposals",
  "reviewSlaHours": 48,
  "message": "string"
}
  • 400 = malformed field (length/date/email). 422 = failed Iron Rule 0 (distress market, no temporal anchor, or past close).
  • Same validation as the MCP tool, so the web form and agents can't propose anything the other couldn't.
POST

/api/appeal

Appeal a resolver dry-run result.

Closes the loop on appealAvailable:true. The verifier may refuse or be wrong; this logs a human-reviewable appeal. Never auto-changes the result. The appeal panel under any /resolver result posts here.

Example

curl -X POST https://foresight.cuvetsmo.com/api/appeal \
  -H "Content-Type: application/json" \
  -d '{
    "question": "Will X happen by Y?",
    "disputedStatus": "ambiguous",
    "reasoning": "The criterion was clearly met on 2027-03-02 — see the official source.",
    "evidenceUrl": "https://example.gov/official-notice"
  }'

Response shape

{
  "status": "received",
  "queued": true,
  "message": "string",
  "reviewSlaHours": 72
}
  • question + reasoning (≥10 chars) are required; identifier, evidenceUrl, email optional.
  • An appeal is logged, never auto-accepted — a moderator reviews the criterion + evidence against the named sources.
GET

/api/openapi.json

OpenAPI 3.1 machine-readable spec.

The whole API in one JSON document. Drop into Postman / Insomnia / Bruno. Generate TypeScript clients via openapi-typescript. Wire into Stainless or Speakeasy for SDK pipelines. 1-hour edge cache.

Example

curl https://foresight.cuvetsmo.com/api/openapi.json | jq '.info'

# Generate TS client
npx openapi-typescript https://foresight.cuvetsmo.com/api/openapi.json -o foresight.d.ts

Response shape

{
  "openapi": "3.1.0",
  "info": { "title": "Foresight Public API", "version": "0.1.0", ... },
  "paths": { /* 8 endpoints */ },
  "components": { "schemas": { /* 12 reusable shapes */ } }
}
  • The OpenAPI version field bumps on every breaking change — track it to catch contract drift in your client.
  • Self-referential — the spec describes the 8 functional endpoints but omits itself (a meta-endpoint about the API).
GET

/api/stats

One-shot aggregate stats — for dashboards.

Market counts (by category + status + sample-vs-real), verifier mode, MCP version. Public-only — never includes per-user state. Powers external status pages without scraping the UI.

Example

curl https://foresight.cuvetsmo.com/api/stats | jq '{ total: .markets.total, verifier: .verifier.mode }'

Response shape

{
  "name": "Foresight",
  "phase": "0",
  "generatedAt": "ISO",
  "markets": {
    "total": 17,
    "sample": 17,
    "real": 0,
    "byStatus": { "open": 12, "closingSoon": 3, "resolved": 2 },
    "byCategory": [{ "key": "thai-politics", "label": "Thai Politics", "count": 3 }]
  },
  "verifier": { "mode": "live | fallback", "providersConfigured": [...] },
  "mcp": { "package": "foresight-mcp", "sourceVersion": "0.2.0", "npmPublished": false, "tools": [...] },
  "docs": { "openapi": "/api/openapi.json", "developerPage": "/docs", "changelog": "/changelog" }
}
  • Caches at the edge — same headers as /api/markets. Safe to poll once a minute from a dashboard.
  • npmPublished flips to true once the foresight-mcp package lands on the npm registry.

04 · Rate limits + status

Fair-use, transparent state.

Phase 0 limits

No quota enforcement. Edge cache absorbs reads. Don't hammer the resolver in a tight loop — it costs real LLM tokens once providers are wired.

Auth

None for reads. Writes (waitlist, future proposals) accept plain JSON. Phase 1 adds API keys + per-key budgets for the resolver.

Found a bug? Want a new endpoint?

Public review queue at /admin/proposals — submit a market proposal through foresight_propose_market and it lands there. For DX feedback, open an issue against the MCP server repo.