Skip to content

API Reference ​

Interactive reference for the OpenTask REST API, auto-generated from the OpenAPI 3.1 specification.

REST API for OpenTask, a self-hosted task management application.

Authentication

All endpoints require authentication via one of the methods below, except
/api/health, /api/openapi and /api/push/vapid-key (public) and
/api/notifications/actions (takes the API token in its request body):

  • Bearer token — Authorization: Bearer <token> header
  • Reverse proxy header — configured via OPENTASK_PROXY_AUTH_HEADER env var
  • Session cookie — set by the web UI login flow

Bearer tokens are created via POST /api/tokens or the db:create-token CLI command.

Response format

Success: { "data": { ... } }

Error: { "error": "message", "code": "ERROR_CODE", "details": { ... } }

Servers​

https://tasks.example.comYour OpenTask server

System​


Health check​

GET
/api/health

Checks that the server is up and can query its database.

Responses​

Server is running and the database answers

application/json
JSON
{
"status": "string"
}

Samples​


OpenAPI specification​

GET
/api/openapi

Responses​

OpenAPI 3.1 YAML specification

text/yaml
JSON
"string"

Samples​


List tasks​

GET
/api/tasks

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Parameters​

Query Parameters

done

Filter by completion status (default false)

Type
boolean
project_id
Type
integer
label

Filter by label name

Type
string
search

Search in title and notes

Type
string
trashed
Type
boolean
archived
Type
boolean
overdue

true = only what is due right now, by the same definition as the
app badge, notifications and the snooze-overdue sweep: reminders
and quotas are never overdue, and a recurring task's due-ness comes
from its schedule. Only the user's own tasks (the badge's
population). Applied before pagination.

Type
boolean
kind

Narrow to one population: ordinary tasks, reminders
(is_reminder), or quotas (is_tracked or progress_target > 1).
Omitted = all three. Applied before pagination; any other value is
a 400.

Type
string
Valid values
"task""reminder""quota"
recurring
Type
boolean
one_off
Type
boolean
limit
Type
integer
Default
200
offset
Type
integer
Default
0

Responses​

List of tasks

application/json
JSON
{
"data": {
"tasks": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
],
"count": 0
}
}

Samples​


Create task​

POST
/api/tasks

Send a raw title string for AI enrichment, or provide structured fields
to bypass enrichment. AI enrichment triggers when only title is provided
(no due_at, priority=0, no labels, no rrule).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"title": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"short_title": "string",
"due_at": "string",
"priority": 0,
"rrule": "string",
"recurrence_mode": "from_due",
"labels": [
"string"
],
"notes": "string",
"project_id": 0,
"is_reminder": false,
"auto_snooze_minutes": 0,
"progress_target": 1,
"create_label": true,
"ai_proposed": true,
"ai_added": true,
"enrich": true
}

Responses​

Created task

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
}

Samples​


Counts for the nav badges​

GET
/api/tasks/counts

{ total, overdue, today } over the user's active tasks — the same
numbers the Tasks page shows in its top bar, computed with the same
function, for pages that do not load the list.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Task counts

application/json
JSON
{
"data": {
"total": 0,
"overdue": 0,
"today": 0
}
}

Samples​


Get task​

GET
/api/tasks/{id}

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Task details

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
}

Samples​


Delete task (soft delete to trash)​

DELETE
/api/tasks/{id}

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Trashed task

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true,
"message": "Task moved to trash"
}
}

Samples​


Update task​

PATCH
/api/tasks/{id}

PATCH semantics — only fields present in the body are updated.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"title": "string",
"short_title": "string",
"due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"retarget": true,
"deadline_nag_off": true,
"affirm_horizon": true,
"allow_past_deadline": true,
"cap_at_deadline": true,
"reset_original_due_at": true,
"priority": 0,
"rrule": "string",
"recurrence_mode": "string",
"labels": [
"string"
],
"notes": "string",
"project_id": 0,
"auto_snooze_minutes": 0,
"progress_target": 0,
"progress_current": 0,
"create_label": true
}

Responses​

Updated task with change metadata

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true,
"fields_changed": [
"string"
],
"description": "string"
}
}

Samples​


Mark task done​

POST
/api/tasks/{id}/done

For recurring tasks, advances due_at to the next occurrence.
For one-off tasks, sets done=true and archives.

Quotas are refused (400) unless the body has close_period: true.
Completing a quota closes its period early and resets
progress_current to 0; to log one, use
POST /api/tasks/{id}/progress. The body is optional — a bare POST
with no body works for everything else.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"close_period": true
}

Responses​

Completed task

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true,
"was_recurring": true,
"next_due_at": "string"
}
}

Samples​


Mark task undone (reopen / put back)​

POST
/api/tasks/{id}/undone

One-off task: reopens it. Recurring task: puts back its most recent
completion — the occurrence returns to the due date it had and the
completion counts roll back one. Refuses (400) when the task's due date
has changed since that completion, or when there is nothing to put back.
Logged as an undoable action either way.

Refuses (400) a quota (2026-09-24): a quota's completions are the period
rollover's record of a met period, and putting one back deleted that
record while the period stayed closed. Correct a count with
POST /api/tasks/{id}/progress and delta: -1 instead.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Reopened task

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
}

Samples​


Log progress on a tracked task​

POST
/api/tasks/{id}/progress

Records progress on a task with progress_target > 1 (REDESIGN-V03 §5).

A sub-target increment is NOT a completion: it dispatches
task.progressed, never task.completed, and the task stays open until
its period boundary. Returns 400 if the task is not tracked.

If the quota's period has ended but the rollover job has not run yet
(it runs every few minutes), the period is closed first, so a +1 just
after midnight counts for the new day. Progress logged today is also
recorded in the quota's quota_day_state (it clears a weekly/monthly
quota prompt for the day); undo restores both.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"delta": 1
}

Responses​

Updated task

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true,
"met": true,
"description": "3/2"
}
}

Samples​


Skip this occurrence without completing it​

POST
/api/tasks/{id}/skip-occurrence

"Not doing this occurrence - advance without recording a completion"
(REDESIGN-V03 §7.5).

Recurring tasks advance to the next occurrence with completion_count
untouched and skip_count incremented. One-off tasks are archived
without being marked done. Dispatches task.skipped, never
task.completed.

Named skip-occurrence rather than skip because the review API
already uses "skip" for a no-op review acknowledgment.

Reminders (is_reminder) are refused (400): a missed one already
rolls forward on its own, and completing one marks it considered.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Skipped task

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true,
"was_recurring": true,
"description": "string"
}
}

Samples​


Confirm an assistant-proposed task​

POST
/api/tasks/{id}/confirm

Swaps the ai-proposed provenance label for ai-added (REDESIGN-V03
§7.2). Task-scoped and narrow: confirming is a statement about where a
task came from, so nothing else is modified. Idempotent.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Confirmed task

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true,
"confirmed": true
}
}

Samples​


Snooze task​

POST
/api/tasks/{id}/snooze

Moves a task's target (due_at) — the same move as PATCH { target_at }
(2026-10-12, spec §6: there is no snooze state). It stops at the task's
deadline while that deadline is still ahead (spec §8.2): the response's
capped_at_deadline is true when it did. Once the deadline has passed
the cap is off and any target can be picked (2026-10-12).
allow_past_deadline and cap_at_deadline are accepted from
old clients and change nothing. Two populations refuse it with a 400,
because neither has a due date to move: reminders (§6), which stay in
their time slot until completed, and quotas (§5), which are counted
within a period rather than due on a day. The bulk snooze sweep skips
both instead of failing.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"until": "string"
}

Responses​

Snoozed task

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true,
"previous_due_at": "string",
"description": "string",
"capped_at_deadline": true
}
}

Samples​


Restore task from trash​

POST
/api/tasks/{id}/restore

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Restored task

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
}

Samples​


Retry AI enrichment​

POST
/api/tasks/{id}/reprocess

Re-runs AI enrichment using the original_title.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Task queued for reprocessing

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
}

Samples​


What Process works through​

GET
/api/process

The in-app Process button's lists, computed by rule (no AI, so it works with AI off). Read-only: Process writes through PATCH, bulk edit (affirm_horizon, target_at: null — on a deadline task that is Back to auto), target-today and DELETE. Only the caller's own open tasks, never reminders or quotas; growth and the stale-target lists also leave out recurring tasks.

  • inbox: tasks still in the Inbox project, oldest first, recurring ones included (the
    dashboard's Inbox group, so the two counts agree).
  • growth: tasks long past their horizon, longest first. Measured from horizon_set_at, else
    created_at; days after 14 days, weeks or unset after 60, months after 180 (eventually never).
    Asks for the next horizon up. Excludes tasks whose target has arrived (they are in the old-target
    lists or on the dashboard) and tasks whose deadline is close enough to steer the horizon (90 days
    or past). from is null for an unset horizon. can_act (priority 2+) offers "do it" / "raise
    priority" beside "grow".
  • stale_targets: tasks whose target arrived 7+ days ago (age_days, measured from the first
    target, so snoozing does not reset it), oldest first.
  • stale_targets_with_deadline: the same for tasks with a deadline, listed apart so a client's
    nag is never stopped by default.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Process lists

application/json
JSON
{
"data": {
"inbox_project_id": 0,
"inbox": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
],
"growth": [
{
"task": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
},
"from": "string",
"to": "string",
"days_on_horizon": 0,
"can_act": true
}
],
"stale_targets": [
{
"task": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
},
"age_days": 0
}
],
"stale_targets_with_deadline": [
{
"task": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
},
"age_days": 0
}
]
}
}

Samples​


List registered labels​

GET
/api/labels

The label registry (REDESIGN-V03 §7.2). Labels used to be bare
free-text, so a typo silently forked the taxonomy. Creating a label is
now a discrete act: a task write carrying an unknown label is rejected
unless it also carries create_label.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Registered labels

application/json
JSON
{
"data": {
"labels": [
{
"id": 0,
"user_id": 0,
"name": "string",
"facet": "string",
"icon": "string",
"color": "string",
"created_at": "string"
}
],
"count": 0
}
}

Samples​


Register a label​

POST
/api/labels

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"name": "string",
"facet": "domain",
"icon": "string",
"color": "string"
}

Responses​

The registered label (idempotent - returns the existing row if present)

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"name": "string",
"facet": "string",
"icon": "string",
"color": "string",
"created_at": "string"
}
}

Samples​


Deregister a label​

DELETE
/api/labels/{name}

Removes the label from the registry. Does NOT strip it from tasks that
already carry it - §7.2 checks only NEWLY added labels, so those tasks
stay editable; the label simply can't be applied to anything new.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Deleted

Samples​


List time slots​

GET
/api/time-slots

Life-moment containers - "Early morning", "Before work", "Evening"
(REDESIGN-V03 §6.0). The dashboard and the Reminders surface group by
this same table, so "morning" means one thing across the app.

An item belongs to the slot with the latest start_time <= its time of
day, where time of day is anchor_time if set, else due_at read in
the user's timezone.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Time slots, earliest first

application/json
JSON
{
"data": {
"time_slots": [
{
"id": 0,
"user_id": 0,
"label": "Early morning",
"start_time": "07:00",
"sort_order": 0,
"created_at": "string"
}
],
"count": 0
}
}

Samples​


Create a time slot​

POST
/api/time-slots

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"label": "string",
"start_time": "string",
"sort_order": 0
}

Responses​

The created slot (start_time must be unique among the user's slots)

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"label": "Early morning",
"start_time": "07:00",
"sort_order": 0,
"created_at": "string"
}
}

Samples​


Remove a time slot​

DELETE
/api/time-slots/{id}

Removes the slot and moves each of its reminders to the start of the
remaining slot nearest the reminder's own time of day (a tie goes to the
earlier slot), so none drop out of every slot. Quota prompts placed in
the slot move the same way (2026-09-25): every stored prompt period
naming it — a quota's quota_prompt_config.slot_id and per-number
overrides, and the user's default prompt period — is repointed to the
remaining slot nearest the removed slot's start (quotas_moved). The
last remaining slot cannot be removed (400). Always an undo entry
(time_slot_delete, undo_id): undo restores the slot under its
original id together with its reminders and prompt placements.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

The removed slot, as it was

application/json
JSON
{
"data": {
"slot": {
"id": 0,
"user_id": 0,
"label": "Early morning",
"start_time": "07:00",
"sort_order": 0,
"created_at": "string"
},
"reminders_moved": 0,
"quotas_moved": 0,
"undo_id": 0
}
}

Samples​


Rename a time slot and/or move its start​

PATCH
/api/time-slots/{id}

Settings → Reminder periods. A reminder belongs to a slot only through
its time of day, so moving a slot's start_time also moves that slot's
reminders to keep them in it: reminders on the old start move to the new
start, reminders inside the slot keep their time unless the new
boundaries would put them in another slot (then they move to the new
start). Other slots' reminders and ordinary tasks are never touched.
The slot change and the reminder moves are one atomic undo entry
(time_slot_edit) that also restores the slot; undo_id names it.
A rename alone moves nothing and returns undo_id: null.

start_time must be unique among the user's slots. There is no
reordering: slots are always ordered by start_time.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"label": "string",
"start_time": "string"
}

Responses​

The slot after the change

application/json
JSON
{
"data": {
"slot": {
"id": 0,
"user_id": 0,
"label": "Early morning",
"start_time": "07:00",
"sort_order": 0,
"created_at": "string"
},
"reminders_moved": 0,
"quotas_moved": 0,
"undo_id": 0
}
}

Samples​


Mark multiple tasks done​

POST
/api/tasks/bulk/done

Quotas in ids are skipped (counted in quota_skipped) unless
close_period: true — completing a quota closes its period early and
resets its count. A batch of nothing but quotas is a 400.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
],
"close_period": true
}

Responses​

Bulk operation result

application/json
JSON
{
"tasks_affected": 0,
"tasks_skipped": 0,
"skipped_urgent": 0,
"skipped_high": 0,
"snoozed_high": 0,
"skipped_reminders": 0,
"skipped_deadline": 0,
"capped_deadline": 0,
"skipped_no_due_date": 0
}

Samples​


Snooze multiple tasks​

POST
/api/tasks/bulk/snooze

Snoozes one or more tasks. Supports absolute mode (until) or relative
mode (delta_minutes) — exactly one must be provided.

Priority filtering, by default:

  • P0-P2 are always snoozed.
  • P3 (High) is snoozed ONLY when no lower-priority task in the same
    batch is still eligible. A mixed batch clears P0-P2 and leaves the
    High tasks; the next call, finding only High left, takes them. A P4 in
    the batch does not hold this back, since a P4 is never swept.
  • P4 (Urgent) is never snoozed by a sweep — its due date is a hard
    deadline and every change to one has to be deliberate.
  • A task whose DEADLINE HAS PASSED is read as at least P3 (2026-10-12):
    it waits while any P0-P2 task without a passed deadline is still
    eligible, and goes with the High tier after. A P3 with a passed
    deadline is still P3; a P4 is still never swept. Those held back
    only by the deadline are counted in skipped_deadline (not in
    skipped_urgent).

Callers that represent an explicit user selection (e.g., the mobile
selection sheet's quick panel) should pass include_task_ids so the
listed high/urgent tasks are snoozed anyway.

skipped_urgent keeps its name for client compatibility and counts BOTH
P3 and P4 skips. skipped_high is the P3 subset of it, so a client can
name the two tiers accurately; Urgent alone is the difference.

Each eligible task is changed exactly as PATCH /api/tasks/{id} with
{ due_at } would change it: a dated task's target moves (its
first-target date kept), stopping at a deadline still ahead
(capped_deadline; a passed deadline caps nothing); a
task with no date (absolute mode only) gets its
first date, which is not counted as a snooze; a task already due at
the target is left alone. tasks_affected counts only the tasks whose
date actually moved, so it plus tasks_skipped can be less than the
number of ids.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
],
"until": "string",
"delta_minutes": 0,
"include_task_ids": [
0
],
"allow_past_deadline_ids": [
0
],
"cap_at_deadline_ids": [
0
]
}

Responses​

Bulk operation result

application/json
JSON
{
"tasks_affected": 0,
"tasks_skipped": 0,
"skipped_urgent": 0,
"skipped_high": 0,
"snoozed_high": 0,
"skipped_reminders": 0,
"skipped_deadline": 0,
"capped_deadline": 0,
"skipped_no_due_date": 0
}

Samples​


Snooze all overdue tasks​

POST
/api/tasks/bulk/snooze-overdue

Snoozes all overdue tasks that the priority rule allows: P0-P2 always,
P3 (High) once no lower-priority task in the batch is still eligible,
and P4 (Urgent) never. See /api/tasks/bulk/snooze for the full rule
and for what skipped_urgent and skipped_high count.

The target is at most one of until, delta_minutes, slot or
tomorrow; with none, the user's default snooze option applies.

Feedback notification. When the call is authenticated with an API
token (not a browser session) and the sweep leaves High or Urgent tasks
overdue, the server sends one quiet push notification to the user's
devices saying how many of each stayed overdue (counts only, never
titles). It is not sent when nothing High or Urgent is left, when the
user has turned off sweep_feedback_notifications_enabled or
notifications as a whole, or when the request says notify: false.
A newer result replaces the older notification.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"until": "string",
"delta_minutes": 0,
"slot": "string",
"tomorrow": true,
"include_task_ids": [
0
],
"allow_past_deadline_ids": [
0
],
"notify": true
}

Responses​

Bulk operation result

application/json
JSON
{
"data": {
"tasks_affected": 0,
"tasks_skipped": 0,
"skipped_urgent": 0,
"skipped_high": 0,
"snoozed_high": 0,
"skipped_reminders": 0,
"skipped_deadline": 0,
"capped_deadline": 0,
"until": "string",
"message": "string",
"feedback_notification": true
}
}

Samples​


Edit multiple tasks​

POST
/api/tasks/bulk/edit

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
],
"changes": {
"priority": 0,
"labels": [
"string"
],
"project_id": 0,
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"deadline_nag_off": true,
"affirm_horizon": true,
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
}
},
"per_task": {
"additionalProperties": {
"rrule": "string"
}
},
"include_task_ids": [
0
],
"allow_past_deadline_ids": [
0
],
"cap_at_deadline_ids": [
0
],
"delta_minutes": 0,
"date_task_ids": [
0
]
}

Responses​

Bulk operation result

application/json
JSON
{
"tasks_affected": 0,
"tasks_skipped": 0,
"skipped_urgent": 0,
"skipped_high": 0,
"snoozed_high": 0,
"skipped_reminders": 0,
"skipped_deadline": 0,
"capped_deadline": 0,
"skipped_no_due_date": 0
}

Samples​


Soft-delete multiple tasks​

POST
/api/tasks/bulk/delete

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
]
}

Responses​

Bulk operation result

application/json
JSON
{
"tasks_affected": 0,
"tasks_skipped": 0,
"skipped_urgent": 0,
"skipped_high": 0,
"snoozed_high": 0,
"skipped_reminders": 0,
"skipped_deadline": 0,
"capped_deadline": 0,
"skipped_no_due_date": 0
}

Samples​


Complete multiple tasks in one request​

POST
/api/tasks/bulk/complete

Built for the §6.1 notification batch checklist: the iOS content
extension commits N staged check-marks with one request. Delegates to
the same core bulk-done path — one transaction, ONE undo entry.

Quotas are skipped unless close_period: true, as for
/api/tasks/bulk/done (and refused when nothing else is in the batch).

Quota prompts (2026-09-24): prompts acts on quota prompts in the SAME
transaction and undo entry — a checklist commit or a slot's "Considered
all" mixing reminders and prompts is one Undo. Each entry is a
prompt_key string (consider it) or { key, did } (did: true = the
square checkbox: log progress and consider it). A quota is acted on
through its prompt, never completed by id. ids may be empty when
prompts is not. A stale-date or foreign key refuses the whole batch.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
],
"close_period": true,
"prompts": [
"string"
]
}

Responses​

Completion result

application/json
JSON
{
"data": {
"tasks_affected": 0,
"recurring_count": 0,
"one_off_count": 0,
"quota_skipped": 0,
"prompts_considered": 0,
"prompts_did": 0
}
}

Samples​


Consider quota prompts (no progress)​

POST
/api/quota-prompts/consider

The round circle on a quota prompt: handled for today, nothing logged.
One transaction and one undo entry for every key; idempotent. Keys come
from GET /api/reminders groups[].prompts[].prompt_key; a key for
another local day, or for a quota that is not the caller's own, refuses
the whole request (400).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"keys": [
"q:42:0:2026-09-24"
]
}

Responses​

What the prompt action did, and the quotas it touched

application/json
JSON
{
"data": {
"considered": 0,
"did": 0,
"tasks": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
]
}
}

Samples​


Did it — log progress on quota prompts​

POST
/api/quota-prompts/did

The square checkbox on a quota prompt: logs progress AND considers the
prompt. Idempotent per key, so a double tap or a retry logs once: a
daily quota's prompt k raises today's count to at least k; any
other quota gets +1 unless that key was already done today. One
transaction, one undo entry; undo restores the count and the prompt.
Dispatches task.progressed for each quota whose count moved.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"keys": [
"q:42:0:2026-09-24"
]
}

Responses​

What the prompt action did, and the quotas it touched

application/json
JSON
{
"data": {
"considered": 0,
"did": 0,
"tasks": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
]
}
}

Samples​


Put back handled quota prompts​

POST
/api/quota-prompts/restore

The filled dashed circle on a handled prompt in a "considered" / DONE
list (2026-09-25): the prompt waits for today again. Its key leaves
today's considered and did; if it was "did it", exactly the
progress that did-it added is taken back (quota_day_state.did_applied
— a daily prompt k done when the count was already at k added 0,
so it takes back 0). Any other did-it the lower count no longer
supports waits again too. A key that is already waiting changes
nothing. One transaction and one undo entry (always logged, so a
toast's Undo never reaches an older action). Keys for another local
day, or for a quota that is not the caller's own, refuse the whole
request (400). Quotas are never put back with /api/tasks/{id}/undone.
Dispatches task.progressed for each quota whose count moved.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"keys": [
"q:42:0:2026-09-24"
]
}

Responses​

How many prompts now wait again, and the quotas touched

application/json
JSON
{
"data": {
"restored": 0,
"tasks": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
]
}
}

Samples​


Mark task-segment rows considered​

POST
/api/task-segments/consider

The circle: stamps last_considered_at. Never a completion. One undo entry; logged even when already considered today. The caller's own open tasks only; anything else refuses the whole request (400).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
]
}

Responses​

Done

application/json
JSON
{
"data": {
"considered": 0
}
}

Samples​


Defer task-segment rows to tomorrow​

POST
/api/task-segments/defer

Defer All: stamps segment_deferred_at; off today's segment, back tomorrow. Not a consider. One undo entry. The caller's own open tasks only; anything else refuses the whole request (400).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
]
}

Responses​

Done

application/json
JSON
{
"data": {
"deferred": 0
}
}

Samples​


Put task-segment rows back​

POST
/api/task-segments/restore

The put-back behind "Show N considered" (2026-10-11): clears last_considered_at and segment_deferred_at, so each row waits in its segment again today and comes back daily until it is considered. One undo entry. The caller's own open tasks only; anything else refuses the whole request (400).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
]
}

Responses​

Done

application/json
JSON
{
"data": {
"restored": 0
}
}

Samples​


Target tasks today​

POST
/api/task-segments/target-today

A move of the target (a target the user picked) to the Snooze-all button's time while still today — the next period or the default snooze option — else the top of the next hour; or to at, when given. Also marks them considered and stamps segment_targeted_at. One undo entry (bulk edit) puts back the target and both stamps. Like every move it stops at the task's deadline (2026-10-12, spec §8.2). The caller's own open tasks only; anything else refuses the whole request (400). REPEATING tasks (an rrule) are targeted too since 2026-10-12 (spec §6): only the target moves, the scheduled occurrence (occurrence_at) stays, so done that day brings the next occurrence round on schedule.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
],
"at": "string"
}

Responses​

Done

application/json
JSON
{
"data": {
"until": "string",
"affected": 0,
"skipped_recurring": [
0
]
}
}

Samples​


Today's incomplete reminders grouped by time slot​

GET
/api/reminders

The Reminders surface (prompted thoughts, not actions). Items carry no
debt: never counted in overdue, never in the badge, never notified
individually — the time slot notifies. A reminder completed today
drops out of its group and returns with its next occurrence.

QUOTA PROMPTS (2026-09-24). Every unmet quota also appears as a prompt
in a period each day, in the SEPARATE groups[].prompts array.
reminders, count, considered, total and considered_total stay
reminder-only, exactly as before, so clients that predate prompts are
unaffected. prompts holds every prompt assigned to the slot today,
handled ones included; a prompt is waiting while !considered && !done, and a client counts it like a reminder (waiting, then
considered). Act on one with POST /api/quota-prompts/consider,
/did, or prompts in POST /api/tasks/bulk/complete. The user's
quota_prompts_enabled preference and the server's
OPENTASK_QUOTA_PROMPTS=off each make every prompts array empty.
The slot notification and its nags count waiting prompts too, so a
client's slot checklist should list them after the reminders.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Reminder groups in slot order

application/json
JSON
{
"data": {
"total": 0,
"considered_total": 0,
"not_today": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
],
"prompts_total": 0,
"prompts_considered_total": 0,
"task_segments": [
{
"horizon": "string",
"tasks": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true,
"segment_state": "string",
"recurring": true
}
],
"waiting": 0,
"considered": 0,
"deferred": 0,
"nudge": true
}
],
"has_any": true,
"groups": [
{
"slot": {
"id": 0,
"label": "string",
"start_time": "07:00",
"sort_order": 0
},
"count": 0,
"reminders": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
],
"considered": 0,
"considered_items": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
],
"prompts": [
{
"prompt_key": "q:42:0:2026-09-24",
"task_id": 0,
"number": 0,
"numbers": [
],
"slot_id": 0,
"title": "string",
"current": 0,
"target": 0,
"period": "string",
"stripe_color": "string",
"has_notes": true,
"considered": true,
"done": true
}
],
"prompts_waiting": 0,
"prompts_considered": 0
}
]
}
}

Samples​


Quotas​


Every open quota for the user​

GET
/api/quotas

The Quotas surface. A quota is "how often", not "when" — four workouts a
week, one date night a month — and is a task carrying progress_target
greater than 1 OR is_tracked true. The flag exists so a target of one
can still be a quota rather than a deadline.

A quota has NO due date (since 2026-09-08): due_at and
original_due_at are always null, creating or updating one with a date
is a 400, and snoozing one is a 400. "Four times this week" cannot be
late on a Tuesday. The period a quota counts within is named by its
rrule's FREQ, and the current period's start is progress_period_start
on the task — that field is the period anchor; use it, not due_at, for
pace.

A quota is also absent from GET /api/tasks-backed list surfaces in the
app — it appears on this endpoint and in the Track panel and nowhere
else — but GET /api/tasks itself still returns quotas, so clients that
build their own Track view from the full corpus keep working.

Order is deliberately NOT specified here. Clients sort quotas
alphabetically so that logging progress on one never reorders the others
under the user's finger.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

The user's open quotas

application/json
JSON
{
"data": {
"quotas": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
],
"total": 0,
"has_any": true
}
}

Samples​


List projects​

GET
/api/projects

The user's own projects plus projects shared with them.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

List of projects with task counts

application/json
JSON
{
"data": {
"projects": [
{
"id": 0,
"name": "string",
"owner_id": 0,
"shared": true,
"sort_order": 0,
"color": "string",
"description": "string",
"active_count": 0,
"overdue_count": 0,
"created_at": "string"
}
],
"count": 0
}
}

Samples​


Create project​

POST
/api/projects

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"name": "string",
"shared": false,
"sort_order": 0,
"color": "string",
"description": "string"
}

Responses​

Created project

application/json
JSON
{
"data": {
"id": 0,
"name": "string",
"owner_id": 0,
"shared": true,
"sort_order": 0,
"color": "string",
"description": "string",
"active_count": 0,
"overdue_count": 0,
"created_at": "string"
}
}

Samples​


Reorder projects​

PATCH
/api/projects/reorder

Sets each project's sort_order to its position in project_ids
(0, 1, 2, ...). Only projects the user owns are reordered; ids of
shared projects owned by someone else, or unknown ids, are ignored and
the remaining owned projects keep their relative order.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"project_ids": [
0
]
}

Responses​

Number of projects reordered

application/json
JSON
{
"data": {
"reordered": 0
}
}

Samples​


Get project​

GET
/api/projects/{id}

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Project details

application/json
JSON
{
"data": {
"id": 0,
"name": "string",
"owner_id": 0,
"shared": true,
"sort_order": 0,
"color": "string",
"description": "string",
"active_count": 0,
"overdue_count": 0,
"created_at": "string"
}
}

Samples​


Delete project​

DELETE
/api/projects/{id}

Permanently deletes the project and moves each of its tasks to its
owner's Inbox (in a shared project, every member's tasks go to that
member's own Inbox). Not undoable. The Inbox itself cannot be deleted (400).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Deletion result

application/json
JSON
{
"data": {
"message": "Project deleted",
"tasks_moved_to_inbox": true
}
}

Samples​


Update project​

PATCH
/api/projects/{id}

Owner only. The Inbox cannot be renamed (400); its other fields can change.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"name": "string",
"shared": true,
"sort_order": 0,
"color": "string",
"description": "string"
}

Responses​

Updated project

application/json
JSON
{
"data": {
"id": 0,
"name": "string",
"owner_id": 0,
"shared": true,
"sort_order": 0,
"color": "string",
"description": "string",
"active_count": 0,
"overdue_count": 0,
"created_at": "string"
}
}

Samples​


Undo last action​

POST
/api/undo

Reverses the most recent undoable action. The body is optional; send
session_start_id to have the returned counts cover only entries after
that id (see GET /api/undo/status) instead of all time.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"session_start_id": 0
}

Responses​

Undo result

application/json
JSON
{
"data": {
"undone_action": "string",
"description": "string",
"tasks_affected": 0,
"undoable_count": 0,
"redoable_count": 0
}
}

Samples​


Redo last undone action​

POST
/api/redo

Re-applies the most recently undone action. The body is optional; send
session_start_id to scope the returned counts, as for POST /api/undo.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"session_start_id": 0
}

Responses​

Redo result

application/json
JSON
{
"data": {
"redone_action": "string",
"description": "string",
"tasks_affected": 0,
"undoable_count": 0,
"redoable_count": 0
}
}

Samples​


Undo stack status​

GET
/api/undo/status

The newest undo-log id and how many entries can be undone and redone.
A client can store latest_id when it starts and pass it back as
session_start_id to scope undo to its own session.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Undo stack status

application/json
JSON
{
"data": {
"latest_id": 0,
"undoable_count": 0,
"redoable_count": 0
}
}

Samples​


Undo history​

GET
/api/undo/history

The user's most recent undo-log entries, newest first, both undone and not.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Parameters​

Query Parameters

limit
Type
integer
Minimum
1
Default
50

Responses​

Undo history

application/json
JSON
{
"data": {
"history": [
{
"id": 0,
"action": "string",
"description": "string",
"tasks_affected": 0,
"created_at": "string",
"undone": true,
"fields_changed": [
"string"
],
"snapshot": [
{
"task_id": 0,
"before_state": {
"additionalProperties": "string"
},
"after_state": {
"additionalProperties": "string"
},
"completion_id": 0
}
]
}
],
"count": 0
}
}

Samples​


Undo several actions​

POST
/api/undo/batch

Undoes entries from the top of the stack, newest first, in one
transaction: all of them are undone or none are. Provide at least one
of the fields; when several are given, every limit applies.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"session_start_id": 0,
"through_id": 0,
"count": 0
}

Responses​

Batch undo result

application/json
JSON
{
"data": {
"count": 0,
"undoable_count": 0,
"redoable_count": 0
}
}

Samples​


Redo several actions​

POST
/api/redo/batch

Redoes undone entries, oldest undone first, in one transaction.
Provide at least one of the fields.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"through_id": 0,
"count": 0
}

Responses​

Batch redo result

application/json
JSON
{
"data": {
"count": 0,
"undoable_count": 0,
"redoable_count": 0
}
}

Samples​


Trash​


List trashed tasks​

GET
/api/trash

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Parameters​

Query Parameters

limit
Type
integer
Default
200

Responses​

Trashed tasks

application/json
JSON
{
"data": {
"tasks": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
],
"count": 0
}
}

Samples​


Empty trash (permanent delete)​

DELETE
/api/trash

Permanently deletes all trashed tasks. Cannot be undone.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Number of tasks permanently deleted

application/json
JSON
{
"data": {
"deleted_count": 0
}
}

Samples​


Export​


Export user data​

GET
/api/export

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Parameters​

Query Parameters

format
Type
string
Valid values
"json""csv"
Default
"json"
type

Required for CSV format. Each table is a separate file.

Type
string
Valid values
"tasks""projects"

Responses​

Exported data

JSON
{
"data": {
"tasks": [
{
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
}
],
"projects": [
{
"id": 0,
"name": "string",
"owner_id": 0,
"shared": true,
"sort_order": 0,
"color": "string",
"description": "string",
"active_count": 0,
"overdue_count": 0,
"created_at": "string"
}
],
"completions": [
{
"id": 0,
"task_id": 0,
"user_id": 0,
"completed_at": "string",
"due_at_was": "string",
"due_at_next": "string",
"task_title": "string",
"project_id": 0,
"is_reminder": true,
"is_tracked": true,
"progress_target": 0
}
],
"exported_at": "string"
}
}

Samples​


List webhooks​

GET
/api/webhooks

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

User's webhooks (secrets excluded)

application/json
JSON
{
"data": {
"webhooks": [
{
"id": 0,
"user_id": 0,
"url": "string",
"events": [
"string"
],
"active": true,
"created_at": "string",
"updated_at": "string"
}
]
}
}

Samples​


Create webhook​

POST
/api/webhooks

Creates a webhook with an auto-generated HMAC secret.
The secret is returned in this response only — store it securely.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"url": "string",
"events": [
"string"
]
}

Responses​

Created webhook with secret (shown once)

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"url": "string",
"secret": "string",
"events": [
"string"
],
"active": true,
"created_at": "string",
"updated_at": "string"
}
}

Samples​


Delete webhook​

DELETE
/api/webhooks/{id}

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Deletion confirmed

application/json
JSON
{
"data": {
"deleted": true
}
}

Samples​


Update webhook​

PATCH
/api/webhooks/{id}

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"url": "string",
"events": [
"string"
],
"active": true
}

Responses​

Updated webhook (secret excluded)

application/json
JSON
{
"data": {
"id": 0,
"user_id": 0,
"url": "string",
"events": [
"string"
],
"active": true,
"created_at": "string",
"updated_at": "string"
}
}

Samples​


List recent webhook deliveries​

GET
/api/webhooks/{id}/deliveries

Returns the last 50 delivery attempts for debugging.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Delivery history

application/json
JSON
{
"data": {
"deliveries": [
{
"id": 0,
"webhook_id": 0,
"event": "string",
"payload": "string",
"status_code": 0,
"error": "string",
"attempt": 0,
"created_at": "string"
}
]
}
}

Samples​


List API tokens​

GET
/api/tokens

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

API tokens (previews only, not full tokens), newest first

application/json
JSON
{
"data": {
"tokens": [
{
"id": 0,
"name": "string",
"token_preview": "string",
"created_at": "string"
}
]
}
}

Samples​


Create API token​

POST
/api/tokens

Creates a new API token. The full token is returned in this response only.
Tokens are stored as SHA-256 hashes — the raw token cannot be retrieved later.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"name": "string"
}

Responses​

Created token (shown once)

application/json
JSON
{
"data": {
"id": 0,
"name": "string",
"token": "string"
}
}

Samples​


Revoke API token​

DELETE
/api/tokens/{id}

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Token revoked

application/json
JSON
{
"data": {
"deleted": true
}
}

Samples​


Exchange a Bearer token for a web session cookie​

POST
/api/auth/session-from-token

Mints a NextAuth session cookie for the owner of the supplied Bearer token and
returns it in a Set-Cookie header. Used by the iOS app to log its WKWebView back
in when the 7-day session JWT expires, using the long-lived token in its keychain.

Bearer authentication only — a missing, malformed, or invalid Authorization
header returns 401. This endpoint never falls back to session cookie or proxy
header auth.

The cookie is HttpOnly, SameSite=Lax, Path=/, expires in 7 days, and is named
__Secure-authjs.session-token over HTTPS (authjs.session-token otherwise).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Session cookie issued

application/json
JSON
{
"data": {
"ok": true
}
}

Samples​


Current user​

GET
/api/auth/me

The authenticated user's identity and core settings. Useful to check which account a token belongs to.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

The authenticated user

application/json
JSON
{
"data": {
"id": 0,
"email": "string",
"name": "string",
"timezone": "America/New_York",
"default_grouping": "string",
"is_demo": true
}
}

Samples​


Stats​


Get user statistics​

GET
/api/stats

Without start and end, returns a summary (today, this week, this
month, all time). With both, returns { stats: [...] }, one row per
day that has stats in the range, oldest first.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Parameters​

Query Parameters

start

Start date for daily stats range (YYYY-MM-DD)

Type
string
Format
"date"
end

End date for daily stats range (YYYY-MM-DD)

Type
string
Format
"date"

Responses​

Stats summary or daily stats for date range

application/json
JSON
{
"data": {
"today": {
"id": 0,
"user_id": 0,
"date": "string",
"completions": 0,
"tasks_created": 0,
"snoozes": 0
},
"week": {
"completions": 0,
"tasks_created": 0,
"snoozes": 0
},
"month": {
"completions": 0,
"tasks_created": 0,
"snoozes": 0
},
"all_time": {
"completions": 0,
"tasks_created": 0,
"snoozes": 0
}
}
}

Samples​


Query completion history​

GET
/api/completions

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Parameters​

Query Parameters

task_id
Type
integer
date

Filter by completion date (YYYY-MM-DD, UTC calendar date)

Type
string
Format
"date"
since

Filter to completions at or after this instant. Must be paired with until.

Type
string
Format
"date-time"
until

Filter to completions before this instant. Must be paired with since.

Type
string
Format
"date-time"

Responses​

Completion records

application/json
JSON
{
"data": {
"completions": [
{
"id": 0,
"task_id": 0,
"user_id": 0,
"completed_at": "string",
"due_at_was": "string",
"due_at_next": "string",
"task_title": "string",
"project_id": 0,
"is_reminder": true,
"is_tracked": true,
"progress_target": 0
}
],
"count": 0
}
}

Samples​


Get the user's preferences​

GET
/api/user/preferences

The signed-in user's settings. Only week_start is described field by
field here; the response carries the rest of the Settings page's
values (view, snooze, schedule, notification and AI settings) as well.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

The user's preferences

application/json
JSON
{
"data": {
"default_grouping": "string",
"project_preview_count": 6,
"auto_snooze_minutes": 30,
"auto_snooze_low_minutes": 240,
"auto_snooze_medium_minutes": 60,
"auto_snooze_high_minutes": 15,
"auto_snooze_urgent_minutes": 5,
"default_reminder_slot_id": 0,
"quota_prompt_slot_id": 0,
"quota_prompts_enabled": true,
"enrichment_notifications_enabled": true,
"sweep_feedback_notifications_enabled": true,
"week_start": "sunday",
"check_in_day": 6,
"default_snooze_option": "60",
"ai_context": "string",
"ai_context_updated_at": "string",
"additionalProperties": "string"
}
}

Samples​


Update the user's preferences​

PATCH
/api/user/preferences

Send only the fields to change. Changing week_start closes, before
the response is returned, any weekly quota period the new boundary has
already ended (see UserPreferences.week_start).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"default_grouping": "string",
"project_preview_count": 6,
"auto_snooze_minutes": 30,
"auto_snooze_low_minutes": 240,
"auto_snooze_medium_minutes": 60,
"auto_snooze_high_minutes": 15,
"auto_snooze_urgent_minutes": 5,
"default_reminder_slot_id": 0,
"quota_prompt_slot_id": 0,
"quota_prompts_enabled": true,
"enrichment_notifications_enabled": true,
"sweep_feedback_notifications_enabled": true,
"week_start": "sunday",
"check_in_day": 6,
"default_snooze_option": "60",
"ai_context": "string",
"ai_context_updated_at": "string",
"additionalProperties": "string"
}

Responses​

The user's preferences after the update

application/json
JSON
{
"data": {
"default_grouping": "string",
"project_preview_count": 6,
"auto_snooze_minutes": 30,
"auto_snooze_low_minutes": 240,
"auto_snooze_medium_minutes": 60,
"auto_snooze_high_minutes": 15,
"auto_snooze_urgent_minutes": 5,
"default_reminder_slot_id": 0,
"quota_prompt_slot_id": 0,
"quota_prompts_enabled": true,
"enrichment_notifications_enabled": true,
"sweep_feedback_notifications_enabled": true,
"week_start": "sunday",
"check_in_day": 6,
"default_snooze_option": "60",
"ai_context": "string",
"ai_context_updated_at": "string",
"additionalProperties": "string"
}
}

Samples​


Review​


Start a review session​

GET
/api/review

Numbers the user's overdue tasks and tasks due within the next 24
hours (overdue first, then by due date) so a client can act on them by
number with POST /api/review/execute. Sessions are held in server
memory: one is discarded once executed, sessions older than an hour
are purged when a new one starts, and all are lost on a server restart.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

The review session

application/json
JSON
{
"data": {
"session_id": "string",
"groups": [
{
"label": "string",
"tasks": [
{
"seq": 0,
"id": 0,
"title": "string",
"due_at": "string",
"priority": 0,
"rrule": "string",
"labels": [
"string"
]
}
]
}
],
"total_tasks": 0
}
}

Samples​


Act on a review session​

POST
/api/review/execute

Applies actions to tasks by their seq numbers from GET /api/review.
Actions run in order, each as its own bulk operation (and undo entry);
they are not one transaction. The whole body is validated first, so a
snooze without until is a 400 before any action runs. Review
targets are explicit picks: snooze moves High and Urgent targets too,
where a sweep would hold them back. The session is discarded afterwards.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"session_id": "string",
"actions": [
{
"type": "string",
"targets": [
[
"1",
"3-5"
]
],
"until": "string"
}
]
}

Responses​

What each action did

application/json
JSON
{
"data": {
"executed": true,
"results": [
{
"type": "string",
"taskIds": [
0
],
"count": 0
}
],
"total_affected": 0
}
}

Samples​


AI status and activity​

GET
/api/ai/status

Diagnostics for the AI features: the state of the warm model slots and
request queue (server-wide), work in progress for this user, and the
user's recent AI activity log. The slot objects use camelCase keys.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Parameters​

Query Parameters

limit

Activity entries to return.

Type
integer
Default
20
Maximum
100
offset
Type
integer
Default
0
action

Only activity entries of this action.

Type
string
Example"enrichment"

Responses​

AI status

application/json
JSON
{
"data": {
"enrichment_slot": {
"state": "string",
"activatedAt": "string",
"totalRequests": 0,
"totalRecycles": 0,
"lastRequestAt": "string",
"model": "string",
"currentOperation": {
"taskId": 0,
"inputText": "string",
"startedAt": "string"
}
},
"quick_take_slot": {
"state": "string",
"activatedAt": "string",
"totalRequests": 0,
"totalRecycles": 0,
"lastRequestAt": "string",
"model": "string",
"totalSuperseded": 0,
"currentOperation": {
"inputText": "string",
"startedAt": "string"
}
},
"queue": {
"active": 0,
"waiting": 0,
"maxConcurrent": 0
},
"in_progress": {
"enrichment": {
"slot": {
},
"pipeline_task_ids": [
0
],
"circuit_breaker_open": true,
"pending_count": 0
},
"insights": {
"session_id": "string",
"status": "string",
"total_tasks": 0,
"completed": 0,
"started_at": "string"
}
},
"recent_activity": [
{
"id": 0,
"user_id": 0,
"task_id": 0,
"action": "string",
"status": "string",
"input": "string",
"output": "string",
"model": "string",
"duration_ms": 0,
"error": "string",
"provider": "string",
"created_at": "string"
}
]
}
}

Samples​


Test an AI feature's model​

POST
/api/ai/test

Sends a minimal prompt through the provider and model configured for
one feature, to check that it responds. A provider failure is reported
in the 200 body (success: false, error), not as an HTTP error.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"feature": "string"
}

Responses​

Test result

application/json
JSON
{
"data": {
"success": true,
"duration_ms": 0,
"provider": "string",
"provider_display": "string",
"model": "string",
"model_display": "string",
"error": "string"
}
}

Samples​


Quick take on a new task​

POST
/api/ai/quick-take

A one-line AI comment on a task title, in the context of the user's task list.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"title": "string",
"has_due_date": false
}

Responses​

The quick take

application/json
JSON
{
"data": {
"text": "string"
}
}

Samples​


What's Next recommendations​

GET
/api/ai/whats-next

Returns today's cached recommendations. Without refresh=true this
never generates: when nothing was generated today (UTC) data is
null. With refresh=true it runs the model and waits for the result.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Parameters​

Query Parameters

refresh

Generate fresh recommendations now.

Type
boolean

Responses​

Recommendations, or null

application/json
JSON
{
"data": {
"tasks": [
{
"task_id": 0,
"reason": "string"
}
],
"summary": "string",
"generated_at": "string",
"duration_ms": 0
}
}

Samples​


Start Insights generation​

POST
/api/ai/insights/generate

Scores all of the user's active tasks in the background and returns at
once. Poll GET /api/ai/insights/status with the returned
session_id, then read GET /api/ai/insights/results.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Generation started. With no active tasks, session_id is null,
total_tasks is 0 and message says so.

application/json
JSON
{
"data": {
"session_id": "string",
"total_tasks": 0,
"single_call": true,
"started_at": "string",
"message": "No active tasks to analyze"
}
}

Samples​


Insights generation progress​

GET
/api/ai/insights/status

Works whether or not AI is enabled (it only reads the stored session).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Parameters​

Query Parameters

session_id*
Type
string
Required

Responses​

Session progress

application/json
JSON
{
"data": {
"session_id": "string",
"status": "string",
"total_tasks": 0,
"completed": 0,
"progress_pct": 0,
"started_at": "string",
"finished_at": "string",
"error": "string"
}
}

Samples​


Insights results​

GET
/api/ai/insights/results

The latest Insights scores for the user's tasks, highest score first,
with the signal vocabulary for display. Works whether or not AI is
enabled (it only reads stored results).

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Insights results

application/json
JSON
{
"data": {
"results": [
{
"task_id": 0,
"score": 0,
"commentary": "string",
"signals": [
"string"
],
"generated_at": "string"
}
],
"generated_at": "string",
"stale": true,
"duration_ms": 0,
"signal_counts": {
"additionalProperties": 0
},
"signals": [
{
"key": "string",
"label": "string",
"color": "string",
"icon": "string",
"description": "string"
}
],
"active_session": {
"session_id": "string",
"completed": 0,
"total_tasks": 0,
"started_at": "string"
}
}
}

Samples​


Recommend a project for each task​

POST
/api/process/recommend-projects

For the Process button's sorting step: the project each given task
probably belongs in, or null when none clearly fits. The AI only
recommends — nothing is written; the user picks. It reads each
project's description (what belongs there) and the user's global
ai_context.

Ids that are not the caller's own live tasks are ignored. A
project_id is always one of the caller's own or shared projects,
never the Inbox; a model answer naming any other project is dropped,
so a task can be missing from recommendations (no recommendation).
Entries follow the order of ids.

AI disabled (server-wide, or the user's Insights mode set to off)
returns { recommendations: [] } with 200, never an error. Uses the
Insights feature's AI mode, model and timeout.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"ids": [
0
]
}

Responses​

Recommendations (empty when AI is off)

application/json
JSON
{
"data": {
"recommendations": [
{
"task_id": 0,
"project_id": 0,
"reason": "string"
}
]
}
}

Samples​


Possible duplicate tasks​

GET
/api/process/duplicates

For the Process button's duplicate step: groups of the caller's open
one-off tasks (not recurring tasks, reminders or quotas) that look like
the same piece of work. Each group has 2+ task ids and a task is in at
most one group. Nothing is written. Scans the newest 600 such tasks.

AI disabled (server-wide, or the user's Insights mode set to off)
returns { groups: [] } with 200, never an error.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Duplicate groups (empty when AI is off or none were found)

application/json
JSON
{
"data": {
"groups": [
{
"task_ids": [
0
],
"reason": "string"
}
]
}
}

Samples​


Act on a task from a notification​

POST
/api/notifications/actions

Done, snooze or delete for a single task, as sent by notification
action buttons. The API token goes in the body, not the Authorization
header, because the iOS notification extension cannot set headers.

  • done — mark the task done
  • snooze30 — snooze 30 minutes from now
  • snooze — snooze 1 hour from now, rounded to the hour
  • snooze2h — snooze 2 hours from now, rounded to the hour
  • delete — move the task to the trash (a soft delete, undoable like
    any other delete; the "AI finished" notification's Delete button).
    A task already in the trash is a 400.

Request Body​

application/json
JSON
{
"action": "string",
"task_id": 0,
"token": "string"
}

Responses​

The action taken. result is the task operation's own result: its
task is the stored task, without the computed is_recurring and
target_at fields.

application/json
JSON
{
"data": {
"action": "string",
"task_id": 0,
"until": "string",
"result": {
"task": {
"id": 0,
"user_id": 0,
"project_id": 0,
"title": "string",
"original_title": "string",
"short_title": "string",
"done": true,
"done_at": "string",
"priority": 0,
"due_at": "string",
"rrule": "string",
"recurrence_mode": "string",
"anchor_time": "string",
"anchor_dow": 0,
"anchor_dom": 0,
"original_due_at": "string",
"target_at": "string",
"deadline_at": "string",
"horizon": "string",
"horizon_set_at": "string",
"last_considered_at": "string",
"segment_deferred_at": "string",
"segment_targeted_at": "string",
"target_set_at": "string",
"target_auto": true,
"occurrence_at": "string",
"deadline_nag_off": true,
"auto_snooze_minutes": 0,
"deleted_at": "string",
"archived_at": "string",
"labels": [
"string"
],
"notes": "string",
"progress_target": 0,
"progress_current": 0,
"progress_period_start": "string",
"quota_prompt_config": {
"enabled": true,
"slot_id": 0,
"numbers": {
"additionalProperties": 0
}
},
"quota_day_state": {
"date": "2026-09-24",
"logged": 0,
"did": [
"string"
],
"considered": [
"string"
],
"did_applied": {
"additionalProperties": 0
}
},
"completion_count": 0,
"skip_count": 0,
"first_completed_at": "string",
"last_completed_at": "string",
"created_at": "string",
"updated_at": "string",
"is_recurring": true
},
"wasRecurring": true,
"nextDueAt": "string",
"previousDueAt": "string",
"originalDueAt": "string",
"description": "string"
}
}
}

Samples​


Dismiss all notifications​

POST
/api/notifications/dismiss-all

Clears the user's delivered notifications on every channel and device.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Dismissal sent

application/json
JSON
{
"data": {
"dismissed": true
}
}

Samples​


Send a test notification​

POST
/api/notifications/test

Creates a real task (labelled test, due a minute ago) and, about 3
seconds later, sends a notification for it to every configured
channel (Web Push and APNs), so Done and Snooze can be tried end to
end. high sends a High (P3) notification; urgent and critical
both send an Urgent (P4) one as an Apple Critical Alert, the same as a
real overdue P4.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"type": "string"
}

Responses​

Test scheduled

application/json
JSON
{
"data": {
"sent": true,
"type": "string",
"task_id": 0,
"delay_seconds": 3
}
}

Samples​


Web Push public key​

GET
/api/push/vapid-key

Responses​

The server's VAPID public key

application/json
JSON
{
"data": {
"publicKey": "string",
"configured": true
}
}

Samples​


Send a test Web Push notification​

POST
/api/push/test

Sends a test notification to the user's Web Push subscriptions only.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Responses​

Whether it was sent

application/json
JSON
{
"data": {
"sent": true,
"reason": "VAPID keys not configured"
}
}

Samples​


Register an APNs device​

POST
/api/push/apns/register

Registers an Apple device token for push notifications. Re-registering
an existing token moves it to the calling user.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"device_token": "string",
"bundle_id": "com.example.app",
"environment": "production"
}

Responses​

Registered

application/json
JSON
{
"data": {
"registered": true
}
}

Samples​


Unregister an APNs device​

DELETE
/api/push/apns/register

Removes the device token, whichever user registered it.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"device_token": "string"
}

Responses​

Unregistered

application/json
JSON
{
"data": {
"unregistered": true
}
}

Samples​


Register a widget push token​

POST
/api/push/apns/widget-token

Registers a WidgetKit push token (a widget extension's token, separate
from the app's device token) so the server can tell widgets to reload
when the user's data changes. Re-registering a token moves it to the
calling user.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"push_token": "string",
"bundle_id": "com.example.app",
"platform": "string",
"environment": "production",
"widget_kind": "string"
}

Responses​

Registered

application/json
JSON
{
"data": {
"registered": true
}
}

Samples​


Unregister a widget push token​

DELETE
/api/push/apns/widget-token

Removes the push token, whichever user registered it.

Authorizations​

bearerAuth

API token created via POST /api/tokens or the db:create-token CLI command

Type
HTTP (bearer)

Request Body​

application/json
JSON
{
"push_token": "string"
}

Responses​

Unregistered

application/json
JSON
{
"data": {
"unregistered": true
}
}

Samples​


Powered by VitePress OpenAPI