API reference
The hosted API is at https://api.euler.sudotank.com. It serves its own schema and interactive explorer:
https://api.euler.sudotank.com/openapi.json
https://api.euler.sudotank.com/docs
The versioned contract is committed at openapi/euler.openapi.json. Generated clients live in sdks/typescript and sdks/python; release tags publish them only when the repository has npm and PyPI tokens configured.
This page maps the surfaces by area. It is a map, not a substitute for the schema: request and response shapes come from openapi.json.
Conventions
- Everything under
/v1is tenant-scoped. The tenant comes from the caller’s principal, never from a path or query parameter. - Roles form an ordered ladder:
viewer<reviewer<engineer<admin. Reads generally needviewer, ingest and pipeline writes needengineer, review decisions needreviewer, and tenant administration needsadmin. - Mutations are recorded in the audit log.
- Errors use standard codes.
402means the tenant’s weekly model budget would be breached,409means a conflicting state such as a layer that is turned off, and422means the request named something the platform does not recognise.
export EULER_BASE_URL="https://api.euler.sudotank.com"
export EULER_TOKEN="…"
curl -fsS -H "Authorization: Bearer $EULER_TOKEN" "$EULER_BASE_URL/v1/projects"
Health
| Method | Path |
|---|---|
| GET | /healthz |
| GET | /readyz |
| GET | /openapi.json |
Tenancy, identity and access
| Method | Path |
|---|---|
| POST · GET | /v1/tenants |
| POST | /v1/login |
| POST | /v1/signup |
| POST · GET | /v1/users |
| PATCH | /v1/users/{email} |
| POST · GET | /v1/invites |
| POST | /v1/invites/{invite_id}/resend |
| POST | /v1/invites/{invite_id}/revoke |
| GET | /v1/invites/lookup |
| POST | /v1/invites/accept |
| POST · GET | /v1/tokens |
| POST | /v1/tokens/refresh |
| GET | /v1/audit |
| POST | /v1/secrets/seal |
Workspaces and projects
| Method | Path |
|---|---|
| GET | /v1/use-cases |
| POST · GET | /v1/workspaces |
| PUT | /v1/workspaces/{workspace_id}/use-case |
| POST · GET | /v1/projects |
| GET · DELETE | /v1/projects/{project_id} |
| PUT | /v1/projects/{project_id}/name |
| PUT | /v1/projects/{project_id}/workspace |
| PUT | /v1/projects/{project_id}/data-kind |
| PUT | /v1/projects/{project_id}/target-profile |
| GET | /v1/target-profiles |
| GET | /v1/source-formats |
Sources, uploads and runs
| Method | Path |
|---|---|
| POST · GET | /v1/projects/{project_id}/sources |
| POST | /v1/projects/{project_id}/sources/upload |
| PUT | /v1/projects/{project_id}/sources/{source_id}/name |
| POST | /v1/projects/{project_id}/sources/{source_id}/test-connection |
| DELETE | /v1/projects/{project_id}/sources/{source_id} |
| POST | /v1/projects/{project_id}/sources/{source_id}/transfer |
| GET · PUT | /v1/projects/{project_id}/ingest/settings |
| POST | /v1/projects/{project_id}/uploads |
| GET | /v1/projects/{project_id}/uploads/{upload_id} |
| PUT | /v1/projects/{project_id}/uploads/{upload_id}/parts/{index} |
| POST | /v1/projects/{project_id}/uploads/{upload_id}/complete |
| PUT | /v1/projects/{project_id}/uploads/{upload_id}/artifact |
| POST · GET | /v1/projects/{project_id}/runs |
| POST | /v1/projects/{project_id}/runs/{run_id}/cancel |
| POST · GET | /v1/projects/{project_id}/runs/{run_id}/actions |
| GET | /v1/projects/{project_id}/runs/{run_id}/checkpoints |
| POST | /v1/projects/{project_id}/runs/{run_id}/checkpoints/resolve |
| POST | /v1/projects/{project_id}/runs/{run_id}/ask |
| POST | /v1/projects/{project_id}/command |
Annotation
See annotation layers, pipeline settings, annotation jobs and the automated review pass.
| Method | Path | Notes |
|---|---|---|
| GET · PUT | /v1/projects/{project_id}/annotation/settings | Resolved layer catalog; write modes, per-layer config, model choice and presets |
| PUT | /v1/workspaces/{workspace_id}/annotation/settings | Workspace defaults |
| GET | /v1/projects/{project_id}/annotation/status | What actually ran, per layer |
| GET | /v1/projects/{project_id}/annotation/coverage | Per-layer, per-kind coverage and quality; ?layer=, ?kind= |
| POST · GET | /v1/projects/{project_id}/annotation/jobs | Enqueue (202) and list. 409 already_produced unless force: true |
| GET | /v1/projects/{project_id}/annotation/jobs/stream | SSE progress feed |
| GET | /v1/projects/{project_id}/annotation/jobs/{job_id} | One job |
| POST | /v1/projects/{project_id}/annotation/jobs/{job_id}/cancel | Cancel from queued or running |
| GET · PUT | /v1/projects/{project_id}/ontology/summary | Observed classes with property distributions, joined against the declared ontology; rename, merge and declared-class edits |
Re-derivations from stored data (no GPU, no spend; send a few episodes per call):
| Method | Path |
|---|---|
| POST | /v1/projects/{project_id}/layers/threed/reattach |
| POST | /v1/projects/{project_id}/layers/hand_pose/wearer-filter |
| POST | /v1/projects/{project_id}/layers/tracking/consolidate |
| POST | /v1/projects/{project_id}/media/overlays/purge |
annotation_review is a valid layer_id on the jobs endpoint and is not an annotation-catalog layer; it grades what the other layers produced.
Synchronous per-episode annotation surfaces remain available alongside the queue:
| Method | Path |
|---|---|
| POST | /v1/projects/{project_id}/perception |
| PATCH | /v1/projects/{project_id}/episodes/{episode_id}/annotation |
| PUT | /v1/projects/{project_id}/episodes/{episode_id}/labels |
| POST | /v1/projects/{project_id}/episodes/{episode_id}/labels/agent |
| POST · PUT | /v1/projects/{project_id}/episodes/{episode_id}/dense-text |
| POST | /v1/projects/{project_id}/episodes/{episode_id}/video/hand-pose |
| POST | /v1/projects/{project_id}/episodes/{episode_id}/video/track |
| POST · PUT | /v1/projects/{project_id}/episodes/{episode_id}/video/subtasks |
| PUT | /v1/projects/{project_id}/episodes/{episode_id}/video/tracks |
| POST | /v1/projects/{project_id}/episodes/{episode_id}/video-quality |
| PUT | /v1/projects/{project_id}/ontology |
| GET · PUT | /v1/workspaces/{workspace_id}/label-ontology |
Readiness, utility and certification
See readiness and data utility.
| Method | Path |
|---|---|
| GET · PUT | /v1/projects/{project_id}/readiness/settings |
| POST | /v1/projects/{project_id}/readiness/remeasure |
| POST | /v1/projects/{project_id}/readiness/rescore |
| GET | /v1/projects/{project_id}/utility |
| POST | /v1/projects/{project_id}/ablation |
| GET | /v1/projects/{project_id}/distribution-gap |
| GET | /v1/projects/{project_id}/target-readiness |
| GET | /v1/projects/{project_id}/data-strategy |
| GET | /v1/projects/{project_id}/vision-audit |
| GET · POST | /v1/projects/{project_id}/certification/policies |
| GET | /v1/projects/{project_id}/certification/report |
| GET | /v1/projects/{project_id}/certification/report.html |
| POST | /v1/projects/{project_id}/certification/export |
| POST | /v1/projects/{project_id}/certification/import |
| POST | /v1/projects/{project_id}/certification/audits |
Episodes, review and media
| Method | Path |
|---|---|
| GET | /v1/projects/{project_id}/episodes (?fields=summary&page=&page_size=) |
| GET | /v1/projects/{project_id}/episodes/{episode_id} |
| POST | /v1/projects/{project_id}/episodes/{episode_id}/decision |
| POST | /v1/projects/{project_id}/review/reroute |
| GET | /v1/projects/{project_id}/episodes/{episode_id}/playback |
| GET | /v1/projects/{project_id}/episodes/{episode_id}/media |
| POST | /v1/projects/{project_id}/episodes/{episode_id}/media-token |
| GET | /v1/projects/{project_id}/episodes/{episode_id}/media-stream |
Paging episodes
The plain list returns every episode in full. For anything larger than a handful, ask for slim rows a page at a time and fetch one episode in full when you need it:
curl -fsS -H "Authorization: Bearer $EULER_TOKEN" \
"$EULER_BASE_URL/v1/projects/$PROJECT/episodes?fields=summary&page=1&page_size=100"
curl -fsS -H "Authorization: Bearer $EULER_TOKEN" \
"$EULER_BASE_URL/v1/projects/$PROJECT/episodes/$EPISODE"
A summary page carries total and pages. Each row has the episode id, session
id, start, end and duration, frame rate and count, task, a short caption, data
kind, review decision, readiness score, flags, the worst certificate status and
the rules behind it, counts (labels, tracks, sub-tasks, dense actions, hand and
body tracks, cameras) and the layers with stored output. page_size defaults to
50 and is at most 500. Episode ids may contain a slash; send them as they are.
Review surface
The routes the review screen reads. Every time is in the episode’s stored
source-clock seconds, the same clock as every annotation. Each data route has a
signed twin (telemetry-stream, signals-stream, frame-stream,
artifact-stream) that takes ?token= from POST .../media-token instead of a
header, for an image tag or a canvas.
| Method | Path | Returns |
|---|---|---|
| GET | /v1/projects/{project_id}/episodes/{episode_id}/telemetry | the camera’s sensor streams, bucketed |
| GET | /v1/projects/{project_id}/episodes/{episode_id}/signals | series derived from the annotations, plus bands and markers |
| GET | /v1/projects/{project_id}/episodes/{episode_id}/frame | one JPEG of the clean clip |
| GET · HEAD | /v1/projects/{project_id}/episodes/{episode_id}/layers/{layer_id}/artifact | a layer’s stored file; for threed, ?part= returns JSON |
| GET | /v1/projects/{project_id}/episodes/{episode_id}/threed/timeline | the 3D layer keyed by time |
| GET | /v1/projects/{project_id}/episodes/{episode_id}/threed/keyframes/{name} | one keyframe point cloud |
E="$EULER_BASE_URL/v1/projects/$PROJECT/episodes/$EPISODE"
curl -fsS -H "Authorization: Bearer $EULER_TOKEN" "$E/telemetry?streams=ACCL,GYRO,GRAV,CORI&points=1200"
curl -fsS -H "Authorization: Bearer $EULER_TOKEN" "$E/signals?points=600&t0=20&t1=50"
curl -fsS -o frame.jpg -H "Authorization: Bearer $EULER_TOKEN" "$E/frame?t=42.5&w=640"
curl -fsS -H "Authorization: Bearer $EULER_TOKEN" "$E/layers/threed/artifact?part=trajectory"
Telemetry. streams defaults to ACCL,GYRO,GRAV,CORI (accelerometer,
gyroscope, gravity, camera orientation); the camera may also record ISOE,
SHUT and YAVG (exposure). points is 1200 by default (10 to 5000);
t0/t1 zoom into a window. Each stream is
{name, columns, units, rate_hz, n_raw, bucket_s, t, min, max, mean}, where
min/max/mean map each column to one value per bucket, so a one-sample
spike survives. Empty buckets are left out, never zero-filled. derived adds
gyro_mag, accel_mag, tilt_deg and yaw_deg/pitch_deg/roll_deg.
withheld names streams that are never served (location and face); missing
names requested streams the recording does not have. No telemetry at all is a
404 with the reason.
Signals. The same block shape under streams: hand_presence_left/right,
hand_speed_left/right, object_count, head_speed_mps (estimated) and
head_angular_speed_dps. bands are sub-task, action and flagged spans
(kind, start, end, label, and the check and severity for a flag);
markers are instants (flags, depth keyframes).
Frame. t in stored seconds (422 outside the episode), w from 64 to
1280 (default 320). Returns image/jpeg, cached; fetch it as a file, not as
JSON.
3D parts (?part= on the threed artifact):
part | Returns |
|---|---|
trajectory | camera poses (t, xyz, quat, conf), the smoothed path with speed and turn rate, and path_length_m raw and smoothed |
hands_3d | per instant, a hand’s 21 joints with a confidence; experimental |
keyframes | per depth keyframe: time, cloud name, intrinsics used, and a note on depth scale |
objects | object-track centres placed in 3D (after a 3D rebuild) |
The SDKs expose these as get_episode, episode_telemetry, episode_signals,
episode_frame and episode_layer_artifact. Query parameters are not yet
arguments of those methods; for paging, windows or parts use the client’s
request with the full path:
rows = client.request("GET", f"/v1/projects/{project}/episodes?fields=summary&page=1&page_size=100")
traj = client.request("GET", f"/v1/projects/{project}/episodes/{episode}/layers/threed/artifact?part=trajectory")
Search, slices and curation
See curation and the search and slices recipe.
| Method | Path |
|---|---|
| POST | /v1/projects/{project_id}/search |
| POST | /v1/projects/{project_id}/search/reindex |
| POST · GET | /v1/projects/{project_id}/slices |
| GET | /v1/projects/{project_id}/curation/strategies |
| POST | /v1/projects/{project_id}/curation/preview |
| POST | /v1/projects/{project_id}/curation/apply |
| GET | /v1/projects/{project_id}/curation/recommend |
| POST | /v1/projects/{project_id}/curate |
| POST | /v1/projects/{project_id}/feedback/suggestions |
| POST | /v1/projects/{project_id}/feedback/close-loop |
Exports
| Method | Path |
|---|---|
| POST · GET | /v1/projects/{project_id}/exports |
| DELETE | /v1/projects/{project_id}/exports/{export_id} |
| POST | /v1/projects/{project_id}/exports/{export_id}/seal |
| GET | /v1/projects/{project_id}/exports/{export_id}/download |
| GET | /v1/projects/{project_id}/exports/{export_id}/artifact |
| GET | /v1/projects/{project_id}/exports/{export_id}/archive |
| POST | /v1/projects/{project_id}/reexport |
Copilot
See Euler Copilot.
| Method | Path |
|---|---|
| POST | /v1/copilot/chat (SSE) |
| GET | /v1/copilot/models |
| GET | /v1/copilot/suggestions |
| POST · GET | /v1/copilot/support |
| GET | /v1/copilot/threads |
| GET · DELETE | /v1/copilot/threads/{thread_id} |
The earlier planning surface remains available:
| Method | Path |
|---|---|
| POST | /v1/agent/plan |
| POST | /v1/agent/execute |
Models, budget and cost
| Method | Path |
|---|---|
| GET | /v1/models |
| PUT | /v1/models/{surface} |
| GET · PUT | /v1/budget |
| GET | /v1/costs |
| POST | /v1/costs/simulate |
GET /v1/models returns the active model per surface with its evidence and the vetted alternatives. PUT accepts only a vetted option on a selectable surface.