The DOEN API
Read your to-dos, add new ones and change them from your own scripts, Shortcuts and other apps. It is off until you turn it on, and you stay in control.
How it works
DOEN keeps your to-dos on your phone, and your phone stays the source of truth. When you turn on API access, DOEN uploads a copy of your open to-dos, and those finished in the last 30 days, to the DOEN server. The API reads that copy.
Reading is quick. Writing is not instant: a change you send waits on the server until your phone picks it up. DOEN does this while it is open, about every 20 seconds, and now and then in the background. If DOEN has been closed for a while, the change waits until you open it.
Every reply includes snapshot_at, the time the phone last uploaded. Use GET /status to see how fresh the data is and whether changes are still waiting.
Get started
- In DOEN, open Settings and choose API access.
- Choose whether the API may change your to-dos, or only read them. You can change this later.
- Tap Turn on API access. Copy your token and the base address.
- Try it from a terminal:
curl "https://doen.software/app/api/v1/tasks?list=today" \
-H "Authorization: Bearer YOUR_TOKEN"
The base address for everything below is https://doen.software/app/api/v1. All requests and replies use JSON.
Authentication
Send your token in the Authorization header as a Bearer token. Treat it like a password. If it leaks, open Settings, API access and choose Get a new token, and the old one stops working at once.
Authorization: Bearer doen_0123456789abcdef0123456789abcdef01234567
Read to-dos
Returns your to-dos. Everything is optional.
| Parameter | What it does |
|---|---|
list | open (default, not completed), today, upcoming, anytime, future, log, or all. Each to-do belongs to one list, matching what DOEN shows. |
completed | true or false. Overrides the default of open to-dos only. |
tag | Only to-dos with this tag. |
project | Only to-dos in this project, by name. |
area | Only to-dos in this area, by name. Includes to-dos in projects inside the area. |
q | Search the title and notes. |
updated_since | An ISO 8601 date and time, or a Unix timestamp. Only to-dos changed since then. |
limit, offset | Paging. The default limit is 100 and the largest is 500. |
{
"ok": true,
"snapshot_at": "2026-10-12T14:03:10Z",
"total": 2,
"limit": 100,
"offset": 0,
"tasks": [
{
"id": "6F0C2B1E-8A47-4D55-9B71-0C5E3A9D2F10",
"title": "Call the plumber",
"notes": "",
"completed": false,
"completed_at": null,
"list": "today",
"when": "today",
"start": null,
"deadline": "2026-10-13",
"reminder": "2026-10-12T21:00:00Z",
"tags": ["home"],
"project": null,
"area": "Home",
"checklist": [],
"created_at": "2026-10-10T09:12:44Z",
"modified_at": "2026-10-12T08:30:01Z"
}
],
"pending_creates": []
}
pending_creates lists to-dos you created through the API that the phone has not collected yet, so they do not seem to go missing.
Returns one to-do as task. If you created it moments ago and the phone has not collected it yet, you get "task": null and a pending entry instead.
Create a to-do
Needs writing switched on. Only title is required.
curl -X POST "https://doen.software/app/api/v1/tasks" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Book flights",
"notes": "Check the Tuesday fare",
"when": "today",
"deadline": "2026-10-20",
"reminder": "2026-10-19T14:00:00Z",
"tags": ["travel"],
"checklist": ["Pick dates", {"title": "Compare fares", "done": false}]
}'
{
"ok": true,
"id": "B7C1D5E2-3F4A-4B68-8C90-1A2B3C4D5E6F",
"status": "queued",
"message": "Queued. The to-do appears in DOEN the next time the phone syncs, ..."
}
The reply is 202 Accepted. The id is the id the to-do will have in DOEN, so you can use it straight away to change or delete it.
whenistoday,evening,anytimeorfuture. If you leave it out, a to-do goes to Today, or to Anytime when it has a project or area.startanddeadlineare dates like2026-10-20. A start date in the future puts the to-do in Upcoming.reminderis a date and time with a time zone, for example2026-10-19T14:00:00-05:00. DOEN stores it as UTC and uses your normal alert settings.projectandareaare names. If DOEN has no match, the to-do is still created, without it.- Tags that do not exist yet are created. At most 5 tags and 50 checklist items.
- To-dos created through the API do not get the Untriaged tag unless you send
"untriaged": true.
Change a to-do
Needs writing switched on. Send only the fields you want to change. POST to the same address works too, for tools that cannot send PATCH.
| Field | Notes |
|---|---|
title, notes | Text. The title cannot be empty. |
completed | true moves the to-do to Log. false brings it back. Repeating to-dos create their next copy, just as when you tick them in the app. |
when | today, evening, anytime or future. Moving to Today or This Evening clears a future start date. |
start, deadline | A date, or null to clear it. |
reminder | A date and time with a time zone, or null to clear it. |
tags | The full list of tags. It replaces the current tags. |
project, area | A name, or null to remove. The name must already exist in DOEN, otherwise the change is refused. Setting a project removes the area, and the other way round. |
checklist | The full checklist. It replaces the current one. |
curl -X PATCH "https://doen.software/app/api/v1/tasks/6F0C2B1E-8A47-4D55-9B71-0C5E3A9D2F10" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"completed": true}'
{
"ok": true,
"command": { "id": "9C9CBA24-7D62-4BD6-BBE1-5FA668854388", "op": "update", "status": "queued" },
"message": "Queued. The change is applied when the phone next syncs."
}
The reply is 202 Accepted. To find out whether the phone applied the change, ask GET /commands/{command id} (see Status). If the phone could not apply it, for example because the project name does not exist, the status is failed with a reason.
Delete a to-do
Needs writing switched on. The to-do is deleted from DOEN when the phone next syncs. This cannot be undone, so check the id first.
Areas, projects and tags
{
"ok": true,
"snapshot_at": "2026-10-12T14:03:10Z",
"areas": [ { "id": "...", "title": "Home" } ],
"projects": [ { "id": "...", "title": "Kitchen", "area": "Home" } ],
"tags": ["errands", "home", "travel"]
}
Status and change results
{
"ok": true,
"snapshot_at": "2026-10-12T14:03:10Z",
"snapshot_age_seconds": 42,
"task_count": 187,
"allow_write": true,
"queued_changes": 0,
"queued_creates": 0
}
Shows the outcome of a change you sent. status is queued, applied, failed or expired (the phone did not pick it up within 24 hours). Results are kept for 24 hours.
Backup and restore
These two endpoints read and write your full DOEN data (areas, projects, headings, tags, to-dos and checklists), not the to-do snapshot above. They need DOEN on the web switched on in the app, because they use the web copy. Restore also needs writing to be on.
Returns everything as JSON, in the same format as the Download backup button on the web. Add ?download=1 to get a file named doen-backup-ddmmyy.json. Limit: 30 requests per hour.
Send a backup file as the body. By default (mode=missing) it adds anything that is missing, brings back deleted items and leaves existing items alone. mode=overwrite replaces items that exist with the version in the file. Add dry_run=1 to see the counts without changing anything. Limit: 10 requests per hour, 8 MB per file.
{
"ok": true,
"dry_run": false,
"mode": "missing",
"added": 12,
"brought_back": 3,
"replaced": 0,
"kept": 215,
"skipped": 0,
"total": 230
}
A body that is not a DOEN backup gets 400. 403 means writing is off, and 409 means DOEN on the web is off.
Automatic daily backup
# crontab -e (every day at 02:30)
30 2 * * * curl -sf -H "Authorization: Bearer YOUR_TOKEN" \
"https://doen.software/app/api/v1/backup" \
-o "$HOME/doen-backups/doen-backup-$(date +\%d\%m\%y).json"
# restore, preview first
curl -s -X POST -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" \
--data-binary @doen-backup-151026.json \
"https://doen.software/app/api/v1/restore?dry_run=1"
MCP: connect Claude or another AI
DOEN is also a Model Context Protocol (MCP) server, so an AI assistant can read and add your to-dos. It uses the same token as the rest of the API and works on the copy kept for DOEN on the web, so DOEN on the web must be switched on.
- In DOEN, turn on DOEN on the web, then Settings, API access. Choose whether the assistant may change your to-dos or only read them.
- Copy your token.
- In your assistant, add a custom connector or MCP server with the address above. Send the token as
Authorization: Bearer YOUR_TOKEN.
Try it from a terminal:
curl -X POST "https://doen.software/app/api/mcp.php" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
| Tool | What it does | Needs writing |
|---|---|---|
list_todos | List or search to-dos by list (inbox, today, upcoming, anytime, logbook, open, all), project or area. | No |
get_todo | One to-do with its notes and checklist. | No |
list_projects | Projects and areas. | No |
list_notes | Note titles, or one note by id. | No |
list_habits | Habits, their days and the last seven days. | No |
add_todo | Add a to-do, with notes, a start date, deadline, reminder, project, area and checklist. | Yes |
update_todo | Change a title, notes, schedule, deadline or reminder. | Yes |
complete_todo | Tick off a to-do, or reopen it. | Yes |
delete_todo | Delete a to-do. | Yes |
check_habit | Mark a habit done for a day, or undo it. | Yes |
Pass utc_offset_minutes (for example -300) so "today" matches your time zone. Changes made through MCP reach your phone, Mac and browser the next time they sync. A tool that needs writing replies with an error if writing is off.
Anyone with your token can use these tools, so treat it like a password. Get a new token in DOEN to cut off an assistant at once.
Assistants that only run local servers
Some desktop apps can start a local program but cannot connect to a web address. For those, use mcp-proxy. It runs on your computer and passes everything on to DOEN. Install it with uv tool install mcp-proxy, then add DOEN to your assistant's MCP settings:
{
"mcpServers": {
"doen": {
"command": "mcp-proxy",
"args": [
"--transport", "streamablehttp",
"--headers", "Authorization", "Bearer YOUR_TOKEN",
"https://doen.software/app/api/mcp.php"
]
}
}
}
If the app cannot find mcp-proxy, give the full path as the command, for example /Users/you/.local/bin/mcp-proxy. Restart the assistant or its connector after saving.
If it does not connect
- 401: the token is missing, wrong, or was replaced. Copy the current token from DOEN, update it in your assistant and restart the connector.
- Writing tools fail: writing is switched off. Turn it on in Settings, API access.
- No tools appear: run the
tools/listcommand above. If it returns your tools, the problem is in the assistant's settings. If it returns an error, the message says what is wrong. - "Invalid result for tools/list": older versions of the DOEN server sent an empty parameter list in a form that strict assistants reject, so they showed no tools at all. This is fixed on the server. Restart your connector and it will load.
To-do fields
| Field | Type | Meaning |
|---|---|---|
id | string | A UUID. Stable for the life of the to-do. |
title, notes | string | The text of the to-do. |
completed | boolean | Whether it is ticked off. |
completed_at | string or null | When it was ticked off (UTC). |
list | string | Where it appears: today, upcoming, anytime, future or log. |
when | string | The schedule you chose: today, evening, anytime or future. |
start, deadline | date or null | Dates like 2026-10-20, in your local calendar. |
reminder | string or null | Date and time in UTC. |
tags | list of strings | Tag names. |
project, area | string or null | Names. A to-do in a project reports the project's area. |
checklist | list | Items like {"title": "Pick dates", "done": false}. |
created_at, modified_at | string | Times in UTC. |
Not included: to-dos made from calendar events, repeat rules and place reminders. They stay in DOEN.
Errors and limits
Errors look like {"ok": false, "message": "..."} with a matching HTTP status.
| Status | Meaning |
|---|---|
| 400 | Something in the request is not valid. The message says what. |
| 401 | The token is missing, wrong, or was replaced or switched off. |
| 403 | Writing is switched off in DOEN. |
| 404 | No to-do with that id. |
| 413 | The request is too large (64 KB at most). |
| 429 | Too many requests, or too many changes waiting for the phone. |
- Reads: up to 600 requests per hour.
- Writes: up to 120 requests per hour, and up to 200 changes waiting at once.
- The copy holds up to 3,000 to-dos: all open ones first, then recently finished ones.
Examples
Python
import requests
BASE = "https://doen.software/app/api/v1"
HEADERS = {"Authorization": "Bearer YOUR_TOKEN"}
today = requests.get(f"{BASE}/tasks", params={"list": "today"}, headers=HEADERS).json()
for task in today["tasks"]:
print(task["title"])
requests.post(f"{BASE}/tasks", headers=HEADERS,
json={"title": "Water the plants", "when": "evening"})
JavaScript
const BASE = "https://doen.software/app/api/v1";
const headers = { Authorization: "Bearer YOUR_TOKEN", "Content-Type": "application/json" };
const res = await fetch(`${BASE}/tasks?tag=errands`, { headers });
const { tasks } = await res.json();
await fetch(`${BASE}/tasks/${tasks[0].id}`, {
method: "PATCH", headers, body: JSON.stringify({ completed: true })
});
Shortcuts, Zapier and n8n
Use a "Get contents of URL" (Shortcuts) or HTTP request step. Set the method, add the header Authorization with the value Bearer YOUR_TOKEN, and send a JSON body for creating and changing. If you only need to add to-dos, the simpler email and webhook options in DOEN work too.
Privacy and security
- API access is off until you turn it on in DOEN.
- While it is on, a copy of your open to-dos, and those finished in the last 30 days, including titles, notes, dates, tags and checklists, is stored on the DOEN server so the API can read it. It is not shared with anyone else and is not used for anything else.
- Turning API access off in DOEN deletes the copy and any waiting changes straight away. Deleting your email and webhook setup does the same.
- Your token is stored on the server only as a one-way hash, so it cannot be recovered. DOEN keeps the token in your iPhone's Keychain so you can copy it again.
- Backup, restore and MCP use the full copy kept for DOEN on the web, which is deleted when you turn that off.
- Anyone with your token can read your to-dos, and change them if writing is on. Switch writing off if you only need to read, and replace the token if you think it has leaked.