Skip to content

Automation ​

Authentication ​

All API requests require a Bearer token in the Authorization header:

Authorization: Bearer $TOKEN

Creating a token ​

bash
# Docker
docker compose exec opentask tsx scripts/create-token.ts <username> [token-name]

# Bare metal
npm run db:create-token -- <username> [token-name]

The token is printed once. Store it securely — it cannot be retrieved later. Tokens are stored as SHA-256 hashes on the server.

Auth methods ​

OpenTask checks authentication in this order:

  1. Bearer token — For scripts and automation
  2. Proxy header — For reverse proxy auth (Authelia, Authentik)
  3. Session cookie — For the web UI (managed automatically)

If a Bearer token is present but invalid, the request is rejected immediately — it never falls through to session auth.

Quick Add ​

Send raw dictated text and let AI enrichment handle the rest:

bash
curl -X POST https://tasks.example.com/api/tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "call the dentist tomorrow morning high priority"}'

AI extracts: clean title, target, priority, labels, recurrence, project, and notes, plus a deadline or horizon when the text states one. The raw input is preserved in original_title for retry if enrichment fails.

See How Enrichment Works for the full list of extracted fields.

Structured Create (Skip AI) ​

Send structured fields to bypass enrichment:

bash
curl -X POST https://tasks.example.com/api/tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Team standup",
    "target_at": "2026-02-15T15:00:00Z",
    "priority": 2,
    "rrule": "FREQ=WEEKLY;BYDAY=MO,WE,FR",
    "labels": ["work"],
    "create_label": true,
    "project_id": 1
  }'

When AI is enabled, enrichment only runs for title-only tasks (no target_at/due_at, deadline_at, or horizon, priority 0, no labels, no rrule).

Target, deadline, and horizon ​

A task's dates are three separate fields (see Target, Deadline, and Horizon):

FieldMeaning
target_atWhen the task starts nagging. due_at is accepted and returned as an older alias with the same value; sending both with different values is a 400
deadline_atThe real cutoff. A snooze never moves it, and every move of the target stops at it while it's still ahead. A one-off task with a deadline and no target gets an automatic one
horizondays, weeks, months, eventually, or null (unset, treated as weeks)

Task responses also include target_auto (the target is the deadline's automatic target), target_set_at (when the target was last set), occurrence_at (a recurring task's scheduled occurrence, which a snooze doesn't move), horizon_set_at, last_considered_at, segment_deferred_at (for task segments), and original_due_at (the first target). Send "enrich": true to request enrichment even when you include structured fields.

Labels must exist ​

Labels are a registry. Writing a label that doesn't exist yet returns 400 ("Unknown label: …"). Either create it first with POST /api/labels, or add "create_label": true to the task request to create any missing labels as part of the write. GET /api/labels lists existing labels.

Tasks, Reminders, and Quotas ​

Every item is stored as a task. A few fields decide which kind it is:

KindHow to create itBehavior
TaskDefaultOptional target, deadline, and horizon. Can be over target and snoozed
Reminder"is_reminder": trueGrouped by reminder period. Never over target, can't be snoozed
Quota"progress_target" greater than 1 (or "is_tracked": true), usually with a period rule like FREQ=WEEKLYCounts progress per period. No target, never over target

An item can't be both a reminder and a quota. Filter lists by kind with GET /api/tasks?kind=task|reminder|quota. See Examples for requests.

Listing Tasks ​

GET /api/tasks returns open (not done) items, sorted by target:

json
{ "data": { "tasks": [ { "id": 42, "title": "..." } ], "count": 1 } }
Query parameterMeaning
projectProject ID (e.g. ?project=3)
kindtask, reminder, or quota
overduetrue for tasks that are over target right now, including deadline tasks whose automatic target has arrived (never reminders or quotas)
donetrue for completed items instead of open ones
searchText match in title and notes
labelItems with this label
recurring / one_offtrue to list only recurring or only one-off items
archived / trashedtrue to list archived or trashed items
limit / offsetPagination (default limit 200, max 1000)

Response Format ​

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

Error: { "error": "message", "code": "ERROR_CODE" }. Validation errors also include a details object keyed by field.

StatusMeaning
200Success
201Created
400Validation error
401Unauthorized
403Forbidden
404Not found
409Conflict
500Internal error

OpenAPI Specification ​

A machine-readable API spec is available at GET /api/openapi (no auth required). OpenAPI 3.1 YAML covering all public endpoints. Use it to generate client libraries or browse the interactive API Reference.

iOS Shortcuts Tips ​

  1. Use "Get Contents of URL" action with method POST
  2. Add Authorization: Bearer $TOKEN and Content-Type: application/json headers
  3. Set Request Body to JSON with a single title key containing the dictated text
  4. Name the shortcut "Add Task" for "Hey Siri, Add Task"
  5. Parse data.id from the response to confirm creation