Skip to main content

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​

StatusWhen
200Accepted for fusion. data.accepted is how many.
400An empty batch, more than 5000 observations in one, or an observation missing a field. The error names it.
401No credential, or one that is spent.
403A 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.