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/infoMake sure continuous acquisition is running so retained frames are being published:
curl -X POST http://camera-e4192f.local/api/v1/camera/acquisition/continuous/startFetch the latest retained JPEG with the simplest frame request:
curl http://camera-e4192f.local/api/v1/frames/latest --output frame.jpgFor 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
| Method | Path | Success | Use |
|---|---|---|---|
GET | / | 200 text/html | Built-in browser control and status document. |
GET | /api/v1 | 200 JSON | API version and link map. |
GET | /api/v1/info | 200 JSON | Model, firmware, chip, sensor, network identity, and feature flags. |
GET | /api/v1/health | 200 JSON | Overall state, warnings, faults, uptime, and heap telemetry. |
GET | /api/v1/time | 200 JSON | Compact PTP status plus device and monotonic nanosecond timestamps. |
GET | /api/v1/system | 200 JSON | Boot ID, reset reason, uptime, and silicon temperature. |
POST | /api/v1/system/reboot | 200 JSON | Schedules a reboot. Optional body: {"delay_ms": integer}; an omitted body or delay_ms uses 250 ms. |
Network
| Method | Path | Success | Use |
|---|---|---|---|
GET | /api/v1/network | 200 JSON | Active address, confirmed config, pending config, DHCP, mDNS, recovery, and discovery state. |
PATCH | /api/v1/network | 200 or 202 JSON | Validates or applies DHCP/static IPv4 network configuration. |
POST | /api/v1/network/confirm | 200 JSON | Confirms a staged network configuration by config_id. |
POST | /api/v1/network/rollback | 202 JSON or 200 JSON | Abandons a staged network change by config_id; idempotent calls can return the current network snapshot. |
UDP | 39590 | JSON datagram | Broadcast discovery request and response protocol. |
Camera
| Method | Path | Success | Use |
|---|---|---|---|
GET | /api/v1/camera | 200 JSON | Compact camera summary and links. |
GET | /api/v1/camera/capabilities | 200 JSON | Sensor, pipeline mode, timing, preview, trigger, network, and feature capabilities. |
GET | /api/v1/camera/config | 200 JSON | Current sensor-backed configuration and timing readback. |
PATCH | /api/v1/camera/config | 200 JSON | Applies mutable sensor timing/control, JPEG, and ISP fields. |
GET | /api/v1/camera/state | 200 JSON | Acquisition state, trigger state, latest frame ID, frame timing, and buffer count. |
GET | /api/v1/camera/acquisition | 200 JSON | Current acquisition mode and retained-frame buffer state. |
POST | /api/v1/camera/acquisition/continuous/start | 200 JSON | Starts continuous free-run acquisition. Request body is rejected if present. |
POST | /api/v1/camera/acquisition/triggered/start | 200 JSON | Starts software-trigger-ready acquisition. Request body is rejected if present. |
POST | /api/v1/camera/acquisition/stop | 200 JSON | Stops acquisition. Request body is rejected if present. |
POST | /api/v1/camera/acquisition/flush | 200 JSON | Flushes retained JPEG frames. Request body is rejected if present. |
Trigger
| Method | Path | Success | Use |
|---|---|---|---|
GET | /api/v1/camera/trigger | 200 JSON | Software trigger readiness, admission window, and active/scheduled/last trigger summaries. |
POST | /api/v1/camera/trigger/schedule | 200 JSON | Schedules a software trigger output, optionally waiting for the associated frame. |
POST | /api/v1/camera/trigger/cancel | 200 JSON | Cancels the current scheduled trigger or a retained scheduled trigger by ID. |
Frames and Streams
| Method | Path | Success | Use |
|---|---|---|---|
GET | /api/v1/frames/latest | 200 image/jpeg | Latest retained JPEG. Query: format=jpeg; any quality parameter returns 501 unsupported. |
GET | /api/v1/frames/latest/meta | 200 JSON | Metadata for the latest retained frame. |
GET | /api/v1/frames/{frame_id} | 200 image/jpeg | Retained JPEG by public frame ID. Query: format=jpeg. |
GET | /api/v1/frames/{frame_id}/meta | 200 JSON | Metadata for a retained frame by public frame ID. |
GET | /api/v1/frames/next | 200 image/jpeg | Next retained frame after after_frame_id. Query: after_frame_id required, timeout_ms optional, format=jpeg optional. |
GET | /api/v1/preview.mjpg | 307 redirect or 503 JSON | Validated API preview entry point; redirects to the direct MJPEG stream server. |
GET | http://CAMERA_IP:81/stream.mjpg | multipart/x-mixed-replace | Direct MJPEG stream. Query: frame_period_us optional. |
GET | /api/v1/events | text/event-stream | Server-sent system and frame events, plus terminal stream error events. |
Metrics
| Method | Path | Success | Use |
|---|---|---|---|
GET | /api/v1/metrics | 200 JSON | Camera, preview, trigger, network, and system counters. |
Debug Diagnostics
| Method | Path | Success | Use |
|---|---|---|---|
GET | /debug/runtime | 200 JSON | Internal resource, task, service, lifecycle, and coherent PTP diagnostics. |
GET | /debug/camera/sensor | 200 JSON | Camera-task sensor registers, AR0144 diagnostics, and raw-slot ownership. |
GET | /debug/time | 200 JSON | Complete 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
| Field | Type | Details |
|---|---|---|
Query string | ignored | Query parameters do not alter this response. |
Request Body
No request body.
Response Statuses
| Field | Type | Details |
|---|---|---|
200 success | text/html | schema: 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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: discovery.api_root |
400 bad_request | application/json | schema: 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/v1HTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: discovery.info |
400 bad_request | application/json | schema: 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/infoHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: discovery.health |
400 bad_request | application/json | schema: 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/healthHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: discovery.time |
400 bad_request | application/json | schema: common.product_error |
503 time_unavailable | application/json | schema: common.product_error |
Success Response Schema
{
"type": "object",
"required": [
"boot_id",
"timestamp_domain",
"device_time_ns",
"monotonic_time_ns",
"ptp_time_ns",
...Response Notes
| Field | Details |
|---|---|
boot_id | Discard cached monotonic timestamps, readiness epochs, and mapping generations when this changes. |
device_time_ns | Use the paired time_quality object to determine readability and synchronization; readable PTP does not necessarily mean strict scheduling readiness. |
monotonic_time_ns | Use 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_ns | Null only when the PTP clock cannot be read; acquiring and holdover values remain available with truthful quality. |
time_quality / ptp.time_quality | Both fields carry the same coherent observation rather than independently sampled clock state. |
Example
curl http://CAMERA_IP/api/v1/timeHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: system.response |
400 bad_request | application/json | schema: 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/systemHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: system.reboot.response |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
503 unavailable | application/json | schema: 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/rebootHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: network.response |
400 bad_request | application/json | schema: common.product_error |
503 network_unavailable | application/json | schema: 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/networkHTTP/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 ascamera-<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
| Group | Field | Details |
|---|---|---|
| Network | mode | Target network mode. Recovery is reported by state, not configured directly through PATCH. |
| Network | hostname | Preserved when omitted. Fresh devices default to camera-<last-three-mac-bytes> and publish <hostname>.local over mDNS. |
| Network | ipv4, netmask, gateway | Static IPv4 validation requires a gateway in the same subnet unless no-gateway mode is explicitly allowed. |
| Network | dns | DNS servers used by static IPv4 configuration. |
| Network | allow_no_gateway | Opt-in guard for static IPv4 deployments that intentionally have no gateway. |
| Network | apply | Validation-only requests leave the active address unchanged; apply requests stage a candidate and usually return 202 Accepted. |
| Network | confirm_timeout_ms | Confirmation window for applied changes before automatic rollback can recover reachability. |
Response Statuses
| Field | Type | Details |
|---|---|---|
200 validated | application/json | schema: network.patch.validate_response |
200 applied | application/json | schema: network.response |
202 pending | application/json | schema: network.response |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
409 network_change_pending | application/json | schema: common.product_error |
409 address_conflict | application/json | schema: common.product_error |
409 network_busy_conflict | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
503 network_busy_unavailable | application/json | schema: common.product_error |
503 network_unavailable | application/json | schema: common.product_error |
500 internal_error | application/json | schema: common.product_error |
503 unavailable | application/json | schema: 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/networkcurl -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/networkcurl -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/networkHTTP/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
applyor settingapply:falsevalidates 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 returnedconfig_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
| Group | Field | Details |
|---|---|---|
| Network | config_id | Identifies the pending configuration to confirm or roll back. |
Response Statuses
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: network.response |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
409 confirm_not_ready | application/json | schema: common.product_error |
409 confirm_not_reachable | application/json | schema: common.product_error |
409 no_pending_config | application/json | schema: common.product_error |
409 network_busy_conflict | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
503 network_busy_unavailable | application/json | schema: common.product_error |
503 network_unavailable | application/json | schema: common.product_error |
500 internal_error | application/json | schema: common.product_error |
503 unavailable | application/json | schema: 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/confirmHTTP/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
| Group | Field | Details |
|---|---|---|
| Network | config_id | Identifies the pending configuration to confirm or roll back. |
Response Statuses
| Field | Type | Details |
|---|---|---|
202 rollback_started | application/json | schema: network.rollback.response |
200 success | application/json | schema: network.response |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
409 no_pending_config | application/json | schema: common.product_error |
409 network_busy_conflict | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
503 network_busy_unavailable | application/json | schema: common.product_error |
503 network_unavailable | application/json | schema: common.product_error |
500 internal_error | application/json | schema: common.product_error |
503 unavailable | application/json | schema: 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/rollbackHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: 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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: camera.camera |
400 bad_request | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: common.product_error |
503 sensor_read_failed | application/json | schema: common.product_error |
503 sensor_state_unsupported | application/json | schema: common.product_error |
500 sensor_readback_mismatch | application/json | schema: 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/cameraHTTP/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_usand observed free-run timing asmeasured_frame_period_us. - Measured period is
0until two same-domain frame timestamps are available, while acquisition is stopped, and while active acquisition istriggered.
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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: camera.capabilities |
400 bad_request | application/json | schema: 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
| Field | Details |
|---|---|
qualified_trigger_to_exposure_start_latency_ns | Qualified fixed output-to-exposure-start prediction for the current timing fingerprint; currently 1469000 ns. |
active_frame_busy_by_mode_us | Base-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_us | Immutable 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/capabilitiesHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: camera.config |
400 bad_request | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: common.product_error |
503 sensor_read_failed | application/json | schema: common.product_error |
503 sensor_state_unsupported | application/json | schema: common.product_error |
500 sensor_readback_mismatch | application/json | schema: 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
| Field | Details |
|---|---|
frame_period_us | Derived from active sensor VTS and may be slightly longer than the requested period because of line-time quantization. |
exposure | The actual duration is verified from sensor timing; quantization_error_ns is signed actual minus requested duration. |
preview_frame_period_*_us | Preview transport policy only; these fields do not change AR0144 VTS. |
Example
curl http://CAMERA_IP/api/v1/camera/configHTTP/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
| Group | Field | Details |
|---|---|---|
| Sensor timing/control | pipeline_mode | Mutable geometry/readout mode. Changing modes requires stopped acquisition. |
| Sensor timing/control | sensor_test_pattern_enabled | Mutable sensor digital test pattern. |
| Sensor timing/control | frame_period_us | Mutable AR0144 free-run frame period; readback may be slightly longer after VTS quantization. |
| Sensor timing/control | exposure_duration_ns | Requested photosensitive integration duration. It is validated against the resolved frame period and quantized to the AR0144 CIT/FIT/LLPCK timing model. |
| Sensor timing/control | manual_gain_x1000 | Mutable manual gain; x1000 units mean 1000 represents 1x gain. |
| Payload/encoding | frame_payload_format | Current public retained-frame transport is JPEG. |
| Payload/encoding | jpeg_quality | Controls the encoder target for retained JPEG frames. |
| ISP | isp_bayer_denoise_enabled | Enables or disables ISP Bayer denoise. |
| ISP | isp_bayer_denoise_level | Controls ISP Bayer denoise strength. |
Response Statuses
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: camera.config_patch |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
409 already_running | application/json | schema: common.product_error |
501 unsupported | application/json | schema: common.product_error |
503 unavailable | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: common.product_error |
503 sensor_read_failed | application/json | schema: common.product_error |
503 sensor_state_unsupported | application/json | schema: common.product_error |
500 sensor_readback_mismatch | application/json | schema: 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/configHTTP/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_usis converted to AR0144FRAME_LENGTH_LINESwith 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
VTSto make an exposure fit. exposure_duration_nsrequests the photosensitive integration duration. Readback reports requested, verified actual, and signed quantization-error nanoseconds underconfig.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_usis the accepted request intent.config.frame_period_usis 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_runningwithdetails.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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: camera.state |
400 bad_request | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: common.product_error |
503 sensor_read_failed | application/json | schema: common.product_error |
503 sensor_state_unsupported | application/json | schema: common.product_error |
500 sensor_readback_mismatch | application/json | schema: 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/stateHTTP/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_usand observed free-run timing asmeasured_frame_period_us. - Measured period is
0until two same-domain frame timestamps are available, while acquisition is stopped, and while active acquisition istriggered.
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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: acquisition.state |
400 bad_request | application/json | schema: 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/acquisitionHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: acquisition.start_response |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
409 already_running | application/json | schema: common.product_error |
503 unavailable | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: 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/startHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: acquisition.start_response |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
409 already_running | application/json | schema: common.product_error |
503 unavailable | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: 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/startHTTP/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_timingif 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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: acquisition.stop_response |
400 bad_request | application/json | schema: common.product_error |
503 unavailable | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: 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/stopHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: acquisition.flush_response |
400 bad_request | application/json | schema: common.product_error |
503 unavailable | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: 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/flushHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: trigger.state |
400 bad_request | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: 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
| Field | Details |
|---|---|
admission_window | Current 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/triggerHTTP/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
| Group | Field | Details |
|---|---|---|
| Software trigger | target | Schedules at the current admission_window earliest legal monotonic trigger output time using the immutable policy advertised by camera capabilities. |
| Software trigger | trigger_output_time_ns | Explicit monotonic or PTP trigger-output instant. This form is mutually exclusive with exposure_start_time_ns. |
| Software trigger | exposure_start_time_ns | Requested exposure-start instant. The camera subtracts the qualified output-to-exposure latency before admission and hardware quantization. |
| Software trigger | timestamp_domain | Interprets an explicit trigger_output_time_ns or exposure_start_time_ns in camera monotonic or PTP time. |
| Software trigger | require_synchronized | True is valid only for an explicit PTP target and requires synchronized admission quality. Readable acquiring or holdover PTP is accepted when false. |
| Software trigger | request_id | Client-supplied request correlation ID. |
| Software trigger | wait_for_frame | When true, the response waits for the associated frame, a terminal trigger state, or timeout. |
| Software trigger | timeout_ms | Wait budget for synchronous frame association or terminal trigger observation. |
Response Statuses
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: trigger.schedule.response |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
409 already_running | application/json | schema: common.product_error |
410 stale_frame_cursor | application/json | schema: common.product_error; details_schema: common.error_details.stale_frame_cursor |
503 frame_publisher_unavailable | application/json | schema: common.product_error; details_schema: common.error_details.frame_publisher_unavailable |
503 trigger_id_exhausted | application/json | schema: common.product_error; details_schema: common.error_details.trigger_id_exhausted |
503 replacement_schedule_failed | application/json | schema: common.product_error; details_schema: common.error_details.replacement_schedule_failed |
409 timestamp_domain_unavailable | application/json | schema: common.product_error; details_schema: common.error_details.time_quality_unavailable |
409 synchronization_unavailable | application/json | schema: common.product_error; details_schema: common.error_details.time_quality_unavailable |
503 unavailable | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
500 internal_error | application/json | schema: common.product_error |
504 trigger_wait_timeout | application/json | schema: common.product_error; details_schema: common.error_details.trigger_wait_timeout |
504 command_timeout | application/json | schema: 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/schedulecurl -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/schedulecurl -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/scheduleHTTP/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_nsorexposure_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 immutableadmission_time_quality. Frame capture time quality is sampled independently and is not rewritten to match trigger admission. timestamp_domainis required whentrigger_output_time_nsorexposure_start_time_nsis present.require_synchronized:trueis valid only for an explicit PTP target. It rejects readable but acquiring/holdover PTP with409 synchronization_unavailable; unreadable PTP returns409 timestamp_domain_unavailable.timeout_msis valid only whenwait_for_frameis true.- If a waiting trigger becomes terminal
canceledorsupersededbefore frame association, the response remains200 OK, returnsframe:null, and includesterminal:truewith 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_nswhile an active trigger is busy returns422 invalid_configwith pacing details andRetry-After-Ms. - A scheduled-trigger replacement that is not early enough to satisfy
replacement_min_gain_usreturns enriched409 already_running. - If a frame ID cannot be reserved after a trigger is accepted, the trigger becomes terminal
faultwith reasonid_exhausted, and trigger waits return503 frame_publisher_unavailablewithreason:"frame_id_exhausted",trigger_id,latest_frame_id, andframe_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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: trigger.cancel.response |
400 bad_request | application/json | schema: common.product_error |
404 trigger_not_retained | application/json | schema: common.product_error; details_schema: common.error_details.trigger_not_retained |
503 unavailable | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: 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/cancelHTTP/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
canceledtrigger returns idempotent success. Retained terminalsuperseded,frame_ready,timeout, andfaulttriggers return409 invalid_statewith terminal-state details.
Frames and Streams
GET /api/v1/frames/latest
Return the latest retained JPEG frame.
Query String
| Field | Type | Details |
|---|---|---|
format | jpeg, optional | Omit or set to jpeg. raw, raw8, and raw10 are recognized but return 501 unsupported. |
quality | string, optional | Recognized 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
| Field | Type | Details |
|---|---|---|
200 success | image/jpeg | headers_schema: streams.frame.jpeg_headers |
400 bad_request | application/json | schema: common.product_error |
404 not_found | application/json | schema: common.product_error; details_schema: common.retained_frame_window_details |
501 unsupported | application/json | schema: 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.jpgHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: frames.metadata_response |
400 bad_request | application/json | schema: common.product_error |
404 not_found | application/json | schema: common.product_error; details_schema: common.retained_frame_window_details |
Success Response Schema
{
"additionalProperties": false,
"properties": {
"frame": {
"oneOf": [
{
"additionalProperties": false,
"properties": {
...Response Notes
| Field | Details |
|---|---|
exposure_* | Triggered frames always include valid exposure timestamp fields. |
time_quality / exposure_time_quality | Retained frame and FLASH-edge capture observations. Exposure quality is null when no valid exposure timestamps are available. |
synchronization_required / admission_time_quality | Triggered-frame scheduling policy and immutable admission observation, retained independently of frame capture quality. |
Example
curl http://CAMERA_IP/api/v1/frames/latest/metaHTTP/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
| Field | Type | Details |
|---|---|---|
format | jpeg, optional | Omit or set to jpeg. raw, raw8, and raw10 are recognized but return 501 unsupported. |
Request Body
No JSON request body.
Response Statuses
| Field | Type | Details |
|---|---|---|
200 success | image/jpeg | headers_schema: streams.frame.jpeg_headers |
400 bad_request | application/json | schema: common.product_error |
400 invalid_frame_id | application/json | schema: common.product_error; details_schema: common.error_details.invalid_frame_id |
422 invalid_config | application/json | schema: common.product_error |
404 future_frame_cursor | application/json | schema: common.product_error; details_schema: common.error_details.future_frame_cursor |
404 frame_not_retained | application/json | schema: common.product_error; details_schema: common.error_details.frame_not_retained |
410 stale_frame_cursor | application/json | schema: common.product_error; details_schema: common.error_details.stale_frame_cursor |
500 internal_error | application/json | schema: common.product_error |
501 unsupported | application/json | schema: 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.jpgHTTP/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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: frames.metadata_response |
400 bad_request | application/json | schema: common.product_error |
400 invalid_frame_id | application/json | schema: common.product_error; details_schema: common.error_details.invalid_frame_id |
422 invalid_config | application/json | schema: common.product_error |
404 future_frame_cursor | application/json | schema: common.product_error; details_schema: common.error_details.future_frame_cursor |
404 frame_not_retained | application/json | schema: common.product_error; details_schema: common.error_details.frame_not_retained |
410 stale_frame_cursor | application/json | schema: common.product_error; details_schema: common.error_details.stale_frame_cursor |
500 internal_error | application/json | schema: common.product_error |
Success Response Schema
{
"additionalProperties": false,
"properties": {
"frame": {
"oneOf": [
{
"additionalProperties": false,
"properties": {
...Response Notes
| Field | Details |
|---|---|
exposure_* | Triggered frames always include valid exposure timestamp fields. |
time_quality / exposure_time_quality | Retained frame and FLASH-edge capture observations. Exposure quality is null when no valid exposure timestamps are available. |
synchronization_required / admission_time_quality | Triggered-frame scheduling policy and immutable admission observation, retained independently of frame capture quality. |
Example
curl http://CAMERA_IP/api/v1/frames/2/metaHTTP/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
| Field | Type | Details |
|---|---|---|
after_frame_id | safe-u53 integer or 0, required | Use 0 to start at the oldest retained frame, otherwise use a public frame ID from the same boot. |
timeout_ms | integer 0..10000, optional | Maximum wait for a newer retained frame. Defaults to the firmware wait policy. |
format | jpeg, optional | Omit or set to jpeg. raw, raw8, and raw10 are recognized but return 501 unsupported. |
Request Body
No JSON request body.
Response Statuses
| Field | Type | Details |
|---|---|---|
200 success | image/jpeg | headers_schema: streams.frame.jpeg_headers |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
404 future_frame_cursor | application/json | schema: common.product_error; details_schema: common.error_details.future_frame_cursor |
410 stale_frame_cursor | application/json | schema: common.product_error; details_schema: common.error_details.stale_frame_cursor |
504 timeout | application/json | schema: common.product_error; details_schema: common.error_details.timeout |
503 frame_publisher_unavailable | application/json | schema: common.product_error; details_schema: common.error_details.frame_publisher_unavailable |
500 internal_error | application/json | schema: common.product_error |
501 unsupported | application/json | schema: 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.jpgHTTP/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=0starts 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
| Field | Type | Details |
|---|---|---|
frame_period_us | integer µs, optional | Validated 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
| Field | Type | Details |
|---|---|---|
307 success | application/json | contract_schema: streams.preview.redirect_contract; schema: preview.redirect_response |
400 bad_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
503 frame_publisher_unavailable | application/json | schema: 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=16811GET http://CAMERA_IP:81/stream.mjpg
Serve the direct multipart MJPEG stream.
Query String
| Field | Type | Details |
|---|---|---|
frame_period_us | integer µs, optional | Best-effort MJPEG delivery period. It does not change AR0144 sensor VTS. |
Request Body
No JSON request body.
Response Statuses
| Field | Type | Details |
|---|---|---|
200 success | multipart/x-mixed-replace | headers_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_request | text/plain | contract_schema: streams.direct_mjpeg.plain_error_response |
404 not_found | text/plain | contract_schema: streams.direct_mjpeg.plain_error_response |
422 invalid_config | text/plain | contract_schema: streams.direct_mjpeg.plain_error_response |
503 capacity_exhausted | text/plain | contract_schema: streams.direct_mjpeg.plain_error_response |
503 frame_publisher_unavailable | application/json | details_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.mjpgHTTP/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
VTSand 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, 404not_found, 422invalid_config, or 503capacity_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 503frame_publisher_unavailableJSON 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
| Field | Type | Details |
|---|---|---|
boot_id | UUID string, optional | When 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
| Field | Type | Details |
|---|---|---|
200 success | text/event-stream | events: 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_request | application/json | schema: common.product_error |
422 invalid_config | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
503 frame_publisher_unavailable | application/json | schema: 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, includingtimeout_ms, return 400bad_requestbefore an SSE slot is reserved. - Reconnect clients may send
Last-Event-IDas 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-IDor 0 replays the boot event and retained dynamic history; stale dynamic cursors emitstale_event_cursorbefore replaying from the oldest retained dynamic event; future IDs reset to the current boot event and retained history. - If
boot_iddiffers from the current boot, the server ignoresLast-Event-IDand replays the current boot event plus retained dynamic history. - SSE capacity exhaustion is reported as a request-local
sse_capacity_exceedederror 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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: metrics.response |
400 bad_request | application/json | schema: 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/metricsHTTP/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
0when 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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: debug.runtime |
400 bad_request | application/json | schema: 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/runtimeDiagnostic contract
- This read-only route is always registered, deliberately outside
/api/v1, and currently reportsschema_version1. 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
| Field | Type | Details |
|---|---|---|
Query string | ignored | Query parameters do not alter this response. |
Request Body
No JSON request body.
Response Statuses
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: debug.camera_sensor |
400 bad_request | application/json | schema: common.product_error |
409 invalid_state | application/json | schema: common.product_error |
503 busy | application/json | schema: common.product_error |
504 timeout | application/json | schema: common.product_error |
500 internal_error | application/json | schema: common.product_error |
503 sensor_read_failed | application/json | schema: common.product_error |
503 sensor_state_unsupported | application/json | schema: common.product_error |
500 sensor_readback_mismatch | application/json | schema: 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/sensorDiagnostic 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, orinternal_errorresponses according to the failure boundary. - The
trigger_flash_captureassociation 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
| Field | Type | Details |
|---|---|---|
200 success | application/json | schema: debug.time |
400 bad_request | application/json | schema: common.product_error |
503 time_unavailable | application/json | schema: common.product_error |
Success Response Schema
{
"additionalProperties": false,
"properties": {
"device_time_ns": {
"pattern": "^(0|[1-9][0-9]*)$",
"type": "string"
},
"boot_id": {
...Response Notes
| Field | Details |
|---|---|
boot_id | Discard cached monotonic timestamps, readiness epochs, and mapping generations when this changes. |
device_time_ns | Use the paired time_quality object to determine readability and synchronization; readable PTP does not necessarily mean strict scheduling readiness. |
monotonic_time_ns | Use 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_ns | Null only when the PTP clock cannot be read; acquiring and holdover values remain available with truthful quality. |
time_quality / ptp.time_quality | Both 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_disposition | The 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_reason | Validity 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/timeDiagnostic 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/v1and 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
| Field | Type | Details |
|---|---|---|
Versioning | string | The public JSON API is rooted at /api/v1 and reports api_version "1.0". |
Authentication | none | The firmware exposes the API on the camera network interface without application-layer authentication; deploy on a trusted network segment. |
IDs | safe-u53 integer | Public 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. |
Timestamps | decimal JSON string | Nanosecond timestamps and scheduling instants that may exceed exact JavaScript integer range are canonical decimal strings. Nullable timestamp fields are strings or null. |
Durations | integer number | Bounded timing values such as timeout_ms, retry_after_us, line_time_ns, exposure_duration_ns, and fields ending _us remain JSON numbers. |
Frame periods | integer µs | Public sensor and preview cadence fields use *_period_us names. |
Unknown fields | request validation | JSON 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
}
}| Field | Type | Details |
|---|---|---|
bad_request / invalid_frame_id | 400 | Malformed 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_retained | 404 | The 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_unavailable | 409 | The request conflicts with current camera, trigger, clock, or staged-network state. |
stale_frame_cursor | 410 | The cursor identifies an older frame that has already left the retained window. |
invalid_config | 422 | A recognized configuration, timing, exposure, trigger, frame-query, preview, or network value is out of range or incompatible. |
internal_error / sensor_readback_mismatch | 500 | An internal operation failed or verified sensor readback did not match the requested state. |
unsupported | 501 | A 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 / unavailable | 503 | The required service, capacity, publisher, network, sensor, time mapping, or trigger resource is temporarily unavailable. |
command_timeout / timeout / trigger_wait_timeout | 504 | A camera command, frame wait, or trigger-to-frame wait did not complete before its deadline. |