Telemetry
POST /telemetry is the single write path into the platform. Gateways, simulators, and batch jobs push JSON arrays of records; everything the Topology projection and the SNR prediction features consume flows through here.
POST /telemetry
Content-Type: application/json
Auth: x-api-key. See Authentication.
Request body
The body is a bare JSON array of records, not a wrapper object:
[
{
"measurement": "satellite",
"time": "2026-07-09T14:31:42Z",
"tags": { "node_id": "SAT-0012", "node_type": "satellite" },
"fields": { "lat": 47.61, "lon": -122.33, "altitude_km": 550.2, "utilization": 0.42 }
},
{
"measurement": "link",
"time": "2026-07-09T14:31:42Z",
"tags": { "link_id": "SAT-0012:GS-SEA-01", "node_id": "SAT-0012" },
"fields": { "snr_db": 12.4, "utilization": 0.55 }
}
]
| Field | Type | Required | Description |
|---|---|---|---|
measurement | string, non-empty | yes | Series name. Any non-empty name is accepted; there is no write allowlist. |
time | ISO 8601 datetime | yes | Sample timestamp. |
tags | object of string values | no, default empty | Indexed dimensions. Use node_id (and link_id for links) so topology and predictions can key entities. |
fields | object of number or string values | no, default empty | The measured values. |
Two things happen server-side that you do not control:
- Tenant stamping. Every record is force-stamped with your tenant, derived from the API key. You cannot write into another tenant, and any tenant value you supply is overwritten.
- No write allowlist. Unlike topology reads, writes accept any
measurementname. But onlylink,satellite,ground_station,weather, andtelemetryare readable back throughGET /topology, so records under other names are stored but invisible to the current read surface. Stick to the five canonical measurements unless the platform team has agreed otherwise.
Response: 202 acknowledgment
Ingest is acknowledged, not silently swallowed:
{
"write_id": "w-8c41f2ae",
"accepted_count": 118,
"rejected_count": 2,
"rejected_indices": [4, 87]
}
| Field | Description |
|---|---|
write_id | Server identifier for this write; log it for support and reconciliation. |
accepted_count | Records written. |
rejected_count | Records rejected. |
rejected_indices | Zero-based indices into your submitted array, or null when nothing was rejected. |
Handling partial rejection
A 202 with rejected_count > 0 means the accepted records are already written. Never resubmit the whole batch: the accepted rows would be written a second time. Instead:
- Map
rejected_indicesback to the records you sent. - Fix or drop those records (a common cause is a malformed field value).
- Resubmit only the corrected records.
Limits
| Limit | Value | Error |
|---|---|---|
| Records per batch | 1,000 | 413 {"error": "batch_too_large", "max": 1000, "received": N} |
| Body size | 1 MB | 413 {"error": "request_too_large", ...} |
Content-Length | required | 411 {"error": "length_required"} |
Chunk on record count first; 1,000 typical records fit well under 1 MB. A batching loop is in Ingest telemetry.
Idempotency warning
Telemetry ingest is not idempotent and the platform does not deduplicate. Retrying a batch that was already written writes every record twice, which skews utilization rollups and prediction features.
- Safe to resubmit:
429,413,422,400. These were rejected before ingest. - Ambiguous: timeouts and dropped connections after the request was sent. The batch may or may not have landed. Log the batch and its intended
write_idcontext for reconciliation instead of auto-replaying, or accept the double-write risk explicitly.
Example
curl -sS -X POST \
-H "x-api-key: $CONSTELLATION_API_TOKEN" \
-H "Content-Type: application/json" \
-d '[
{
"measurement": "ground_station",
"time": "2026-07-09T14:31:55Z",
"tags": { "node_id": "GS-SEA-01", "node_type": "ground_station" },
"fields": { "lat": 47.44, "lon": -122.3, "utilization": 0.61 }
}
]' \
"https://api.constellation.space/telemetry"
Errors
| Status | Body | Cause |
|---|---|---|
422 | detail list | A record failed schema validation (empty measurement, bad time, wrong tag types). |
413 | batch_too_large or request_too_large | Over the batch or body cap; split and resend. |
411 / 400 | length_required / invalid_content_length | Content-Length problems. |
401 / 403 / 404 | see Authentication | Key or tenant problems. |
429 | rate_limited or auth_lockout | Rate limits; honor Retry-After. |
503 | {"error": "telemetry_unavailable", "upstream": "influx", "retry_after_seconds": 5} | Backend transient; retrying is a write, so mind the idempotency warning. |
Related
- Topology: how ingested records surface as fleet state.
- Ingest telemetry example: batching loop with partial-rejection handling.
- Integration bundle: the fleet agent that ships this loop for you.