Skip to main content

Telemetry

POST /telemetry is the HTTP telemetry write endpoint. Gateways, simulators, and batch jobs push JSON arrays of records; the telemetry behind the Topology projection and prediction features flows through here.

POST /telemetry
Content-Type: application/json

Auth: x-api-key, scope telemetry:write. 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": { "entity_id": "SAT-0012", "entity_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": { "entity_id": "SAT-0012-GS-SEA-01" },
"fields": { "snr_db": 12.4, "utilization": 0.55 }
}
]
FieldTypeRequiredDescription
measurementstring, non-emptyyesSeries name. Any non-empty name is accepted on write, on both REST and gRPC. The six-name set only narrows the GET /topology?measurement= read filter; see Transports.
timeISO 8601 datetimeyesSample timestamp. A naive (no-offset) value is read as UTC.
tagsobject of string valuesno, default emptyIndexed dimensions, at most 64 per record. Set entity_id on every record: Topology keys the entity on it, and predictions look up a model's inputs by it.
fieldsobject of boolean, numeric, or string valuesInclude at least one fieldMeasured values. Records without fields are rejected. Numeric values are stored as floats; use strings for exact large-integer identifiers.

Record handling:

  • Request identity. Use the key and endpoint issued for your account or deployment. Client-supplied ownership tags do not establish an isolation boundary. See Tenant isolation.
  • No write allowlist on either transport. POST /telemetry and the gRPC TelemetryIngest.Write both accept any measurement name, and any tags and fields. The six-name set (link, satellite, ground_station, weather, attenuation, telemetry) constrains only the measurement query filter on GET /topology. An unfiltered topology read still projects any measurement, so a custom name written over either transport is stored and readable; you just cannot narrow a topology read to it by name. See Transports.
  • Entity keying. GET /topology projects one row per entity keyed on the record's entity_id tag. Over REST, a record with no entity_id tag is ingested but skipped by the topology projection. Over gRPC it is rejected outright. Set entity_id on anything you expect to read back as fleet state.

Response: 202 acknowledgment​

A 202 response reports acceptance and rejection counts:

{
"write_id": "w-8c41f2ae",
"accepted_count": 118,
"rejected_count": 2,
"rejected_indices": [4, 87]
}
FieldDescription
write_idServer identifier for this write; log it for support and reconciliation.
accepted_countRecords accepted by the configured ingest path. In asynchronous mode, acceptance precedes durable storage.
rejected_countRecords rejected.
rejected_indicesZero-based indices into your submitted array, or null when nothing was rejected.

Records with no fields or timestamps before 2000-01-01T00:00:00Z are rejected individually. HTTP responses identify their positions in rejected_indices. Future timestamps are clamped to the ingest time.

A synchronous acknowledgment follows the store write. Asynchronous ingestion acknowledges buffer acceptance and can lose buffered data if the process fails before flushing. Verify delivery through topology when persistence matters; a 202 alone is not a universal durability guarantee.

Handling partial rejection​

A 202 with rejected_count > 0 means part of the batch was rejected. Avoid replaying accepted records:

  1. Map rejected_indices back to the records you sent.
  2. Correct the rejected records, including missing fields or invalid timestamps.
  3. Resubmit only the corrected records.

Limits​

LimitValueError
Records per batch1,000413 {"error": "batch_too_large", "max": 1000, "received": N}
Tags per record64400 {"error": "invalid_input", "field": "tags"}
Body size1 MiB413 {"error": "request_too_large", ...}
Content-Lengthrequired411 {"error": "length_required"}

Enforce both the record count and encoded body-size limits. Inspect per-record rejections in successful responses. See Ingest telemetry.

Choose a sampling cadence for your equipment, then batch within the request-size and rate limits.

Transports​

Telemetry can be published over HTTP or gRPC. Both require ingestion access. The fleet agent uses gRPC by default and also supports HTTP egress.

REST POST /telemetrygRPC TelemetryIngest.Write
Authx-api-key headerx-api-key request metadata
Records per call1,00010,000
PayloadJSON arrayprotobuf TelemetryRecord
AccessIngestion enabled with the required scopeIngestion enabled with the required scope
measurementany non-empty nameany non-empty name
entity_id tagoptionalrequired, non-empty
Booleanstrue / false preservedtrue / false preserved
Integerscoerced to floatcoerced to float
Missing fields or pre-2000 timestampPer-record rejection with indicesPer-record rejection count, without indices
Invalid field or tag contentRequest rejectedCall rejected with INVALID_ARGUMENT
Designed shapeany client, any cadenceone warm channel per ground station, batched write about once a second

gRPC uses a unary Write call. Its default batch cap is 10,000 records and can vary by environment. HTTP middleware lockout and rate-limit thresholds do not apply to the gRPC listener; use the RPC status codes below for recovery.

Cross-transport hazards​

  • Field types pin per field. The time-series store fixes one type per field name. Keep each field's type consistent across producers and transports.
  • entity_id is required on gRPC. REST ingests a record without it (topology just skips it); gRPC rejects the whole call. Set entity_id on every record.

Status codes​

StatusMeaning
INVALID_ARGUMENTinvalid_input:<field>, where <field> is entity_id or tags. Not retryable — the whole call was rejected and the payload must be fixed
UNAUTHENTICATEDMissing or invalid API key
PERMISSION_DENIEDMissing telemetry:write scope or ingestion entitlement
RESOURCE_EXHAUSTEDbatch_too_large (split and resend) or ingest_buffer_full (back off, resend the same payload)
UNAVAILABLEAuthentication infrastructure, entitlement lookup, or storage is unavailable. Back off before retrying.
INTERNALA store-side deterministic failure. Inspect the record and contact support; do not replay in a tight loop.

The two RESOURCE_EXHAUSTED cases carry distinct messages because the remedies differ: one needs a smaller batch, the other needs patience.

TelemetryIngest.Health is open and takes no API key, but do not branch on its ok field — it is always true. Liveness travels in the gRPC status: an unhealthy server aborts with UNAVAILABLE rather than answering ok=false. A client checking health must catch the RPC error, not read the body.

The endpoint is on port 443, TLS required, HTTP/2 only — there is no plaintext port: ingest.api.constellation.space:443. See Environments.

Idempotency warning​

The API does not offer a request idempotency key or an exactly-once delivery guarantee. Treat a timed-out write as ambiguous and reconcile delivery before replaying it.

  • For authentication failures, rate limits, schema errors, or body caps, resolve the cause and follow retry guidance. A store-side field conflict may involve a partial write; reconcile the batch before retrying.
  • 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_id context for reconciliation instead of auto-replaying, or accept the double-write risk explicitly.

Example​

Use Ingest telemetry to publish a current sample and read it back.

Errors​

StatusBodyCause
400{"error": "invalid_input", "field": "tags"}More than 64 tags on a record; fails the batch.
400{"error": "field_type_conflict", "field": "<field>", ...}The field was written with a different type. Correct it and reconcile any partial write.
403{"error": "live_ingest_not_entitled"}Account ingestion access is not enabled.
503{"error": "ingest_buffer_full", "retry_after_seconds": 1}Buffer capacity exhausted. Honor Retry-After and retry.
503{"error": "entitlement_unavailable"}Ingestion access could not be checked. Back off.
422detail listA record failed schema validation (empty measurement, bad time, wrong tag types).
413batch_too_large or request_too_largeOver the batch or body cap; split and resend.
411 / 400length_required / invalid_content_lengthContent-Length problems.
401 / 403auth_failed / insufficient_scopeKey invalid, or the key lacks telemetry:write. See Authentication.
429rate_limited or auth_lockoutRate 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.

Next: Predictions — how ingested telemetry becomes model forecasts.