HTTP API
Everything the web app does — submitting build/test jobs and driving your BenchPod devices (power, capture, signal generation, flashing, the waveform library) — is a plain HTTP API you can call from scripts or CI. All paths below are under your server's /api base (e.g. https://www.embeddedci.com/api).
Authentication
Create a personal API key and send it as an Authorization: ApiKey header. A key authenticates as you, scoped to your organization — it is not limited to job submission; it works across the whole API, including the BenchPod endpoints.
# Create a key on the API keys page, then send it on every request: Authorization: ApiKey eci_<kid>_<secret>
Keys carry scopes, enforced per endpoint: jobs:submit for job submission, benchpod:control for the BenchPod endpoints (devices, command, capture, waveforms, DAC, and the /auth/token exchange), and emi:analyze for the EMI Analyzer (board upload and analysis runs — see EMI analysis in CI). Pick scopes when you create a key, or edit them later, on the API keys page. A key missing the required scope gets a 403. (Scopes constrain API keys only — interactive web sessions are unrestricted.)
Direct device access (REST)
These accept your API key directly — no exchange step. This is the same surface the BenchPod page uses. You address a device by its id (from GET /benchpod/devices).
| Method | Path | Purpose |
|---|---|---|
GET | /benchpod/devices | List your devices + online/capability state |
PATCH | /benchpod/devices/{id} | Rename a device |
DELETE | /benchpod/devices/{id} | Remove a device |
POST | /benchpod/devices/{id}/command | Single command: ping, status, target_power, la, dac_set, generate, dac_stop, … |
GET | /benchpod/devices/{id}/flashable-artifacts | List artifacts that can be flashed |
POST | /benchpod/devices/{id}/flash | Flash firmware over the pod's CMSIS-DAP probe |
GET | /benchpod/devices/{id}/firmware | The pod's own running firmware version |
POST | /benchpod/devices/{id}/ota | Start a pod firmware update (returns 202; the update runs in the background) |
GET | /benchpod/devices/{id}/ota | Poll the running firmware update: phase, percent, bytes |
POST | /benchpod/devices/{id}/ota/abort | Drop a staged image and release the pod |
POST | /scope/captures/start | Start an ADC (scope) capture |
POST | /scope/measure/start | Stimulus + capture (DAC drive while sampling ADC) |
POST | /analyzer/captures/start | Start an I²C logic-analyzer capture |
POST | /la/captures/start | Start a raw multi-channel LA capture |
POST | /capture/start | Start a simultaneous ADC + LA capture (one trigger) |
GET | /capture/{id} | Fetch a capture's raw frames for replay (ADC + LA + I²C) |
GET / POST | /benchpod/waveforms | List / save DAC waveforms (the library) |
GET / DELETE | /benchpod/waveforms/{id} | Fetch / delete a saved waveform |
POST | /dac/replay/start | Replay a saved/inline waveform out the DAC (AC-DAC boards) |
WS | /ws/capture · /benchpod/devices/{id}/uart/ws | Live streams (unified capture: ADC + LA + I²C); auth via ?token=eci_… (your key) or a JWT |
# List your BenchPod devices
curl -H "Authorization: ApiKey $ECI_KEY" \
https://www.embeddedci.com/api/benchpod/devices
# Power the target on (efuse 1 = internal 5V) via the generic command channel
curl -H "Authorization: ApiKey $ECI_KEY" -H 'Content-Type: application/json' \
-d '{"command":{"cmd":"target_power","efuse":1,"state":1}}' \
https://www.embeddedci.com/api/benchpod/devices/<device_id>/command
# Start an ADC (scope) capture, then poll the result
curl -H "Authorization: ApiKey $ECI_KEY" -H 'Content-Type: application/json' \
-d '{"device_id":"<id>","samples":1024,"sample_rate_mhz":1}' \
https://www.embeddedci.com/api/scope/captures/start
curl -H "Authorization: ApiKey $ECI_KEY" \
https://www.embeddedci.com/api/capture/<capture_id>For the live WebSocket streams, pass the key in the ?token= query parameter (an eci_ token is recognised as an API key):
# Live capture/terminal streams take the key as a ?token= query param # (eci_ tokens are recognised as API keys): wss://www.embeddedci.com/api/ws/capture?device_id=<id>&token=$ECI_KEY
Capabilities gate some features per board: e.g. generate/replay require an AC waveform DAC and are rejected on DC-only boards.
Drive devices by name (cloud) — token exchange
The cloud endpoints address a device by name and add a one-consumer-at-a-time lease and a raw byte-tunnel (for flashing/captures from CI). They take a short-lived session token, not your static key — so a long-lived secret never ends up in a WebSocket URL or a CI log. Mint one from your API key with POST /auth/token (the API-key analogue of the GitHub Actions OIDC exchange).
# 1) Exchange your API key for a short-lived cloud-session token.
# Omit the body for any device in your org, or scope it down:
curl -H "Authorization: ApiKey $ECI_KEY" -H 'Content-Type: application/json' \
-d '{"devices":["pod-a"],"ttl_seconds":3600}' \
https://www.embeddedci.com/api/auth/token
# -> { "token": "<jwt>", "token_type": "bearer", "expires_at": "...", "expires_in": 3600 }
# 2) Drive a device BY NAME with the minted token (Bearer):
curl -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-H 'X-Benchpod-Device: pod-a' \
-d '{"cmd":"status"}' \
https://www.embeddedci.com/api/cloud/devices/command
# WebSocket byte-tunnel (flash / capture) — token in the query string is a
# short-lived JWT, never your static key:
wss://www.embeddedci.com/api/cloud/devices/ws?device=pod-a&token=$TOKEN| Method | Path | Purpose |
|---|---|---|
POST | /auth/token | API key → short-lived cloud_session JWT (optional devices[] + ttl_seconds) |
POST | /cloud/devices/command | One command on a device by name (X-Benchpod-Device) |
WS | /cloud/devices/ws | Raw byte-tunnel for flashing / captures |
POST | /cloud/devices/lease · /lease/renew · /lease/release | Acquire / renew / release the device lease |
Higher-level clients
Most users don't call these endpoints by hand. The pytest SDK, CLI, and MCP server wrap them (auth, leasing, tunnels, chunk reassembly) — reach for those first; drop to the raw API for custom integrations. The Python SDK performs the /auth/token exchange for you: set BENCHPOD_API_KEY (or pass api_key=) and connect to embeddedci:<device-name>.