Developers
API
Last updated: 6 October 2026
Read and change your own MooseDoList from scripts and AI agents: today's list, tasks and subtasks, labels, history and stats. It's a plain REST API with JSON in and out, made for your own account.
The short version
- Make a key in Profile > API keys and send it as
Authorization: Bearer mdl_…. - Everything lives under
https://moosedolist.com/api/v1, described in OpenAPI at openapi.json. - Keys are read only, or read and write. Revoke one anytime and it stops working at once.
- Up to 120 requests a minute per key. Use an
Idempotency-Keyto retry POSTs safely.
Quickstart
Open Profile in the app, choose New API key, give it a name (say, the agent that will use it), pick what it may do and copy the key. It's shown once. Then:
export MOOSEDOLIST_KEY="mdl_..."
# What's on today
curl -s "https://moosedolist.com/api/v1/tasks?view=today" \
-H "Authorization: Bearer $MOOSEDOLIST_KEY"
# Add a task, due Friday, high priority
curl -s -X POST "https://moosedolist.com/api/v1/tasks" \
-H "Authorization: Bearer $MOOSEDOLIST_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"title": "Book the dentist", "dueDate": "2026-10-09", "priority": "high"}'
# Tick it off
curl -s -X POST "https://moosedolist.com/api/v1/tasks/TASK_ID/complete" \
-H "Authorization: Bearer $MOOSEDOLIST_KEY" Authentication
Every request needs Authorization: Bearer mdl_<id>_<secret>. Keys work only on /api/v1, and the app's sign-in cookie doesn't work there, so a key is the only way in. We keep a
one-way hash of each key, never the key itself, so treat it like a password: keep it in an environment variable or
a secrets manager, not in code. A key can expire after 30 or 90 days, or never; revoke it in Profile when you stop
using it.
A read only key can call every GET. A read and write key can also create, change,
complete and delete. Making and revoking keys needs you signed in to the app; a key can't do it.
What you can do
GET /me: your email, timezone, today's date for you, whether passkey protection is on, and the key.GET /taskswithview=today|upcoming|ideas|recurring|all,status=open|done|all,labelId,parentId,updatedSinceandinclude=subtasks.GET /tasks/{id}with its subtasks, andPOST,PATCH,DELETE.POST /tasks/{id}/completeand/uncomplete(a parent takes its open subtasks with it; the last subtask completes its parent),/snoozeand/move.GET /tasks/{id}/history,GET /days/{day}andGET /stats?from&to.GET,POST,PATCHandDELETE /labels.
Days are YYYY-MM-DD, times HH:MM and timestamps ISO 8601. "Today" is today in the
timezone saved in your Profile (UTC if you haven't picked one); add ?day=YYYY-MM-DD to any request to
choose the day yourself. Changes made with a key show in the task's history in the app as via API key “name”.
Errors
Every error has the same shape and a status code that means what it says:
{
"error": {
"code": "validation_failed",
"message": "title must be 1-200 characters of text"
}
} 400malformed JSON or query,401missing or unknown, revoked or expired key.403insufficient_scope(a read-only key tried to write) orkey_requires_recreation(see encryption below).404no such task or label,409conflicts with the current state,422a rule says no.429too many requests: wait for theRetry-Afterseconds.
Pagination
Lists return { "data": [...], "nextCursor": "..." }. Pass limit (up to 100,
50 by default) and, for the next page, cursor set to the last nextCursor. It's null on the last page.
curl -s "https://moosedolist.com/api/v1/tasks?status=all&limit=50&cursor=NEXT_CURSOR" \
-H "Authorization: Bearer $MOOSEDOLIST_KEY" Idempotency
Send Idempotency-Key: <a new UUID> with a POST. If the connection drops and you send the same
request with the same key within 24 hours, you get the first response back (marked Idempotent-Replayed: true) instead of a second task. The same key with a different body is a 422.
Rate limits
Each key can make 120 requests a minute. Responses say so in RateLimit-Limit and RateLimit-Policy; over the limit you get 429 with Retry-After. There's no
CORS: call the API from a server, script or agent, not from a web page.
Encryption and API keys
With passkey protection off, the API reads and writes your tasks as the app does. With it on, your task names are encrypted with a key only your devices hold, so for the API to read them your browser locks a copy of that key with the new API key when you make it. Each request you send unlocks it on our server, in memory, just while the request is handled: names are decrypted for the response and anything you send is encrypted before it's stored. Neither the key nor any task name is ever stored in readable form, so our database alone still can't read your list.
Turning passkey protection on makes a new encryption key, so keys made before can't read it: they answer 403 key_requires_recreation until you revoke them and make new ones. Turning it off leaves your keys
working. More in the Privacy notice.
Using it with an AI agent
Agents that read OpenAPI (custom GPT actions, MCP OpenAPI bridges, coding agents with a shell) can learn the whole API from https://moosedolist.com/api/v1/openapi.json. Give the agent a key in an environment variable rather than in the chat, start it on a read-only key, and use a read and write key once you trust what it does. A prompt to start from:
You can manage my to-do list in MooseDoList through its REST API.
The OpenAPI description is at https://moosedolist.com/api/v1/openapi.json.
Authenticate every request with the header
"Authorization: Bearer <key from the MOOSEDOLIST_KEY environment variable>".
Start with GET /me to learn today's date, then GET /tasks?view=today.
Send an Idempotency-Key header (a new UUID) with every POST.
Ask me before deleting anything.