SageOx Firmware OTA API

The Firmware OTA API from SageOx — 1 operation(s) for firmware ota.

OpenAPI Specification

sageox-firmware-ota-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: SageOx Admin Firmware OTA 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 OTA
paths:
  /api/v1/firmware/latest:
    get:
      operationId: getFirmwareManifest
      summary: Check for firmware updates
      description: 'Device-facing endpoint that returns the latest available firmware manifest

        for the specified device type. The device polls this endpoint periodically

        and compares the returned version against its current firmware.


        This endpoint is **unauthenticated** (devices have no JWT) but rate-limited

        by X-Device-ID header. Returns 204 No Content when no update is available.


        The response `url` field contains a pre-signed S3 URL (15-minute TTL) for

        direct firmware binary download. The device fetches this URL directly with

        no auth headers.

        '
      tags:
      - Firmware OTA
      security: []
      parameters:
      - name: device
        in: query
        required: true
        description: Hardware type identifier (e.g., `scribe`, `clip`).
        schema:
          type: string
          example: scribe
      - name: version
        in: query
        required: true
        description: Currently running firmware version (semver format).
        schema:
          type: string
          example: 1.0.0
      - name: channel
        in: query
        required: false
        description: Release channel to check. Defaults to `stable`.
        schema:
          type: string
          default: stable
          enum:
          - stable
          - early-access
          - beta
          - nightly
      - name: X-Device-ID
        in: header
        required: false
        description: Hardware-derived stable identifier. For scribe, this is the 12-character lowercase hex of the ESP32 base MAC address.
        schema:
          type: string
          example: aabbccddeeff
      - name: X-Device-Type
        in: header
        description: Hardware type, same as the `device` query parameter. Informational.
        schema:
          type: string
          example: scribe
      - name: User-Agent
        in: header
        description: Device firmware user agent string.
        schema:
          type: string
          example: sageox-scribe/1.0.0 (esp32-s3; xtensa-lx7)
      responses:
        '200':
          description: A newer firmware version is available.
          content:
            application/json:
              schema:
                type: object
                required:
                - version
                - url
                description: 'Firmware manifest returned to devices. Field sizes are constrained by

                  the ESP32 firmware parser buffer sizes.

                  '
                properties:
                  version:
                    type: string
                    description: Semver version string. Must be newer than the device's current version.
                    maxLength: 31
                    example: 1.2.0
                  url:
                    type: string
                    description: Pre-signed S3 URL for direct firmware binary download (15-minute TTL, no auth required).
                    maxLength: 511
                  sha256:
                    type: string
                    description: SHA-256 hash of the firmware binary, lowercase hex.
                    pattern: ^[0-9a-f]{64}$
                    example: a3f1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1
                  signature:
                    type: string
                    description: Base64-encoded RSA-2048 signature over the SHA-256 digest. Required for production builds.
                    maxLength: 511
                  size:
                    type: integer
                    format: int64
                    description: Firmware binary size in bytes. Used for progress bar and hash verification.
                  changelog:
                    type: string
                    description: Human-readable changelog. Devices with small screens truncate on their end.
                    maxLength: 2048
                  release_date:
                    type: string
                    format: date
                    description: ISO-8601 release date.
                  min_version:
                    type: string
                    description: Security floor. Devices below this version get a forced update (no skip option).
                    maxLength: 31
        '204':
          description: No update available. Device is up to date or not targeted.
        '400':
          description: Missing required query parameters.
        '429':
          description: Rate limit exceeded. Back off and retry.
        '500':
          description: Server error.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'JWT token obtained from the auth service (/api/auth/token).

        Token is validated using JWKS from the auth service.

        Required claims: sub (user_id), email, name, tier.

        '