The tokidu API
Read your projects and tasks, create tasks from a form or a script and close them from another app. The API goes through the same door as the web: a token never does anything its account couldn't do.
It's part of the Max plan: anyone with Max can use it, and so can anyone working in a project shared by someone with Max. It only reaches those projects.
- Base URL
https://tokidu.com/api/v1- Header
Authorization: Bearer tkd_…- Limits
- 1000 requests per hour per account · 60 per minute per token
- Full reference
- /api/v1/openapi.json
Getting started
- In Account → Integrations, create a token and tick “See your projects and tasks” (and “Create and change tasks”, if it's going to write).
- Copy it, because it's only shown once, and keep it like a password. In the examples it lives in the
TOKIDU_TOKENvariable. - Try it by asking for your account:
curl https://tokidu.com/api/v1/me \
-H "Authorization: Bearer $TOKIDU_TOKEN"Call it from a server or a script, not from a web page: the API doesn't allow CORS and ignores cookies.
Tokens and scopes
When you create a token you choose what it can do, which projects it reaches and when it expires:
read, “See your projects and tasks”: read projects, tasks and “Up next”.write, “Create and change tasks”: create projects and tasks, change them, finish them, reopen them and delete them, as on the web.- Projects: all you can see or only the ones you tick. If you stop seeing one, the token stops reaching it.
- Expiry: after 30, 90 or 365 days. If you don't choose, after a year.
The Madrugo token (integrations:read) only reads “Up next” for Madrugo, across all the projects you can see: it doesn't open this API and can't be limited to some projects.
You can have 10 active tokens. Revoke them one by one or all at once, and see the latest changes for each one: we keep what changes were made with each token, without the content, for 90 days.
Limits
- 1000 requests per hour for your whole account and 60 per minute for each token.
- Every response carries
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset: the limit, what's left and when it renews. - If you go over, the response is 429
demasiadas_peticioneswithRetry-After: wait that many seconds and try again. - A request body can take up 64 KiB at most.
Creating tasks without duplicates
Creating a task requires the Idempotency-Key header: 1 to 100 characters, different for each task you want to create (for instance, the id of the row, email or form it comes from).
If you repeat the request with the same key, say after a network drop, no new task is created: you get the one that already exists, with 200 and Idempotent-Replayed: true.
If the task for that key was deleted, it doesn't come back: the answer is 410 tarea_borrada. The same key in another project creates another task.
Errors
An error answers with its HTTP status and the error field in the body; with 422, also field, the field that isn't valid.
- 401
token_invalido - The token is missing, doesn't exist, has been revoked or has expired (it doesn't say which).
- 403
ambito - The token lacks the scope the route asks for.
- 403
proyecto_fuera_del_token - The token is limited to other projects.
- 403
max_necesario - It's part of the Max plan and whoever shares the project doesn't have it (or you don't, when creating a project).
- 403
solo_lectura - In that project you can only look, not change.
- 403
en_pausa - The project is paused: whoever shares it no longer has the Team plan, so it can only be viewed.
- 404
proyecto_inexistente - The project doesn't exist or you can't see it.
- 404
tarea_inexistente - The task doesn't exist or you can't see it.
- 404
ruta_inexistente - That route isn't part of the API: check the method and the address.
- 404
webhook_inexistente - That notification doesn't exist or this token didn't create it.
- 409
id_ocupado - That
Idempotency-Keybelongs to a task you can't see: use another one. - 409
horas_de_otros - It can't be deleted because it has other people's hours, as on the web.
- 409
adjuntos_de_otros - It can't be deleted because it has other people's files, as on the web.
- 409
entrada_facturada - It can't be deleted because it has hours that were already billed, as in the web app.
- 409
ciclo - That wait would close a loop: a task would end up waiting for itself.
- 409
webhooks_completo - You already have 20 notifications: delete one to create another.
- 410
tarea_borrada - That
Idempotency-Keybelongs to a task that was deleted: it isn't created again. To create another one, use another key. - 413
cuerpo_grande - The body is over 64 KiB.
- 415
json_necesario - The body has to be JSON, with
Content-Type: application/json. - 422
peticion_invalida - A field isn't valid:
fieldsays which. - 422
url_no_valida - A notification's address isn't valid:
reasonsays why (https,puerto,usuario,local,tokidu,dnsorip_privada). - 428
idempotencia_necesaria - Creating a task needs the
Idempotency-Keyheader. - 429
demasiadas_peticiones - You've gone over the limit: wait as long as
Retry-Aftersays. - 429
demasiados_intentos - You've checked lots of notification addresses in a row: wait for what
Retry-Aftersays. - 503
ocupado - The project was busy with another change: repeat the request after what
Retry-Aftersays. - 503
no_disponible - The service couldn't answer right now: repeat the request after what
Retry-Aftersays.
Reference
Every route hangs off the base URL and answers in JSON. Project and task ids are the ones the API itself returns.
GET /api/v1/me
Your account, the token (name, scopes, projects and expiry) and what's left of the hour.
Scope: any
GET /api/v1/projects
The projects you see from people with Max: name, colour, who shares it, your role, traffic light, expected finish, plan snapshot and progress.
Scope: read
GET /api/v1/projects/{id}
One project, with its sprints.
Scope: read
GET /api/v1/projects/{id}/tasks
A project's tasks, without notes: what they wait for, whether they're blocked and when they're expected to finish. Filter with status (open, done or all) and updatedSince, and page with cursor.
Scope: read
curl "https://tokidu.com/api/v1/projects/$PROJECT_ID/tasks?status=open" \
-H "Authorization: Bearer $TOKIDU_TOKEN"GET /api/v1/tasks/{id}
One task with its notes, what it waits for and “Why that date?”.
Scope: read
GET /api/v1/next
“Up next”: your tasks that can start now.
Scope: read
POST /api/v1/projects
Creates a project: name and, if you like, color. Only if you have Max.
Scope: write
POST /api/v1/projects/{id}/tasks
Creates a task: title and, if you like, notes, priority, due, assignee, sprintId, estimateMin and after. Requires Idempotency-Key.
Scope: write
curl https://tokidu.com/api/v1/projects/$PROJECT_ID/tasks \
-H "Authorization: Bearer $TOKIDU_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: form-42" \
-d '{"title":"Call Ana","due":"2026-10-20"}'PATCH /api/v1/tasks/{id}
Changes only the fields you send. Answers with the task, the ones that unlock and, if someone else changed the same thing at the same time, what was kept.
Scope: write
curl -X PATCH https://tokidu.com/api/v1/tasks/$TASK_ID \
-H "Authorization: Bearer $TOKIDU_TOKEN" \
-H "Content-Type: application/json" \
-d '{"due":"2026-10-24"}'POST /api/v1/tasks/{id}/done
Marks the task as done and answers with the ones that unlock.
Scope: write
curl -X POST https://tokidu.com/api/v1/tasks/$TASK_ID/done \
-H "Authorization: Bearer $TOKIDU_TOKEN"POST /api/v1/tasks/{id}/reopen
Reopens a finished task.
Scope: write
PUT /api/v1/tasks/{id}/after/{otherId}
Makes the task wait for another one in the same project.
Scope: write
DELETE /api/v1/tasks/{id}/after/{otherId}
Removes that wait.
Scope: write
DELETE /api/v1/tasks/{id}
Deletes the task, as on the web.
Scope: write
GET /api/v1/webhooks
The notifications to other apps this token has created: address, events, projects and status (never the secret).
Scope: webhooks
POST /api/v1/webhooks
Registers an https address to receive signed notifications: url, events and, optionally, projects (your own only). Answers with the secret, just once.
Scope: webhooks
DELETE /api/v1/webhooks/{id}
Deletes one of the notifications this token created and stops sending it.
Scope: webhooks
Notifications to other apps (webhooks)
When something happens in your projects, tokidu sends a signed POST to the address you give. Create them in Account → Integrations, or through the API with a token with the webhooks scope (what Zapier or Make use). Only the owner with Max, and only for their own projects; up to 20 per account.
What you can listen to
task.created· A task is createdtask.done· A task is donetask.reopened· A task is reopenedtask.unblocked· A task is unlockedtask.assigned· A task is assignedtask.deleted· A task is deletedproject.status· A project's traffic light changes
Each notification carries the type, an id for the event, the time, the project and, for a task, its title, status, assignee, deadline and link. Never notes, links, files, emails or hours.
Checking the signature
It follows Standard Webhooks: headers webhook-id, webhook-timestamp (seconds) and webhook-signature (v1, and a base64 HMAC-SHA256 of id.timestamp.body, with the key after whsec_). Check it against the body exactly as it arrives and reject anything older than 5 minutes. In Node:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyTokidu(secret, headers, body, toleranceSeconds = 300) {
const id = headers["webhook-id"], ts = headers["webhook-timestamp"], sigs = headers["webhook-signature"];
if (!id || !ts || !sigs) return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSeconds) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = createHmac("sha256", key).update(`${id}.${ts}.${body}`).digest();
return sigs.split(" ").some((s) => {
const [version, sig] = s.split(",");
const given = Buffer.from(sig ?? "", "base64");
return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);
});
}Delivery and retries
Any 2xx is fine; redirects aren't followed. If it fails, it's retried after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h. After 20 failed deliveries in a row, or 3 days without a 2xx, the notification is turned off and we email you.
Only public https:// addresses, on port 443 or 8443, with no user or password: no localhost, internal networks or private IPs (also checked when sending).
OpenAPI
Every field of every request and response, in OpenAPI format, to generate a client or import them into your tool: /api/v1/openapi.json.