Signals API
Connect a real sensor feed to DomeCommand over a simple HTTP API. Authenticate, POST detections, and the live picture is driven by your data.
:::note This is the integration page Everywhere else these docs describe the product. This page is for engineers wiring a sensor feed, bridge or middleware into DomeCommand. It documents the live HTTP API. :::
Every sensor, bridge or fusion box speaks to DomeCommand the same way: it POSTs detections to the signals endpoint. Each detection is a signal, one observation from one sensor. DomeCommand ingests the stream, fuses it into tracks (see Sensor fusion), and drives the live picture from it.
Base URL
Everything is under /api:
https://<your-backend-host>/api
Authentication
Every request is authenticated and scoped to a workspace.
Authenticate with either a bearer token or a workspace API key:
Authorization: Bearer <jwt>
# or
X-Api-Key: dak_<your_api_key>
And always name the workspace the data belongs to:
X-Workspace-Id: wsp_<id>
A request with no credential gets 401; one whose credential does not reach the
named workspace gets 403; a read-only role on a write gets 403 as well.
:::tip Use an API key for machine-to-machine feeds
A long-lived, workspace-scoped API key (prefixed dak_) is the right credential for a
sensor bridge or middleware that runs unattended. Bearer tokens suit interactive
sessions. Mint one at POST /api/keys; it is shown once and hashed thereafter.
:::
Push detections, POST /api/signals
Send one tick's worth of observations in one request. Batching is not an optimisation here, it is the contract: a source accumulates and flushes on a tick, and a sender that posts one detection per message turns every message into a pipeline cycle.
The observations are the frozen obs.v1 shape, described field by field on
Observations & events. Two things a first request usually gets
wrong: a position on the detection is alt_m, while the sensor's own position is
alt_m_agl, and every detection carries a signal naming its modality. For the whole path,
from key to track, see Working with assets. source says which door the batch
enters through: live (something out there saw something) or simulated, which
is how a generator that knows it is inventing feeds a run without its output being
mistaken for a real detection.
curl -X POST https://<your-backend-host>/api/signals \
-H "X-Api-Key: $DOME_KEY" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"source": "live",
"observations": [
{
"schema": "obs.v1",
"obs_id": "giraffe-01-000148",
"t": "2026-06-02T08:14:22Z",
"platform": { "id": "giraffe-radar-01",
"geo": { "lat": 1.2841, "lon": 103.8510, "alt_m_agl": 4.0 } },
"sensor": { "id": "giraffe-01-radar", "modality": "radar" },
"detection": {
"class": "uav_multirotor",
"class_conf": 0.92,
"geo": { "lat": 1.2843, "lon": 103.8512, "alt_m": 120.0 },
"signal": { "modality": "radar" }
},
"provenance": { "decoder": "my-bridge/1.2" },
"quality": { "confidence": 0.92 }
},
{
"schema": "obs.v1",
"obs_id": "rf-03-000512",
"t": "2026-06-02T08:14:22Z",
"platform": { "id": "rf-detector-03" },
"sensor": { "id": "rf-03-df", "modality": "rf" },
"detection": {
"class": "uav_multirotor",
"class_conf": 0.74,
"signal": { "modality": "rf", "freq_mhz": 5806.0, "bearing_deg": 206.0 }
},
"quality": { "confidence": 0.74, "bearing_sigma_deg": 4.0 }
}
]
}'
A successful call answers with how many were taken for fusion:
{ "ok": true, "data": { "accepted": 2 } }
From JavaScript
await fetch("https://<your-backend-host>/api/signals", {
method: "POST",
headers: {
"X-Api-Key": key,
"X-Workspace-Id": workspaceId,
"Content-Type": "application/json",
},
body: JSON.stringify({
source: "live",
observations: flushed, // one tick's worth, not one detection
}),
});
Limits and refusals
| Status | When |
|---|---|
200 | Accepted for fusion. data.accepted is how many. |
400 | An empty batch, more than 5000 observations in one, or an observation missing a field. The error names it. |
401 | No credential, or one that is spent. |
403 | A credential that does not reach the named workspace, or a read-only role. |
Your credential names the workspace. The body never does, and a body that tried would be ignored.
Read a track back, GET /api/tracks
Detections go in; tracks come out.
curl https://<your-backend-host>/api/tracks \
-H "X-Api-Key: $DOME_KEY" -H "X-Workspace-Id: $WORKSPACE_ID"
That is the read for a service that wants the live track set. A console hydrates once
from GET /api/picture and then holds GET /api/stream instead. See
Picture & stream. Both exist because reading the whole
picture to get at part of it is the thing to avoid.
Everything the pipeline persists (observations, tracks, alerts, plans, taskings) is
queryable back out at GET /api/events.
How the ingested stream becomes the picture
Once your detections are flowing in, DomeCommand associates them by contact, fuses across sensor kinds, and renders the result as the live air picture the operator works from. You push raw detections; DomeCommand turns them into one clean, current view.
Read how that fusion works in Sensor fusion.