Datasets
Create and read datasets, and append rows to one over HTTP.
A dataset is one run: a flight, a test firing, a bench session. It belongs to an
experiment, it owns a set of channels, and its rows live in a dedicated
time-series table named hyper-<datasetId>.
Two things on this page are easy to conflate, so start here:
| You want | Use |
|---|---|
| An empty dataset you will fill later | POST /api/datasets/create |
| A dataset ready for live telemetry, with channels | POST /api/streams/register |
create makes a row and nothing else — no storage and no channels. Until
one exists, appending to it fails with a 409. register makes the row, the
channel snapshot and the storage in one call, which is what you want in almost
every case.
There is no delete endpoint. Deleting a dataset destroys its telemetry, and a key pasted into a producer's config should not be able to do that. Delete from the console.
List datasets
/api/datasets/listscope datasets:readProp
Type
curl -s "https://app.xpectraflow.com/api/datasets/list?experimentId=$EXPERIMENT_ID" \
-H "x-api-key: $XPECTRA_API_KEY"[
{
"id": "3d81f0a2-55c7-4f9e-9a1b-77c2e0d43a10",
"name": "sortie-2026-07-31-0914-sysid1",
"experimentId": "9f2c1b44-7e30-4a11-b8d2-1f4a6c0e8b91",
"status": "completed",
"rowCount": 1204998,
"startedAt": "2026-07-31T09:14:02.310Z",
"endedAt": "2026-07-31T09:41:55.002Z",
"timeOffsetMs": 0,
"codec": "mavlink",
"storageLocation": "timescale",
"meta": { "storageShape": "narrow", "dialect": "ardupilotmega" },
"createdAt": "2026-07-31T09:13:58.771Z"
}
]Three fields deserve attention:
status—queued→running→completedorfailed. A live stream sits atrunninguntil the consumer sees no frames for 30 seconds, then closes it.storageLocation—timescalemeans queryable now.s3means the run has been archived to Parquet and needs restoring first. A poller checking "is my data ready" wants this, notstatus.rowCount— approximate. Both the ingest pipeline and the append route maintain it, and neither takes a lock to do so.
Get one dataset
/api/datasets/getscope datasets:readProp
Type
Both ids are required. The dataset id alone would be enough to find the row — asking for the experiment as well is what makes this run the same ownership check the console runs, rather than a second implementation of it.
curl -s "https://app.xpectraflow.com/api/datasets/get?experimentId=$EXPERIMENT_ID&datasetId=$DATASET_ID" \
-H "x-api-key: $XPECTRA_API_KEY"Create a dataset
/api/datasets/createscope datasets:writeProp
Type
curl -s -X POST https://app.xpectraflow.com/api/datasets/create \
-H "x-api-key: $XPECTRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"experimentId": "'"$EXPERIMENT_ID"'",
"name": "bench-run-014"
}'{
"id": "3d81f0a2-55c7-4f9e-9a1b-77c2e0d43a10",
"name": "bench-run-014",
"experimentId": "9f2c1b44-7e30-4a11-b8d2-1f4a6c0e8b91",
"status": "queued",
"rowCount": 0,
"startedAt": null,
"endedAt": null,
"timeOffsetMs": 0,
"codec": "frame",
"storageLocation": "timescale",
"meta": null,
"createdAt": "2026-07-31T12:00:00.000Z",
"hypertableName": "hyper-3d81f0a2-55c7-4f9e-9a1b-77c2e0d43a10",
"ingestToken": "b7f1…9ac2"
}ingestToken is returned once. Only a bcrypt hash is stored and there is no
endpoint that recovers it — the same treatment as an API key, for the same
reason. If you lose it, create another dataset.
Update a dataset
/api/datasets/updatescope datasets:writeProp
Type
Like the experiment update, this replaces rather than patches: name and
status are required, and startedAt/endedAt are cleared when omitted.
timeOffsetMs and daqName are the exceptions and are only touched when you
send them.
Append rows
/api/datasets/appendscope telemetry:writeThe HTTP way to add data to a dataset that already has storage. Good for backfills, slow sensors, and filling in what a producer missed. It is not the high-rate path — a sustained feed belongs on gRPC, where a columnar frame avoids re-sending a timestamp per row.
Note the scope: telemetry:write, not datasets:write. This writes telemetry,
not metadata, and it is the same scope gRPC ingest requires — so a producer that
streams and backfills needs one credential rather than two.
Prop
Type
curl -s -X POST https://app.xpectraflow.com/api/datasets/append \
-H "x-api-key: $XPECTRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"experimentId": "'"$EXPERIMENT_ID"'",
"datasetId": "'"$DATASET_ID"'",
"channels": ["ch_0", "chamber_pressure"],
"rows": [
{ "time": "2026-07-31T12:00:00.000Z", "values": [1.50, 214.7] },
{ "time": "2026-07-31T12:00:00.010Z", "values": [1.52, 215.1] },
{ "time": "2026-07-31T12:00:00.020Z", "values": [1.49, null] }
]
}'{ "inserted": 3, "duplicates": 0, "rowCount": 1204998 }Rules that will bite you
Append errors
| Status | code | When |
|---|---|---|
| 400 | invalid_request | Duplicate timestamps, unknown channel, values length mismatch, unparseable time |
| 403 | insufficient_scope | Key lacks telemetry:write |
| 404 | not_found | No such dataset, or not yours |
| 409 | conflict | Narrow dataset, or the dataset has no storage yet |