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.