# api contract base url example: ```text http://jeeves.mother:8765 ``` all write endpoints require: ```text authorization: bearer content-type: application/json ``` ## get /health request: ```http GET /health ``` response: ```json { "ok": true, "service": "synq", "version": "0.1.0" } ``` ## post /capture request: ```http POST /capture Authorization: Bearer Content-Type: application/json ``` body: ```json { "id": "phone-20260517-143122-a8f2", "created_at": "2026-05-17T14:31:22-04:00", "kind": "todo", "body": "buy printer paper", "tags": ["home", "errands"], "device": "android" } ``` field rules: - `id`: required, stable, unique per capture. - `created_at`: required iso-8601 datetime with timezone. - `kind`: required, either `note` or `todo`. - `body`: required, trimmed, non-empty. - `tags`: required array, may be empty. - `device`: required string, e.g. `android`. success, newly accepted: ```json { "ok": true, "status": "accepted", "id": "phone-20260517-143122-a8f2" } ``` success, duplicate/idempotent retry: ```json { "ok": true, "status": "already_seen", "id": "phone-20260517-143122-a8f2" } ``` invalid token: ```json { "detail": "unauthorized" } ``` invalid payload: ```json { "detail": "body must not be empty" } ``` ## org formatting rules todo capture: ```org * TODO buy printer paper :home:errands: :PROPERTIES: :CREATED: [2026-05-17 sun 14:31] :SOURCE: android :ID: phone-20260517-143122-a8f2 :END: ``` note capture: ```org * note :retcon: :PROPERTIES: :CREATED: [2026-05-17 sun 14:33] :SOURCE: android :ID: phone-20260517-143322-b91c :END: mobile capture should stay dumb and append-only. ``` multiline note capture: input body: ```text retcon capture idea phone should produce records, not edit org files. ``` output: ```org * note: retcon capture idea :retcon: :PROPERTIES: :CREATED: [2026-05-17 sun 14:33] :SOURCE: android :ID: phone-20260517-143322-b91c :END: retcon capture idea phone should produce records, not edit org files. ``` suggested behavior: use first line as heading suffix for notes when helpful, but preserve full body below the drawer. ## idempotency server must never append the same `id` twice. recommended implementation: 1. begin sqlite transaction. 2. check whether id already exists. 3. if exists, return `already_seen`. 4. append org entry. 5. insert id into sqlite. 6. commit. if append succeeds but sqlite insert fails, log loudly. safest implementation may insert first inside a transaction and mark appended after write, but keep the code simple and test the duplicate path. ## status codes - 200: accepted or already seen. - 400: malformed request or invalid capture. - 401: missing/invalid token. - 500: server failed to append or persist state. android should treat `accepted` and `already_seen` as synced.