Open REST API
Pulling program runs, quality data, and raw capture files from the open REST API — polling, incremental sync, and reconciliation patterns.
The open REST API is the pull half of an integration: plain HTTP and JSON, served by cheetah-service on the inspection cell. You call it on your own schedule — watching a program run finish, reconciling after downtime, backfilling history, or pulling raw capture data for your own analysis. For when to pull versus when to let event dispatch push to you, see Developers.
Where the API lives#
The API is served from the cell itself on port 8000, reachable from your factory network. Examples on these pages use http://cheetah-cell.local:8000 — substitute your cell's hostname or address.
Every response carries an X-Trace-ID header — log it, and include it when you contact support about a specific request.
Observe, don't operate#
The integration surface treats program runs as something you observe, not something you drive. Listing, fetching, and reconciling are open; starting and cancelling a program run stays with the cell's own applications during beta. If your integration needs to trigger inspections programmatically, talk to us.
What you can pull#
| Resource | Endpoints |
|---|---|
| Program runs | GET /runs · GET /runs/active · GET /runs/{run_id} |
| Programs | GET /programs · GET /programs/{program_id} · GET /programs/{program_id}/runs |
| Quality report | GET /runs/{run_id}/quality-report |
| Run files | GET /runs/{run_id}/files · GET /runs/{run_id}/files/{name} |
| Raw capture data | Point clouds, height maps, and luminance images per frame (GET /sensor/frames/{frame_id}/…) · CL-sweep CSV per step |
| Webhook management | /webhooks and /webhook-deliveries — covered in Webhooks |
| Health | GET /health |
Per-endpoint request and response schemas are the API reference's job. The shared conventions — identifiers, pagination, errors, retries — live in API concepts.
Poll one program run#
When you hold a run_id — from a lifecycle event, or noted when the inspection was started at the cell — fetch it until it reaches a terminal status:
curl http://cheetah-cell.local:8000/runs/0196a3f2-7c1e-7d2a-b3a4-9e8d1c2b3a4f
The response's status moves through the sequence documented in the data model: only completed, failed, and cancelled are terminal. A poll interval of a few seconds is plenty — a program run's life is measured in minutes.
The full program-run detail also carries per-step results, and for capture steps the frame identifiers: results.frame_ids lists every captured frame, and each capture_frame step's result.frame_id ties a frame to its step. Those ids are what the raw-data endpoints take.
Incremental sync with updated_since#
The workhorse pattern for keeping your system current is a high-water-mark loop against GET /runs:
- Request every program run updated since your last mark.
- Upsert each into your store, keyed on the program run's
id. - Advance the mark to the latest
updated_atyou saw. - Sleep; repeat.
import time
import httpx
BASE = "http://cheetah-cell.local:8000"
def sync_runs(mark: str | None) -> str | None:
"""One sync pass. Returns the new high-water mark."""
offset = 0
while True:
params: dict = {"limit": 200, "offset": offset}
if mark:
params["updated_since"] = mark
page = httpx.get(f"{BASE}/runs", params=params).raise_for_status().json()
for run in page["runs"]:
upsert_run(run) # keyed on run["id"] — re-processing is harmless
if mark is None or run["updated_at"] > mark:
mark = run["updated_at"]
offset += len(page["runs"])
if offset >= page["total"]:
return mark
mark = None # first pass backfills everything
while True:
mark = sync_runs(mark)
time.sleep(30)
Three properties make this loop safe to run unattended:
- Re-processing is harmless because the upsert is keyed on the program run's
id. Seeing the same program run twice — page drift, an overlap window, a crash-restart from an older mark — collapses to one row. - Pages drift toward duplicates, not gaps. Lists are ordered newest-first, so a program run created mid-walk shifts later pages down and you re-see an item; you never skip one. The mechanics are in Pagination.
- The first pass is the backfill. With no
updated_since, the same loop walks the full history — there is no separate backfill code path.
After a crash, resume from the last mark you persisted — or from a slightly older one if you want the overlap as insurance.
Reconcile after an outage#
When your webhook receiver has been down, deliveries may have failed. Recovery is the same loop: run an incremental sync from your last mark, and the upsert absorbs everything the events would have told you. Filters narrow the sweep when you know what you're looking for — GET /runs accepts program_id, status, and serial_number alongside updated_since.
Pull run files#
Every file a program run produces — CL sweep CSVs, sensor frame files, co-processor outputs — is available through a single consolidated surface.
GET /runs/{run_id}/files returns all files for a run. Each entry is a FileMetadata object:
| Field | Type | Notes |
|---|---|---|
name |
string | Unique file name within the run. Pass to the download endpoint. |
type |
string | File category — one of pointcloud, image, array, measurement, report. |
content_type |
string | MIME type — e.g. text/csv, application/octet-stream. |
size_in_bytes |
integer | null | File size at upload time. null when size is unknown. |
description |
string | null | Human-readable label, if set. |
href |
string | Absolute download URL. |
step_index |
integer | null | Program step that produced this file (1-indexed), if applicable. |
frame_id |
string (UUID) | null | Sensor frame this file belongs to, if applicable. |
created_at |
string (ISO 8601) | When the file was stored. |
updated_at |
string (ISO 8601) | Last modification time. |
deleted_at |
string (ISO 8601) | null | Set when the file's bytes were removed by a retention policy. null for live files. |
Retention and deletion. When a retention policy removes a file's bytes, the metadata entry stays in the list with deleted_at set. A request to GET /runs/{run_id}/files/{name} for a deleted file returns 410 Gone. Entries with deleted_at set are included in the list so you can tell the difference between "this file was never produced" (absent from the list) and "this file existed but its bytes are gone" (deleted_at is set).
Pre-feature runs. Runs that completed before this feature was deployed are backfilled on upgrade. Backfilled manifests are best-effort: files removed under the raw-data retention policy before the backfill ran are not listed, so older runs may have partial manifests. Entries are stamped with the run's completion time.
The ProgramCompleted webhook payload and the run-detail response both carry a files array with the same fields, so you don't need to make a separate list request after receiving a webhook. One difference: href is an absolute URL in REST responses and a relative URL in webhook payloads (join with the cell's base URL) — see Event reference.
The per-resource endpoints (GET /sensor/frames/{frame_id}/file.*, GET /runs/{run_id}/steps/{step_index}/cl_sweep.csv) remain available during alpha. The consolidated endpoints are the recommended integration surface.
Pull raw capture data#
Clients are entitled to the data captured from their parts. Per frame (ids from the program-run detail, as above):
| File | What it is |
|---|---|
GET /sensor/frames/{frame_id}/file.ply |
Filtered point cloud (outliers removed) |
GET /sensor/frames/{frame_id}/file.raw.ply |
Unfiltered raw point cloud |
GET /sensor/frames/{frame_id}/file.png |
Height-map image |
GET /sensor/frames/{frame_id}/file.luminance.png |
Luminance image |
GET /sensor/frames/{frame_id}/file.npy |
Raw NumPy array |
GET /sensor/frames/{frame_id}/files lists which formats exist for a frame — request one that doesn't and you get a 404. CL-sweep steps expose their measurement series as CSV at GET /runs/{run_id}/steps/{step_index}/cl_sweep.csv (steps are 1-indexed).
Next#
- API concepts — the conventions every endpoint shares.
- API reference — per-endpoint schemas.
- Webhooks — the push half this page reconciles against.