XpectraFlow docs
Ingestion

Register a stream

One call that creates the dataset, its channel columns, and the storage behind them.

Every ingestion path starts here. POST /api/streams/register creates three things at once:

  1. a dataset row for this run
  2. a channel snapshot — your channels, mapped to ch_0, ch_1, …
  3. the time-series table the data will actually live in, with the column types your declared dataTypes imply

The third is why this call and not datasets/create: registration is the only HTTP path that leaves you with storage you can immediately write to. If it fails to provision, it fails the whole request rather than handing back a dataset whose first append would be refused.

Do not use POST /api/datasets/create for this. It makes a dataset row and nothing else — no channels, no storage — and appending to it fails with a 409 until something else provisions the table. register is what you want in almost every case.

The call

POST/api/streams/registerscope datasets:write

Prop

Type

curl -s -X POST https://app.xpectraflow.com/api/streams/register \
  -H "x-api-key: $XPECTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "experimentId": "'"$EXPERIMENT_ID"'",
    "datasetName": "cold-flow-014",
    "sampleRateHz": 1000,
    "format": "columnar-v1",
    "channels": [
      { "name": "thrust",           "unit": "kN",  "dataType": "float" },
      { "name": "chamber_pressure", "unit": "bar", "dataType": "float" },
      { "name": "valve_open",       "unit": "",    "dataType": "bool"  }
    ]
  }'
{
  "datasetId": "3d81f0a2-55c7-4f9e-9a1b-77c2e0d43a10",
  "hypertableName": "hyper-3d81f0a2-55c7-4f9e-9a1b-77c2e0d43a10",
  "channelIndices": [0, 1, 2],
  "ingestToken": "b7f1…9ac2"
}

Reading the response

Declaring channels well

Declare all of them up front

The order fixes the indices, and the column types are set from dataType when the table is created. Adding a channel later is possible but does not widen the table — see the channels page.

Get dataType right the first time

floatDOUBLE PRECISION, intBIGINT, boolBOOLEAN. Changing it afterwards relabels the channel; it does not convert data already written or alter the column.

Use names you will recognise in six months

chamber_pressure, not ch3. The name is what append accepts as an alias, and what appears on every chart axis.

Narrow streams

Omit channels entirely and set the shape:

curl -s -X POST https://app.xpectraflow.com/api/streams/register \
  -H "x-api-key: $XPECTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "experimentId": "'"$EXPERIMENT_ID"'",
    "datasetName": "sortie-2026-07-31-sysid1",
    "storageShape": "narrow",
    "codec": "mavlink",
    "dialect": "ardupilotmega",
    "format": "mavlink-raw-v1"
  }'

channelIndices comes back empty, which is correct: a narrow dataset's signals are discovered from the data as it arrives, because which messages a vehicle sends is not knowable before it starts talking.

A wide stream with no channels is rejected — a wide table's columns are its schema, so it cannot be provisioned without them:

"details": [{ "field": "channels", "message": "at least one channel is required for a wide stream" }]

Errors

StatuscodeWhen
400invalid_requestNo channels on a wide stream, unknown codec, more than 4096 channels
403insufficient_scopeKey lacks datasets:write
404not_foundNo such experiment in this organization

Next

On this page