Latch

Self-hosting

API, Shortcuts and bots

A bearer-token JSON API for phone Shortcuts and local bots โ€” off until you enable it, every write stamped with who did it, and deliberately unable to pull your journal in bulk.

On this page

/api/v1 is for things that are not a browser: an iPhone Shortcut that logs a photo from the share sheet, a local bot that spins the wheel, an AI Handler acting through the same doors you use. It is off by default.

Enabling

Set LATCH_API_TOKEN in .env and recreate the container. Empty means every /api/v1 route answers 503 โ€” the API fails closed, never open. Send the token as a bearer:

Authorization: Bearer <token>
X-Latch-Actor: shortcut

X-Latch-Actor is one of user, api, ai, shortcut (anything else is recorded as api). It is written on every event, so the log says who did what.

The token can read your journal

/api/v1/handler/entries returns reflection and check-in bodies by design. Treat the token as a LAN-only secret: a password manager, a Shortcut on your own phone, a bot on your own box. Never anything that leaves the house.

Timing: at

Every write takes an optional at โ€” epoch seconds โ€” for something that already happened. The same rules as the web's when field apply (the retroactive log): never the future, never before the lock it lands in, marked logged later if it is more than two minutes old.

Routes

Method Route What
GET /state the clock (status, remaining, end_at, frozen, hiddenโ€ฆ) and lifetime totals. The session note is omitted.
POST /session/lock {seconds โ‰ฅ 60, note?, hidden?, at?}
POST /session/adjust {seconds (ยฑ), reason?, at?}
POST /session/freeze {minutes? (0 = until unfrozen), reason?, at?}
POST /session/unfreeze {at?}
POST /session/unlock {force?} โ€” force: true is the emergency key
GET /session/undo/candidate what undo would hit and, if it cannot, why โ€” the same text the UI shows
POST /session/undo {reason?}
POST /note {text, at?}
POST /media/photo multipart photo, caption?, at? โ€” a photo check-in. Returns ids and the clock, never the caption
GET /events ?limit=&module= โ€” whitelisted payloads (no prose)
POST /wheel/spin spin as the calling actor
GET /wheel/segments the wheel
GET /tasks {open, library}
POST /tasks/assign {task_id?, due_hours?} โ€” omit task_id for random
POST /tasks/{id}/complete multipart photo?, note?, at?
POST /tasks/{id}/fail form reason?, at?
GET /handler/today what is due โ€” facts only
GET /handler/entries ?kind=&limit= โ€” bodies included, by design
POST /handler/reflect {body, prompt_id?, at?}
POST /handler/reflect/voice multipart audio, duration_seconds, prompt_id?, note?, at?
POST /handler/checkin {scores: {headspace, obedience, mood, denial, energy}, note?, prompt_id?, at?}
POST /handler/recite {kind: mantra\|affirmation, prompt_id, at?}
POST /handler/meditation {minutes, prompt_id?, note?, at?}

That table is the whole surface. A test in the repository holds /api/v1 to exactly this allowlist in both directions โ€” a new route fails the suite until it is written down.

Errors come back as {"error": "โ€ฆ"} with a 4xx, using the same sentences the web UI shows.

What is deliberately not on the API

Examples

Lock for four hours:

curl -s -X POST https://latch.your.lan/api/v1/session/lock \
  -H "Authorization: Bearer $LATCH_API_TOKEN" -H "X-Latch-Actor: shortcut" \
  -H "Content-Type: application/json" -d '{"seconds": 14400}'

A photo check-in from an iPhone Shortcut (Receive images from Share Sheet โ†’ Convert Image to JPEG โ†’ Get Contents of URL, method POST, form body, field photo = the image):

POST /api/v1/media/photo   (multipart)  photo=<jpeg>  caption=""

A spoken reflection from Record Audio โ†’ same pattern with field audio and duration_seconds โ€” the server refuses a recording whose length it cannot establish.

Ask the Handler what is due before nagging yourself:

curl -s https://latch.your.lan/api/v1/handler/today -H "Authorization: Bearer $LATCH_API_TOKEN"

Bots and the AI Handler

The API is the door every future bot uses: the Matrix bot (reminders, reflections and commands outside the web UI) and the AI Handler persona (a local model, acting as actor=ai, rate-limited, every action an event). Both are on the roadmap. Whatever you build, build it as a client of this API rather than a second writer to the database โ€” that is what keeps the clock derived and the log honest.


This page also ships inside the app, at /guide โ€” so your own instance always serves the guide for the version you are running, with the internet unplugged. Get Latch ยท Something wrong here? Tell me.