SageOx Firmware Admin API
The Firmware Admin API from SageOx — 9 operation(s) for firmware admin.
The Firmware Admin API from SageOx — 9 operation(s) for firmware admin.
openapi: 3.1.0
info:
title: SageOx Admin Firmware Admin API
version: 1.0.0
description: "# API Reference\n\n## Overview\n\nSageOx is a platform that captures team knowledge from discussions, decisions, and work context into a Ledger (per-repo historical record) and Team Context (team-wide shared knowledge). The SageOx API provides programmatic access to manage repositories, recordings, notifications, and team data with high performance and reliability.\n\n## Base URLs\n\n| Environment | URL |\n|---|---|\n| Local Development | `http://localhost:3000` |\n| Test | `https://test.sageox.ai` |\n| Production | `https://sageox.ai` |\n\n## Authentication\n\nAll requests to the SageOx API require authentication via JWT tokens issued by the Better Auth service.\n\nInclude your token in the `Authorization` header:\n```\nAuthorization: Bearer <token>\n```\n\n**Initial Auth Flow (CLI)**\n- CLI initiates device flow to obtain initial credentials\n- Exchange device code for JWT token\n- Store token securely for subsequent API requests\n- Refresh token when expired\n\n**Authenticated Endpoints**\n- Pass JWT in `Authorization: Bearer <token>` header\n- Tokens contain user identity and team membership\n- Invalid or expired tokens return 401 Unauthorized\n\n## Response Format\n\n### Success\nStandard response format with optional pagination metadata:\n```json\n{\n \"success\": true,\n \"data\": { ... }\n}\n```\n\n### Error\nAll errors follow a consistent format:\n```json\n{\n \"success\": false,\n \"error\": \"Human-readable error description\"\n}\n```\n\n**Status Codes**\n- `400` — Bad Request: Invalid parameters or malformed request\n- `401` — Unauthorized: Missing or invalid authentication token\n- `403` — Forbidden: Insufficient permissions for this resource\n- `404` — Not Found: Resource does not exist\n- `409` — Conflict: Request conflicts with current state (e.g., duplicate)\n- `429` — Rate Limited: Too many requests; retry with backoff\n- `500` — Internal Error: Server error; contact support if persistent\n\n## Pagination\n\nList endpoints support cursor-based pagination via query parameters:\n\n**Query Parameters**\n- `limit` — Number of results per page (default: 20, max: 100)\n- `offset` — Number of results to skip (default: 0)\n\n**Response Metadata**\nList responses include pagination info:\n```json\n{\n \"success\": true,\n \"data\": [ ... ],\n \"meta\": {\n \"total\": 150,\n \"limit\": 20,\n \"offset\": 0,\n \"hasMore\": true\n }\n}\n```\n\n## ID Formats\n\nResources use standardized ID formats for easy identification:\n\n| Resource | Format | Length | Example |\n|---|---|---|---|\n| Team | `team_<cuid2>` | 10 chars | `team_abc1234567` |\n| User | `usr_<cuid2>` | 10 chars | `usr_xyz9876543` |\n| Repository | `repo_<uuidv7>` | 36 chars | `repo_01934f5a-8b9c-7def-abcd-0123456789ab` |\n| Recording | `rec_<uuidv7>` | 36 chars | `rec_01934f5a-8b9c-7def-abcd-0123456789ab` |\n| Image | `img_<uuidv7>` | 36 chars | `img_01934f5a-8b9c-7def-abcd-0123456789ab` |\n| Notification | `notif_<cuid2>` | 14 chars | `notif_ab1cd2ef3g` |\n\nUse these IDs for all API operations. IDs are unique across environments.\n\n## Rate Limiting\n\nThe API applies rate limiting to protect against abuse and ensure fair access:\n\n**Authenticated Endpoints**\n- Limited per user: depends on subscription tier\n- Quota resets hourly\n\n**Unauthenticated Endpoints**\n- Limited per IP address\n- More restrictive than authenticated limits\n\nWhen rate limited, the API returns `429 Too Many Requests`. Retry requests with exponential backoff:\n```\nwait = min(2^attempt * 100ms, 10s)\n```\n\n## Timestamps\n\nAll timestamps in API responses use RFC 3339 / ISO 8601 format in UTC:\n```\n2025-12-03T10:30:00Z\n```\n\nUse this format when providing timestamps in requests as well. The API rejects timestamps in other formats.\n\n## SDKs & Tools\n\n- **OpenAPI Spec** — Full schema available at `/api/openapi.json`\n- **Postman Collection** — Import from `/api/postman.json` for interactive exploration\n- **CLI** — Use `ox` CLI for command-line access\n"
contact:
name: SageOx Team
license:
name: MIT
servers:
- url: http://localhost:3000
description: Devcontainer
- url: https://test.sageox.ai
description: Test
- url: https://sageox.ai
description: Production
security:
- bearerAuth: []
tags:
- name: Firmware Admin
paths:
/api/v1/internal/hq/firmware:
post:
operationId: uploadFirmwareRelease
summary: Upload new firmware release
description: 'Create a firmware release record and get a pre-signed S3 upload URL for
the binary. The release starts in `staged` status and must be explicitly
activated via the activate endpoint.
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- device_type
- version
- sha256
- size_bytes
properties:
device_type:
type: string
example: scribe
version:
type: string
maxLength: 31
example: 1.2.0
sha256:
type: string
pattern: ^[0-9a-f]{64}$
signature:
type: string
description: Base64 RSA-2048 signature. Required for production releases.
size_bytes:
type: integer
format: int64
maximum: 4194304
changelog:
type: string
maxLength: 2048
release_date:
type: string
format: date
min_version:
type: string
maxLength: 31
channel:
type: string
default: stable
enum:
- stable
- early-access
- beta
- nightly
responses:
'201':
description: Release created. Upload the binary to the returned URL.
content:
application/json:
schema:
type: object
properties:
release:
$ref: '#/paths/~1api~1v1~1internal~1hq~1firmware~1%7Bfirmware_id%7D/patch/responses/200/content/application~1json/schema'
upload_url:
type: string
description: Pre-signed S3 PUT URL for uploading the firmware binary. Expires in 15 minutes.
'400':
description: Invalid request (missing fields, bad SHA-256 format, etc.).
'409':
description: Version already exists for this device type and channel.
get:
operationId: listFirmwareReleases
summary: List firmware releases
description: 'List all firmware releases for a device type, newest first.
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: device_type
in: query
description: Filter by device type. Defaults to `scribe`.
schema:
type: string
default: scribe
- name: limit
in: query
schema:
type: integer
default: 50
maximum: 100
- name: offset
in: query
schema:
type: integer
default: 0
responses:
'200':
description: Paginated list of firmware releases.
content:
application/json:
schema:
type: object
required:
- items
- pagination
properties:
items:
type: array
items:
$ref: '#/paths/~1api~1v1~1internal~1hq~1firmware~1%7Bfirmware_id%7D/patch/responses/200/content/application~1json/schema'
pagination:
type: object
required:
- total
- limit
- offset
- has_more
properties:
total:
type: integer
format: int64
limit:
type: integer
offset:
type: integer
has_more:
type: boolean
/api/v1/internal/hq/firmware/{firmware_id}:
patch:
operationId: updateFirmwareRelease
summary: Update min_version security floor and/or rollout percentage
description: "Update mutable fields on a firmware release. At least one of `min_version`\nor `rollout_percent` must be supplied.\n\n- `min_version`: devices running below this floor get a forced update (no\n skip option). Send an empty string to clear the floor.\n- `rollout_percent`: percentage of the cohort (0-100) eligible to receive\n this release. Device IDs are hashed into a 0-99 bucket; a device is\n served only if its bucket is below the configured percent. Use this to\n gradually expand a release from canary to full rollout.\n"
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: firmware_id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
description: At least one of min_version or rollout_percent is required.
properties:
min_version:
type: string
nullable: true
description: Semver minimum version. Devices below this get forced update. Empty string clears.
maxLength: 31
rollout_percent:
type: integer
nullable: true
minimum: 0
maximum: 100
description: Cohort fraction served this release (0-100). 100 means everyone in cohort.
responses:
'200':
description: Updated release.
content:
application/json:
schema:
type: object
properties:
id:
type: string
example: fw_019d68dd-eda3-70d6-84bf-000000000001
device_type:
type: string
version:
type: string
s3_key:
type: string
sha256:
type: string
signature:
type: string
size_bytes:
type: integer
format: int64
changelog:
type: string
release_date:
type: string
format: date
min_version:
type: string
rollout_percent:
type: integer
minimum: 0
maximum: 100
description: Cohort fraction (0-100) currently served this release.
status:
type: string
enum:
- staged
- active
channel:
type: string
enum:
- stable
- early-access
- beta
- nightly
uploaded_by:
type: string
activated_at:
type: string
format: date-time
paused_at:
type: string
format: date-time
nullable: true
description: When non-null, manifest resolver returns 204 for this release fleet-wide.
paused_by:
type: string
nullable: true
description: User ID of the operator who paused the release.
pause_reason:
type: string
nullable: true
description: Human-readable justification for the pause.
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
'400':
description: Neither min_version nor rollout_percent supplied, or rollout_percent out of range.
'404':
description: Release not found.
/api/v1/internal/hq/firmware/{firmware_id}/activate:
post:
operationId: activateFirmwareRelease
summary: Activate a staged firmware release
description: 'Atomically returns the current active release to `staged` and activates
the specified one. Only releases in `staged` status can be activated.
**Rollback:** To rollback, activate any previous staged release.
The old active release returns to `staged`, so it can be re-activated later.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: firmware_id
in: path
required: true
schema:
type: string
example: fw_019d68dd-eda3-70d6-84bf-000000000001
responses:
'200':
description: Release activated.
content:
application/json:
schema:
$ref: '#/paths/~1api~1v1~1internal~1hq~1firmware~1%7Bfirmware_id%7D/patch/responses/200/content/application~1json/schema'
'400':
description: Release is not in staged status.
'404':
description: Release not found.
/api/v1/internal/hq/firmware/{firmware_id}/pause:
post:
operationId: pauseFirmwareRelease
summary: Pause a firmware release (fleet-wide kill-switch)
description: 'Sets `paused_at` on the release. While paused, the manifest resolver
returns 204 No Content for this release even for devices in its rollout
cohort. Independent of `rollout_percent` — pause is the kill-switch primitive.
The audit trail records `paused_by` and `pause_reason`.
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: firmware_id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- reason
properties:
reason:
type: string
description: Human-readable justification (e.g. 'elevated crash rate', 'customer complaint').
maxLength: 512
responses:
'200':
description: Release paused.
content:
application/json:
schema:
$ref: '#/paths/~1api~1v1~1internal~1hq~1firmware~1%7Bfirmware_id%7D/patch/responses/200/content/application~1json/schema'
'400':
description: Missing reason or release already paused.
'404':
description: Release not found.
/api/v1/internal/hq/firmware/{firmware_id}/resume:
post:
operationId: resumeFirmwareRelease
summary: Resume a paused firmware release
description: 'Clears `paused_at`, `paused_by`, and `pause_reason` on the release. The
manifest resolver will once again serve this release to eligible devices.
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: firmware_id
in: path
required: true
schema:
type: string
responses:
'200':
description: Release resumed.
content:
application/json:
schema:
$ref: '#/paths/~1api~1v1~1internal~1hq~1firmware~1%7Bfirmware_id%7D/patch/responses/200/content/application~1json/schema'
'400':
description: Release is not paused.
'404':
description: Release not found.
/api/v1/internal/hq/firmware/catalog:
get:
operationId: listFirmwareCatalog
summary: List firmware catalog with fleet rollout signals
description: 'Catalog view of firmware releases enriched with fleet-derived signals:
device counts, adoption series, crash counts, and pause status. Used by
the HQ web console to render the fleet dashboard. Distinct from the basic
list endpoint because it joins against `physical_devices` and
`device_update_events`.
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: device_type
in: query
description: Filter by hardware type (e.g. scribe). Defaults to scribe.
schema:
type: string
default: scribe
- name: channel
in: query
description: Filter by release channel.
schema:
type: string
enum:
- stable
- early-access
- beta
- nightly
- name: status
in: query
description: Filter by release status.
schema:
type: string
enum:
- staged
- active
responses:
'200':
description: Catalog entries, newest first.
content:
application/json:
schema:
type: array
items:
type: object
required:
- id
- version
- device_type
- channel
- status
description: 'Catalog entry enriched with fleet-derived adoption and crash signals.
Computed by joining `firmware_releases` against `physical_devices` and
`device_update_events`.
'
properties:
id:
type: string
example: fw_019d68dd-eda3-70d6-84bf-000000000001
version:
type: string
device_type:
type: string
channel:
type: string
enum:
- stable
- early-access
- beta
- nightly
status:
type: string
enum:
- staged
- active
uploaded_by:
type: string
created_at:
type: string
format: date-time
activated_at:
type: string
format: date-time
nullable: true
paused_at:
type: string
format: date-time
nullable: true
paused_by:
type: string
nullable: true
pause_reason:
type: string
nullable: true
sha256:
type: string
pattern: ^[0-9a-f]{64}$
size_bytes:
type: integer
format: int64
current_device_count:
type: integer
description: Devices currently reporting this version via firmware check-in.
ever_updated_count:
type: integer
description: Distinct devices that have reached `verified` for this release.
crash_count_last_7d:
type: integer
description: Crash reports in the last 7 days from devices on this release.
adoption_series:
type: array
description: Daily adoption counts (newest last) for sparkline rendering.
items:
type: object
required:
- date
- count
properties:
date:
type: string
format: date
count:
type: integer
/api/v1/internal/hq/firmware/{firmware_id}/dryrun:
post:
operationId: dryrunFirmwareRollout
summary: Preview rollout cohort without targeting devices
description: 'Evaluate a candidate cohort selector and report how many devices would
match, how many are eligible (after policy/version/hardware filters), and
which devices are excluded and why. Read-only — no devices are targeted.
Used by the HQ console to give operators confidence before they create a
real rollout.
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: firmware_id
in: path
required: true
schema:
type: string
requestBody:
required: false
description: 'Optional cohort selector. Omit the body to preview the release against
its default cohort.
'
content:
application/json:
schema:
type: object
description: 'Optional cohort selector for the dryrun preview. The body itself is
optional — POSTing an empty body returns the default preview for the
release.
'
properties:
target_cohort:
type: object
description: Cohort selector to evaluate. Same shape as `firmware_rollouts.target_cohort`.
properties:
channel:
type: string
enum:
- stable
- early-access
- beta
- nightly
from_version_glob:
type: string
description: Semver glob (e.g. '1.2.*') matching device's current firmware_version.
group_ids:
type: array
items:
type: string
responses:
'200':
description: Dryrun result.
content:
application/json:
schema:
type: object
required:
- firmware_id
- matched_count
- eligible_count
- excluded_disabled_devices
- excluded_pinned_elsewhere
- below_min_version
- already_on_target_version
properties:
firmware_id:
type: string
description: fw_-prefixed release ID this dryrun was evaluated against.
example: fw_019d68dd-eda3-70d6-84bf-000000000001
matched_count:
type: integer
format: int64
description: Devices matching the cohort selector before policy filters.
eligible_count:
type: integer
format: int64
description: Devices that would actually be served the release.
excluded_disabled_devices:
type: integer
format: int64
description: Count of devices excluded because their `ota_policy` is `disabled`.
excluded_pinned_elsewhere:
type: integer
format: int64
description: Count of devices with `ota_policy=pinned` pinned to a different release.
below_min_version:
type: integer
format: int64
description: Count of devices below this release's `min_version` floor.
already_on_target_version:
type: integer
format: int64
description: Count of devices already running the release's target version.
notes:
type: string
description: 'Free-form operator-facing notes. Used to flag approximations or
degraded preview modes when precise cohort matching is unavailable.
'
'400':
description: Invalid cohort selector.
'404':
description: Release not found.
/api/v1/internal/hq/devices/{device_id}/firmware-target:
put:
operationId: pinDeviceFirmwareTarget
summary: Pin a device to a specific firmware release
description: 'Creates or replaces the firmware pin for a single device. While pinned,
the manifest resolver serves the targeted release regardless of the
device''s channel — provided the device''s `ota_policy` is `pinned`.
Pins are the primary pilot-testing primitive: bring a single rig onto
beta firmware without affecting the rest of the fleet.
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: device_id
in: path
required: true
description: Hardware device ID (12-character lowercase hex MAC for scribe).
schema:
type: string
example: aabbccddeeff
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- firmware_id
- reason
properties:
firmware_id:
type: string
description: fw_-prefixed release ID to pin.
example: fw_019d68dd-eda3-70d6-84bf-000000000001
reason:
type: string
description: Human-readable justification (e.g. 'pilot device for beta firmware').
maxLength: 512
responses:
'200':
description: Pin created or replaced.
content:
application/json:
schema:
type: object
required:
- device_id
- firmware_id
- pinned_by
- pinned_at
description: A pin overriding channel-based resolution for one device.
properties:
device_id:
type: string
description: Hardware device ID (e.g. 12-char lowercase hex MAC for scribe).
example: aabbccddeeff
firmware_id:
type: string
description: fw_-prefixed release ID this device is pinned to.
example: fw_019d68dd-eda3-70d6-84bf-000000000001
reason:
type: string
nullable: true
pinned_by:
type: string
description: User ID of the operator who created the pin.
pinned_at:
type: string
format: date-time
'400':
description: Invalid request (missing fields, unknown firmware_id).
'404':
description: Device not found.
delete:
operationId: unpinDeviceFirmwareTarget
summary: Remove a device firmware pin
description: 'Deletes the pin for a device. The device returns to channel-based
resolution. Note this does not change `ota_policy`; if the device is
still in `pinned` policy with no pin, the resolver returns 204
(intentional: prevents accidental re-targeting onto the channel).
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: device_id
in: path
required: true
schema:
type: string
responses:
'204':
description: Pin removed (or never existed).
'404':
description: Device not found.
/api/v1/internal/hq/devices/{device_id}/ota-policy:
get:
operationId: getDeviceOTAPolicy
summary: Get the OTA policy for a device
description: 'Returns the device''s OTA policy plus optional firmware pin (when policy is
`pinned`). Precedence in the resolver: `disabled` > `pinned` > `auto`.
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: device_id
in: path
required: true
schema:
type: string
responses:
'200':
description: Device OTA policy state.
content:
application/json:
schema:
type: object
required:
- policy
properties:
policy:
$ref: '#/paths/~1api~1v1~1internal~1hq~1devices~1%7Bdevice_id%7D~1ota-policy/put/requestBody/content/application~1json/schema/properties/policy'
reason:
type: string
nullable: true
description: Human-readable justification for non-`auto` policy.
set_by:
type: string
nullable: true
description: User ID of the operator who last set the policy.
set_at:
type: string
format: date-time
nullable: true
firmware_target:
allOf:
- $ref: '#/paths/~1api~1v1~1internal~1hq~1devices~1%7Bdevice_id%7D~1firmware-target/put/responses/200/content/application~1json/schema'
nullable: true
description: Present when `policy` is `pinned` and a pin exists.
'404':
description: Device not found.
put:
operationId: setDeviceOTAPolicy
summary: Set the OTA policy for a device
description: 'Set `ota_policy` to `auto`, `pinned`, or `disabled` for a single device.
Records `set_by` and `set_at` for the audit trail.
Switching to `pinned` does NOT create a pin — call PUT
`/devices/{device_id}/firmware-target` separately. Until a pin exists,
a `pinned` device receives 204 from the manifest resolver.
Switching to `disabled` is the strongest signal: the resolver returns 204
unconditionally. Used for engineering rigs and devices flashed manually.
**Auth:** JWT + HQ admin only.
'
tags:
- Firmware Admin
security:
- bearerAuth: []
parameters:
- name: device_id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- policy
- reason
properties:
policy:
type: string
enum:
- auto
- pinned
- disabled
description: 'Per-device OTA policy.
- `auto`: normal channel/rollout resolution.
-
# --- truncated at 32 KB (33 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/sageox/refs/heads/main/openapi/sageox-firmware-admin-api-openapi.yml