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"
}
- ↳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.
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 public APIs. Free-text mode lets you pass an arbitrary query + AND-group keyword sets. Returns exclusiveToForesight: true when neither 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": [{ "title": "string", "url": "string", "yesProbability": 0.18, "volumeUsd": 0 }],
"kalshi": [{ "title": "string", "url": "string", "yesProbability": 0.22 }],
"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.
- ↳exclusiveToForesight=true is positive social proof — it means the topic is on our venue and no global aggregator lists it.
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
Public read of the market proposal queue.
Mirrors the UI at /admin/proposals — single source of truth, no second list. Phase 0 queue is intentionally empty; submit via the foresight_propose_market MCP tool. 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-only. Submit via the MCP tool, not via POST here. The review panel for approvals lands in Phase 2.
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.