# 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 ```json { "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.