MooseDoList Sign in

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-Key to 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 /tasks with view=today|upcoming|ideas|recurring|all, status=open|done|all, labelId, parentId, updatedSince and include=subtasks.
  • GET /tasks/{id} with its subtasks, and POST, PATCH, DELETE.
  • POST /tasks/{id}/complete and /uncomplete (a parent takes its open subtasks with it; the last subtask completes its parent), /snooze and /move.
  • GET /tasks/{id}/history, GET /days/{day} and GET /stats?from&to.
  • GET, POST, PATCH and DELETE /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"
  }
}
  • 400 malformed JSON or query, 401 missing or unknown, revoked or expired key.
  • 403 insufficient_scope (a read-only key tried to write) or key_requires_recreation (see encryption below).
  • 404 no such task or label, 409 conflicts with the current state, 422 a rule says no.
  • 429 too many requests: wait for the Retry-After seconds.

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.
Read next Privacy notice
© 2026 Moose Ltd Optional end-to-end encryption with passkeys. Privacy · Terms · API · Icon: Flaticon