Spaces:
Running
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:
{ "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:
{
"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, default0)tags(string, optional, comma-separated, example:urban-combat,artillery)limit(integer, optional, default100, max500)offset(integer, optional, default0)
Response body:
{
"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, default7)limit(integer, optional, default100)
Response body:
{
"status": 200,
"success": true,
"count": 2,
"data": [{ "...event fields..." : "..." }],
"meta": { "from_cache": false }
}
GET /api/v1/conflicts/ongoing
Query parameters:
limit(integer, optional, default50)
Response body:
{
"status": 200,
"success": true,
"count": 2,
"data": [{ "...event fields without geom..." : "..." }]
}
GET /api/v1/conflicts/historical
Query parameters:
days_ago(integer, optional, default2)limit(integer, optional, default100)
Response body:
{
"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, default50)days(integer, optional, default7)limit(integer, optional, default100)
Response body:
{
"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, default30)limit(integer, optional, default100)
Response body:
{
"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:
{
"status": 200,
"success": true,
"data": { "...single event fields without geom..." : "..." },
"meta": { "from_cache": false }
}
404 response:
{ "detail": "Event not found" }
GET /api/v1/conflicts/clusters
Query parameters:
precision(number, optional, default1.0, min0.1, max5.0)days(integer, optional, default7)
Response body:
{
"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, default30)
Response body:
{
"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:
[
{
"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:
[
{
"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:
{
"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:
{
"summary": "Stable. No major conflict events reported in the last 24h.",
"intensity": "LOW"
}
GET /api/v1/intel/forecast
Request parameters: none
Response body:
{
"forecast": "AI-generated strategic forecast text...",
"risk_level": "CRITICAL",
"timestamp": "2026-04-20"
}
GET /api/v1/intel/actors
Request parameters: none
Response body:
[
{
"actor1": "Actor Name",
"involvement_count": 17,
"fatal_impact": 93
}
]
GET /api/v1/intel/trends
Request parameters: none
Response body:
[
{
"country_iso3": "UKR",
"current_count": 21,
"previous_count": 8,
"surge_percentage": 162.5
}
]
GET /api/v1/intel/hotspots
Request parameters: none
Response body:
[
{
"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:
[
{
"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:
{ "detail": "Exception message" }
Intelligence Hub API (/api/v1/intel/articles)
GET /api/v1/intel/articles
Query parameters:
limit(integer, optional, default20)offset(integer, optional, default0)
Response body:
[
{
"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 length2)
Response body:
[
{
"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_articlesrow as JSON object:id,title,content,author,tags,created_at,updated_at,search_vector
404 response:
{ "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:
{
"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, matchesactor1oractor2usingILIKE)start_date(date, optional)end_date(date, optional)limit(integer, optional, default100)offset(integer, optional, default0)
Response body:
- Array of conflict event objects (raw DB rows).
GET /api/v1/data/acled
Query parameters:
country(string, optional)limit(integer, optional, default100)
Response body:
[
{
"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/csvstreamed file download.- Header includes:
Content-Disposition: attachment; filename=conflictiq_data_<YYYY-MM-DD>.csv
GET /api/v1/data/geojson
Query parameters:
limit(integer, optional, default500)
Response body:
{
"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:
data: <partial text token>
Error behavior:
- On failure, still returns an SSE stream with an error message token:
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_eventsinsert (via PostgreSQLNOTIFY), server broadcasts an event payload to all connected clients. - Payload is JSON text of the inserted row plus:
priority: trueifcategoryisMILITARYorTERRORISTpriority: falseotherwise
Example pushed message:
{
"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/clustersmay be captured asevent_id="clusters"depending on router matching behavior. GET /api/v1/intel/theatersusesmode() ... ORDER BY weapon, butweaponis not present inconflict_eventsschema indb/init.sql, which can cause runtime SQL errors unless schema differs in production.POST /api/v1/intel/articlescurrently 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.