conflictData / API_REFERENCE.md
Hardik Singh
Upgrade: High-Fidelity Geocoding & Precision UI
48486c6
# 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_<YYYY-MM-DD>.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: <partial text token>
```
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.