API

Todoly's API is the API the app itself uses, so everything you can do in the app you can do from a script or an agent. The full description is the OpenAPI document at app.todoly.io/api/v1/openapi.json; this page is the part that is easier read than generated.

Authentication

Create a personal token under Settings → Profile → API tokens. It is shown once. Send it on every request:

Authorization: Bearer tdl_…

A token acts as you in every workspace you belong to, exactly like your sign-in. There are no scopes: revoke a token from the same page when it should stop. You can hold ten at a time.

Base URL and shape

Every path below is under https://app.todoly.io/api. Requests and responses are JSON; dates are ISO 8601, due dates are YYYY-MM-DD, ids are UUIDs, and every record carries created_at and updated_at. Field names are snake_case.

Errors

Every error has one shape and an HTTP status that matches:

{ "error": { "code": "task_limit", "message": "100 of 100 tasks on Free. Deleting tasks frees room." } }

The message is written for a person and can be shown as is. Codes worth handling: unauthenticated (401), not_found (404, also for workspaces you are not in), validation (400), read_only (403, the workspace is over its plan), task_limit, project_limit, seat_limit, trial_required and storage_limit (400, a Free cap), email_unverified (403, inviting needs a confirmed address), bad_cursor (400).

Rate limits

600 requests a minute per token, counted separately from your browser. Every response carries x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset; past the limit you get 429 with retry-after.

Listing tasks

For a whole workspace, use GET /v1/workspaces/{id}/tasks rather than one call per project. Filters: status (a comma list of backlog, todo, in_progress, in_review, done, cancelled), assignee_id (a member id), label_id, project_id, updated_since. Pages: limit up to 500 and the next_cursor from the previous page passed back as cursor; null means the last page. Results are ordered by updated_at, so polling is updated_since set to the time of your last call.

Live updates

GET /v1/workspaces/{id}/events is a server-sent event stream of every change in the workspace: task.updated, task.deleted, project.updated, comment.created and so on, each with the record. Apply them newest updated_at wins. Keep at most a few streams open per token.

Endpoints

The OpenAPI document lists every public route with its request and response schemas. In short:

Not part of the API and free to change: sign-in, configuration, billing, email settings and the email-change flow.

Examples

curl https://app.todoly.io/api/v1/me \
  -H "Authorization: Bearer $TODOLY_TOKEN"
curl "https://app.todoly.io/api/v1/workspaces/$WS/tasks?status=todo,in_progress&limit=200" \
  -H "Authorization: Bearer $TODOLY_TOKEN"
curl https://app.todoly.io/api/v1/tasks \
  -H "Authorization: Bearer $TODOLY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "project_id": "'$PROJECT'", "title": "Renew the domain", "due_date": "2026-11-01", "priority": "high" }'

Versions

Additive changes (new fields, new endpoints) land in v1 without notice; code that ignores unknown fields keeps working. A breaking change would be a /v2, with v1 kept for six months after. Changes are noted on this page.

Help

hello@todoly.io.