Turqoa Docs

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

ParameterTypeDefaultDescription
categorystringallFilter by category: gate, perimeter, maritime, drone, system, decision
severitystringallFilter by severity: low, medium, high, critical
source_modulestringallFilter by originating module
zonestring--Filter by zone name or ID
start_datestring24h agoISO 8601 start date
end_datestringnowISO 8601 end date
requires_actionboolean--Filter events that need operator action
incident_idstring--Filter events linked to a specific incident
searchstring--Full-text search across title and description
sortstringtimestampSort field: timestamp, severity
orderstringdescSort direction
pageinteger1Page number
per_pageinteger50Items 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

BehaviorDetails
ReconnectionClient should implement exponential backoff (1s, 2s, 4s, 8s, max 30s)
Heartbeat timeoutIf no heartbeat received in 60s, reconnect
Max connections10 concurrent WebSocket connections per API key
Message bufferServer buffers up to 1000 messages during brief disconnects
Compressionpermessage-deflate supported

Event Types

Gate Events

TypeSeverityDescription
gate_transaction_createdlowNew vehicle/container at gate
ocr_mismatchmediumContainer ID does not match expected
seal_violationhighSeal missing, broken, or tampered
damage_detectedmediumContainer damage identified
gate_overridemediumOperator overrode automated decision
gate_lockedhighGate locked by system or operator

Perimeter Events

TypeSeverityDescription
motion_detectedmediumMotion in monitored zone
perimeter_breachhighBreach of perimeter barrier
fence_tamperhighFence sensor triggered
camera_offlinemediumCamera feed lost

Maritime Events

TypeSeverityDescription
zone_entryvariesVessel entered a threat zone
zone_exitlowVessel exited a threat zone
ais_gaphighAIS signal lost
risk_score_changevariesVessel risk score changed significantly
dwell_thresholdmediumVessel exceeded dwell time limit
speed_anomalymediumVessel speed outside expected range

Drone Events

TypeSeverityDescription
mission_launchedlowDrone mission started
mission_completedlowDrone mission finished
evidence_capturedlowDrone captured evidence (photo/video)
drone_low_batterymediumDrone battery below threshold
drone_lost_linkhighCommunication lost with drone
mission_abortedmediumMission 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"
# 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"