5.5 KiB
Optional external integration contract
Ticktar integration is optional. Frame Flow has no runtime dependency on Ticktar. No external client receives a KoKonna API key, accesses the SQLite database, or imports a vendor SDK. Web UI and external clients use the same application services.
Architecture
External client → /api/v1 → Application / DisplayService → FrameDeviceDriver → KoKonna. Configure the caller with FRAME_FLOW_URL and FRAME_FLOW_TOKEN only. No caller-specific branch exists in core. Frame Flow starts and works with no integration configuration.
Authentication and permissions
Authorization: Bearer FRAME_FLOW_TOKEN. The bootstrap token is provided through .env or a secret file; database stores SHA-256 hash only. The client model supports client_id, name, permissions, enabled, created_at, last_used_at. Permissions: devices:read, images:read, images:delete, display:write, templates:read, render:write, jobs:read, jobs:write, history:read, settings:write, admin. The deployment token seeds an administrator. Administrators can create separate scoped clients through Connections or POST /api/v1/clients. The returned token is shown once; GET /api/v1/clients never returns tokens. DELETE /api/v1/clients/{id} revokes access. For Ticktar, grant devices:read, templates:read, render:write and display:write; add images:read or jobs permissions only when needed. Use a trusted server-to-server connection; CORS is opt-in by exact origins, never wildcard by default.
Discovery and capabilities
GET /api/v1/devices; GET /api/v1/devices/{id}; GET /api/v1/templates. Device provides id/name/status, display geometry, power, current_content, firmware, model/model_code, timezone, last_seen_at, capabilities. Model fallback is flagged model_verified=false when the cloud omits model fields. Do not branch on driver: check capabilities upload/gallery/delete/switch/battery/color/remote_refresh. Native slideshow members/editing are unavailable in KoKonna's documented API.
Display
POST /api/v1/display, Idempotency-Key: a-unique-client-request-key
{
"device_id": "living-room",
"content": {
"type": "template",
"template": "daily-dashboard",
"data": {"title": "Good Morning", "events": []},
"profile": "color"
},
"options": {"duration": 1800, "restore_previous": true, "priority": 50},
"source": {"client": "external-app", "request_id": "caller-trace"}
}
Content supports image (exactly one image_id/image_name/asset_id/base64), image_url (HTTPS/public DNS/exact allowlist), template. html/markdown explicitly return UNSUPPORTED_CAPABILITY. For upload-only use device /upload with display_now=false; this is safe local staging because KoKonna cloud upload always activates an image.
Returns HTTP 202: {operation_id, job_id, status:"accepted"}. job_id is a legacy acceptance alias for operation_id, not a scheduler job ID.
Render and operations
POST /api/v1/render with template content returns 202 and a local rendered asset when completed; no device write. GET /api/v1/operations/{operation_id}: QUEUED → RENDERING → UPLOADING → SYNCING → COMPLETED / FAILED / UNKNOWN. Result includes image_id or asset_id/url. SYNCING confirms imageId plus synced through read-only device status; there is no documented physical-screen telemetry. Token must own the operation or be admin. Never interpret HTTP 202 as refreshed.
Idempotency
Key is scoped to authenticated client, retained seven days. Same key and same normalized action/body returns the same operation, even across restart. A different body with the same key returns CONFLICT (409). Do not retry UNKNOWN with a new key. Cloud writes have no automatic retries. UNKNOWN blocks further device writes; admin /operations/{id}/reconcile reads device/gallery and may acknowledge a manually verified outcome without reissuing a write.
Errors and tracing
All API errors: {error:{code,message,request_id,details:{}}}. Stable codes include INVALID_REQUEST, UNAUTHORIZED, FORBIDDEN, DEVICE_NOT_FOUND, DEVICE_OFFLINE, UNSUPPORTED_CAPABILITY, RENDER_FAILED, UPLOAD_FAILED, WRITE_RESULT_UNKNOWN, RATE_LIMITED, INTERNAL_ERROR, CONFIG_REQUIRED, WRITE_DISABLED, CONFLICT, NOT_FOUND, CLOUD_UNAVAILABLE. Use code, not message text. Validation does not echo inputs. X-Request-ID accepts a bounded safe identifier; it is returned on the response. The authenticated client is authoritative; source.client is optional metadata. History stores client_id/request_id/operation_id/device_id/action/result/timestamp. No internal endpoints, headers, stacks, paths or secrets are returned.
Webhooks
Administrators register callbacks through POST /api/v1/webhooks. Allowed public HTTPS hosts must be configured explicitly. Durable callbacks cover operation.completed, operation.failed, display.changed and observed device.online/device.offline/device.low_battery transitions. Device observation runs at most once per minute; failed cloud queries do not imply offline.
X-Frame-Flow-Signature: sha256= is HMAC-SHA256 over the exact request body. Signing secrets are shown once and stored in mode-0600 files. Verify signatures and deduplicate event IDs. Delivery retries at most three times; events before registration are not replayed. Polling Operations remains available. OAuth, tenancy and generated SDKs are future work.
Compatibility
/openapi.json and /docs are the contract. Snapshot tests cover paths, response codes, standard device fields, security and operation enums. Static snapshot is also supplied in docs/openapi.json. Breaking changes require /api/v2.