{"openapi": "3.0.3", "info": {"title": "POLYCRISIS.WORLD API", "description": "Global crisis intelligence platform API. Real-time geopolitical event tracking, scenario assessment, and cross-crisis analysis.", "version": "1.0.0", "contact": {"email": "admin@polycrisis.world"}, "termsOfService": "https://polycrisis.world/terms"}, "servers": [{"url": "https://polycrisis.world/api/v1", "description": "Production"}, {"url": "http://localhost:8084/api/v1", "description": "Local development"}], "security": [{"BearerAuth": []}], "components": {"securitySchemes": {"BearerAuth": {"type": "http", "scheme": "bearer", "description": "API key obtained from your account dashboard. Pass as `Authorization: Bearer <key>` or `?key=<key>` query param."}}, "schemas": {"Event": {"type": "object", "properties": {"id": {"type": "string", "example": "evt_12345"}, "crisis": {"type": "string", "example": "iran"}, "title": {"type": "string", "example": "IRGC naval exercise in Strait of Hormuz"}, "event_type": {"type": "string", "enum": ["military", "naval", "nuclear", "political", "protest", "humanitarian", "enforcement", "climate", "cyber", "news"]}, "lat": {"type": "number", "example": 26.5}, "lng": {"type": "number", "example": 56.2}, "timestamp": {"type": "string", "format": "date-time"}, "description": {"type": "string"}, "source_url": {"type": "string", "format": "uri"}, "source": {"type": "string", "enum": ["static", "ai"]}, "confidence": {"type": "number", "minimum": 0, "maximum": 1, "example": 0.85}}}, "Crisis": {"type": "object", "properties": {"id": {"type": "string"}, "name": {"type": "string"}, "emoji": {"type": "string"}, "color": {"type": "string"}, "event_count": {"type": "integer"}, "latest_event": {"type": "string", "format": "date-time"}, "intensity": {"type": "number"}, "center": {"type": "object", "properties": {"lat": {"type": "number"}, "lng": {"type": "number"}}}, "bounds": {"type": "object", "nullable": true, "properties": {"lat_min": {"type": "number"}, "lat_max": {"type": "number"}, "lng_min": {"type": "number"}, "lng_max": {"type": "number"}}}}}, "Scenario": {"type": "object", "properties": {"id": {"type": "string"}, "title": {"type": "string"}, "description": {"type": "string"}, "probability": {"type": "integer", "minimum": 0, "maximum": 100}, "color": {"type": "string"}, "band": {"type": "string", "enum": ["negligible", "unlikely", "plausible", "likely", "imminent", "confirmed"]}, "justification": {"type": "string"}, "source_url": {"type": "string"}, "assessed_at": {"type": "string", "format": "date-time"}, "previous_probability": {"type": "integer", "description": "Probability from the prior assessment (if available)"}, "probability_delta": {"type": "integer", "description": "Change since prior assessment (current - previous)"}}}, "Error": {"type": "object", "properties": {"error": {"type": "string"}}}, "PaginationMeta": {"type": "object", "properties": {"total": {"type": "integer", "description": "Total matching events (before pagination)"}, "returned": {"type": "integer", "description": "Events in this response"}, "offset": {"type": "integer"}, "limit": {"type": "integer"}}}}, "parameters": {"crisis": {"name": "crisis", "in": "query", "schema": {"type": "string", "enum": ["iran", "ukraine", "climate", "usa", "agi", "gaza", "scs"]}, "description": "Filter by crisis theater"}}, "headers": {"X-RateLimit-Limit": {"description": "Daily request limit for your plan", "schema": {"type": "integer"}}, "X-RateLimit-Remaining": {"description": "Remaining requests today", "schema": {"type": "integer"}}, "X-RateLimit-Reset": {"description": "When the rate limit resets", "schema": {"type": "string", "example": "midnight UTC"}}}}, "paths": {"/account": {"get": {"summary": "Account overview", "description": "Returns the authenticated user's account: callsign, email, current plan, and quota usage. Requires Supabase JWT, not an API key.", "operationId": "getAccount", "tags": ["Account"], "responses": {"200": {"description": "Account overview", "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}}}, "patch": {"summary": "Update account", "description": "Update callsign and/or emoji avatar.", "operationId": "updateAccount", "tags": ["Account"], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"callsign": {"type": "string"}, "emoji": {"type": "string"}}}}}}, "responses": {"200": {"description": "Updated"}, "401": {"description": "Unauthorized"}}}}, "/account/invoices": {"get": {"summary": "Recent invoices", "description": "Returns the last 12 Stripe invoices for the authenticated account: date, amount, status, hosted invoice URL.", "operationId": "listAccountInvoices", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "tags": ["Account"]}}, "/account/keys": {"get": {"summary": "List API keys", "operationId": "listApiKeys", "tags": ["Account"], "responses": {"200": {"description": "Keys list"}, "401": {"description": "Unauthorized"}}}, "post": {"summary": "Create API key", "operationId": "createApiKey", "tags": ["Account"], "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string"}}}}}}, "responses": {"201": {"description": "Key created \u2014 full key string returned ONCE in the response, never retrievable again"}, "401": {"description": "Unauthorized"}}}}, "/account/usage": {"get": {"summary": "Account usage stats", "description": "Current period quota usage: calls today, calls this month, overage, credits used. Requires JWT.", "operationId": "getAccountUsage", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "tags": ["Account"]}}, "/account/usage/history": {"get": {"summary": "30-day usage history", "description": "Returns per-day API call counts for the last 30 days, used by the Account \u2192 API \u2192 30-Day History chart.", "operationId": "getAccountUsageHistory", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "tags": ["Account"]}}, "/actors": {"get": {"summary": "Key actors across crises", "description": "Returns actors (entities, organizations, states) extracted from events, with activity trends and event type breakdown.", "operationId": "listActors", "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 20, "minimum": 1, "maximum": 50}, "description": "Maximum number of actors to return"}], "responses": {"200": {"description": "List of actors with activity metrics", "content": {"application/json": {"schema": {"type": "object", "properties": {"actors": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string"}, "crises": {"type": "array", "items": {"type": "string"}}, "event_count_7d": {"type": "integer"}, "event_count_30d": {"type": "integer"}, "trend": {"type": "string", "enum": ["rising", "falling", "stable"]}, "last_seen": {"type": "string", "format": "date-time", "nullable": true}, "primary_event_types": {"type": "array", "items": {"type": "string"}}}}}, "meta": {"type": "object", "properties": {"total": {"type": "integer"}}}, "generated_at": {"type": "string", "format": "date-time"}}}}}}, "401": {"description": "Unauthorized"}}}}, "/actors/{name}": {"get": {"summary": "Actor detail", "description": "Returns detailed information about a specific actor including recent events and crisis involvement.", "operationId": "getActor", "parameters": [{"name": "name", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Actor name (URL-encoded)"}], "responses": {"200": {"description": "Actor details with recent events"}, "404": {"description": "Actor not found"}}}}, "/analogs": {"get": {"summary": "Find historical analogs", "description": "Returns ranked historical periods most similar to the current state of a crisis, with what-followed market reactions. Powers the Markets analog ghost-line forecast.", "operationId": "listAnalogs", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 10, "minimum": 1, "maximum": 50}}], "tags": ["Discovery"]}}, "/anomalies": {"get": {"summary": "Detect statistical anomalies", "description": "Returns events flagged as statistically unusual based on event-volume z-score against the trailing 30-day baseline per crisis/type.", "operationId": "listAnomalies", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "type", "in": "query", "schema": {"type": "string"}}, {"name": "min_z", "in": "query", "schema": {"type": "number", "default": 2.0}}], "tags": ["Discovery"]}}, "/briefing": {"get": {"summary": "AI intelligence briefing", "description": "AI-generated intelligence briefing for a crisis theater.", "operationId": "getBriefing", "parameters": [{"name": "crisis", "in": "query", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "Briefing text"}}}}, "/connections": {"get": {"summary": "Cross-crisis connections", "operationId": "getConnections", "parameters": [{"$ref": "#/components/parameters/crisis"}], "responses": {"200": {"description": "Crisis connection graph"}}}}, "/correlations": {"get": {"summary": "Market correlations", "description": "Correlation analysis between crisis events and financial markets. Includes a 'reliable' boolean flag (true when p<=0.05 and n>=30).", "operationId": "getCorrelations", "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "market", "in": "query", "schema": {"type": "string", "default": "CL=F"}, "description": "Market symbol (e.g., CL=F for oil, ^GSPC for S&P 500)"}, {"name": "window", "in": "query", "schema": {"type": "string", "default": "30d"}, "description": "Time window (e.g., 7d, 14d, 30d, 90d)"}], "responses": {"200": {"description": "Correlation data with coefficient, p_value, interpretation, sample_size, avg_market_move, and reliable flag"}}}}, "/crises": {"get": {"summary": "List all crisis theaters", "description": "Returns metadata for all monitored crisis theaters including event counts and intensity.", "operationId": "listCrises", "responses": {"200": {"description": "List of crises", "content": {"application/json": {"schema": {"type": "object", "properties": {"crises": {"type": "array", "items": {"$ref": "#/components/schemas/Crisis"}}, "last_updated": {"type": "string", "format": "date-time"}}}}}}, "401": {"description": "Unauthorized", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"description": "Rate limit exceeded"}}}}, "/event-stats": {"get": {"summary": "Event Track Record (Brier calibration)", "description": "Returns the historical track record for an event type within a crisis: per-ticker UP-rate, average pct move, and Brier score across {15m, 1h, 4h, 1d} horizons. Predictions use a self-referential rolling baseline (the rolling UP-rate of prior events of the same type/crisis/ticker) with an uninformative prior of 0.5 for the first 5 events. Move thresholds: \u00b10.3% for equities, \u00b10.5% for commodities (CL=F, NG=F, BZ=F). Lookback window: 90 days, with a 1-day floor so the 1d horizon has actually completed. NO_MOVE outcomes count as not-UP for the binary Brier target. Response cached server-side for 6 hours.", "operationId": "eventStats", "tags": ["Markets"], "parameters": [{"name": "type", "in": "query", "required": true, "description": "Canonical event type (military, naval, nuclear, political, protest, humanitarian, enforcement, climate, cyber, news).", "schema": {"type": "string", "enum": ["military", "naval", "nuclear", "political", "protest", "humanitarian", "enforcement", "climate", "cyber", "news"]}}, {"name": "crisis", "in": "query", "required": true, "description": "Crisis key (iran, ukraine, climate, usa, agi, gaza, scs).", "schema": {"type": "string", "enum": ["iran", "ukraine", "climate", "usa", "agi", "gaza", "scs"]}}, {"name": "ticker", "in": "query", "required": false, "description": "If provided, returns stats only for this ticker. Otherwise returns the matrix for all anchor tickers for the crisis.", "schema": {"type": "string"}}, {"name": "breakdown", "in": "query", "required": false, "description": "summary (default) or full. full adds a samples array with the 5 most recent past events and their per-ticker outcomes (the 'receipts' list).", "schema": {"type": "string", "enum": ["summary", "full"], "default": "summary"}}], "responses": {"200": {"description": "Track record stats", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"example": {"type": "military", "crisis": "iran", "n": 47, "tickers": {"CL=F": {"n": 47, "horizons": {"15m": {"n": 45, "up_rate": 0.58, "avg_move_pct": 0.003, "brier": 0.21}, "1h": {"n": 45, "up_rate": 0.62, "avg_move_pct": 0.008, "brier": 0.18}, "4h": {"n": 44, "up_rate": 0.55, "avg_move_pct": 0.011, "brier": 0.22}, "1d": {"n": 44, "up_rate": 0.49, "avg_move_pct": 0.004, "brier": 0.27}}}}, "as_of": "2026-04-07T15:30:00Z"}}}}, "400": {"description": "Missing required type or crisis param"}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}}}, "/events": {"get": {"summary": "Query events", "description": "Returns geolocated crisis events with filtering, pagination, and time-range support. Events with future timestamps are automatically excluded.", "operationId": "listEvents", "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "type", "in": "query", "schema": {"type": "string"}, "description": "Filter by event type (military, naval, nuclear, etc.)"}, {"name": "since", "in": "query", "schema": {"type": "string", "format": "date-time"}, "description": "ISO 8601 \u2014 only return events after this time"}, {"name": "until", "in": "query", "schema": {"type": "string", "format": "date-time"}, "description": "ISO 8601 \u2014 only return events before this time"}, {"name": "q", "in": "query", "schema": {"type": "string"}, "description": "Full-text search on title and description"}, {"name": "min_confidence", "in": "query", "schema": {"type": "number", "minimum": 0, "maximum": 1}, "description": "Minimum confidence score"}, {"name": "sort", "in": "query", "schema": {"type": "string", "enum": ["newest", "chronological", "confidence"], "default": "chronological"}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 100, "minimum": 1, "maximum": 1000}}, {"name": "offset", "in": "query", "schema": {"type": "integer", "default": 0}}], "responses": {"200": {"description": "Paginated event list", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object", "properties": {"events": {"type": "array", "items": {"$ref": "#/components/schemas/Event"}}, "meta": {"$ref": "#/components/schemas/PaginationMeta"}}}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}}}, "/hotspots": {"get": {"summary": "Geographic hotspots", "description": "Returns clusters of events grouped by geographic proximity, ranked by recent intensity. Useful for surfacing 'where is the action right now' on a crisis.", "operationId": "listHotspots", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "since", "in": "query", "schema": {"type": "string", "format": "date-time"}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 25}}], "tags": ["Discovery"]}}, "/intensity": {"get": {"summary": "Intensity time series", "operationId": "getIntensity", "parameters": [{"$ref": "#/components/parameters/crisis"}], "responses": {"200": {"description": "Intensity over time"}}}}, "/markets/cone": {"get": {"summary": "Markets cone of possibility", "description": "Returns a probability-weighted price cone for a ticker over a horizon, computed from active scenario probabilities and historical sensitivities. The cone bands are 50% and 75% confidence intervals around the expected move. Cached server-side for 60 seconds.", "operationId": "marketsCone", "tags": ["Markets"], "parameters": [{"name": "ticker", "in": "query", "required": true, "schema": {"type": "string"}, "description": "Ticker symbol (e.g. CL=F, USO, GLD, SPY)"}, {"name": "crisis", "in": "query", "schema": {"type": "string"}, "description": "Crisis context for sensitivity lookup (default: iran)"}, {"name": "scenarios", "in": "query", "schema": {"type": "string"}, "description": "Comma-separated list of scenario IDs to weight into the cone"}, {"name": "days", "in": "query", "schema": {"type": "integer", "default": 90, "minimum": 1, "maximum": 365}, "description": "Forecast horizon in days"}, {"name": "overrides", "in": "query", "schema": {"type": "string"}, "description": "Optional probability overrides, e.g. 'hormuz:50,deescalation:80' overrides those scenarios' probabilities to 50%/80%"}], "responses": {"200": {"description": "Cone series", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"example": {"ticker": "CL=F", "current_price": 78.42, "sigma_daily": 0.018, "e_move": 0.034, "days": 90, "contributions": {"hormuz": {"prob": 35, "impact_pct": 12.5, "direction": "up", "weighted_move": 4.4, "source": "historical", "r_squared": 0.62}}, "series": {"center": [{"time": 1717000000, "value": 78.42}], "upper50": [{"time": 1717000000, "value": 80.1}], "lower50": [{"time": 1717000000, "value": 76.8}], "upper75": [{"time": 1717000000, "value": 82.3}], "lower75": [{"time": 1717000000, "value": 74.6}]}}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}, "500": {"description": "Impact table or sensitivities unavailable"}}}}, "/markets/events": {"get": {"summary": "Markets-relevant events", "description": "Returns AI-extracted geolocated events filtered to those most likely to move markets (confidence >= 0.5, deduped by source URL). Used by the Markets event-pill strip on the chart.", "operationId": "marketsEvents", "tags": ["Markets"], "parameters": [{"name": "crises", "in": "query", "required": true, "schema": {"type": "string"}, "description": "Comma-separated crisis keys (e.g. 'iran,ukraine')"}, {"name": "since", "in": "query", "schema": {"type": "string"}, "description": "Unix timestamp \u2014 only return events after this time"}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 100, "minimum": 1, "maximum": 2000}}], "responses": {"200": {"description": "Event list", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object", "properties": {"events": {"type": "array", "items": {"type": "object"}}}}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}}}, "/markets/sensitivity": {"get": {"summary": "Scenario\u2192ticker sensitivities", "description": "Returns historical price sensitivities (impact_pct per unit probability) for each scenario in a crisis against each anchor ticker. Source can be 'historical' (regression on past events), 'ai' (Haiku-extracted), or 'static' (curated baseline).", "operationId": "marketsSensitivity", "tags": ["Markets"], "parameters": [{"name": "crisis", "in": "query", "required": true, "schema": {"type": "string"}}, {"name": "ticker", "in": "query", "schema": {"type": "string"}, "description": "Optional \u2014 if omitted, returns matrix across all anchor tickers"}], "responses": {"200": {"description": "Sensitivity matrix", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}}}, "/openapi.json": {"get": {"summary": "OpenAPI specification", "description": "This document.", "operationId": "getOpenApiSpec", "responses": {"200": {"description": "OpenAPI 3.0 JSON spec"}}}}, "/patterns": {"get": {"summary": "Event pattern analysis", "operationId": "getPatterns", "parameters": [{"$ref": "#/components/parameters/crisis"}], "responses": {"200": {"description": "Pattern clusters and trends"}}}}, "/patterns/analogs": {"get": {"summary": "Historical analogs", "description": "AI-powered historical analog matching for crisis patterns.", "operationId": "findAnalogs", "parameters": [{"$ref": "#/components/parameters/crisis"}], "responses": {"200": {"description": "Historical analog matches"}}}}, "/patterns/similar": {"get": {"summary": "Find similar events", "operationId": "findSimilarPatterns", "parameters": [{"name": "event_id", "in": "query", "schema": {"type": "string"}}, {"name": "q", "in": "query", "schema": {"type": "string"}}], "responses": {"200": {"description": "Similar events ranked by relevance"}}}}, "/patterns/{cluster_id}": {"get": {"summary": "Pattern cluster detail", "description": "Returns full detail for a single pattern cluster: member events, pattern signature, frequency over time, and crisis distribution.", "operationId": "getPatternCluster", "tags": ["Patterns"], "parameters": [{"name": "cluster_id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "Pattern cluster detail", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "404": {"description": "Cluster not found"}, "429": {"description": "Rate limit exceeded"}}}}, "/scenarios": {"get": {"summary": "Scenario assessments", "description": "Returns AI-assessed scenario probabilities for a crisis theater. Returns empty list for unknown crises.", "operationId": "listScenarios", "parameters": [{"name": "crisis", "in": "query", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "Scenario list with probabilities", "content": {"application/json": {"schema": {"type": "object", "properties": {"crisis": {"type": "string"}, "scenarios": {"type": "array", "items": {"$ref": "#/components/schemas/Scenario"}}, "intensity": {"type": "number", "nullable": true}}}}}}}}}, "/scenarios/changes": {"get": {"summary": "Recent scenario probability changes", "description": "Returns scenarios whose probability has changed by more than a threshold within the lookback window, ranked by absolute change.", "operationId": "listScenarioChanges", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "since", "in": "query", "schema": {"type": "string", "format": "date-time"}}, {"name": "min_delta", "in": "query", "schema": {"type": "number", "default": 5}}], "tags": ["Scenarios"]}}, "/scenarios/history": {"get": {"summary": "Scenario probability history", "description": "Returns the time series of probability assessments for one or all scenarios in a crisis. Each entry includes the assessed probability, justification, and source URL.", "operationId": "listScenarioHistory", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "id", "in": "query", "schema": {"type": "string"}, "description": "Optional scenario id to filter to one scenario"}], "tags": ["Scenarios"]}}, "/scenarios/paths": {"get": {"summary": "Escalation paths", "description": "Returns ranked escalation paths through a scenario tree from current state to a target outcome, ordered by probability.", "operationId": "listScenarioPaths", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "id", "in": "query", "required": true, "schema": {"type": "string"}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 10}}], "tags": ["Scenarios"]}}, "/scenarios/ripple": {"get": {"summary": "Scenario ripple effects", "description": "Returns downstream cascading effects when a scenario's probability changes \u2014 which other scenarios, indices, or market signals are correlated with this one.", "operationId": "getScenarioRipple", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "id", "in": "query", "required": true, "schema": {"type": "string"}}], "tags": ["Scenarios"]}}, "/scenarios/tree": {"get": {"summary": "Scenario decision tree", "description": "Returns the full decision tree for a scenario: nodes, edges, and the cascading consequence chain. Used by the Scenarios view canvas.", "operationId": "getScenarioTree", "responses": {"200": {"description": "Success", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "429": {"description": "Rate limit exceeded"}}, "parameters": [{"$ref": "#/components/parameters/crisis"}, {"name": "id", "in": "query", "required": true, "schema": {"type": "string"}}], "tags": ["Scenarios"]}}, "/scenarios/{id}": {"get": {"summary": "Scenario detail", "description": "Returns full detail for a single scenario including current probability, historical assessment trail, related events, and tree structure.", "operationId": "getScenarioDetail", "tags": ["Scenarios"], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Scenario identifier"}, {"$ref": "#/components/parameters/crisis"}], "responses": {"200": {"description": "Scenario detail", "headers": {"X-RateLimit-Limit": {"$ref": "#/components/headers/X-RateLimit-Limit"}, "X-RateLimit-Remaining": {"$ref": "#/components/headers/X-RateLimit-Remaining"}, "X-RateLimit-Reset": {"$ref": "#/components/headers/X-RateLimit-Reset"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}, "404": {"description": "Scenario not found"}, "429": {"description": "Rate limit exceeded"}}}}, "/signals": {"get": {"summary": "Trading signals", "operationId": "getSignals", "parameters": [{"$ref": "#/components/parameters/crisis"}], "responses": {"200": {"description": "Market trading signals"}}}}, "/stream": {"get": {"summary": "Server-Sent Events stream", "description": "Real-time event stream via SSE. Pass API key as ?key= query param.", "operationId": "streamEvents", "parameters": [{"$ref": "#/components/parameters/crisis"}], "responses": {"200": {"description": "SSE stream of new events"}}}}, "/webhooks": {"get": {"summary": "List webhooks", "description": "Returns all webhooks configured for the authenticated API key.", "operationId": "listWebhooks", "tags": ["Webhooks"], "responses": {"200": {"description": "List of webhooks", "content": {"application/json": {"schema": {"type": "object"}}}}, "401": {"description": "Unauthorized"}}}, "post": {"summary": "Create webhook", "description": "Register a webhook URL to receive event notifications. Returns a signing_secret used to verify incoming POSTs via the X-Polycrisis-Signature header (HMAC-SHA256 of the request body).", "operationId": "createWebhook", "tags": ["Webhooks"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["url", "triggers"], "properties": {"url": {"type": "string", "format": "uri"}, "triggers": {"type": "array", "items": {"type": "string", "enum": ["new_event", "intensity_spike", "scenario_change"]}}, "crisis": {"type": "string"}, "intensity_threshold": {"type": "number"}, "event_type": {"type": "string", "description": "Filter to one canonical event type"}, "min_confidence": {"type": "number", "minimum": 0, "maximum": 1, "description": "Only fire for events with confidence >= this value"}}}}}}, "responses": {"201": {"description": "Webhook created", "content": {"application/json": {"example": {"id": 42, "url": "https://example.com/hook", "signing_secret": "whsec_...", "triggers": ["new_event"]}}}}, "400": {"description": "Invalid request body"}, "401": {"description": "Unauthorized"}}}, "delete": {"summary": "Delete webhook", "description": "Deactivate a webhook by id. The webhook will stop receiving events but its history is retained.", "operationId": "deleteWebhook", "tags": ["Webhooks"], "parameters": [{"name": "id", "in": "query", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "Webhook deleted"}, "401": {"description": "Unauthorized"}, "404": {"description": "Webhook not found"}}}}, "/indicators": {"get": {"summary": "Composite risk indicators", "description": "Returns 8 normalized (0-1) risk indicators for a crisis, plus a composite risk score. Indicators cover escalation probability, event intensity, signal velocity, actor concentration, confidence drift, market contagion, narrative contagion, and anomaly detection. Cached for 15 minutes.", "operationId": "getIndicators", "tags": ["Intelligence"], "parameters": [{"name": "crisis", "in": "query", "description": "Crisis ID (e.g. iran, ukraine, gaza). Required if country is not provided.", "schema": {"type": "string", "enum": ["iran", "ukraine", "gaza", "scs", "climate", "usa", "agi"]}}, {"name": "country", "in": "query", "description": "Country name (e.g. Turkey, Taiwan). Mapped to the relevant crisis ID. Required if crisis is not provided.", "schema": {"type": "string"}}], "responses": {"200": {"description": "Risk indicators", "content": {"application/json": {"schema": {"type": "object", "properties": {"crisis": {"type": "string"}, "timestamp": {"type": "string", "format": "date-time"}, "composite_risk": {"type": "number", "minimum": 0, "maximum": 1}, "indicators": {"type": "object", "additionalProperties": {"type": "object", "properties": {"value": {"type": "number", "minimum": 0, "maximum": 1}, "trend": {"type": "string", "enum": ["rising", "falling", "stable"]}, "period": {"type": "string"}}}}}}}}}, "400": {"description": "Missing or invalid crisis/country parameter"}, "401": {"description": "Unauthorized"}}}}}}