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:
- Convert MGRS
38T YQ 45123 67890to decimal degrees (internal conversion, no API call). POST /api/cotwith typea-h-G(hostile ground), the converted coordinates, and a system-generated callsign prefixedAI-HOSTILE-plus a short UUID.GET /api/mission?name=Alpha-Companyto resolve the mission UID from the operator's partial name reference.POST /api/mission/{uid}/cotto associate the injected CoT event UID with the mission, making it visible in the Alpha-Company mission feed.- 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 aretrack_update, notnew_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 prefixb-a(alert/alarm) was received — useful for routing emergency beacons and 9-line MEDEVACs to an external notification system.