CloudTAK's REST API turns a TAK server from a passive relay into an active integration hub. Rather than requiring every data source to speak native CoT over TCP/TLS — the only path into legacy TAK Server — CloudTAK exposes a full HTTP and WebSocket surface that any modern system can call: sensor arrays, logistics platforms, AI inference pipelines, SIGINT feeds, and custom dashboards. This guide covers every major API surface in detail: authentication, CoT injection, real-time track streaming, mission management, data package upload, group administration, webhooks, and the TAKpilot AI copilot as a worked integration example. If you need to first stand up a CloudTAK server, see our companion guide on CloudTAK server deployment.

Authentication: JWT tokens, API keys, and scopes

The CloudTAK REST API enforces authentication on every endpoint except the health check (GET /api/health). Two credential types are supported, both passed as an HTTP Authorization: Bearer <token> header.

JWT tokens

JWT tokens are short-lived and issued per user session. Obtain one by posting credentials to the login endpoint:

curl -s -X POST https://tak.yourdomain.com:8443/api/login \
  -H "Content-Type: application/json" \
  -d '{"username": "operator01", "password": "s3cur3p@ss"}' \
  | jq -r '.token'

The response body contains token (the JWT string) and expires (Unix timestamp). The default TTL is 86400 seconds (24 hours); configurable via CLOUDTAK_JWT_TTL. JWTs carry the user's role and group memberships as claims, so permission checks are stateless — CloudTAK validates the signature without a database round trip.

API keys

API keys are long-lived tokens suitable for automated service-to-service integrations. Generate them via the admin API:

curl -s -X POST https://tak.yourdomain.com:8443/api/token \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "sensor-array-east",
    "scopes": ["cot:write"],
    "rateLimit": 500
  }' | jq -r '.token'

Supported scopes and what they unlock:

Scope Grants access to
cot:write Inject CoT events via POST /api/cot
cot:read Query historical tracks via GET /api/cot
events:subscribe Connect to the WebSocket stream at /api/ws
mission:write Create, update, and delete missions
package:write Upload data packages and attachments
group:write Create groups and manage membership
admin All scopes plus user management and server configuration

Revoke a key at any time without affecting other integrations: DELETE /api/token/{id}. List active keys with GET /api/token — the response includes usage statistics (last used, total calls) useful for detecting abandoned integrations.

Base URL and rate limits

All REST endpoints are served under https://<host>:8443/api/. The default global rate limit is 100 requests per second per API key. Per-key limits are set at creation time via the rateLimit field (shown above). HTTP 429 responses include a Retry-After header. The OpenAPI specification is available at GET /api/docs as a Swagger UI and at GET /api/openapi.json as a raw JSON schema.

CoT injection: POST /api/cot

The CoT injection endpoint is the primary path for pushing position reports, contact markers, alerts, and any other CoT event into the TAK picture from external systems. It accepts both raw CoT XML and a convenience JSON format.

XML payload

For systems that already produce native CoT XML — sensors running legacy ATAK plugins, SIGINT platforms, ground radar feeds — post the raw envelope directly:

curl -s -X POST https://tak.yourdomain.com:8443/api/cot \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0" encoding="UTF-8"?>
<event version="2.0"
       uid="sensor-east-001"
       type="a-f-G-U-C"
       how="m-g"
       time="2026-05-29T14:32:00Z"
       start="2026-05-29T14:32:00Z"
       stale="2026-05-29T14:37:00Z">
  <point lat="50.4501" lon="30.5234" hae="145.0" ce="10.0" le="5.0"/>
  <detail>
    <contact callsign="SENSOR-EAST-01"/>
    <remarks>Ground radar contact, speed 12 kph, bearing 045</remarks>
    <group name="Surveillance" role="Team Member"/>
  </detail>
</event>'

Required CoT fields: uid (unique string per entity — reusing the same UID updates an existing track), type (CoT type hierarchy string — a-f friendly, a-h hostile, a-n neutral, a-u unknown), time, start, stale (ISO 8601 timestamps), and a <point> element with lat, lon, hae (height above ellipsoid in metres), ce (circular error), and le (linear error).

JSON payload

For integrations built on modern stacks without CoT XML libraries, use the JSON format:

curl -s -X POST https://tak.yourdomain.com:8443/api/cot \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "uid": "logistics-truck-07",
    "type": "a-f-G-U-C",
    "callsign": "LOG-07",
    "lat": 50.4610,
    "lon": 30.5190,
    "hae": 138.0,
    "ce": 5.0,
    "le": 3.0,
    "staleSeconds": 300,
    "remarks": "Resupply convoy, ETA checkpoint 14:45Z",
    "group": "Logistics",
    "role": "Team Member"
  }'

CloudTAK serializes this JSON to a full CoT XML envelope internally, setting time and start to the current server time and computing stale as now + staleSeconds. A successful injection returns HTTP 201 with the generated CoT UID in the response body. The event immediately propagates to all subscribed clients and the WebSocket stream.

Track streaming: WebSocket /api/ws

For real-time consumption of the tactical picture — building a custom dashboard, feeding an AI pipeline, archiving tracks to a data lake — the WebSocket endpoint provides a continuous, low-latency stream of CoT events as they arrive at the server.

Connecting and subscribing

// Node.js example using 'ws' library
const WebSocket = require('ws');

const ws = new WebSocket('wss://tak.yourdomain.com:8443/api/ws', {
  headers: { 'Authorization': `Bearer ${process.env.CLOUDTAK_API_KEY}` }
});

ws.on('open', () => {
  // Send subscription filter immediately after connection
  ws.send(JSON.stringify({
    type: 'subscribe',
    filter: {
      groups: ['Surveillance', 'Logistics'],   // only these groups
      bbox: [29.5, 50.0, 31.5, 51.0],          // [minLon, minLat, maxLon, maxLat]
      types: ['a-f-G', 'a-h-G', 'a-u-G']       // friendly/hostile/unknown ground
    }
  }));
});

ws.on('message', (data) => {
  const event = JSON.parse(data);
  if (event.type === 'cot') {
    console.log(`Track: ${event.payload.uid} at ${event.payload.lat},${event.payload.lon}`);
  } else if (event.type === 'ping') {
    ws.send(JSON.stringify({ type: 'pong' }));  // heartbeat — must respond
  }
});

The server sends a ping message every 30 seconds. Clients must respond with pong within 10 seconds or the connection is closed. Each message envelope has a type field (cot, track_update, mission_update, user_event, system_event, ping) and a payload object.

Updating filters on an active connection

Send a new subscribe message at any time to replace the current filter — no disconnect or reconnect required. This is useful for following an operation as it moves across geographic areas: update the bbox to the new area of operations without interrupting the stream.

Mission API: create, update, delete, and assign

Missions in CloudTAK are named operational contexts that group tracks, data packages, and users. The mission API allows external systems to create missions programmatically — for example, a planning tool that generates missions from an order of battle and pushes them to CloudTAK before an operation begins.

Creating a mission

curl -s -X POST https://tak.yourdomain.com:8443/api/mission \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "OPERATION CLEARWATER",
    "description": "Checkpoint seizure, Phase 1",
    "classification": "UNCLASSIFIED",
    "bbox": [30.10, 50.35, 30.25, 50.45],
    "groups": ["Alpha-Company", "ISR-Detachment"]
  }' | jq '{uid: .uid, name: .name}'

The response includes the mission uid (a UUID) used in all subsequent operations. The groups array assigns the mission to named groups immediately — members of those groups see the mission in their ATAK/WinTAK mission feed on next sync. Assign additional groups later via PATCH /api/mission/{uid}/groups.

Updating and deleting missions

Update mission metadata (name, description, classification, bbox) with PATCH /api/mission/{uid}. All connected subscribers receive a mission_update WebSocket event. Delete a mission with DELETE /api/mission/{uid} — this also removes all attachments and disassociates tracks. Soft-delete is available via PATCH /api/mission/{uid} with "archived": true, which hides the mission from active feeds but retains data for post-operation analysis.

Data package API: MBTiles, KMZ, GeoJSON, and versioning

Data packages attached to missions distribute map layers and reference data to field devices. CloudTAK accepts three formats: MBTiles (raster and vector tile databases), KMZ (KML with embedded assets), and GeoJSON (vector features). Uploads use multipart form data:

# Upload an MBTiles layer for offline map coverage
curl -s -X POST \
  https://tak.yourdomain.com:8443/api/mission/${MISSION_UID}/attachment \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@/data/aoi-sector-north.mbtiles" \
  -F "name=Sector North Basemap" \
  -F "contentType=application/vnd.mbtiles" \
  | jq '{attachmentId: .id, version: .version}'

CloudTAK validates the file format on upload and rejects corrupt MBTiles files before they can reach clients. Each upload creates a new version — the API returns the version number in the response. To list all versions of an attachment:

GET /api/mission/{missionUid}/attachment/{attachmentId}/versions

Clients always receive the latest version unless a specific version is pinned in the mission configuration. This allows staging a map update — uploading version 2 without activating it — and pushing it to field devices at a controlled time with PATCH /api/mission/{uid}/attachment/{attachmentId}/activate.

Standalone map layers (not tied to a specific mission) are uploaded via POST /api/layer. These are available to all users on the server. For offline-first field deployments, see our guide on MBTiles and PMTiles for offline maps.

Groups and channels: visibility filtering and track access control

CloudTAK groups implement track visibility segregation. A track carrying a group tag is only visible to connected clients that are members of that group. This enables need-to-know separation within a shared TAK server: ISR assets share tracks only with the intelligence cell, logistics tracks are visible to the supply chain but not to all front-line units.

Creating a group and assigning users

# Create a group
curl -s -X POST https://tak.yourdomain.com:8443/api/group \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "ISR-Detachment", "description": "Intelligence, Surveillance, Reconnaissance cell"}' \
  | jq '.id'

# Add a user to the group
curl -s -X POST https://tak.yourdomain.com:8443/api/group/ISR-Detachment/user \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username": "isr-analyst-01"}'

Group membership controls three things: which tracks the client receives in the WebSocket stream (only tracks tagged with the client's groups), which missions appear in the client's mission feed, and which data packages are distributed on connection. A client can belong to multiple groups simultaneously — they receive the union of all track sets across their groups. The group field in a CoT injection payload (shown in the XML example above) assigns that track to a specific group. Tracks without a group tag are visible to all users on the server.

TAKpilot as an integration example

TAKpilot is Corvus Intelligence's AI copilot for TAK-based operations. It is a concrete example of deep CloudTAK API integration: TAKpilot subscribes to the WebSocket track stream, processes natural language commands from operators, and executes them as CloudTAK API calls — translating human intent directly into tactical picture updates.

A natural language command such as "mark grid 38T YQ 45123 67890 as hostile contact, assign to Alpha-Company mission" triggers the following API sequence in TAKpilot:

  1. Convert MGRS 38T YQ 45123 67890 to decimal degrees (internal conversion, no API call).
  2. POST /api/cot with type a-h-G (hostile ground), the converted coordinates, and a system-generated callsign prefixed AI-HOSTILE- plus a short UUID.
  3. GET /api/mission?name=Alpha-Company to resolve the mission UID from the operator's partial name reference.
  4. POST /api/mission/{uid}/cot to associate the injected CoT event UID with the mission, making it visible in the Alpha-Company mission feed.
  5. Respond to the operator via the TAKpilot chat interface confirming the action, including the generated callsign and coordinates in the confirmation.

TAKpilot also uses the WebSocket stream reactively: when a new hostile track appears (type a-h) within a monitored bounding box, it can automatically trigger threat pattern analysis and post a synthesized assessment as a CoT remark visible to the operator group — without waiting for an explicit command. This closed-loop integration — consuming from the stream and writing back to it — is the architectural pattern that makes AI copilots effective in TAK environments.

Webhooks: subscribing to events and forwarding to external systems

Webhooks allow CloudTAK to push events to external systems rather than requiring those systems to poll the REST API or maintain a persistent WebSocket connection. This is well suited to integration patterns where an external system needs to react to specific lifecycle events: a new user joining the network, a mission status change, or a high-priority CoT alert type appearing.

Registering a webhook

curl -s -X POST https://tak.yourdomain.com:8443/api/webhook \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "kafka-bridge",
    "url": "https://integration.yourdomain.com/cloudtak-events",
    "events": ["new_track", "mission_update", "user_join"],
    "secret": "hmac_signing_secret_32chars_min",
    "active": true
  }' | jq '{id: .id, status: .status}'

CloudTAK signs each webhook delivery with an HMAC-SHA256 signature computed over the raw JSON body using your secret, delivered in the X-CloudTAK-Signature header. Verify the signature on your receiving end to prevent spoofed webhook calls:

// Express.js webhook receiver with signature verification
const crypto = require('crypto');

app.post('/cloudtak-events', express.raw({type: 'application/json'}), (req, res) => {
  const sig = req.headers['x-cloudtak-signature'];
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).send('Signature mismatch');
  }

  const event = JSON.parse(req.body);
  // Forward to Kafka, Slack, SIEM, etc.
  kafkaProducer.send({ topic: 'tak-events', messages: [{ value: req.body }] });
  res.status(200).send('OK');
});

Delivery failures (non-2xx response or timeout after 5 seconds) trigger exponential backoff retries: 1 second, 2 seconds, 4 seconds, 8 seconds, 16 seconds — then the event is dropped and logged. View delivery history and retry manually via GET /api/webhook/{id}/deliveries. For high-frequency CoT events (hundreds per second), use the WebSocket endpoint instead — webhooks add HTTP round-trip overhead per event that becomes prohibitive at volume.

Supported webhook event types

  • new_track — a new UID appears on the picture for the first time (subsequent position updates are track_update, not new_track).
  • track_stale — a track's stale timestamp has passed with no update; the entity has gone dark.
  • mission_update — a mission was created, modified, archived, or deleted.
  • user_join — a new client connected and authenticated.
  • user_leave — a client disconnected or session expired.
  • cot_alert — a CoT event with type prefix b-a (alert/alarm) was received — useful for routing emergency beacons and 9-line MEDEVACs to an external notification system.