API reference
One REST API for every room your Cymos can see: 65 operations, generated from the same contract as the SDK and CLI. Test keys read the simulator; live keys read your paired Cymos.
Base URL
The Cymo API is a REST API over HTTPS. Every operation lives under one base URL, and every path starts with /v1.
https://api.cymatics.techAuthentication
Send your API key as a bearer token in the Authorization header. A key belongs to one project and one mode: cym_test_ keys read the simulator, cym_live_ keys read your paired Cymos. Every response says which in meta.livemode. Create keys in the dashboard.
Authorization: Bearer cym_test_…curl "https://api.cymatics.tech/v1/rooms/room_bedroom" \
-H "Authorization: Bearer $CYMO_API_KEY"Optionally name your client, such as my-app/1.2.0. The SDK, CLI and MCP server set it for you, and request logs show it.
X-Cymo-Clientstring, up to 100 characters | headerThe calling surface and version, such as sdk/0.3.0 or cli/0.2.0. Request logs group requests by its prefix: sdk, cli, mcp or dashboard, and other for anything else. |
Response shape
Success responses are { data, meta }; lists add page. Errors are { error, meta }. Fields are snake_case with units in the name, such as heart_rate_bpm, and timestamps are ISO 8601 in UTC.
// One resource
{ "data": { … }, "meta": { … } }
// A list
{ "data": [ … ], "page": { "has_more": true, "next_cursor": "…" }, "meta": { … } }
// An error
{ "error": { "code": "resource_not_found", "message": "…" }, "meta": { … } }
// meta, on every response and stream message
{
"request_id": "req_01",
"project_id": "proj_demo",
"livemode": false,
"generated_at": "2026-10-24T23:55:30.000Z"
}Response codes
The API uses standard HTTP status codes: 2xx for success, 4xx for a problem with the request and 5xx for a problem on our side. Each error carries a stable error.code; branch on it, not on the message.
400 | invalid_arguments, invalid_scenario |
401 | invalid_api_key, expired_api_key, revoked_api_key |
403 | insufficient_scope, feature_not_entitled, mode_mismatch |
404 | resource_not_found |
409 | configuration_conflict, revision_conflict, idempotency_conflict |
410 | replay_expired, data_expired |
413 | invalid_arguments |
422 | invalid_arguments |
429 | rate_limit_exceeded, quota_exceeded |
500 | internal_error |
503 | live_unavailable, service_unavailable |
Errors describes every code and whether to retry it.
Rate limits
Requests are limited per key in fixed one-minute windows, and to twice that across the project. Over the limit, the API answers 429 rate_limit_exceeded with a Retry-After. Authenticated responses carry your remaining budget:
RateLimit-Limitinteger | headerRequests per minute allowed for the key. On every authenticated response. |
RateLimit-Remaininginteger | headerRequests left in this minute, for the key and its project, whichever is lower. |
RateLimit-Resetinteger | headerSeconds until the one-minute window resets. |
Retry-Afterinteger | headerSeconds to wait before retrying. Sent with rate_limit_exceeded and service_unavailable, never with quota_exceeded. |
Pagination
Lists are cursor-paginated. They take limit (default 100, up to 1000) and cursor: pass page.next_cursor until page.has_more is false. A cursor that outlives its snapshot answers 410 data_expired; start again from the first page.
Idempotency
15 writes accept an Idempotency-Key header. Replays the first response for an identical request within the project and mode. Retrying with the same key can't act twice; reusing it for a different request answers 409 idempotency_conflict.
Idempotency-Key: 4f9c1d2e-8b3a-4c6d-9e7f-1a2b3c4d5e6fStreaming
Stream room, Stream person answer with Server-Sent Events. Each message holds the same { data, meta } envelope as a JSON response; reconnect with Last-Event-ID to resume. See Streaming.
Scopes and modes
Each operation needs one scope on the key: devices:write, models:write, raw:read, results:read, rooms:write, simulation:write, webhooks:write. A key without it gets 403 insufficient_scope.
Every operation answers test and live keys at the same path with the same shapes. Test only marks the simulator controls, which live keys can never call. See Test and live mode.
Versioning
The version is in the path, /v1. This reference is generated from the Cymo API's OpenAPI document, version 0.2.0 at commit 3745fd2. The SDK, CLI and this page read the same definitions, so they can't disagree.
Operations
Project
Capabilities
Sensing profiles
Retention profiles
Coordinate frames
Skeleton definitions
Rooms
- List roomsGET/v1/rooms
- Create roomPOST/v1/rooms
- Get roomGET/v1/rooms/{room}
- Update roomPATCH/v1/rooms/{room}
- Archive roomDELETE/v1/rooms/{room}
- Get zonesGET/v1/rooms/{room}/zones
- Replace zonesPUT/v1/rooms/{room}/zones
- Get calibrationGET/v1/rooms/{room}/calibration
- Replace calibrationPUT/v1/rooms/{room}/calibration
- Get processing configGET/v1/rooms/{room}/processing-config
- Replace processing configPUT/v1/rooms/{room}/processing-config
- Stream roomGET/v1/rooms/{room}/streamSSE
Devices
- List devicesGET/v1/devices
- Get deviceGET/v1/devices/{device}
- Update devicePATCH/v1/devices/{device}
- Get device healthGET/v1/devices/{device}/health
- Get sensing configGET/v1/devices/{device}/sensing-config
- Replace sensing configPUT/v1/devices/{device}/sensing-config
- List device observationsGET/v1/devices/{device}/observations
- Stream raw dataGET/v1/devices/{device}/raw-streamBinary
Scenes
Presence
Movement
Stress
Skeleton
Classifications
Results
People
Events
Webhooks
Scenario runs
Raw data
Request logs