# ConflictIQ API Reference This document describes the API routes currently implemented in the backend (`/api/v1`), including request parameters and response body shapes. ## Base URLs - API base: `/api/v1` - Health (also exposed outside API prefix): `/health` ## Authentication - No authentication/authorization is currently enforced in these routes. ## Common Notes - All dates use `YYYY-MM-DD`. - Most timestamps are ISO-like strings and often end with `Z`. - Error responses from FastAPI typically look like: ```json { "detail": "Error message" } ``` ## Core Event Object (`conflict_events` row) Many endpoints return raw or near-raw `conflict_events` rows. Typical fields: - `id` (integer) - `event_id` (string) - `source` (string) - `source_reliability` (string) - `event_time` (string datetime) - `event_date` (string date) - `year` (integer) - `week` (integer) - `country` (string) - `country_iso3` (string) - `region`, `admin1`, `admin2`, `city` (string/null) - `lat`, `lon` (number/null) - `geo_precision` (integer) - `event_type`, `event_subtype`, `interaction_code` (string/null) - `actor1`, `actor1_type`, `actor2`, `actor2_type` (string/null) - `fatalities`, `fatalities_civilians` (integer) - `fatalities_confidence` (string) - `severity` (string/null) - `severity_score` (number/null) - `title`, `notes` (string/null) - `tags` (string array/null) - `source_url` (string/null) - `conflict_name` (string/null) - `conflict_id` (integer/null) - `category` (string) - `ingested_at` (string datetime) - `geom` (internal PostGIS object, excluded in some endpoints) --- ## Health Endpoints ### `GET /health` ### `GET /api/v1/health` Request parameters: none Response body: ```json { "status": "OK", "events_total": 1234, "database_connected": true, "redis_connected": true } ``` --- ## Conflicts API (`/api/v1/conflicts`) ### `GET /api/v1/conflicts` Query parameters: - `country` (string, optional) - `category` (string, optional) - `from_date` (date, optional, default = today - 7 days) - `to_date` (date, optional, default = today) - `event_type` (string, optional) - `severity` (string, optional) - `min_fatalities` (integer, optional, default `0`) - `tags` (string, optional, comma-separated, example: `urban-combat,artillery`) - `limit` (integer, optional, default `100`, max `500`) - `offset` (integer, optional, default `0`) Response body: ```json { "status": 200, "success": true, "count": 2, "data": [{ "...event fields..." : "..." }], "meta": { "from_cache": false, "page": 1, "per_page": 100 } } ``` ### `GET /api/v1/conflicts/recent` Query parameters: - `days` (integer, optional, default `7`) - `limit` (integer, optional, default `100`) Response body: ```json { "status": 200, "success": true, "count": 2, "data": [{ "...event fields..." : "..." }], "meta": { "from_cache": false } } ``` ### `GET /api/v1/conflicts/ongoing` Query parameters: - `limit` (integer, optional, default `50`) Response body: ```json { "status": 200, "success": true, "count": 2, "data": [{ "...event fields without geom..." : "..." }] } ``` ### `GET /api/v1/conflicts/historical` Query parameters: - `days_ago` (integer, optional, default `2`) - `limit` (integer, optional, default `100`) Response body: ```json { "status": 200, "success": true, "count": 2, "data": [{ "...event fields without geom..." : "..." }] } ``` ### `GET /api/v1/conflicts/near` Query parameters: - `lat` (number, required) - `lon` (number, required) - `radius_km` (integer, optional, default `50`) - `days` (integer, optional, default `7`) - `limit` (integer, optional, default `100`) Response body: ```json { "status": 200, "success": true, "count": 2, "data": [{ "...event fields without geom..." : "..." }], "meta": { "from_cache": false } } ``` ### `GET /api/v1/conflicts/country/{iso3}` Path parameters: - `iso3` (string, required) Query parameters: - `days` (integer, optional, default `30`) - `limit` (integer, optional, default `100`) Response body: ```json { "status": 200, "success": true, "count": 2, "data": [{ "...event fields without geom..." : "..." }], "meta": { "from_cache": false } } ``` ### `GET /api/v1/conflicts/{event_id}` Path parameters: - `event_id` (string, required) Response body: ```json { "status": 200, "success": true, "data": { "...single event fields without geom..." : "..." }, "meta": { "from_cache": false } } ``` 404 response: ```json { "detail": "Event not found" } ``` ### `GET /api/v1/conflicts/clusters` Query parameters: - `precision` (number, optional, default `1.0`, min `0.1`, max `5.0`) - `days` (integer, optional, default `7`) Response body: ```json { "status": 200, "count": 2, "data": [ { "lon": 36.8219, "lat": -1.2921, "count": 7, "main_category": "MILITARY", "main_severity": "HIGH" } ] } ``` --- ## Stats API ### `GET /api/v1/stats` ### `GET /api/v1/stats/stats` Query parameters: - `country` (string, optional) - `days` (integer, optional, default `30`) Response body: ```json { "country": "Global", "period_days": 30, "total_events": 120, "by_type": { "Airstrike / Artillery": 20, "Unknown": 5 }, "by_severity": { "HIGH": 10, "UNKNOWN": 3 }, "total_fatalities": 450, "civilian_fatalities": 120, "events_last_24h": 14, "trend": "STABLE" } ``` ### `GET /api/v1/active-conflicts` Request parameters: none Response body: ```json [ { "conflict_id": 1, "name": "Russo-Ukrainian War", "countries": ["UKR", "RUS"], "region": "Europe", "start_date": "2022-02-24", "status": "ACTIVE", "intensity": "WAR", "total_events": 2000, "last_event_at": "2026-04-20T09:25:00Z" } ] ``` --- ## Intelligence API (`/api/v1/intel`) ### `GET /api/v1/intel/theaters` Request parameters: none Response body: ```json [ { "conflict_id": 1, "name": "Example Theater", "intensity": "WAR", "center_lat": 48.5, "center_lon": 37.9, "max_severity": 9.2, "total_fatalities": 300, "dominant_actor": "Actor Name", "primary_weapon": "unknown", "spread_km": 220.5, "total_events": 124, "stability_rating": 35.4 } ] ``` ### `GET /api/v1/intel/sitrep` Request parameters: none Response body: ```json { "summary": "Global Intelligence Alert: ...", "intensity": "HIGH", "stats": { "total_events": 50, "total_fatalities": 210, "top_country": "UKR", "top_category": "MILITARY", "most_active_actor": "Actor Name" } } ``` If no recent events: ```json { "summary": "Stable. No major conflict events reported in the last 24h.", "intensity": "LOW" } ``` ### `GET /api/v1/intel/forecast` Request parameters: none Response body: ```json { "forecast": "AI-generated strategic forecast text...", "risk_level": "CRITICAL", "timestamp": "2026-04-20" } ``` ### `GET /api/v1/intel/actors` Request parameters: none Response body: ```json [ { "actor1": "Actor Name", "involvement_count": 17, "fatal_impact": 93 } ] ``` ### `GET /api/v1/intel/trends` Request parameters: none Response body: ```json [ { "country_iso3": "UKR", "current_count": 21, "previous_count": 8, "surge_percentage": 162.5 } ] ``` ### `GET /api/v1/intel/hotspots` Request parameters: none Response body: ```json [ { "lon": 37.61, "lat": 48.01, "event_count": 12, "country_iso3": "UKR" } ] ``` ### `GET /api/v1/intel/monitor` Request parameters: none Response body: - Array of conflict event objects (`SELECT * FROM conflict_events ... LIMIT 15`). ### `GET /api/v1/intel/frontlines` Request parameters: none Response body: ```json [ { "lon": 37.6, "lat": 47.9, "event_count": 5, "country": "Ukraine", "country_iso3": "UKR", "highest_severity": 9.1, "primary_engagement": "Armed clash" } ] ``` 500 response for intel endpoints: ```json { "detail": "Exception message" } ``` --- ## Intelligence Hub API (`/api/v1/intel/articles`) ### `GET /api/v1/intel/articles` Query parameters: - `limit` (integer, optional, default `20`) - `offset` (integer, optional, default `0`) Response body: ```json [ { "id": 42, "title": "Daily Theater Summary", "author": "Ops Desk", "tags": ["ukraine", "airstrike"], "created_at": "2026-04-20T06:10:00" } ] ``` ### `GET /api/v1/intel/articles/search` Query parameters: - `q` (string, required, min length `2`) Response body: ```json [ { "id": 42, "title": "Daily Theater Summary", "author": "Ops Desk", "tags": ["ukraine"], "created_at": "2026-04-20T06:10:00", "rank": 0.425 } ] ``` ### `GET /api/v1/intel/articles/{article_id}` Path parameters: - `article_id` (integer, required) Response body: - Full `intel_articles` row as JSON object: - `id`, `title`, `content`, `author`, `tags`, `created_at`, `updated_at`, `search_vector` 404 response: ```json { "detail": "Article not found" } ``` ### `POST /api/v1/intel/articles` Request parameters (implemented as function params; currently handled as query/form-style fields): - `title` (string, required) - `content` (string, required) - `author` (string, required) - `tags` (string array, optional, default `[]`) Response body: ```json { "status": "published", "id": 43, "created_at": "2026-04-20T08:30:00" } ``` --- ## Data API (`/api/v1/data`) ### `GET /api/v1/data/events` Query parameters: - `country` (string, optional, ISO3) - `actor` (string, optional, matches `actor1` or `actor2` using `ILIKE`) - `start_date` (date, optional) - `end_date` (date, optional) - `limit` (integer, optional, default `100`) - `offset` (integer, optional, default `0`) Response body: - Array of conflict event objects (raw DB rows). ### `GET /api/v1/data/acled` Query parameters: - `country` (string, optional) - `limit` (integer, optional, default `100`) Response body: ```json [ { "event_id_cnty": "CIQ-20260420-UKR-00001", "event_date": "2026-04-20", "year": 2026, "event_type": "Airstrike / Artillery", "actor1": "Actor A", "actor2": "Actor B", "country": "Ukraine", "location": "Dnipro", "latitude": 48.4647, "longitude": 35.0462, "source": "GDELT", "notes": "Text...", "fatalities": 8, "severity": 8.7 } ] ``` ### `GET /api/v1/data/export/csv` Query parameters: - `country` (string, optional) Response: - `text/csv` streamed file download. - Header includes: - `Content-Disposition: attachment; filename=conflictiq_data_.csv` ### `GET /api/v1/data/geojson` Query parameters: - `limit` (integer, optional, default `500`) Response body: ```json { "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [36.82, -1.29] }, "properties": { "event_id": "CIQ-...", "title": "Event title", "event_type": "Armed clash", "fatalities": 3 } } ] } ``` --- ## AI Analyst API (`/api/v1/ai`) ### `GET /api/v1/ai/analyze` Query parameters: - `context` (string, optional) Response: - Content type: Server-Sent Events (SSE) stream - Stream emits token chunks as: ```text data: ``` Error behavior: - On failure, still returns an SSE stream with an error message token: ```text data: [COMMUNICATION LINK ERROR: ...] ``` --- ## WebSocket API ### `WS /api/v1/ws` Client behavior: - Connect with WebSocket to `/api/v1/ws`. - Server accepts the socket and keeps it open. - Any text the client sends is received (currently not processed into replies). Server push behavior: - On every new `conflict_events` insert (via PostgreSQL `NOTIFY`), server broadcasts an event payload to all connected clients. - Payload is JSON text of the inserted row plus: - `priority: true` if `category` is `MILITARY` or `TERRORIST` - `priority: false` otherwise Example pushed message: ```json { "id": 1001, "event_id": "CIQ-20260420-UKR-00001", "category": "MILITARY", "title": "Artillery strike near frontline", "event_time": "2026-04-20T09:45:00", "priority": true } ``` --- ## Implementation Caveats (Current Code) - Route order issue: `/api/v1/conflicts/{event_id}` is declared before `/api/v1/conflicts/clusters`, so requests to `/conflicts/clusters` may be captured as `event_id="clusters"` depending on router matching behavior. - `GET /api/v1/intel/theaters` uses `mode() ... ORDER BY weapon`, but `weapon` is not present in `conflict_events` schema in `db/init.sql`, which can cause runtime SQL errors unless schema differs in production. - `POST /api/v1/intel/articles` currently uses plain function parameters (not an explicit JSON body model), so clients should send these fields as query/form parameters unless route code is updated.