Events
The Events API provides access to the unified security event stream. Query historical events via REST or subscribe to real-time events via WebSocket.
GET /api/v1/events
Retrieve a paginated list of security events with filtering.
Request
curl "https://api.turqoa.com/v1/events?severity=high&category=maritime&per_page=25" \
-H "Authorization: Bearer $TURQOA_API_KEY"
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
category | string | all | Filter by category: gate, perimeter, maritime, drone, system, decision |
severity | string | all | Filter by severity: low, medium, high, critical |
source_module | string | all | Filter by originating module |
zone | string | -- | Filter by zone name or ID |
start_date | string | 24h ago | ISO 8601 start date |
end_date | string | now | ISO 8601 end date |
requires_action | boolean | -- | Filter events that need operator action |
incident_id | string | -- | Filter events linked to a specific incident |
search | string | -- | Full-text search across title and description |
sort | string | timestamp | Sort field: timestamp, severity |
order | string | desc | Sort direction |
page | integer | 1 | Page number |
per_page | integer | 50 | Items per page (max 200) |
Response
{
"data": [
{
"id": "evt_20260406_1045_001",
"timestamp": "2026-04-06T10:45:00Z",
"category": "maritime",
"severity": "high",
"source_module": "maritime_security",
"source_device": "ais_receiver_01",
"title": "AIS gap detected for MV Nordic Star",
"description": "Vessel MMSI 123456789 lost AIS signal for 8 minutes while in restricted zone 'LNG Terminal'.",
"location": {
"lat": 51.4835,
"lng": -0.0472,
"zone": "LNG Terminal Restricted"
},
"metadata": {
"mmsi": 123456789,
"vessel_name": "MV Nordic Star",
"gap_duration_seconds": 480,
"risk_score": 62,
"last_known_speed_knots": 3.2,
"last_known_heading": 195
},
"requires_action": true,
"assigned_to": "maritime_operator_01",
"incident_id": "inc_20260406_003"
},
{
"id": "evt_20260406_1042_005",
"timestamp": "2026-04-06T10:42:15Z",
"category": "perimeter",
"severity": "high",
"source_module": "terminal_security",
"source_device": "camera_north_07",
"title": "Motion detected in restricted zone",
"description": "Unidentified movement detected by camera_north_07 in sector North-3.",
"location": {
"lat": 51.4890,
"lng": -0.0510,
"zone": "North Perimeter Sector 3"
},
"metadata": {
"camera_id": "camera_north_07",
"confidence": 0.87,
"object_class": "person",
"drone_dispatched": true,
"drone_mission_id": "msn_20260406_012"
},
"requires_action": true,
"assigned_to": "perimeter_operator_02",
"incident_id": null
}
],
"meta": {
"request_id": "req_ghi789",
"timestamp": "2026-04-06T11:00:00Z"
},
"pagination": {
"page": 1,
"per_page": 25,
"total_items": 312,
"total_pages": 13,
"has_next": true,
"has_previous": false
}
}
Code Examples
Python
import requests
from datetime import datetime, timedelta
api_key = "tqa_live_..."
base_url = "https://api.turqoa.com/v1"
# Get high-severity events from the last 6 hours
response = requests.get(
f"{base_url}/events",
headers={"Authorization": f"Bearer {api_key}"},
params={
"severity": "high",
"start_date": (datetime.utcnow() - timedelta(hours=6)).isoformat() + "Z",
"per_page": 50,
},
)
events = response.json()["data"]
for event in events:
print(f"[{event['severity'].upper()}] {event['title']}")
print(f" Category: {event['category']}")
print(f" Time: {event['timestamp']}")
if event.get("incident_id"):
print(f" Incident: {event['incident_id']}")
print()
Node.js
const apiKey = "tqa_live_...";
const baseUrl = "https://api.turqoa.com/v1";
const sixHoursAgo = new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString();
const response = await fetch(
`${baseUrl}/events?` +
new URLSearchParams({
severity: "high",
start_date: sixHoursAgo,
per_page: "50",
}),
{
headers: { Authorization: `Bearer ${apiKey}` },
}
);
const { data: events } = await response.json();
events.forEach((event) => {
console.log(`[${event.severity.toUpperCase()}] ${event.title}`);
console.log(` Category: ${event.category} | Time: ${event.timestamp}`);
});
WebSocket Real-Time Stream
Subscribe to the live event stream for real-time updates. The WebSocket endpoint pushes events as they occur with minimal latency.
Connection
wss://api.turqoa.com/v1/events/stream?token=YOUR_API_KEY
Authentication
Pass the API key as a query parameter or in the first message after connection:
const ws = new WebSocket(
"wss://api.turqoa.com/v1/events/stream?token=tqa_live_..."
);
ws.onopen = () => {
console.log("Connected to event stream");
// Subscribe to specific categories
ws.send(
JSON.stringify({
type: "subscribe",
filters: {
categories: ["maritime", "perimeter"],
min_severity: "medium",
},
})
);
};
ws.onmessage = (msg) => {
const event = JSON.parse(msg.data);
console.log(`[${event.severity}] ${event.title}`);
};
ws.onerror = (err) => {
console.error("WebSocket error:", err);
};
ws.onclose = (e) => {
console.log(`Connection closed: ${e.code} ${e.reason}`);
// Implement reconnection logic
};
Python WebSocket Client
import asyncio
import json
import websockets
async def listen_events():
uri = "wss://api.turqoa.com/v1/events/stream?token=tqa_live_..."
async with websockets.connect(uri) as ws:
# Subscribe to maritime events
await ws.send(json.dumps({
"type": "subscribe",
"filters": {
"categories": ["maritime"],
"min_severity": "high",
},
}))
async for message in ws:
event = json.loads(message)
if event.get("type") == "event":
print(f"[{event['severity']}] {event['title']}")
print(f" {event['description']}")
elif event.get("type") == "heartbeat":
pass # connection keepalive
elif event.get("type") == "error":
print(f"Error: {event['message']}")
asyncio.run(listen_events())
Subscription Messages
After connecting, send subscription messages to filter the event stream:
Subscribe
{
"type": "subscribe",
"filters": {
"categories": ["maritime", "perimeter", "gate"],
"min_severity": "medium",
"zones": ["lng_terminal", "north_perimeter"],
"source_modules": ["maritime_security"]
}
}
Unsubscribe
{
"type": "unsubscribe"
}
Update Filters
{
"type": "update_filters",
"filters": {
"categories": ["maritime"],
"min_severity": "high"
}
}
Server Messages
Event
{
"type": "event",
"id": "evt_20260406_1045_001",
"timestamp": "2026-04-06T10:45:00Z",
"category": "maritime",
"severity": "high",
"title": "AIS gap detected for MV Nordic Star",
"description": "...",
"location": { "lat": 51.4835, "lng": -0.0472 },
"metadata": { }
}
Heartbeat
Sent every 30 seconds to keep the connection alive:
{
"type": "heartbeat",
"timestamp": "2026-04-06T10:45:30Z",
"events_sent": 142,
"connection_uptime_seconds": 3600
}
Error
{
"type": "error",
"code": "invalid_filter",
"message": "Unknown category: 'unknown'. Valid categories: gate, perimeter, maritime, drone, system, decision"
}
Connection Management
| Behavior | Details |
|---|---|
| Reconnection | Client should implement exponential backoff (1s, 2s, 4s, 8s, max 30s) |
| Heartbeat timeout | If no heartbeat received in 60s, reconnect |
| Max connections | 10 concurrent WebSocket connections per API key |
| Message buffer | Server buffers up to 1000 messages during brief disconnects |
| Compression | permessage-deflate supported |
Event Types
Gate Events
| Type | Severity | Description |
|---|---|---|
gate_transaction_created | low | New vehicle/container at gate |
ocr_mismatch | medium | Container ID does not match expected |
seal_violation | high | Seal missing, broken, or tampered |
damage_detected | medium | Container damage identified |
gate_override | medium | Operator overrode automated decision |
gate_locked | high | Gate locked by system or operator |
Perimeter Events
| Type | Severity | Description |
|---|---|---|
motion_detected | medium | Motion in monitored zone |
perimeter_breach | high | Breach of perimeter barrier |
fence_tamper | high | Fence sensor triggered |
camera_offline | medium | Camera feed lost |
Maritime Events
| Type | Severity | Description |
|---|---|---|
zone_entry | varies | Vessel entered a threat zone |
zone_exit | low | Vessel exited a threat zone |
ais_gap | high | AIS signal lost |
risk_score_change | varies | Vessel risk score changed significantly |
dwell_threshold | medium | Vessel exceeded dwell time limit |
speed_anomaly | medium | Vessel speed outside expected range |
Drone Events
| Type | Severity | Description |
|---|---|---|
mission_launched | low | Drone mission started |
mission_completed | low | Drone mission finished |
evidence_captured | low | Drone captured evidence (photo/video) |
drone_low_battery | medium | Drone battery below threshold |
drone_lost_link | high | Communication lost with drone |
mission_aborted | medium | Mission aborted (weather, manual, etc.) |
Filtering
Multiple Values
Filter by multiple values using comma separation:
# Events from maritime or perimeter categories
curl "https://api.turqoa.com/v1/events?category=maritime,perimeter"
# High or critical severity
curl "https://api.turqoa.com/v1/events?severity=high,critical"
Date Ranges
# Events from a specific time window
curl "https://api.turqoa.com/v1/events?start_date=2026-04-06T08:00:00Z&end_date=2026-04-06T12:00:00Z"
Full-Text Search
# Search for events mentioning a specific vessel
curl "https://api.turqoa.com/v1/events?search=Nordic+Star"
Combining Filters
All filters are combined with AND logic:
# Critical maritime events in the LNG zone from today
curl "https://api.turqoa.com/v1/events?category=maritime&severity=critical&zone=lng_terminal&start_date=2026-04-06T00:00:00Z"