Developer API¶
CRUD endpoints for humans and CI pipelines. All endpoints accept either a personal API token (PAT) or a project-scoped token (constrained to that project).
Base path: /api/v1/ (project resources under /api/v1/projects/, personal tokens under /api/v1/me/)
Conventions¶
- All requests/responses are JSON unless noted (artifact upload uses
multipart/form-data). - Timestamps are ISO 8601 UTC.
- IDs are UUIDs unless explicitly slugged (
project.slug). - List endpoints return plain JSON arrays (no pagination wrapper).
Resource map¶
| Resource | Endpoint | Methods |
|---|---|---|
| Projects | /projects/ |
GET, POST |
/projects/{id}/ |
GET | |
| Personal tokens | /me/tokens/ |
GET, POST |
/me/tokens/{tid}/ |
DELETE | |
| Project tokens | /projects/{id}/tokens/ |
GET, POST |
/projects/{id}/tokens/{tid}/ |
DELETE | |
| Signing keys | /projects/{id}/signing-keys/ |
GET, POST |
/projects/{id}/signing-keys/{key_id}/ |
DELETE | |
| Devices | /projects/{id}/devices/ |
GET |
/projects/{id}/devices/{did}/ |
GET, PATCH | |
| Groups | /projects/{id}/groups/ |
GET, POST |
/projects/{id}/groups/{gid}/members/ |
POST | |
/projects/{id}/groups/{gid}/members/{did}/ |
DELETE | |
| Channels | /projects/{id}/channels/ |
GET, POST |
| Artifacts | /projects/{id}/artifacts/ |
GET, POST |
| Deployments | /projects/{id}/deployments/ |
GET, POST |
/projects/{id}/deployments/{did}/ |
GET | |
/projects/{id}/deployments/{did}/transition/ |
POST | |
/projects/{id}/deployments/{did}/states/ |
GET |
Devices are read-only via API. They are created automatically the first time a device calls
/api/v1/ota/check/. UsePATCHto update theactiveflag or metadata. There is no delete endpoint; deactivate instead.Groups and channels are collection-only. There are no individual
GET /groups/{id}/orGET /channels/{id}/endpoints; fetch the list and filter client-side.
Selected endpoints¶
POST /projects/{id}/artifacts/¶
Uploads a firmware artifact. multipart/form-data only.
curl -X POST https://simpleota.com/api/v1/projects/${PID}/artifacts/ \
-H "Authorization: Bearer ${TOKEN}" \
-F "manifest=$(jq -c . manifest.json)" \
-F "[email protected]"
Form fields:
| Field | Type | Notes |
|---|---|---|
manifest |
string (JSON) | Validated against the manifest schema. |
binary |
file | Streamed to object storage; SHA-256 computed. |
Response 201:
{
"id": "8e1f…",
"build_number": 24,
"version_label": "1.4.2",
"framework": "arduino",
"chip_family": "esp32",
"board_id": "esp32dev",
"checksum_sha256": "8f3c…",
"file_size": 1287456,
"security_mode": "basic",
"archived": false,
"created_at": "2026-04-25T17:21:00Z"
}
Failure modes:
400 manifest_invalid: manifest doesn't validate.400 monthly artifact upload limit reached for current plan; upgrade your plan to upload more.(plan cap reached)413: binary exceeds your plan's size cap (default 8 MiB).
POST /projects/{id}/deployments/¶
{
"artifact_id": "8e1f…",
"target_group_ids": ["<group-uuid>"],
"target_channel_ids": ["<channel-uuid>"],
"rollout_percentage": 5,
"rollout_strategy": "immediate",
"notes": ""
}
All fields except artifact_id are optional. Deployments are created
in draft state; call /transition/ with {"action": "start"} to
activate. Returns the new deployment object.
GET /projects/{id}/deployments/{did}/states/¶
Returns per-device rollout state records for the deployment; one entry per device that has been offered the update or reported a result:
status values: pending, offered, download_started, downloaded,
flashed, validated, reboot, confirmed, failed, rolled_back.
Use this to monitor rollout health without polling individual devices.
confirmed is the terminal success state; failed and rolled_back are
the terminal failure states.
POST /projects/{id}/deployments/{did}/transition/¶
Transitions a deployment's state. Payload:
Valid action values: start, pause, resume, cancel, complete,
ramp (requires rollout_percentage). Invalid transitions return 400.
~~POST /projects/{id}/deployments/{did}/pause/~~¶
Removed. Use
POST /transition/with{"action": "pause"}instead.
Errors¶
All endpoints return errors in a uniform envelope:
{
"detail": "monthly artifact upload limit reached…",
"code": "over_artifact_limit",
"request_id": "req_01HX…"
}
See Errors.