h-cam B1280

h-cam B1280 Camera API

The camera exposes a local JSON control API under /api/v1, an API-validated MJPEG redirect at /api/v1/preview.mjpg, a direct MJPEG stream on port 81, a long-running SSE event stream on the main HTTP port, always-registered debug diagnostics, and the built-in browser document at /. The single normal firmware image does not expose test-only or fault-injection routes. All examples use http://CAMERA_IP; replace CAMERA_IP with the DHCP, static, mDNS, or discovery result for your device.

Reference Source

This reference follows the firmware-generated zorba.camera-api-bundle/v1 artifact. That bundle embeds the authoritative JSON Schemas, the complete 37-surface normal-firmware inventory, and schema-validated examples captured from a normally built and already-running camera. Its capture record retains the real firmware Git SHA, ESP-IDF version, and build time; volatile example values are normalized without changing their types or relationships.

Except for the browser document at / and the sensor diagnostic at /debug/camera/sensor, which ignore query parameters, routes without a query schema reject any query string. JSON request bodies are limited to 2,048 bytes, must be objects where a body is required, and reject unknown fields, duplicate keys after escape decoding, non-ASCII request keys, and non-canonical integer lexemes.

Quick Start

Start with the identity endpoint so your client can confirm it is talking to an h-cam B1280 and learn the firmware/API version.

These commands use the connected device mDNS name camera-e4192f.local; substitute the mDNS name reported by your camera if it differs.

curl http://camera-e4192f.local/api/v1/info

Make sure continuous acquisition is running so retained frames are being published:

curl -X POST http://camera-e4192f.local/api/v1/camera/acquisition/continuous/start

Fetch the latest retained JPEG with the simplest frame request:

curl http://camera-e4192f.local/api/v1/frames/latest --output frame.jpg

For interactive operation, open the camera root URL in a browser. The built-in UI shows acquisition, reboot, and status controls; edits pipeline mode, preview and sensor frame periods, exposure, gain, JPEG quality, denoise, and test pattern; auto-starts continuous acquisition; and pauses preview while trigger-ready acquisition is active.

Endpoint Index

Service

MethodPathSuccessUse
GET/200 text/htmlBuilt-in browser control and status document.
GET/api/v1200 JSONAPI version and link map.
GET/api/v1/info200 JSONModel, firmware, chip, sensor, network identity, and feature flags.
GET/api/v1/health200 JSONOverall state, warnings, faults, uptime, and heap telemetry.
GET/api/v1/time200 JSONCompact PTP status plus device and monotonic nanosecond timestamps.
GET/api/v1/system200 JSONBoot ID, reset reason, uptime, and silicon temperature.
POST/api/v1/system/reboot200 JSONSchedules a reboot. Optional body: {"delay_ms": integer}; an omitted body or delay_ms uses 250 ms.

Network

MethodPathSuccessUse
GET/api/v1/network200 JSONActive address, confirmed config, pending config, DHCP, mDNS, recovery, and discovery state.
PATCH/api/v1/network200 or 202 JSONValidates or applies DHCP/static IPv4 network configuration.
POST/api/v1/network/confirm200 JSONConfirms a staged network configuration by config_id.
POST/api/v1/network/rollback202 JSON or 200 JSONAbandons a staged network change by config_id; idempotent calls can return the current network snapshot.
UDP39590JSON datagramBroadcast discovery request and response protocol.

Camera

MethodPathSuccessUse
GET/api/v1/camera200 JSONCompact camera summary and links.
GET/api/v1/camera/capabilities200 JSONSensor, pipeline mode, timing, preview, trigger, network, and feature capabilities.
GET/api/v1/camera/config200 JSONCurrent sensor-backed configuration and timing readback.
PATCH/api/v1/camera/config200 JSONApplies mutable sensor timing/control, JPEG, and ISP fields.
GET/api/v1/camera/state200 JSONAcquisition state, trigger state, latest frame ID, frame timing, and buffer count.
GET/api/v1/camera/acquisition200 JSONCurrent acquisition mode and retained-frame buffer state.
POST/api/v1/camera/acquisition/continuous/start200 JSONStarts continuous free-run acquisition. Request body is rejected if present.
POST/api/v1/camera/acquisition/triggered/start200 JSONStarts software-trigger-ready acquisition. Request body is rejected if present.
POST/api/v1/camera/acquisition/stop200 JSONStops acquisition. Request body is rejected if present.
POST/api/v1/camera/acquisition/flush200 JSONFlushes retained JPEG frames. Request body is rejected if present.

Trigger

MethodPathSuccessUse
GET/api/v1/camera/trigger200 JSONSoftware trigger readiness, admission window, and active/scheduled/last trigger summaries.
POST/api/v1/camera/trigger/schedule200 JSONSchedules a software trigger output, optionally waiting for the associated frame.
POST/api/v1/camera/trigger/cancel200 JSONCancels the current scheduled trigger or a retained scheduled trigger by ID.

Frames and Streams

MethodPathSuccessUse
GET/api/v1/frames/latest200 image/jpegLatest retained JPEG. Query: format=jpeg; any quality parameter returns 501 unsupported.
GET/api/v1/frames/latest/meta200 JSONMetadata for the latest retained frame.
GET/api/v1/frames/{frame_id}200 image/jpegRetained JPEG by public frame ID. Query: format=jpeg.
GET/api/v1/frames/{frame_id}/meta200 JSONMetadata for a retained frame by public frame ID.
GET/api/v1/frames/next200 image/jpegNext retained frame after after_frame_id. Query: after_frame_id required, timeout_ms optional, format=jpeg optional.
GET/api/v1/preview.mjpg307 redirect or 503 JSONValidated API preview entry point; redirects to the direct MJPEG stream server.
GEThttp://CAMERA_IP:81/stream.mjpgmultipart/x-mixed-replaceDirect MJPEG stream. Query: frame_period_us optional.
GET/api/v1/eventstext/event-streamServer-sent system and frame events, plus terminal stream error events.

Metrics

MethodPathSuccessUse
GET/api/v1/metrics200 JSONCamera, preview, trigger, network, and system counters.

Debug Diagnostics

MethodPathSuccessUse
GET/debug/runtime200 JSONInternal resource, task, service, lifecycle, and coherent PTP diagnostics.
GET/debug/camera/sensor200 JSONCamera-task sensor registers, AR0144 diagnostics, and raw-slot ownership.
GET/debug/time200 JSONComplete coherent PTP, correlation, regression, path, actuator, readiness, and DelayReq diagnostics.

Endpoints

Each endpoint section names the accepted query string, request body, complete response status set, and successful response contract. The response-status tables are projected from the generated bundle; JSON errors use the shared error schema described after the endpoint reference.

Service

GET /

Return the built-in browser control and status document served by the normal firmware.

Query String
FieldTypeDetails
Query stringignoredQuery parameters do not alter this response.
Request Body

No request body.

Response Statuses
FieldTypeDetails
200 successtext/htmlschema: web.root_html_response
Success Response

Returns 200 text/html; the HTML document is not a JSON API response.

Example
curl http://CAMERA_IP/
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

[built-in camera control and status document]

GET /api/v1

Return API version metadata and the root link map.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: discovery.api_root
400 bad_requestapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["api_version", "links"],
  "additionalProperties": false,
  "properties": {
    "api_version": { "const": "1.0" },
    "links": {
      "type": "object",
      "additionalProperties": { "type": "string" }
...
Example
curl http://CAMERA_IP/api/v1
HTTP/1.1 200 OK
Content-Type: application/json

{
  "api_version": "1.0",
  "links": {
    "info": "/api/v1/info",
    "health": "/api/v1/health",
    "network": "/api/v1/network",
    "time": "/api/v1/time",
    "camera": "/api/v1/camera",
    "camera_config": "/api/v1/camera/config",
    "camera_state": "/api/v1/camera/state",
    "latest_frame": "/api/v1/frames/latest?format=jpeg",
    "preview": "/api/v1/preview.mjpg",
    "events": "/api/v1/events",
    "system": "/api/v1/system"
  }
}

GET /api/v1/info

Return product identity, firmware build identity, chip/sensor identity, network identity, and high-level capability flags.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: discovery.info
400 bad_requestapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["model", "serial_number", "firmware_version", "api_version", "firmware", "chip", "sensor", "network", "capabilities"],
  "additionalProperties": false,
  "properties": {
    "model": { "type": "string" },
    "serial_number": { "type": ["string", "null"] },
    "firmware_version": { "type": "string" },
    "api_version": { "const": "1.0" },
...
Example
curl http://CAMERA_IP/api/v1/info
HTTP/1.1 200 OK
Content-Type: application/json

{
  "model": "custom-esp32-p4-camera-demo",
  "serial_number": null,
  "firmware_version": "dev",
  "api_version": "1.0",
  "firmware": {
    "name": "esp32_p4_ar0144_camera",
    "idf_version": "v5.5.4",
    "git_sha": "df63260b70569d616ce148de501d7d572c57ba32-dirty",
    "build_time": "Aug  4 2026 16:06:26"
  },
  "chip": {
    "target": "esp32p4",
    "revision": "v1.3",
    "flash_bytes": 16777216,
    "psram_bytes": 33554432
  },
  "sensor": {
    "model": "ar0144",
    "color": false,
    "shutter": "global",
    "native_width": 1280,
    "native_height": 800,
    "pipeline_mode_default": "raw10_1280x800",
    "frame_payload_format_default": "jpeg"
  },
  "network": {
    "mac": "02:00:00:00:00:01",
    "hostname": "camera-example-1"
  },
  "capabilities": {
    "software_trigger": true,
    "ptp": true,
    "ptp_best_effort_scheduling": true,
    "ptp_strict_scheduling": true,
    "retained_time_quality": true,
    "preview_mjpeg": true,
    "raw_bayer": false,
    "pipeline_mode_switching": true,
    "temperature": true
  }
}

GET /api/v1/health

Return overall service health, warnings, faults, uptime, and heap telemetry.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: discovery.health
400 bad_requestapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["state", "uptime_ms", "faults", "warnings", "heap"],
  "additionalProperties": false,
  "properties": {
    "state": { "enum": ["ok", "degraded", "fault"] },
    "uptime_ms": { "type": "integer", "minimum": 0 },
    "faults": {
      "type": "array",
...
Example
curl http://CAMERA_IP/api/v1/health
HTTP/1.1 200 OK
Content-Type: application/json

{
  "state": "ok",
  "uptime_ms": 20832,
  "faults": [],
  "warnings": [],
  "heap": {
    "internal_free_bytes": 147327,
    "internal_min_free_bytes": 134279,
    "psram_free_bytes": 13067300,
    "psram_min_free_bytes": 13067100
  }
}

GET /api/v1/time

Return the compact operational PTP status and current device/monotonic nanosecond timestamps.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: discovery.time
400 bad_requestapplication/jsonschema: common.product_error
503 time_unavailableapplication/jsonschema: common.product_error
Success Response Schema

{
  "type": "object",
  "required": [
    "boot_id",
    "timestamp_domain",
    "device_time_ns",
    "monotonic_time_ns",
    "ptp_time_ns",
...
Response Notes
FieldDetails
boot_idDiscard cached monotonic timestamps, readiness epochs, and mapping generations when this changes.
device_time_nsUse the paired time_quality object to determine readability and synchronization; readable PTP does not necessarily mean strict scheduling readiness.
monotonic_time_nsUse it for elapsed-time measurement and scheduling within the current boot. It is not comparable across reboots or devices unless translated through another time source.
ptp_time_nsNull only when the PTP clock cannot be read; acquiring and holdover values remain available with truthful quality.
time_quality / ptp.time_qualityBoth fields carry the same coherent observation rather than independently sampled clock state.
Example
curl http://CAMERA_IP/api/v1/time
HTTP/1.1 200 OK
Content-Type: application/json

{
  "boot_id": "00000000-0000-4000-8000-000000000002",
  "timestamp_domain": "ptp",
  "device_time_ns": "1700000000000000000",
  "monotonic_time_ns": "1700000000000000000",
  "ptp_time_ns": "1700000000000000000",
  "time_quality": {
    "domain": "ptp",
    "clock_readable": true,
    "sync_state": "acquiring",
    "synchronized": false,
    "readiness_epoch": null,
    "clock_mapping_generation": 2
  },
  "ptp": {
    "supported": true,
    "enabled": true,
    "scheduling_ready": false,
    "correlation_observation_eligible": true,
    "correlation_age_us": "7274",
    "correlation_uncertainty_ns": "2228",
    "correlation_bracket_span_ns": "1000",
    "time_quality": {
      "domain": "ptp",
      "clock_readable": true,
      "sync_state": "acquiring",
      "synchronized": false,
      "readiness_epoch": null,
      "clock_mapping_generation": 2
    },
    "grandmaster_id": "grandmaster-1"
  }
}
  • The complete coherent diagnostic projection is available from /debug/time; do not poll it when the compact operational fields are sufficient.

GET /api/v1/system

Return boot identity, reset reason, uptime, and ESP32-P4 silicon temperature.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: system.response
400 bad_requestapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["uptime_s", "boot_id", "reset_reason", "temperature_c"],
  "additionalProperties": false,
  "properties": {
    "uptime_s": { "type": "number", "minimum": 0 },
    "boot_id": { "type": "string" },
    "reset_reason": {
      "enum": [
...
Example
curl http://CAMERA_IP/api/v1/system
HTTP/1.1 200 OK
Content-Type: application/json

{
  "uptime_s": 53,
  "boot_id": "00000000-0000-4000-8000-000000000002",
  "reset_reason": "software",
  "temperature_c": 35.2
}

POST /api/v1/system/reboot

Schedule a device reboot after an optional delay. An omitted body or delay_ms uses 250 ms.

Query String

No query string parameters are defined for this endpoint.

Request Body Schema
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "delay_ms": { "type": "integer", "minimum": 0, "maximum": 10000 }
  }
}
Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: system.reboot.response
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
503 unavailableapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["ok", "rebooting", "delay_ms"],
  "additionalProperties": false,
  "properties": {
    "ok": { "const": true },
    "rebooting": { "const": true },
    "delay_ms": { "type": "integer", "minimum": 0, "maximum": 10000 }
  }
}
Example
curl -X POST -H 'Content-Type: application/json' --data '{"delay_ms":250}' http://CAMERA_IP/api/v1/system/reboot
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "rebooting": true,
  "delay_ms": 250
}

Network

GET /api/v1/network

Return active address state, confirmed configuration, pending configuration, DHCP, mDNS, recovery, and UDP discovery state.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: network.response
400 bad_requestapplication/jsonschema: common.product_error
503 network_unavailableapplication/jsonschema: common.product_error
Success Response Schema
{
  "description": "network.response snapshot",
  "type": "object",
  "required": ["hostname", "effective_hostname", "effective_mdns_name", "state", "mode", "dhcp", "ipv4", "netmask", "gateway", "dns", "mac", "mdns_name", "link_up", "speed_mbps", "full_duplex", "dhcp_lease", "active", "configured", "pending", "recovery", "mdns", "discovery"],
  "additionalProperties": false,
  "properties": {
    "hostname": { "type": "string" },
    "effective_hostname": { "type": ["string", "null"] },
    "effective_mdns_name": { "type": ["string", "null"] },
...
Example
curl http://CAMERA_IP/api/v1/network
HTTP/1.1 200 OK
Content-Type: application/json

{
  "hostname": "camera-example-1",
  "state": "dhcp_bound",
  "mdns_name": "camera-example-1.local",
  "mac": "02:00:00:00:00:01",
  "link_up": true,
  "speed_mbps": 100,
  "full_duplex": true,
  "mode": "dhcp",
  "dhcp": true,
  "ipv4": "192.0.2.1",
  "netmask": "255.255.255.0",
  "gateway": "192.0.2.1",
  "dns": [],
  "dhcp_lease": {
    "state": "bound",
    "server_ipv4": null,
    "lease_expires_at_uptime_ms": 0,
    "last_attempt_uptime_ms": 2030,
    "last_success_uptime_ms": 13185,
    "last_error": null
  },
  "active": {
    "mode": "dhcp",
    "ipv4": "192.0.2.1",
    "netmask": "255.255.255.0",
    "gateway": "192.0.2.1",
    "dns": [],
    "hostname": "camera-example-1",
    "source": "dhcp",
    "usable": true,
    "since_uptime_ms": 13185
  },
  "configured": {
    "config_id": "config-1",
    "mode": "dhcp",
    "hostname": "camera-example-1",
    "static_ipv4": null,
    "updated_generation": 993,
    "confirmed_this_boot_uptime_ms": null,
    "confirmed": true
  },
  "pending": null,
  "effective_hostname": "camera-example-1",
  "effective_mdns_name": "camera-example-1.local",
  "recovery": {
    "active": false,
    "reason": null,
    "rollback_available": false,
    "last_failure": "pending_reboot_rollback",
    "dhcp_retry_at_uptime_ms": 0,
    "link_local_candidate_count": 0
  },
  "mdns": {
    "enabled": true,
    "started": true,
    "publication_phase": "running",
    "observation_stale": false,
    "renamed": false,
    "requested_hostname": "camera-example-1",
    "effective_hostname": "camera-example-1",
    "name": "camera-example-1.local",
    "last_error": null,
    "services": {
      "http": {
        "type": "_http._tcp",
        "port": 80,
        "txt": {
          "api": "1.0"
        }
      },
      "discovery": {
        "type": "_esp32-p4-camera._udp",
        "port": 39590,
        "txt": {
          "protocol": "1"
        }
      }
    }
  },
  "discovery": {
    "enabled": true,
    "udp_port": 39590,
    "protocol_version": 1
  }
}
  • The camera advertises its HTTP API over mDNS as <hostname>.local. Fresh devices derive the default hostname from the Ethernet MAC address as camera-<last-three-mac-bytes>.

PATCH /api/v1/network

Validate or apply a DHCP/static IPv4 network configuration.

Query String

No query string parameters are defined for this endpoint.

Request Body Schema
{
  "type": "object",
  "required": ["mode"],
  "additionalProperties": false,
  "properties": {
    "mode": { "enum": ["dhcp", "static"] },
    "hostname": { "type": "string", "minLength": 1, "maxLength": 63 },
    "ipv4": { "type": "string", "minLength": 7, "maxLength": 15 },
    "netmask": { "type": "string", "minLength": 7, "maxLength": 15 },
...
Request Notes
GroupFieldDetails
NetworkmodeTarget network mode. Recovery is reported by state, not configured directly through PATCH.
NetworkhostnamePreserved when omitted. Fresh devices default to camera-<last-three-mac-bytes> and publish <hostname>.local over mDNS.
Networkipv4, netmask, gatewayStatic IPv4 validation requires a gateway in the same subnet unless no-gateway mode is explicitly allowed.
NetworkdnsDNS servers used by static IPv4 configuration.
Networkallow_no_gatewayOpt-in guard for static IPv4 deployments that intentionally have no gateway.
NetworkapplyValidation-only requests leave the active address unchanged; apply requests stage a candidate and usually return 202 Accepted.
Networkconfirm_timeout_msConfirmation window for applied changes before automatic rollback can recover reachability.
Response Statuses
FieldTypeDetails
200 validatedapplication/jsonschema: network.patch.validate_response
200 appliedapplication/jsonschema: network.response
202 pendingapplication/jsonschema: network.response
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
409 network_change_pendingapplication/jsonschema: common.product_error
409 address_conflictapplication/jsonschema: common.product_error
409 network_busy_conflictapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
503 network_busy_unavailableapplication/jsonschema: common.product_error
503 network_unavailableapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
503 unavailableapplication/jsonschema: common.product_error
Success Response Schema
{
  "oneOf": [
    {
      "description": "200 validation result",
      "type": "object",
      "required": ["valid", "candidate"],
      "additionalProperties": false,
      "properties": {
        "valid": { "const": true },
...
Example
curl -X PATCH -H 'Content-Type: application/json' --data '{"mode":"dhcp","hostname":"camera-example-1"}' http://CAMERA_IP/api/v1/network
curl -X PATCH -H 'Content-Type: application/json' --data '{"mode":"dhcp","hostname":"camera-example-2","apply":true,"confirm_timeout_ms":30000}' http://CAMERA_IP/api/v1/network
curl -X PATCH -H 'Content-Type: application/json' --data '{"mode":"dhcp","hostname":"camera-example-2","apply":true,"confirm_timeout_ms":30000}' http://CAMERA_IP/api/v1/network
HTTP/1.1 200 OK
Content-Type: application/json

{
  "valid": true,
  "candidate": {
    "mode": "dhcp",
    "hostname": "camera-example-1",
    "static_ipv4": null
  }
}
  • Static mode requires ipv4, netmask, gateway, and any DNS servers needed by the deployment.
  • Omitting apply or setting apply:false validates without changing the active address.
  • Network changes are staged so a bad static address can be rolled back if it is not confirmed.
  • To restore DHCP, PATCH {"mode":"dhcp","apply":true}, reconnect at the DHCP address, and confirm the returned config_id.

POST /api/v1/network/confirm

Confirm a staged network configuration before its confirmation window expires.

Query String

No query string parameters are defined for this endpoint.

Request Body Schema
{
  "type": "object",
  "required": ["config_id"],
  "additionalProperties": false,
  "properties": {
    "config_id": { "type": "string", "minLength": 1, "maxLength": 36 }
  }
}
Request Notes
GroupFieldDetails
Networkconfig_idIdentifies the pending configuration to confirm or roll back.
Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: network.response
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
409 confirm_not_readyapplication/jsonschema: common.product_error
409 confirm_not_reachableapplication/jsonschema: common.product_error
409 no_pending_configapplication/jsonschema: common.product_error
409 network_busy_conflictapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
503 network_busy_unavailableapplication/jsonschema: common.product_error
503 network_unavailableapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
503 unavailableapplication/jsonschema: common.product_error
Success Response Schema
{
  "description": "network.response snapshot",
  "type": "object",
  "required": ["hostname", "effective_hostname", "effective_mdns_name", "state", "mode", "dhcp", "ipv4", "netmask", "gateway", "dns", "mac", "mdns_name", "link_up", "speed_mbps", "full_duplex", "dhcp_lease", "active", "configured", "pending", "recovery", "mdns", "discovery"],
  "additionalProperties": false,
  "properties": {
    "hostname": { "type": "string" },
    "effective_hostname": { "type": ["string", "null"] },
    "effective_mdns_name": { "type": ["string", "null"] },
...
Example
curl -X POST -H 'Content-Type: application/json' --data '{"config_id":"config-2"}' http://CAMERA_IP/api/v1/network/confirm
HTTP/1.1 200 OK
Content-Type: application/json

{
  "hostname": "camera-example-2",
  "state": "dhcp_bound",
  "mdns_name": null,
  "mac": "02:00:00:00:00:01",
  "link_up": true,
  "speed_mbps": 100,
  "full_duplex": true,
  "mode": "dhcp",
  "dhcp": true,
  "ipv4": "192.0.2.1",
  "netmask": "255.255.255.0",
  "gateway": "192.0.2.1",
  "dns": [],
  "dhcp_lease": {
    "state": "bound",
    "server_ipv4": null,
    "lease_expires_at_uptime_ms": 0,
    "last_attempt_uptime_ms": 21224,
    "last_success_uptime_ms": 28785,
    "last_error": null
  },
  "active": {
    "mode": "dhcp",
    "ipv4": "192.0.2.1",
    "netmask": "255.255.255.0",
    "gateway": "192.0.2.1",
    "dns": [],
    "hostname": "camera-example-2",
    "source": "dhcp",
    "usable": true,
    "since_uptime_ms": 28785
  },
  "configured": {
    "config_id": "config-2",
    "mode": "dhcp",
    "hostname": "camera-example-2",
    "static_ipv4": null,
    "updated_generation": 994,
    "confirmed_this_boot_uptime_ms": 29038,
    "confirmed": true
  },
  "pending": null,
  "effective_hostname": null,
  "effective_mdns_name": null,
  "recovery": {
    "active": false,
    "reason": null,
    "rollback_available": false,
    "last_failure": "pending_reboot_rollback",
    "dhcp_retry_at_uptime_ms": 0,
    "link_local_candidate_count": 0
  },
  "mdns": {
    "enabled": true,
    "started": false,
    "publication_phase": "unknown",
    "observation_stale": false,
    "renamed": false,
    "requested_hostname": "camera-example-2",
    "effective_hostname": null,
    "name": null,
    "last_error": null,
    "services": {
      "http": {
        "type": "_http._tcp",
        "port": 80,
        "txt": {
          "api": "1.0"
        }
      },
      "discovery": {
        "type": "_esp32-p4-camera._udp",
        "port": 39590,
        "txt": {
          "protocol": "1"
        }
      }
    }
  },
  "discovery": {
    "enabled": true,
    "udp_port": 39590,
    "protocol_version": 1
  }
}

POST /api/v1/network/rollback

Abandon a staged network configuration and return to the previous reachable state.

Query String

No query string parameters are defined for this endpoint.

Request Body Schema
{
  "type": "object",
  "required": ["config_id"],
  "additionalProperties": false,
  "properties": {
    "config_id": { "type": "string", "minLength": 1, "maxLength": 36 }
  }
}
Request Notes
GroupFieldDetails
Networkconfig_idIdentifies the pending configuration to confirm or roll back.
Response Statuses
FieldTypeDetails
202 rollback_startedapplication/jsonschema: network.rollback.response
200 successapplication/jsonschema: network.response
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
409 no_pending_configapplication/jsonschema: common.product_error
409 network_busy_conflictapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
503 network_busy_unavailableapplication/jsonschema: common.product_error
503 network_unavailableapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
503 unavailableapplication/jsonschema: common.product_error
Success Response Schema
{
  "oneOf": [
    {
      "description": "202 rollback started",
      "type": "object",
      "required": ["rollback_started", "config_id"],
      "additionalProperties": false,
      "properties": {
        "rollback_started": { "const": true },
...
Example
curl -X POST -H 'Content-Type: application/json' --data '{"config_id":"config-4"}' http://CAMERA_IP/api/v1/network/rollback
HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "rollback_started": true,
  "config_id": "config-4"
}

UDP 39590

Discover cameras by UDP broadcast without knowing their HTTP address.

Request Body Schema
{
  "type": "object",
  "required": ["type", "protocol", "version"],
  "additionalProperties": false,
  "properties": {
    "type": { "const": "esp32_p4.discover" },
    "protocol": { "const": "esp32-p4-discovery" },
    "version": { "const": 1 }
  }
}
Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: discovery.udp.response
Success Response Schema
{
  "type": "object",
  "required": ["type", "protocol", "version", "product_class", "model", "serial_number", "firmware_version", "api_version", "boot_id", "mac", "hostname", "effective_hostname", "network_mode", "confirmed_config_id", "pending_config_id", "mdns_name", "mdns_publication_phase", "mdns_observation_stale", "mdns_renamed", "ipv4", "netmask", "gateway", "dns", "requires_link_local_host_route", "link_local_network", "base_url", "link_up", "recovery_active", "last_failure", "recovery_reason", "uptime_ms", "http_port", "udp_port", "ptp_sync_state", "ptp_clock_readable", "ptp_synchronized"],
  "additionalProperties": false,
  "properties": {
    "type": { "const": "esp32_p4.discovery" },
    "protocol": { "const": "esp32-p4-discovery" },
    "version": { "const": 1 },
    "product_class": { "const": "camera" },
...
Example
{
  "type": "esp32_p4.discover",
  "protocol": "esp32-p4-discovery",
  "version": 1
}
{
  "type": "esp32_p4.discovery",
  "protocol": "esp32-p4-discovery",
  "version": 1,
  "product_class": "camera",
  "model": "custom-esp32-p4-camera-demo",
  "serial_number": null,
  "firmware_version": "dev",
  "api_version": "1.0",
  "boot_id": "00000000-0000-4000-8000-000000000002",
  "mac": "02:00:00:00:00:01",
  "hostname": "camera-example-1",
  "effective_hostname": "camera-example-1",
  "network_mode": "dhcp",
  "confirmed_config_id": "config-3",
  "pending_config_id": null,
  "mdns_name": "camera-example-1.local",
  "mdns_publication_phase": "running",
  "mdns_observation_stale": false,
  "mdns_renamed": false,
  "ipv4": "192.0.2.1",
  "netmask": "255.255.255.0",
  "gateway": "192.0.2.1",
  "dns": [],
  "requires_link_local_host_route": false,
  "link_local_network": null,
  "base_url": "http://camera-example-1",
  "link_up": true,
  "recovery_active": false,
  "ptp_sync_state": "synchronized",
  "ptp_clock_readable": true,
  "ptp_synchronized": true,
  "last_failure": "pending_reboot_rollback",
  "recovery_reason": null,
  "uptime_ms": 59604,
  "http_port": 80,
  "udp_port": 39590
}
  • In link-local recovery mode the firmware also sends a limited-broadcast reply so a host without an ARP cache entry can still discover the board.

Camera

GET /api/v1/camera

Return a compact camera state summary and link map.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: camera.camera
400 bad_requestapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
503 sensor_read_failedapplication/jsonschema: common.product_error
503 sensor_state_unsupportedapplication/jsonschema: common.product_error
500 sensor_readback_mismatchapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["state", "active_acquisition_mode", "pipeline_mode", "frame_payload_format", "width", "height", "frame_period_us", "measured_frame_period_us", "last_frame_id", "dropped_frames", "links"],
  "additionalProperties": false,
  "properties": {
    "state": { "enum": ["idle", "acquiring_continuous", "trigger_ready", "stopping", "fault"] },
    "active_acquisition_mode": { "enum": ["continuous", "triggered", null] },
    "pipeline_mode": { "type": "string" },
    "frame_payload_format": { "const": "jpeg" },
...
Example
curl http://CAMERA_IP/api/v1/camera
HTTP/1.1 200 OK
Content-Type: application/json

{
  "state": "idle",
  "active_acquisition_mode": null,
  "pipeline_mode": "raw10_1280x800",
  "frame_payload_format": "jpeg",
  "width": 1280,
  "height": 800,
  "frame_period_us": 16811,
  "measured_frame_period_us": 0,
  "last_frame_id": 1,
  "dropped_frames": 0,
  "links": {
    "capabilities": "/api/v1/camera/capabilities",
    "config": "/api/v1/camera/config",
    "state": "/api/v1/camera/state",
    "trigger": "/api/v1/camera/trigger",
    "acquisition": "/api/v1/camera/acquisition"
  }
}
  • Includes fresh sensor timing readback as frame_period_us and observed free-run timing as measured_frame_period_us.
  • Measured period is 0 until two same-domain frame timestamps are available, while acquisition is stopped, and while active acquisition is triggered.

GET /api/v1/camera/capabilities

Return authoritative sensor, pipeline, timing, preview, trigger, network, and feature limits.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: camera.capabilities
400 bad_requestapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["sensor", "limits", "trigger", "frame_payload_formats", "timestamp_domains", "features", "network", "pipeline_modes", "time_quality_sync_states"],
  "additionalProperties": false,
  "properties": {
    "sensor": {
      "type": "object",
      "required": ["model", "shutter", "color", "interface", "lanes"],
      "additionalProperties": false,
...
Immutable Trigger Capability Fields
FieldDetails
qualified_trigger_to_exposure_start_latency_nsQualified fixed output-to-exposure-start prediction for the current timing fingerprint; currently 1469000 ns.
active_frame_busy_by_mode_usBase-mode busy-time examples. Do not use free-run frame_period_us as software-trigger cadence.
pulse_width_us / pulse_width_min_us / pulse_width_max_usImmutable advertised pulse-width policy values and supported bounds. pulse_width_us currently equals the upper bound; each accepted trigger summary reports its runtime-selected pulse width.
Example
curl http://CAMERA_IP/api/v1/camera/capabilities
HTTP/1.1 200 OK
Content-Type: application/json

{
  "sensor": {
    "model": "ar0144",
    "shutter": "global",
    "color": false,
    "interface": "mipi_csi_2",
    "lanes": 2
  },
  "limits": {
    "requested_exposure_duration_min_ns": 11134,
    "requested_exposure_duration_max_ns": 1332147254,
    "frame_period_min_us": 8680,
    "frame_period_max_us": 1332187,
    "manual_gain_min_x1000": 1000,
    "manual_gain_max_x1000": 15992,
    "isp_bayer_denoise_level_min": 2,
    "isp_bayer_denoise_level_max": 20,
    "jpeg_quality_min": 60,
    "jpeg_quality_max": 95,
    "preview_frame_period_min_us": 1000,
    "preview_frame_period_max_us": 1000000,
    "preview_frame_period_request_min_us": 1000,
    "preview_frame_period_request_max_us": 1000000,
    "preview_frame_period_recommended_min_us": 16667,
    "retained_frame_capacity": 10
  },
  "trigger": {
    "minimum_schedule_lead_us": 2637,
    "backend_schedule_guard_us": 2637,
    "backend_no_cancel_guard_us": 26047,
    "sensor_rearm_guard_us": 0,
    "default_immediate_lead_us": 7637,
    "scheduled_horizon_us": 4293000000,
    "qualified_trigger_to_exposure_start_latency_ns": 1469000,
    "replacement_min_gain_us": 1000,
    "active_frame_busy_by_mode_us": {
      "raw10_1280x800": 16811,
      "raw10_1280x400": 8680,
      "raw10_640x400": 8680
    },
    "active_trigger_slots": 1,
    "scheduled_trigger_slots": 1,
    "replacement_allowed_states": [
      "scheduled"
    ],
    "cancellation_allowed_states": [
      "scheduled"
    ],
    "cancel_by_id_supported": true,
    "pulse_width_us": 1333729,
    "pulse_width_min_us": 1,
    "pulse_width_max_us": 1333729,
    "schedule_tolerance_us": 100,
    "schedule_timeout_max_ms": 10000
  },
  "frame_payload_formats": [
    "jpeg"
  ],
  "timestamp_domains": [
    "monotonic",
    "ptp"
  ],
  "features": {
    "manual_exposure": true,
    "manual_gain": true,
    "free_run_frame_timing": true,
    "auto_exposure": false,
    "jpeg_transport": true,
    "raw_capture": false,
    "pipeline_mode_switching": true,
    "preview_mjpeg": true,
    "ptp_timestamps": true,
    "software_trigger": true,
    "ptp_best_effort_scheduling": true,
    "ptp_strict_scheduling": true,
    "require_synchronized_default": false,
    "flash_exposure_timestamping": true,
    "temperature": true,
    "sensor_test_pattern": true,
    "persistent_config": false,
    "network_config": true
  },
  "network": {
    "modes": [
      "dhcp",
      "static"
    ],
    "ipv4_static": true,
    "ipv6_config": false,
    "mdns": true,
    "broadcast_discovery": true,
    "recovery_mode": true,
    "confirm_timeout_default_ms": 30000,
    "confirm_timeout_min_ms": 5000,
    "confirm_timeout_max_ms": 120000,
    "hostname_max_length": 63,
    "dns_server_max_count": 2,
    "discovery_udp_port": 39590,
    "discovery_protocol_version": 1,
    "dhcp_acquisition_timeout_ms": 30000,
    "recovery_dhcp_retry_interval_ms": 60000,
    "pending_candidate_readiness_timeout_ms": 120000
  },
  "time_quality_sync_states": [
    "not_applicable",
    "unavailable",
    "unsynchronized",
    "acquiring",
    "holdover",
    "synchronized"
  ],
  "pipeline_modes": [
    {
      "name": "raw10_1280x800",
      "width": 1280,
      "height": 800,
      "sensor_raw_bit_depth": 10,
      "base_frame_period_us": 16811,
      "min_frame_period_us": 16811,
      "max_frame_period_us": 1332187,
      "line_time_ns": 20328,
      "base_vts": 827,
      "hts": 1488,
      "mode_validated": true,
      "default_exposure_duration_ns": 10000000,
      "base_max_actual_exposure_duration_ns": 16771516,
      "preview_frame_period_default_us": 100000,
      "preview_frame_period_min_us": 33334,
      "preview_frame_period_max_us": 1000000,
      "preview_frame_period_request_min_us": 1000,
      "preview_frame_period_request_max_us": 1000000,
      "preview_frame_period_recommended_min_us": 33334,
      "requires_reboot": false
    },
    {
      "name": "raw10_1280x400",
      "width": 1280,
      "height": 400,
      "sensor_raw_bit_depth": 10,
      "base_frame_period_us": 8680,
      "min_frame_period_us": 8680,
      "max_frame_period_us": 1332187,
      "line_time_ns": 20328,
      "base_vts": 427,
      "hts": 1488,
      "mode_validated": true,
      "default_exposure_duration_ns": 5000000,
      "base_max_actual_exposure_duration_ns": 8640369,
      "preview_frame_period_default_us": 33334,
      "preview_frame_period_min_us": 16667,
      "preview_frame_period_max_us": 1000000,
      "preview_frame_period_request_min_us": 1000,
      "preview_frame_period_request_max_us": 1000000,
      "preview_frame_period_recommended_min_us": 16667,
      "requires_reboot": false
    },
    {
      "name": "raw10_640x400",
      "width": 640,
      "height": 400,
      "sensor_raw_bit_depth": 10,
      "base_frame_period_us": 8680,
      "min_frame_period_us": 8680,
      "max_frame_period_us": 1332187,
      "line_time_ns": 20328,
      "base_vts": 427,
      "hts": 1488,
      "mode_validated": true,
      "default_exposure_duration_ns": 2500000,
      "base_max_actual_exposure_duration_ns": 8640369,
      "preview_frame_period_default_us": 16667,
      "preview_frame_period_min_us": 16667,
      "preview_frame_period_max_us": 1000000,
      "preview_frame_period_request_min_us": 1000,
      "preview_frame_period_request_max_us": 1000000,
      "preview_frame_period_recommended_min_us": 16667,
      "requires_reboot": false
    }
  ]
}
  • The trigger object is the sole owner of immutable scheduling policy. Fetch it during client setup; trigger-state polls return only current readiness, retained summaries, and the admission window.

GET /api/v1/camera/config

Return current sensor-backed configuration and timing readback.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: camera.config
400 bad_requestapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
503 sensor_read_failedapplication/jsonschema: common.product_error
503 sensor_state_unsupportedapplication/jsonschema: common.product_error
500 sensor_readback_mismatchapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["pipeline_mode", "sensor_test_pattern_enabled", "frame_period_us", "frame_period_min_us", "frame_period_max_us", "max_actual_exposure_duration_ns", "timing_source", "exposure", "manual_gain_x1000", "frame_payload_format", "jpeg_quality", "width", "height", "mode_validated", "preview_frame_period_default_us", "preview_frame_period_min_us", "preview_frame_period_max_us", "preview_frame_period_request_min_us", "preview_frame_period_request_max_us", "preview_frame_period_recommended_min_us", "isp_bayer_denoise_enabled", "isp_bayer_denoise_level", "readback_duration_us", "timing"],
  "additionalProperties": false,
  "properties": {
    "pipeline_mode": { "type": "string" },
    "sensor_test_pattern_enabled": { "type": "boolean" },
    "frame_period_us": { "type": "integer", "minimum": 1 },
    "frame_period_min_us": { "type": "integer", "minimum": 1 },
...
Response Notes
FieldDetails
frame_period_usDerived from active sensor VTS and may be slightly longer than the requested period because of line-time quantization.
exposureThe actual duration is verified from sensor timing; quantization_error_ns is signed actual minus requested duration.
preview_frame_period_*_usPreview transport policy only; these fields do not change AR0144 VTS.
Example
curl http://CAMERA_IP/api/v1/camera/config
HTTP/1.1 200 OK
Content-Type: application/json

{
  "pipeline_mode": "raw10_1280x800",
  "frame_period_us": 16811,
  "frame_period_min_us": 16811,
  "frame_period_max_us": 1332187,
  "max_actual_exposure_duration_ns": 16771516,
  "timing_source": "mode_default",
  "sensor_test_pattern_enabled": false,
  "exposure": {
    "requested_duration_ns": 10000000,
    "actual_duration_ns": 10000000,
    "quantization_error_ns": 0
  },
  "manual_gain_x1000": 1000,
  "frame_payload_format": "jpeg",
  "jpeg_quality": 80,
  "width": 1280,
  "height": 800,
  "mode_validated": true,
  "preview_frame_period_default_us": 100000,
  "preview_frame_period_min_us": 33334,
  "preview_frame_period_max_us": 1000000,
  "preview_frame_period_request_min_us": 1000,
  "preview_frame_period_request_max_us": 1000000,
  "preview_frame_period_recommended_min_us": 33334,
  "isp_bayer_denoise_enabled": false,
  "isp_bayer_denoise_level": 5,
  "readback_duration_us": 3976,
  "timing": {
    "hts": 1488,
    "vts": 827,
    "line_time_ns": 20328,
    "frame_time_us": 16811,
    "base_vts": 827
  }
}

PATCH /api/v1/camera/config

Apply mutable sensor timing/control, JPEG, and ISP fields.

Query String

No query string parameters are defined for this endpoint.

Request Body Schema
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "pipeline_mode": { "enum": ["raw10_1280x800", "raw10_1280x400", "raw10_640x400"] },
    "sensor_test_pattern_enabled": { "type": "boolean" },
    "frame_period_us": { "type": "integer", "minimum": 1, "maximum": 2147483647 },
    "exposure_duration_ns": { "type": "integer", "minimum": 11134, "maximum": 1332147254 },
    "manual_gain_x1000": { "type": "integer", "minimum": 1000, "maximum": 15992 },
...
Request Notes
GroupFieldDetails
Sensor timing/controlpipeline_modeMutable geometry/readout mode. Changing modes requires stopped acquisition.
Sensor timing/controlsensor_test_pattern_enabledMutable sensor digital test pattern.
Sensor timing/controlframe_period_usMutable AR0144 free-run frame period; readback may be slightly longer after VTS quantization.
Sensor timing/controlexposure_duration_nsRequested photosensitive integration duration. It is validated against the resolved frame period and quantized to the AR0144 CIT/FIT/LLPCK timing model.
Sensor timing/controlmanual_gain_x1000Mutable manual gain; x1000 units mean 1000 represents 1x gain.
Payload/encodingframe_payload_formatCurrent public retained-frame transport is JPEG.
Payload/encodingjpeg_qualityControls the encoder target for retained JPEG frames.
ISPisp_bayer_denoise_enabledEnables or disables ISP Bayer denoise.
ISPisp_bayer_denoise_levelControls ISP Bayer denoise strength.
Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: camera.config_patch
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
409 already_runningapplication/jsonschema: common.product_error
501 unsupportedapplication/jsonschema: common.product_error
503 unavailableapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
503 sensor_read_failedapplication/jsonschema: common.product_error
503 sensor_state_unsupportedapplication/jsonschema: common.product_error
500 sensor_readback_mismatchapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["ok", "applied", "config", "requires_restart_of_acquisition", "requires_reboot"],
  "additionalProperties": false,
  "properties": {
    "ok": { "const": true },
    "applied": {
      "type": "object",
      "description": "Partial echo of accepted PATCH fields.",
...
Example
curl -X PATCH -H 'Content-Type: application/json' --data '{"sensor_test_pattern_enabled":true}' http://CAMERA_IP/api/v1/camera/config
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "applied": {
    "sensor_test_pattern_enabled": true
  },
  "config": {
    "pipeline_mode": "raw10_1280x800",
    "frame_period_us": 16811,
    "frame_period_min_us": 16811,
    "frame_period_max_us": 1332187,
    "max_actual_exposure_duration_ns": 16771516,
    "timing_source": "mode_default",
    "sensor_test_pattern_enabled": true,
    "exposure": {
      "requested_duration_ns": 10000000,
      "actual_duration_ns": 10000000,
      "quantization_error_ns": 0
    },
    "manual_gain_x1000": 1000,
    "frame_payload_format": "jpeg",
    "jpeg_quality": 80,
    "width": 1280,
    "height": 800,
    "mode_validated": true,
    "preview_frame_period_default_us": 100000,
    "preview_frame_period_min_us": 33334,
    "preview_frame_period_max_us": 1000000,
    "preview_frame_period_request_min_us": 1000,
    "preview_frame_period_request_max_us": 1000000,
    "preview_frame_period_recommended_min_us": 33334,
    "isp_bayer_denoise_enabled": false,
    "isp_bayer_denoise_level": 5,
    "readback_duration_us": 3366,
    "timing": {
      "hts": 1488,
      "vts": 827,
      "line_time_ns": 20328,
      "frame_time_us": 16811,
      "base_vts": 827
    }
  },
  "requires_restart_of_acquisition": false,
  "requires_reboot": false
}
Timing And Acquisition Rules
  • frame_period_us is converted to AR0144 FRAME_LENGTH_LINES with exact integer ceiling arithmetic, so physical frame time is never shorter than requested.
  • Exposure is validated against the resolved frame period; the firmware does not silently enlarge VTS to make an exposure fit.
  • exposure_duration_ns requests the photosensitive integration duration. Readback reports requested, verified actual, and signed quantization-error nanoseconds under config.exposure.
  • Triggered-frame exposure timestamps describe the integration interval derived from the captured AR0144 FLASH falling edge and the verified CIT/FIT/LLPCK duration; they are not the raw physical FLASH pulse edges.
  • In successful PATCH responses, applied.frame_period_us is the accepted request intent. config.frame_period_us is the active VTS-derived readback and may be slightly longer because of line-time quantization.
  • When changing both exposure and period, send both fields in one PATCH; an exposure-only request can be rejected when the current frame period cannot support it.
  • While continuous acquisition is running, VTS-changing patches return 409 already_running with details.stop_required: true.
  • While triggered acquisition is running, patches that change VTS, exposure, or pipeline mode return 409 already_running.
  • If a software trigger is pending, timing or exposure patches include details.trigger_pending: true.

GET /api/v1/camera/state

Return acquisition state, latest frame ID, timing, output state, and retained buffer count.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: camera.state
400 bad_requestapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
503 sensor_read_failedapplication/jsonschema: common.product_error
503 sensor_state_unsupportedapplication/jsonschema: common.product_error
500 sensor_readback_mismatchapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["state", "active_acquisition_mode", "pipeline_mode", "frame_payload_format", "last_frame_id", "last_frame_timestamp_ns", "last_frame_timestamp_domain", "last_frame_time_quality", "frame_period_us", "measured_frame_period_us", "output_state", "dropped_frames", "buffered_frames"],
  "additionalProperties": false,
  "properties": {
    "state": { "enum": ["idle", "acquiring_continuous", "trigger_ready", "stopping", "fault"] },
    "active_acquisition_mode": { "enum": ["continuous", "triggered", null] },
    "pipeline_mode": { "type": "string" },
    "frame_payload_format": { "const": "jpeg" },
...
Example
curl http://CAMERA_IP/api/v1/camera/state
HTTP/1.1 200 OK
Content-Type: application/json

{
  "state": "idle",
  "active_acquisition_mode": null,
  "pipeline_mode": "raw10_1280x800",
  "frame_payload_format": "jpeg",
  "last_frame_id": 1,
  "last_frame_timestamp_ns": null,
  "last_frame_timestamp_domain": null,
  "last_frame_time_quality": null,
  "frame_period_us": 16811,
  "measured_frame_period_us": 0,
  "output_state": false,
  "dropped_frames": 0,
  "buffered_frames": 0
}
  • Includes fresh sensor timing readback as frame_period_us and observed free-run timing as measured_frame_period_us.
  • Measured period is 0 until two same-domain frame timestamps are available, while acquisition is stopped, and while active acquisition is triggered.

GET /api/v1/camera/acquisition

Return current acquisition mode and retained-frame buffer state.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: acquisition.state
400 bad_requestapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["state", "active_acquisition_mode", "buffer_capacity", "buffered_frames", "dropped_frames"],
  "additionalProperties": false,
  "properties": {
    "state": { "enum": ["idle", "acquiring_continuous", "trigger_ready", "stopping", "fault"] },
    "active_acquisition_mode": { "enum": ["continuous", "triggered", null] },
    "buffer_capacity": { "type": "integer", "minimum": 1 },
    "buffered_frames": { "type": "integer", "minimum": 0 },
...
Example
curl http://CAMERA_IP/api/v1/camera/acquisition
HTTP/1.1 200 OK
Content-Type: application/json

{
  "state": "trigger_ready",
  "active_acquisition_mode": "triggered",
  "buffer_capacity": 10,
  "buffered_frames": 2,
  "dropped_frames": 0
}

POST /api/v1/camera/acquisition/continuous/start

Start continuous free-run acquisition.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body; a request body is rejected.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: acquisition.start_response
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
409 already_runningapplication/jsonschema: common.product_error
503 unavailableapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["ok", "state"],
  "additionalProperties": false,
  "properties": {
    "ok": { "const": true },
    "state": { "enum": ["acquiring_continuous", "trigger_ready", "fault"] }
  }
}
Example
curl -X POST http://CAMERA_IP/api/v1/camera/acquisition/continuous/start
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "state": "acquiring_continuous"
}
  • Calling this endpoint while continuous acquisition is already active is idempotent.
  • To switch from triggered acquisition, stop acquisition first.

POST /api/v1/camera/acquisition/triggered/start

Start software-trigger-ready acquisition without publishing a frame yet.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body; a request body is rejected.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: acquisition.start_response
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
409 already_runningapplication/jsonschema: common.product_error
503 unavailableapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["ok", "state"],
  "additionalProperties": false,
  "properties": {
    "ok": { "const": true },
    "state": { "enum": ["acquiring_continuous", "trigger_ready", "fault"] }
  }
}
Example
curl -X POST http://CAMERA_IP/api/v1/camera/acquisition/triggered/start
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "state": "trigger_ready"
}
  • Calling this endpoint while triggered acquisition is already active is idempotent.
  • To switch from continuous acquisition, stop acquisition first.
  • The start is rejected with 422 trigger_timing if the exposure-derived physical FLASH pulse is outside the precision-trigger backend limits.

POST /api/v1/camera/acquisition/stop

Stop active acquisition.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body; a request body is rejected.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: acquisition.stop_response
400 bad_requestapplication/jsonschema: common.product_error
503 unavailableapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["ok", "state"],
  "additionalProperties": false,
  "properties": {
    "ok": { "const": true },
    "state": { "const": "idle" }
  }
}
Example
curl -X POST http://CAMERA_IP/api/v1/camera/acquisition/stop
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "state": "idle"
}
  • Stopping an already-idle camera is idempotent. Stop is serialized with camera lifecycle work and leaves the retained-frame store intact.

POST /api/v1/camera/acquisition/flush

Flush retained JPEG frames.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body; a request body is rejected.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: acquisition.flush_response
400 bad_requestapplication/jsonschema: common.product_error
503 unavailableapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["ok", "flushed_frames"],
  "additionalProperties": false,
  "properties": {
    "ok": { "const": true },
    "flushed_frames": { "type": "integer", "minimum": 0 }
  }
}
Example
curl -X POST http://CAMERA_IP/api/v1/camera/acquisition/flush
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "flushed_frames": 0
}
  • Flush is serialized on the camera command queue so retained-frame invalidation cannot interleave with frame publication.

Trigger

GET /api/v1/camera/trigger

Return software trigger readiness, current admission window, active/scheduled trigger slots, and last terminal trigger summary.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: trigger.state
400 bad_requestapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["can_schedule_trigger", "can_schedule_ptp", "can_schedule_synchronized", "ptp_time_quality", "output_state", "active_trigger", "scheduled_trigger", "last_trigger", "admission_window"],
  "additionalProperties": false,
  "properties": {
    "can_schedule_trigger": { "type": "boolean" },
    "can_schedule_ptp": { "type": "boolean" },
    "can_schedule_synchronized": { "type": "boolean" },
    "ptp_time_quality": { "allOf": [
...
Operational Trigger State
FieldDetails
admission_windowCurrent monotonic earliest_legal_trigger_output_ns and nullable earliest_legal_exposure_start_ns bounds for the next schedule request.
Example
curl http://CAMERA_IP/api/v1/camera/trigger
HTTP/1.1 200 OK
Content-Type: application/json

{
  "can_schedule_trigger": false,
  "can_schedule_ptp": false,
  "can_schedule_synchronized": false,
  "ptp_time_quality": {
    "domain": "ptp",
    "clock_readable": true,
    "sync_state": "acquiring",
    "synchronized": false,
    "readiness_epoch": null,
    "clock_mapping_generation": 2
  },
  "output_state": false,
  "active_trigger": null,
  "scheduled_trigger": null,
  "last_trigger": null,
  "admission_window": {
    "earliest_legal_trigger_output_ns": "53682069000",
    "earliest_legal_exposure_start_ns": "53683538000"
  }
}
  • Internally, the camera also retains a bounded terminal-trigger history for waiter recovery and cancel idempotency; the current history depth is six terminal trigger summaries.

POST /api/v1/camera/trigger/schedule

Schedule a software trigger by earliest output, explicit trigger-output time, or explicit exposure-start time in monotonic/PTP time.

Query String

No query string parameters are defined for this endpoint.

Request Body Schema
{
  "type": "object",
  "additionalProperties": false,
  "allOf": [
    {
      "if": { "required": ["timeout_ms"] },
      "then": {
        "properties": { "wait_for_frame": { "const": true } },
        "required": ["wait_for_frame"]
...
Request Notes
GroupFieldDetails
Software triggertargetSchedules at the current admission_window earliest legal monotonic trigger output time using the immutable policy advertised by camera capabilities.
Software triggertrigger_output_time_nsExplicit monotonic or PTP trigger-output instant. This form is mutually exclusive with exposure_start_time_ns.
Software triggerexposure_start_time_nsRequested exposure-start instant. The camera subtracts the qualified output-to-exposure latency before admission and hardware quantization.
Software triggertimestamp_domainInterprets an explicit trigger_output_time_ns or exposure_start_time_ns in camera monotonic or PTP time.
Software triggerrequire_synchronizedTrue is valid only for an explicit PTP target and requires synchronized admission quality. Readable acquiring or holdover PTP is accepted when false.
Software triggerrequest_idClient-supplied request correlation ID.
Software triggerwait_for_frameWhen true, the response waits for the associated frame, a terminal trigger state, or timeout.
Software triggertimeout_msWait budget for synchronous frame association or terminal trigger observation.
Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: trigger.schedule.response
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
409 already_runningapplication/jsonschema: common.product_error
410 stale_frame_cursorapplication/jsonschema: common.product_error; details_schema: common.error_details.stale_frame_cursor
503 frame_publisher_unavailableapplication/jsonschema: common.product_error; details_schema: common.error_details.frame_publisher_unavailable
503 trigger_id_exhaustedapplication/jsonschema: common.product_error; details_schema: common.error_details.trigger_id_exhausted
503 replacement_schedule_failedapplication/jsonschema: common.product_error; details_schema: common.error_details.replacement_schedule_failed
409 timestamp_domain_unavailableapplication/jsonschema: common.product_error; details_schema: common.error_details.time_quality_unavailable
409 synchronization_unavailableapplication/jsonschema: common.product_error; details_schema: common.error_details.time_quality_unavailable
503 unavailableapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
504 trigger_wait_timeoutapplication/jsonschema: common.product_error; details_schema: common.error_details.trigger_wait_timeout
504 command_timeoutapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["ok", "trigger", "scheduled_trigger", "active_trigger", "frame"],
  "additionalProperties": false,
  "dependentRequired": {
    "terminal": ["terminal_reason"],
    "terminal_reason": ["terminal"]
  },
  "properties": {
...
Example
curl -X POST -H 'Content-Type: application/json' --data '{"target":"asap","wait_for_frame":false,"request_id":"request-1"}' http://CAMERA_IP/api/v1/camera/trigger/schedule
curl -X POST -H 'Content-Type: application/json' --data '{"timestamp_domain":"ptp","require_synchronized":true,"wait_for_frame":false,"trigger_output_time_ns":"1700000007399310400","request_id":"request-2"}' http://CAMERA_IP/api/v1/camera/trigger/schedule
curl -X POST -H 'Content-Type: application/json' --data '{"target":"asap","wait_for_frame":true,"timeout_ms":5000,"request_id":"request-3"}' http://CAMERA_IP/api/v1/camera/trigger/schedule
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "trigger": {
    "trigger_id": 1,
    "state": "scheduled",
    "request_id": "request-1",
    "schedule_basis": "asap",
    "requested_trigger_output_timestamp_ns": null,
    "requested_exposure_start_timestamp_ns": null,
    "programmed_trigger_output_timestamp_ns": "1700000000000000000",
    "trigger_output_timestamp_ns": null,
    "predicted_exposure_start_timestamp_ns": "1700000000001469000",
    "actual_exposure_start_timestamp_ns": null,
    "trigger_timestamp_domain": "monotonic",
    "synchronization_required": false,
    "admission_time_quality": {
      "domain": "monotonic",
      "clock_readable": true,
      "sync_state": "not_applicable",
      "synchronized": false,
      "readiness_epoch": null,
      "clock_mapping_generation": null
    },
    "pulse_width_us": 11582,
    "status_flags": [],
    "frame_id": null,
    "jpeg_url": null,
    "meta_url": null
  },
  "scheduled_trigger": {
    "trigger_id": 1,
    "state": "scheduled",
    "request_id": "request-1",
    "schedule_basis": "asap",
    "requested_trigger_output_timestamp_ns": null,
    "requested_exposure_start_timestamp_ns": null,
    "programmed_trigger_output_timestamp_ns": "1700000000000000000",
    "trigger_output_timestamp_ns": null,
    "predicted_exposure_start_timestamp_ns": "1700000000001469000",
    "actual_exposure_start_timestamp_ns": null,
    "trigger_timestamp_domain": "monotonic",
    "synchronization_required": false,
    "admission_time_quality": {
      "domain": "monotonic",
      "clock_readable": true,
      "sync_state": "not_applicable",
      "synchronized": false,
      "readiness_epoch": null,
      "clock_mapping_generation": null
    },
    "pulse_width_us": 11582,
    "status_flags": [],
    "frame_id": null,
    "jpeg_url": null,
    "meta_url": null
  },
  "active_trigger": null,
  "frame": null
}
  • Start triggered acquisition with POST /api/v1/camera/acquisition/triggered/start before scheduling triggers.
  • Immediate schedule requests use {"target":"asap"} and the advertised trigger policy to choose the next legal output instant.
  • Explicit requests use exactly one of trigger_output_time_ns or exposure_start_time_ns. The latter preserves exposure-start intent while the camera derives and quantizes the hardware trigger target.
  • Trigger summaries retain caller intent, the programmed output, predicted exposure start, actual exposure start when known, synchronization_required, and immutable admission_time_quality. Frame capture time quality is sampled independently and is not rewritten to match trigger admission.
  • timestamp_domain is required when trigger_output_time_ns or exposure_start_time_ns is present.
  • require_synchronized:true is valid only for an explicit PTP target. It rejects readable but acquiring/holdover PTP with 409 synchronization_unavailable; unreadable PTP returns 409 timestamp_domain_unavailable.
  • timeout_ms is valid only when wait_for_frame is true.
  • If a waiting trigger becomes terminal canceled or superseded before frame association, the response remains 200 OK, returns frame:null, and includes terminal:true with the terminal reason.
  • If terminal history says a frame was ready but the retained by-trigger frame was evicted, the wait returns 410 stale_frame_cursor.
  • An explicit target before earliest_legal_trigger_output_ns while an active trigger is busy returns 422 invalid_config with pacing details and Retry-After-Ms.
  • A scheduled-trigger replacement that is not early enough to satisfy replacement_min_gain_us returns enriched 409 already_running.
  • If a frame ID cannot be reserved after a trigger is accepted, the trigger becomes terminal fault with reason id_exhausted, and trigger waits return 503 frame_publisher_unavailable with reason:"frame_id_exhausted", trigger_id, latest_frame_id, and frame_publisher_status:"frame_id_exhausted".

POST /api/v1/camera/trigger/cancel

Cancel a retained scheduled trigger by ID.

Query String

No query string parameters are defined for this endpoint.

Request Body Schema
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "trigger_id": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 }
  },
  "required": ["trigger_id"]
}
Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: trigger.cancel.response
400 bad_requestapplication/jsonschema: common.product_error
404 trigger_not_retainedapplication/jsonschema: common.product_error; details_schema: common.error_details.trigger_not_retained
503 unavailableapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["ok", "canceled", "canceled_trigger", "scheduled_trigger", "active_trigger", "already_canceled"],
  "additionalProperties": false,
  "properties": {
    "ok": { "const": true },
    "canceled": { "type": "boolean" },
    "canceled_trigger": { "anyOf": [{ "allOf": [{
  "type": "object",
...
Example
curl -X POST -H 'Content-Type: application/json' --data '{"trigger_id":2}' http://CAMERA_IP/api/v1/camera/trigger/cancel
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "canceled": true,
  "canceled_trigger": {
    "trigger_id": 2,
    "state": "canceled",
    "request_id": "request-2",
    "schedule_basis": "trigger_output",
    "requested_trigger_output_timestamp_ns": "1700000000000000000",
    "requested_exposure_start_timestamp_ns": null,
    "programmed_trigger_output_timestamp_ns": "1700000000000000040",
    "trigger_output_timestamp_ns": null,
    "predicted_exposure_start_timestamp_ns": "1700000000001469040",
    "actual_exposure_start_timestamp_ns": null,
    "trigger_timestamp_domain": "ptp",
    "synchronization_required": true,
    "admission_time_quality": {
      "domain": "ptp",
      "clock_readable": true,
      "sync_state": "synchronized",
      "synchronized": true,
      "readiness_epoch": "2",
      "clock_mapping_generation": 2
    },
    "pulse_width_us": 11582,
    "status_flags": [
      "trigger_canceled"
    ],
    "frame_id": null,
    "jpeg_url": null,
    "meta_url": null
  },
  "scheduled_trigger": null,
  "active_trigger": null,
  "already_canceled": false
}
  • Cancellation only affects future scheduled pulses; it does not abort a trigger whose output has already fired, sensor exposure/readout, JPEG encoding, or frame association.
  • Cancel-by-id for a trigger that is no longer retained returns 404 trigger_not_retained.
  • A retained terminal canceled trigger returns idempotent success. Retained terminal superseded, frame_ready, timeout, and fault triggers return 409 invalid_state with terminal-state details.

Frames and Streams

GET /api/v1/frames/latest

Return the latest retained JPEG frame.

Query String
FieldTypeDetails
formatjpeg, optionalOmit or set to jpeg. raw, raw8, and raw10 are recognized but return 501 unsupported.
qualitystring, optionalRecognized as a future string option, but any present lexeme returns 501 unsupported; per-request JPEG quality is not parsed or applied.
Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successimage/jpegheaders_schema: streams.frame.jpeg_headers
400 bad_requestapplication/jsonschema: common.product_error
404 not_foundapplication/jsonschema: common.product_error; details_schema: common.retained_frame_window_details
501 unsupportedapplication/jsonschema: common.product_error
Success Response

Returns image/jpeg bytes with Cache-Control: no-store, camera lineage, frame metadata, and exposure timestamp headers.

Example
curl 'http://CAMERA_IP/api/v1/frames/latest?format=jpeg' --output frame.jpg
HTTP/1.1 200 OK
Cache-Control: no-store
X-Camera-Boot-Id: 00000000-0000-4000-8000-000000000002
X-Frame-Status-Flags: 0
X-Camera-Configuration-Generation: 2
Content-Type: image/jpeg
X-Exposure-Duration-Ns: 10000000
X-Exposure-End-Timestamp-Ns: 1785948052828077773
X-Exposure-Midpoint-Timestamp-Ns: 1785948052823077773
X-Exposure-Start-Timestamp-Ns: 1785948052818077773
X-Exposure-Timestamp-Domain: ptp
X-Exposure-Timestamp-Valid: true
X-Exposure-Sync-State: synchronized
X-Exposure-Clock-Readable: true
X-Exposure-Synchronized: true
X-Exposure-Readiness-Epoch: 2
X-Exposure-Clock-Mapping-Generation: 2
X-Frame-Height: 800
X-Frame-Id: 1
X-Frame-Payload-Format: jpeg
X-Frame-Pipeline-Mode: raw10_1280x800
X-Frame-Timestamp-Domain: ptp
X-Frame-Timestamp-Ns: 1785948052828463773
X-Frame-Sync-State: synchronized
X-Frame-Clock-Readable: true
X-Frame-Synchronized: true
X-Frame-Readiness-Epoch: 2
X-Frame-Clock-Mapping-Generation: 2
X-Frame-Width: 1280

[JPEG data]

GET /api/v1/frames/latest/meta

Return metadata for the latest retained frame.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: frames.metadata_response
400 bad_requestapplication/jsonschema: common.product_error
404 not_foundapplication/jsonschema: common.product_error; details_schema: common.retained_frame_window_details
Success Response Schema

{
  "additionalProperties": false,
  "properties": {
    "frame": {
      "oneOf": [
        {
          "additionalProperties": false,
          "properties": {
...
Response Notes
FieldDetails
exposure_*Triggered frames always include valid exposure timestamp fields.
time_quality / exposure_time_qualityRetained frame and FLASH-edge capture observations. Exposure quality is null when no valid exposure timestamps are available.
synchronization_required / admission_time_qualityTriggered-frame scheduling policy and immutable admission observation, retained independently of frame capture quality.
Example
curl http://CAMERA_IP/api/v1/frames/latest/meta
HTTP/1.1 200 OK
Content-Type: application/json

{
  "frame": {
    "frame_id": 3,
    "timestamp_ns": "1700000000304754000",
    "timestamp_domain": "ptp",
    "width": 1280,
    "height": 800,
    "pipeline_mode": "raw10_1280x800",
    "frame_payload_format": "jpeg",
    "bytes": 67467,
    "status_flags": [],
    "time_quality": {
      "domain": "ptp",
      "clock_readable": true,
      "sync_state": "synchronized",
      "synchronized": true,
      "readiness_epoch": "2",
      "clock_mapping_generation": 2
    },
    "exposure_start_timestamp_ns": "1700000000321471000",
    "exposure_end_timestamp_ns": "1700000000331471000",
    "exposure_midpoint_timestamp_ns": "1700000000326471000",
    "exposure_duration_ns": 10000000,
    "exposure_timestamp_domain": "ptp",
    "exposure_timestamp_valid": true,
    "exposure_time_quality": {
      "domain": "ptp",
      "clock_readable": true,
      "sync_state": "synchronized",
      "synchronized": true,
      "readiness_epoch": "2",
      "clock_mapping_generation": 2
    },
    "jpeg_url": "/api/v1/frames/3?format=jpeg",
    "meta_url": "/api/v1/frames/3/meta"
  }
}

GET /api/v1/frames/{frame_id}

Return a retained JPEG frame by public frame ID from the current boot.

Query String
FieldTypeDetails
formatjpeg, optionalOmit or set to jpeg. raw, raw8, and raw10 are recognized but return 501 unsupported.
Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successimage/jpegheaders_schema: streams.frame.jpeg_headers
400 bad_requestapplication/jsonschema: common.product_error
400 invalid_frame_idapplication/jsonschema: common.product_error; details_schema: common.error_details.invalid_frame_id
422 invalid_configapplication/jsonschema: common.product_error
404 future_frame_cursorapplication/jsonschema: common.product_error; details_schema: common.error_details.future_frame_cursor
404 frame_not_retainedapplication/jsonschema: common.product_error; details_schema: common.error_details.frame_not_retained
410 stale_frame_cursorapplication/jsonschema: common.product_error; details_schema: common.error_details.stale_frame_cursor
500 internal_errorapplication/jsonschema: common.product_error
501 unsupportedapplication/jsonschema: common.product_error
Success Response

Returns image/jpeg bytes. The path frame_id must be a safe-u53 decimal integer in the current boot retained-frame window.

Example
curl 'http://CAMERA_IP/api/v1/frames/2?format=jpeg' --output frame.jpg
HTTP/1.1 200 OK
Cache-Control: no-store
X-Camera-Boot-Id: 00000000-0000-4000-8000-000000000002
X-Frame-Status-Flags: 0
X-Camera-Configuration-Generation: 2
Content-Type: image/jpeg
X-Exposure-Duration-Ns: 10000000
X-Exposure-End-Timestamp-Ns: 1785948052506606773
X-Exposure-Midpoint-Timestamp-Ns: 1785948052501606773
X-Exposure-Start-Timestamp-Ns: 1785948052496606773
X-Exposure-Timestamp-Domain: ptp
X-Exposure-Timestamp-Valid: true
X-Exposure-Sync-State: synchronized
X-Exposure-Clock-Readable: true
X-Exposure-Synchronized: true
X-Exposure-Readiness-Epoch: 2
X-Exposure-Clock-Mapping-Generation: 2
X-Frame-Height: 800
X-Frame-Id: 3
X-Frame-Payload-Format: jpeg
X-Frame-Pipeline-Mode: raw10_1280x800
X-Frame-Timestamp-Domain: ptp
X-Frame-Timestamp-Ns: 1785948052523709773
X-Frame-Sync-State: synchronized
X-Frame-Clock-Readable: true
X-Frame-Synchronized: true
X-Frame-Readiness-Epoch: 2
X-Frame-Clock-Mapping-Generation: 2
X-Frame-Width: 1280
X-Trigger-Schedule-Basis: asap
X-Programmed-Trigger-Output-Timestamp-Ns: 58964600000
X-Trigger-Id: 1
X-Predicted-Exposure-Start-Timestamp-Ns: 58966069000
X-Trigger-To-Exposure-Start-Latency-Ns: 1469000
X-Trigger-Output-Timestamp-Ns: 58964600000
X-Trigger-Request-Id: request-3
X-Trigger-Timestamp-Domain: monotonic
X-Trigger-Synchronization-Required: false
X-Trigger-Admission-Sync-State: not_applicable
X-Trigger-Admission-Clock-Readable: true
X-Trigger-Admission-Synchronized: false

[JPEG data]

GET /api/v1/frames/{frame_id}/meta

Return retained-frame metadata by public frame ID from the current boot.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: frames.metadata_response
400 bad_requestapplication/jsonschema: common.product_error
400 invalid_frame_idapplication/jsonschema: common.product_error; details_schema: common.error_details.invalid_frame_id
422 invalid_configapplication/jsonschema: common.product_error
404 future_frame_cursorapplication/jsonschema: common.product_error; details_schema: common.error_details.future_frame_cursor
404 frame_not_retainedapplication/jsonschema: common.product_error; details_schema: common.error_details.frame_not_retained
410 stale_frame_cursorapplication/jsonschema: common.product_error; details_schema: common.error_details.stale_frame_cursor
500 internal_errorapplication/jsonschema: common.product_error
Success Response Schema

{
  "additionalProperties": false,
  "properties": {
    "frame": {
      "oneOf": [
        {
          "additionalProperties": false,
          "properties": {
...
Response Notes
FieldDetails
exposure_*Triggered frames always include valid exposure timestamp fields.
time_quality / exposure_time_qualityRetained frame and FLASH-edge capture observations. Exposure quality is null when no valid exposure timestamps are available.
synchronization_required / admission_time_qualityTriggered-frame scheduling policy and immutable admission observation, retained independently of frame capture quality.
Example
curl http://CAMERA_IP/api/v1/frames/2/meta
HTTP/1.1 200 OK
Content-Type: application/json

{
  "frame": {
    "frame_id": 2,
    "timestamp_ns": "1700000000000000000",
    "timestamp_domain": "ptp",
    "width": 1280,
    "height": 800,
    "pipeline_mode": "raw10_1280x800",
    "frame_payload_format": "jpeg",
    "bytes": 66970,
    "status_flags": [],
    "time_quality": {
      "domain": "ptp",
      "clock_readable": true,
      "sync_state": "synchronized",
      "synchronized": true,
      "readiness_epoch": "2",
      "clock_mapping_generation": 2
    },
    "exposure_start_timestamp_ns": "1700000000000000000",
    "exposure_end_timestamp_ns": "1700000000010000000",
    "exposure_midpoint_timestamp_ns": "1700000000005000000",
    "exposure_duration_ns": 10000000,
    "exposure_timestamp_domain": "ptp",
    "exposure_timestamp_valid": true,
    "exposure_time_quality": {
      "domain": "ptp",
      "clock_readable": true,
      "sync_state": "synchronized",
      "synchronized": true,
      "readiness_epoch": "2",
      "clock_mapping_generation": 2
    },
    "trigger_id": 3,
    "request_id": "request-3",
    "schedule_basis": "asap",
    "requested_trigger_output_timestamp_ns": null,
    "requested_exposure_start_timestamp_ns": null,
    "programmed_trigger_output_timestamp_ns": "1700000005232999000",
    "trigger_output_timestamp_ns": "1700000005232999000",
    "predicted_exposure_start_timestamp_ns": "1700000005234468000",
    "trigger_timestamp_domain": "monotonic",
    "trigger_to_exposure_start_latency_ns": 1469000,
    "synchronization_required": false,
    "admission_time_quality": {
      "domain": "monotonic",
      "clock_readable": true,
      "sync_state": "not_applicable",
      "synchronized": false,
      "readiness_epoch": null,
      "clock_mapping_generation": null
    },
    "jpeg_url": "/api/v1/frames/2?format=jpeg",
    "meta_url": "/api/v1/frames/2/meta"
  }
}

GET /api/v1/frames/next

Wait for and return the next retained JPEG frame after a cursor.

Query String
FieldTypeDetails
after_frame_idsafe-u53 integer or 0, requiredUse 0 to start at the oldest retained frame, otherwise use a public frame ID from the same boot.
timeout_msinteger 0..10000, optionalMaximum wait for a newer retained frame. Defaults to the firmware wait policy.
formatjpeg, optionalOmit or set to jpeg. raw, raw8, and raw10 are recognized but return 501 unsupported.
Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successimage/jpegheaders_schema: streams.frame.jpeg_headers
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
404 future_frame_cursorapplication/jsonschema: common.product_error; details_schema: common.error_details.future_frame_cursor
410 stale_frame_cursorapplication/jsonschema: common.product_error; details_schema: common.error_details.stale_frame_cursor
504 timeoutapplication/jsonschema: common.product_error; details_schema: common.error_details.timeout
503 frame_publisher_unavailableapplication/jsonschema: common.product_error; details_schema: common.error_details.frame_publisher_unavailable
500 internal_errorapplication/jsonschema: common.product_error
501 unsupportedapplication/jsonschema: common.product_error
Success Response

Returns image/jpeg bytes on success. Cursor errors return retained-window JSON details.

Example
curl 'http://CAMERA_IP/api/v1/frames/next?format=jpeg&timeout_ms=5000&after_frame_id=2' --output frame.jpg
HTTP/1.1 200 OK
Cache-Control: no-store
X-Camera-Boot-Id: 00000000-0000-4000-8000-000000000002
X-Frame-Status-Flags: 0
X-Camera-Configuration-Generation: 2
Content-Type: image/jpeg
X-Exposure-Duration-Ns: 10000000
X-Exposure-End-Timestamp-Ns: 1785948052654038973
X-Exposure-Midpoint-Timestamp-Ns: 1785948052649038973
X-Exposure-Start-Timestamp-Ns: 1785948052644038973
X-Exposure-Timestamp-Domain: ptp
X-Exposure-Timestamp-Valid: true
X-Exposure-Sync-State: synchronized
X-Exposure-Clock-Readable: true
X-Exposure-Synchronized: true
X-Exposure-Readiness-Epoch: 2
X-Exposure-Clock-Mapping-Generation: 2
X-Frame-Height: 800
X-Frame-Id: 2
X-Frame-Payload-Format: jpeg
X-Frame-Pipeline-Mode: raw10_1280x800
X-Frame-Timestamp-Domain: ptp
X-Frame-Timestamp-Ns: 1785948052654483973
X-Frame-Sync-State: synchronized
X-Frame-Clock-Readable: true
X-Frame-Synchronized: true
X-Frame-Readiness-Epoch: 2
X-Frame-Clock-Mapping-Generation: 2
X-Frame-Width: 1280

[JPEG data]
  • after_frame_id=0 starts at the oldest retained frame; otherwise use a public frame ID from the same boot.

GET /api/v1/preview.mjpg

Validate API preview query parameters and redirect to the direct MJPEG stream.

Query String
FieldTypeDetails
frame_period_usinteger µs, optionalValidated against preview request limits, preserved in the redirect, and acted on by the direct MJPEG stream. Unknown query keys return bad_request.
Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
307 successapplication/jsoncontract_schema: streams.preview.redirect_contract; schema: preview.redirect_response
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
503 frame_publisher_unavailableapplication/jsonschema: common.product_error; details_schema: common.error_details.frame_publisher_unavailable
Success Response

Returns 307 Temporary Redirect with frame_period_us preserved in Location, or 503 JSON if frame publication is unavailable.

Example
curl -i 'http://CAMERA_IP/api/v1/preview.mjpg?frame_period_us=16811'
HTTP/1.1 307 Temporary Redirect
Content-Type: application/json
Location: http://camera-example-1:81/stream.mjpg?frame_period_us=16811

GET http://CAMERA_IP:81/stream.mjpg

Serve the direct multipart MJPEG stream.

Query String
FieldTypeDetails
frame_period_usinteger µs, optionalBest-effort MJPEG delivery period. It does not change AR0144 sensor VTS.
Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successmultipart/x-mixed-replaceheaders_schema: streams.mjpeg.headers; part_headers_schema: streams.mjpeg.frame_part_headers; terminal_part_headers_schema: streams.mjpeg.error_part_headers; terminal_schema: common.frame_publisher_stream_error
400 bad_requesttext/plaincontract_schema: streams.direct_mjpeg.plain_error_response
404 not_foundtext/plaincontract_schema: streams.direct_mjpeg.plain_error_response
422 invalid_configtext/plaincontract_schema: streams.direct_mjpeg.plain_error_response
503 capacity_exhaustedtext/plaincontract_schema: streams.direct_mjpeg.plain_error_response
503 frame_publisher_unavailableapplication/jsondetails_schema: common.error_details.frame_publisher_unavailable; headers_schema: streams.direct_mjpeg.json_error_headers; schema: common.product_error
Success Response

Returns multipart/x-mixed-replace JPEG parts and stream pacing headers. Terminal stream errors are emitted as a final JSON multipart part when possible.

Example
curl 'http://CAMERA_IP:81/stream.mjpg?frame_period_us=16811' --output preview.mjpg
HTTP/1.1 200 OK
Connection: close
Cache-Control: no-store, max-age=0
Pragma: no-cache
X-Preview-Frame-Period-Us: 16811
X-Requested-Frame-Period-Us: 16811
X-Best-Effort: true
X-Preview-Frame-Period-Recommended-Min-Us: 33334
X-Preview-Frame-Period-Request-Min-Us: 1000
X-Preview-Frame-Period-Request-Max-Us: 1000000
X-Client-Limit: 3
Content-Type: multipart/x-mixed-replace;boundary=esp32p4frame

--esp32p4frame
Content-Type: image/jpeg
Content-Length: 67342
X-Frame-Id: 4
X-Frame-Timestamp-Ns: 1700000000000000000
X-Frame-Timestamp-Domain: ptp
X-Frame-Clock-Readable: true
X-Frame-Sync-State: synchronized
X-Frame-Synchronized: true
X-Frame-Readiness-Epoch: 2
X-Frame-Clock-Mapping-Generation: 2

[JPEG part bytes]
  • Preview throttling does not change AR0144 VTS and does not persist in camera config or state.
  • A direct stream that fails after multipart headers have started sends a final JSON error part, then closes.
  • Before multipart streaming begins, malformed and unsupported requests return nonempty text/plain errors: 400 bad_request, 404 not_found, 422 invalid_config, or 503 capacity_exhausted. Those responses require Content-Type: text/plain, Cache-Control containing no-store, and a canonical decimal Content-Length. A pre-stream frame-publisher failure instead returns the shared 503 frame_publisher_unavailable JSON envelope with application/json content type, no-store cache control, and canonical decimal content length.

GET /api/v1/events

Serve server-sent system, frame, and terminal stream error events.

Query String
FieldTypeDetails
boot_idUUID string, optionalWhen present, identifies the expected boot for replay using a lowercase RFC 4122 version-4 UUID. Unknown query parameters, including timeout_ms, are rejected.
Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successtext/event-streamevents: events.system_event; events: events.frame_event; events: events.error_event; events: events.stale_event_cursor; events: events.sse_capacity_exceeded; events: events.event_publisher_unavailable; headers_schema: streams.sse.headers
400 bad_requestapplication/jsonschema: common.product_error
422 invalid_configapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
503 frame_publisher_unavailableapplication/jsonschema: common.product_error; details_schema: common.error_details.frame_publisher_unavailable
Success Response

Returns text/event-stream with Cache-Control: no-store and Connection: keep-alive. Event payloads are JSON; terminal frame-publisher failures are emitted as SSE error events.

Example
curl 'http://CAMERA_IP/api/v1/events?boot_id=00000000-0000-4000-8000-000000000002'
HTTP/1.1 200 OK
Content-Type: text/event-stream
Transfer-Encoding: chunked
Cache-Control: no-store
Connection: keep-alive

id: 4
event: frame
data: {"frame_id":4,"timestamp_ns":"1699999994766946440","timestamp_domain":"ptp","pipeline_mode":"raw10_1280x800","time_quality":{"domain":"ptp","clock_readable":true,"sync_state":"acquiring","synchronized":false,"readiness_epoch":null,"clock_mapping_generation":1}}
  • The only accepted query parameter is optional boot_id. Unknown query parameters, including timeout_ms, return 400 bad_request before an SSE slot is reserved.
  • Reconnect clients may send Last-Event-ID as a canonical safe-u53 decimal integer. Empty, signed, fractional, exponent, hexadecimal, percent-encoded, leading-zero nonzero, and overflow values are rejected before an SSE slot is reserved.
  • Retained history contains one pinned boot system event plus 100 dynamic frame/error events. Absent Last-Event-ID or 0 replays the boot event and retained dynamic history; stale dynamic cursors emit stale_event_cursor before replaying from the oldest retained dynamic event; future IDs reset to the current boot event and retained history.
  • If boot_id differs from the current boot, the server ignores Last-Event-ID and replays the current boot event plus retained dynamic history.
  • SSE capacity exhaustion is reported as a request-local sse_capacity_exceeded error without id before closing, so EventSource cursors are not advanced.

Metrics

GET /api/v1/metrics

Return camera, preview, trigger, network, and system counters.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: metrics.response
400 bad_requestapplication/jsonschema: common.product_error
Success Response Schema
{
  "type": "object",
  "required": ["camera", "preview", "trigger", "network", "system"],
  "additionalProperties": false,
  "properties": {
    "camera": {
      "type": "object",
      "required": ["pipeline_mode", "frame_payload_format", "measured_frame_period_us", "frame_counter", "frame_publisher_status", "frame_publisher_unavailable", "frame_publisher_unavailable_reason", "dropped_frames", "buffer_capacity", "buffered_frames", "latest_jpeg_bytes", "average_jpeg_bytes", "max_jpeg_bytes", "capture_errors", "jpeg_encode_errors"],
      "additionalProperties": false,
...
Example
curl http://CAMERA_IP/api/v1/metrics
HTTP/1.1 200 OK
Content-Type: application/json

{
  "camera": {
    "pipeline_mode": "raw10_1280x800",
    "frame_payload_format": "jpeg",
    "measured_frame_period_us": 0,
    "frame_counter": 4,
    "frame_publisher_status": "available",
    "frame_publisher_unavailable": false,
    "frame_publisher_unavailable_reason": null,
    "dropped_frames": 0,
    "buffer_capacity": 10,
    "buffered_frames": 4,
    "latest_jpeg_bytes": 67467,
    "average_jpeg_bytes": 67320,
    "max_jpeg_bytes": 67578,
    "capture_errors": 0,
    "jpeg_encode_errors": 0
  },
  "preview": {
    "mjpeg_clients": 0,
    "mjpeg_client_limit": 3,
    "preview_frame_period_request_min_us": 1000,
    "preview_frame_period_request_max_us": 1000000,
    "preview_frame_period_requested_fastest_us": 0,
    "preview_frame_period_recommended_min_us": 33334,
    "recent_mjpeg_frame_period_us": 0,
    "aggregate_delivered_frame_period_us": 0,
    "per_client_average_frame_period_us": 0,
    "moving_average_samples": 5,
    "underrun": false,
    "limit_reason": "none",
    "mjpeg_streamed_frames": 0,
    "mjpeg_streamed_bytes": 0,
    "skipped_frames": 0,
    "send_errors": 0,
    "backpressure_events": 0,
    "rejected_clients": 0
  },
  "trigger": {
    "schedule_requests_total": 3,
    "schedule_accepted_total": 3,
    "schedule_replaced_scheduled_total": 0,
    "cancel_requests_total": 1,
    "cancel_accepted_total": 1,
    "cancel_noop_total": 0,
    "cancel_rejected_not_found_total": 0,
    "cancel_rejected_not_cancelable_total": 0,
    "cancel_backend_failed_total": 0,
    "schedule_replacement_cancel_failed_total": 0,
    "schedule_rejected_scheduled_slot_total": 0,
    "schedule_rejected_active_busy_total": 0,
    "schedule_rejected_rate_limited_total": 0,
    "schedule_rejected_horizon_total": 0,
    "schedule_busy_total": 0,
    "backend_schedule_guard_us": 2637,
    "backend_no_cancel_guard_us": 26047,
    "sensor_rearm_guard_us": 0,
    "active_frame_busy_us": 0,
    "earliest_legal_trigger_output_ns": "59360882000",
    "last_retry_after_us": 0
  },
  "network": {
    "tx_bytes": 0,
    "rx_bytes": 0,
    "tx_errors": 0,
    "rx_errors": 0
  },
  "system": {
    "uptime_ms": 59354,
    "internal_free_bytes": 147075,
    "psram_free_bytes": 13067300
  }
}
  • Period metrics are integer µs and report 0 when no samples are available.

Debug Diagnostics

GET /debug/runtime

Return versioned internal resource, worker, service, lifecycle, and coherent PTP diagnostics.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: debug.runtime
400 bad_requestapplication/jsonschema: common.product_error
Success Response

Returns versioned, non-stable JSON diagnostics for development and qualification.

Example

This non-stable diagnostic response is intentionally not reproduced here; use the public product endpoints for application state.

curl http://CAMERA_IP/debug/runtime
Diagnostic contract
  • This read-only route is always registered, deliberately outside /api/v1, and currently reports schema_version 1. It performs no sensor I/O and submits no camera or network command.
  • Use the public camera, frame, time, network, trigger, metrics, info, and system endpoints for product state; external clients must not depend on this non-stable schema.

GET /debug/camera/sensor

Read camera-task sensor state, AR0144 hardware diagnostics, and raw-buffer ownership.

Query String
FieldTypeDetails
Query stringignoredQuery parameters do not alter this response.
Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: debug.camera_sensor
400 bad_requestapplication/jsonschema: common.product_error
409 invalid_stateapplication/jsonschema: common.product_error
503 busyapplication/jsonschema: common.product_error
504 timeoutapplication/jsonschema: common.product_error
500 internal_errorapplication/jsonschema: common.product_error
503 sensor_read_failedapplication/jsonschema: common.product_error
503 sensor_state_unsupportedapplication/jsonschema: common.product_error
500 sensor_readback_mismatchapplication/jsonschema: common.product_error
Success Response

Returns non-stable sensor and camera-pipeline JSON diagnostics.

Example

This non-stable sensor diagnostic response is intentionally not reproduced here.

curl http://CAMERA_IP/debug/camera/sensor
Diagnostic contract
  • This always-registered development route submits a sensor-debug command to the camera task. Read failures use invalid_state, busy, timeout, sensor_read_failed, sensor_state_unsupported, sensor_readback_mismatch, or internal_error responses according to the failure boundary.
  • The trigger_flash_capture association fields are authoritative for triggered-frame FLASH association. Legacy active and queued-window fields describe diagnostic/free-run history.

GET /debug/time

Return the complete coherent PTP, correlation, regression, path, actuator, readiness, and DelayReq diagnostic projection.

Query String

No query string parameters are defined for this endpoint.

Request Body

No JSON request body.

Response Statuses
FieldTypeDetails
200 successapplication/jsonschema: debug.time
400 bad_requestapplication/jsonschema: common.product_error
503 time_unavailableapplication/jsonschema: common.product_error
Success Response Schema

{
  "additionalProperties": false,
  "properties": {
    "device_time_ns": {
      "pattern": "^(0|[1-9][0-9]*)$",
      "type": "string"
    },
    "boot_id": {
...
Response Notes
FieldDetails
boot_idDiscard cached monotonic timestamps, readiness epochs, and mapping generations when this changes.
device_time_nsUse the paired time_quality object to determine readability and synchronization; readable PTP does not necessarily mean strict scheduling readiness.
monotonic_time_nsUse it for elapsed-time measurement and scheduling within the current boot. It is not comparable across reboots or devices unless translated through another time source.
ptp_time_nsNull only when the PTP clock cannot be read; acquiring and holdover values remain available with truthful quality.
time_quality / ptp.time_qualityBoth fields carry the same coherent observation rather than independently sampled clock state.
sync_leg_ns / delay_leg_ns / exchange_path_ns / path_reference_ns / path_residual_ns / path_reference_valid / path_acquisition_sample_count / consecutive_path_reject_count / path_reject_count / phase_outlier_count / consecutive_holdover_outliers / last_observation_dispositionThe path reference freezes after acquisition; rejection counters and the latest disposition explain why later observations did not update it.
lock_confirmation_count / qualified_model_update_count / qualified_model_age_us / phase_correction_valid_before_monotonic_us / model_valid_before_monotonic_us / servo_readiness_loss_reason / scheduling_readiness_loss_reasonValidity deadlines are exclusive, and servo readiness loss is reported separately from scheduling-facade readiness loss.
Example

This response preserves the complete coherent timing diagnostic projection; use /api/v1/time for normal operational polling.

curl http://CAMERA_IP/debug/time
Diagnostic contract
  • This route preserves the complete pre-Spec-31 time shape for the built-in status UI and qualification tools. It samples one coherent time view and does not call the operational route.
  • It is intentionally outside /api/v1 and is not advertised by the API root or UDP discovery. External application clients should use the compact operational time endpoint unless their verdict requires these diagnostics.

Shared Contracts

API Contract

FieldTypeDetails
VersioningstringThe public JSON API is rooted at /api/v1 and reports api_version "1.0".
AuthenticationnoneThe firmware exposes the API on the camera network interface without application-layer authentication; deploy on a trusted network segment.
IDssafe-u53 integerPublic frame_id and trigger_id values are JSON integers or decimal path/query/body values in 1..9007199254740991. The value 0 is only a documented sentinel.
Timestampsdecimal JSON stringNanosecond timestamps and scheduling instants that may exceed exact JavaScript integer range are canonical decimal strings. Nullable timestamp fields are strings or null.
Durationsinteger numberBounded timing values such as timeout_ms, retry_after_us, line_time_ns, exposure_duration_ns, and fields ending _us remain JSON numbers.
Frame periodsinteger µsPublic sensor and preview cadence fields use *_period_us names.
Unknown fieldsrequest validationJSON PATCH/POST bodies reject unknown fields. Recognized but unsupported values return 501 unsupported; out-of-range values return 422 invalid_config.

Frame and trigger IDs are unique only for the current boot. Store boot_id beside cached cursors and discard those cursors when boot_id changes.

Time Quality And Timestamp Lineage

Every quality-bearing timestamp is paired with a time_quality object whose fields are domain, clock_readable, sync_state, synchronized, nullable readiness_epoch, and nullable clock_mapping_generation. Readable PTP can be unsynchronized, acquiring, or in holdover; only synchronized observations carry a nonzero readiness epoch.

Trigger admission and frame capture are independent retained observations. Trigger summaries keep immutable admission_time_quality and requested/programmed scheduling lineage. Frame metadata keeps capture time_quality and nullable exposure_time_quality. Neither lineage is relabeled from a later PTP state.

The PTP service maintains its PTP-to-monotonic correlation ahead of the published validity deadline. ptp.scheduling_ready is the strict scheduling gate; readable best-effort PTP may remain available after strict readiness closes.

Frame And Stream Timing Headers

JPEG responses project retained frame quality in X-Frame-Clock-Readable, X-Frame-Sync-State, X-Frame-Synchronized, and conditional readiness-epoch/mapping-generation headers. Exposure and trigger admission use the corresponding X-Exposure-* and X-Trigger-Admission-* headers.

Triggered JPEGs also project X-Trigger-Schedule-Basis, requested output or exposure-start intent, programmed and fired output, predicted exposure start, and X-Trigger-To-Exposure-Start-Latency-Ns. MJPEG parts carry frame ID, timestamp/domain, clock readability, sync state, synchronized state, and conditional epoch/mapping headers. Nullable headers are omitted.

Connection Capacity

If all configured public HTTP sessions are occupied, the first overflow connection is rejected before route dispatch with 503 Service Unavailable, JSON busy, Cache-Control: no-store, and Connection: close. This pre-route capacity response is distinct from cause-specific busy errors returned by a routed endpoint.

{"error":{"code":"busy","message":"Maximum HTTPD public session clients are already connected.","retryable":true,"details":{}}}

Errors

JSON API errors use a top-level error object. Clients should branch on the HTTP status and error.code, not on human-readable messages.

{
  "error": {
    "code": "invalid_config",
    "message": "gateway must be in the same subnet as ipv4",
    "field": "gateway",
    "retryable": false
  }
}
FieldTypeDetails
bad_request / invalid_frame_id400Malformed JSON, invalid type, unknown field, malformed query, or a non-canonical frame-ID path value.
not_found / future_frame_cursor / frame_not_retained / trigger_not_retained404The requested resource or retained frame/trigger does not exist in the current boot-lifetime retention window.
address_conflict / already_running / confirm_not_reachable / confirm_not_ready / invalid_state / network_busy_conflict / network_change_pending / no_pending_config / synchronization_unavailable / timestamp_domain_unavailable409The request conflicts with current camera, trigger, clock, or staged-network state.
stale_frame_cursor410The cursor identifies an older frame that has already left the retained window.
invalid_config422A recognized configuration, timing, exposure, trigger, frame-query, preview, or network value is out of range or incompatible.
internal_error / sensor_readback_mismatch500An internal operation failed or verified sensor readback did not match the requested state.
unsupported501A recognized value or transport exists but is not supported by this hardware/firmware.
busy / capacity_exhausted / frame_publisher_unavailable / network_busy_unavailable / network_unavailable / replacement_schedule_failed / sensor_read_failed / sensor_state_unsupported / time_unavailable / trigger_id_exhausted / unavailable503The required service, capacity, publisher, network, sensor, time mapping, or trigger resource is temporarily unavailable.
command_timeout / timeout / trigger_wait_timeout504A camera command, frame wait, or trigger-to-frame wait did not complete before its deadline.
About Us
By using this website, you agree to our Terms and Conditions and Privacy Policy