Headless task store
Tasklet
Tasklet has no interface of its own. Point a client at the endpoints below and it keeps every device in step.
Endpoints
- GET
/api/todos
List tasks, newest ordering first by position. Narrow with completed, includeDeleted, since, limit and offset.
- POST
/api/todos
Create a task. Only title is required; notes, completed, position and dueAt are optional.
- GET
/api/todos/:id
Read a single task. Add includeDeleted=1 to see one that was removed.
- PATCH
/api/todos/:id
Update any subset of fields. Anything you leave out stays untouched.
- DELETE
/api/todos/:id
Mark a task deleted so other clients learn about the removal. Add hard=1 to erase the record for good.
- POST
/api/todos/sync
Two-way sync in one round trip: push local changes, pull everything that moved since your cursor.
- GET
/api/health
Confirm the task store is reachable and see how many tasks are open.
Sync
POST /api/todos/sync
Content-Type: application/json
{
"since": "2026-09-17T08:00:00.000Z",
"changes": [
{
"id": "8f0a2c1e-6b4d-4f2a-9c11-2d5f7ab30c41",
"title": "Ship the sync endpoint",
"completed": true,
"updatedAt": "2026-09-17T08:04:11.920Z"
},
{
"clientId": "draft-2",
"title": "Write the client",
"updatedAt": "2026-09-17T08:05:02.100Z"
}
]
}{
"syncedAt": "2026-09-17T08:05:02.100Z",
"hasMore": false,
"applied": [
{ "id": "8f0a2c1e-6b4d-4f2a-9c11-2d5f7ab30c41", "status": "updated" },
{ "id": "b41d77a0-1e93-4c88-bf20-6ac0f9e4d712", "clientId": "draft-2", "status": "created" }
],
"changes": [
{
"id": "8f0a2c1e-6b4d-4f2a-9c11-2d5f7ab30c41",
"title": "Ship the sync endpoint",
"notes": null,
"completed": true,
"position": 0,
"dueAt": null,
"deletedAt": null,
"createdAt": "2026-09-17T07:58:40.310Z",
"updatedAt": "2026-09-17T08:04:11.920Z"
}
]
}How it behaves
- Cursor
- Store the syncedAt you get back and send it as since next time. When hasMore is true, call sync again straight away with the new cursor.
- Conflicts
- Last write wins, compared on updatedAt. If the stored copy is newer, your change is reported as skipped and the canonical task comes back in changes.
- New tasks
- Send an id you generated yourself to keep it stable offline, or leave id out and pass clientId so you can match the generated id in the applied list.
- Deletions
- Deletes are recorded, not erased: set deletedAt on a change, or call DELETE. Deleted tasks keep arriving in sync so every client can drop them.
- Access
- The endpoints are open until a TODO_API_KEY is set in the Secrets tab. Once it holds a real value, send it as an Authorization Bearer header or an X-Api-Key header.