NewTwos API

v1.0.0

A simple REST API to read and write your NewTwos lists and things. Everything is JSON, over HTTPS, authenticated with a personal API key.

Base URLhttps://writethingsdown.com/api/v1

Quickstart

  1. Create an API key in Settings → API Keys and copy it (it’s shown once).
  2. Send it on every request as an Authorization: Bearer twos_ header.
  3. Call an endpoint — for example, list your lists:
curl "https://writethingsdown.com/api/v1/lists?page=0" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Authentication

The API uses bearer authentication with personal API keys. A key looks like twos_a1b2c3_… and carries the scopes you chose when creating it. Keys are tied to your account, so every request only ever sees your own data. Create, view, and revoke keys in Settings → API Keys.

Authorization: Bearer twos_a1b2c3d4_0f9e8d7c6b5a4938271605f4e3d2c1b0

Keep keys secret. Never embed them in client-side code or commit them to source control. If a key leaks, revoke it — revocation takes effect immediately.

Rate limits

Each key may make up to 1,000 requests per hour. Every response includes the current limit state in its headers. When you exceed the limit you’ll get a 429 until the window resets.

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 994
X-RateLimit-Reset: 1780000000

Scopes

Each key is granted a set of scopes. An endpoint returns 403 if your key is missing the scope it requires.

read:listsRead your lists.
write:listsCreate, update, and delete lists.
read:thingsRead the things inside your lists, and your tags.
write:thingsCreate, update, and delete things.
searchSearch across your lists and things.

Endpoints

Everything is a list or a thing. A thing is a todo or a note; give it a url to make it a hyperlink, and tags to categorize it.

List lists

read:lists
GET/api/v1/lists

Returns your lists (non-hidden, non-archived), most recently modified first, 50 per page.

Parameters

pageintegerZero-based page index.

Request

curl "https://writethingsdown.com/api/v1/lists?page=0" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "lists": [
    {
      "id": "64f0c2a1e4b0a1234567890a",
      "title": "Groceries",
      "emoji": "🛒",
      "today": false,
      "favorited": false,
      "archived": false,
      "sort": "chronological",
      "created": "2026-06-01T12:00:00.000Z",
      "updated": "2026-06-02T08:30:00.000Z"
    }
  ],
  "page": 0,
  "page_size": 50,
  "has_more": false
}

Create a list

write:lists
POST/api/v1/lists

Creates a new list. Fields you omit fall back to your account defaults (e.g. list sort). Pass `things` to create the list already filled, in one call (returns `created`/`skipped` counts alongside the list).

Request body

titlestringrequiredThe list name.
emojistringAn emoji shown next to the list.
thingsobject[]Optional initial things (max 500), in order. Each item: { text, type?, url?, list_ref?, tags?, photos?, completed?, tabs?, created?, and formatting: bold?, italic?, underline?, header?, subheader?, quote?, code? }.

Request

curl -X POST "https://writethingsdown.com/api/v1/lists" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Groceries","emoji":"🛒","things":[{"text":"Oat milk","type":"todo"}]}'

Response · 201

{
  "list": {
    "id": "64f0c2a1e4b0a1234567890a",
    "title": "Groceries",
    "emoji": "🛒",
    "today": false,
    "favorited": false,
    "archived": false,
    "sort": "chronological",
    "created": "2026-06-01T12:00:00.000Z",
    "updated": "2026-06-02T08:30:00.000Z"
  },
  "created": 1,
  "skipped": 0
}

Import a list from markdown

write:lists
POST/api/v1/lists/import

Create a NEW list from a markdown document in one call. The title comes from `title`, or the document’s first heading (`# …`) when omitted; the rest becomes the list’s things (checkboxes → todos, `1.` → numbered, `-`/`*` → bullets, `##` → heading, `###` → subheading, indentation → nesting).

Request body

markdownstringrequiredThe markdown document to import.
titlestringThe new list’s name. Omit to use the markdown’s first "# Heading".
emojistringAn emoji for the new list.

Request

curl -X POST "https://writethingsdown.com/api/v1/lists/import" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Trip plan","markdown":"# Trip plan\n- [ ] Book flights\n- [ ] Pack"}'

Response · 201

{
  "list": {
    "id": "64f0c2a1e4b0a1234567890a",
    "title": "Groceries",
    "emoji": "🛒",
    "today": false,
    "favorited": false,
    "archived": false,
    "sort": "chronological",
    "created": "2026-06-01T12:00:00.000Z",
    "updated": "2026-06-02T08:30:00.000Z"
  },
  "created": 2,
  "skipped": 0
}

Get a list

read:lists
GET/api/v1/lists/{id}

Returns a single list you own, its things (in the app’s display order, per the list’s `sort`), and any reminders on those things.

Parameters

idstring · pathrequiredThe list id.
max_textstringTruncate each thing’s text to this many characters (… marks a cut). For cheap reads of long lists.

Request

curl "https://writethingsdown.com/api/v1/lists/64f0c2a1e4b0a1234567890a?max_text=120" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "list": {
    "id": "64f0c2a1e4b0a1234567890a",
    "title": "Groceries",
    "emoji": "🛒",
    "today": false,
    "favorited": false,
    "archived": false,
    "sort": "chronological",
    "created": "2026-06-01T12:00:00.000Z",
    "updated": "2026-06-02T08:30:00.000Z"
  },
  "things": [
    {
      "id": "64f0c2a1e4b0a1234567890b",
      "list_id": "64f0c2a1e4b0a1234567890a",
      "list_ref": null,
      "text": "Buy oat milk",
      "type": "todo",
      "url": "",
      "tags": [
        "errands"
      ],
      "tabs": 0,
      "photos": [],
      "completed": false,
      "canceled": false,
      "favorited": false,
      "clone_of": null,
      "has_clones": false,
      "bold": false,
      "italic": false,
      "underline": false,
      "header": false,
      "subheader": false,
      "quote": false,
      "code": false,
      "note": "Prefer the **barista** blend — the oat one at the corner shop.",
      "created": "2026-06-02T08:30:00.000Z",
      "updated": "2026-06-02T08:30:00.000Z"
    }
  ],
  "reminders": [
    {
      "id": "64f0c2a1e4b0a1234567890c",
      "thing_id": "64f0c2a1e4b0a1234567890b",
      "list_id": "64f0c2a1e4b0a1234567890a",
      "text": "Buy oat milk",
      "at": "2026-06-03T17:00:00.000Z",
      "all_day": false,
      "repeat": "none",
      "every": 1,
      "repeat_until": null,
      "days": [],
      "end": "2026-06-03T18:00:00.000Z",
      "duration_minutes": 60,
      "alert_minutes": 10,
      "second_alert_minutes": null,
      "color": "#FF9500"
    }
  ]
}

Get a day list

read:lists
GET/api/v1/today

Returns the user’s date-titled day list for a date — resolved in the user’s timezone — creating it if it doesn’t exist yet (like opening that day in the app). Same response shape as Get a list. Use it for the daily-list loop without hunting for an id.

Parameters

datestring"today" (default), "yesterday", "tomorrow", or an ISO 8601 date (e.g. 2026-04-01).
max_textstringTruncate each thing’s text to this many characters.

Request

curl "https://writethingsdown.com/api/v1/today?date=today&max_text=120" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "list": {
    "id": "64f0c2a1e4b0a1234567890a",
    "title": "Tues Apr 1, 2026",
    "emoji": "🛒",
    "today": true,
    "favorited": false,
    "archived": false,
    "sort": "chronological",
    "created": "2026-06-01T12:00:00.000Z",
    "updated": "2026-06-02T08:30:00.000Z"
  },
  "things": [
    {
      "id": "64f0c2a1e4b0a1234567890b",
      "list_id": "64f0c2a1e4b0a1234567890a",
      "list_ref": null,
      "text": "Buy oat milk",
      "type": "todo",
      "url": "",
      "tags": [
        "errands"
      ],
      "tabs": 0,
      "photos": [],
      "completed": false,
      "canceled": false,
      "favorited": false,
      "clone_of": null,
      "has_clones": false,
      "bold": false,
      "italic": false,
      "underline": false,
      "header": false,
      "subheader": false,
      "quote": false,
      "code": false,
      "note": "Prefer the **barista** blend — the oat one at the corner shop.",
      "created": "2026-06-02T08:30:00.000Z",
      "updated": "2026-06-02T08:30:00.000Z"
    }
  ],
  "reminders": [
    {
      "id": "64f0c2a1e4b0a1234567890c",
      "thing_id": "64f0c2a1e4b0a1234567890b",
      "list_id": "64f0c2a1e4b0a1234567890a",
      "text": "Buy oat milk",
      "at": "2026-06-03T17:00:00.000Z",
      "all_day": false,
      "repeat": "none",
      "every": 1,
      "repeat_until": null,
      "days": [],
      "end": "2026-06-03T18:00:00.000Z",
      "duration_minutes": 60,
      "alert_minutes": 10,
      "second_alert_minutes": null,
      "color": "#FF9500"
    }
  ]
}

Update a list

write:lists
PATCH/api/v1/lists/{id}

Updates a list. Only the fields you send are changed. Renaming cascades to the list’s things.

Path & query

idstring · pathrequiredThe list id.

Request body

titlestringRename the list.
emojistringChange the emoji.
favoritedbooleanStar or unstar the list.
archivedbooleanArchive or unarchive the list.

Request

curl -X PATCH "https://writethingsdown.com/api/v1/lists/64f0c2a1e4b0a1234567890a" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Weekend groceries","emoji":"🥑"}'

Response · 200

{
  "list": {
    "id": "64f0c2a1e4b0a1234567890a",
    "title": "Weekend groceries",
    "emoji": "🥑",
    "today": false,
    "favorited": false,
    "archived": false,
    "sort": "chronological",
    "created": "2026-06-01T12:00:00.000Z",
    "updated": "2026-06-02T08:30:00.000Z"
  }
}

Delete a list

write:lists
DELETE/api/v1/lists/{id}

Permanently deletes a list and all of its things. Idempotent.

Path & query

idstring · pathrequiredThe list id.

Request

curl -X DELETE "https://writethingsdown.com/api/v1/lists/64f0c2a1e4b0a1234567890a" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "success": true
}

List things

read:things
GET/api/v1/things

List your things newest-first across all lists (or one list). Built for automation polling: each thing has a stable `id` for dedup, and `since` + `sort` form an incremental cursor. Filter by list, completion, type, or tag.

Parameters

list_idstringOnly things in this list.
completedbooleanFilter by completion (true/false).
typestringFilter by type: todo, note, dash, number, or bullet.
tagstringOnly things with this tag.
sincestringOnly things newer than this ISO 8601 time (by the chosen sort).
sortstringcreated (default) or updated.
pagestringZero-based page (50 per page). Page until has_more is false to read everything.
max_textstringTruncate each thing’s text to this many characters (… marks a cut).

Request

curl "https://writethingsdown.com/api/v1/things?list_id=64f0c2a1e4b0a1234567890a&completed=false&type=todo&tag=errands&since=2026-06-01T00%3A00%3A00.000Z&sort=created&page=0&max_text=120" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "things": [
    {
      "id": "64f0c2a1e4b0a1234567890b",
      "list_id": "64f0c2a1e4b0a1234567890a",
      "list_ref": null,
      "text": "Buy oat milk",
      "type": "todo",
      "url": "",
      "tags": [
        "errands"
      ],
      "tabs": 0,
      "photos": [],
      "completed": false,
      "canceled": false,
      "favorited": false,
      "clone_of": null,
      "has_clones": false,
      "bold": false,
      "italic": false,
      "underline": false,
      "header": false,
      "subheader": false,
      "quote": false,
      "code": false,
      "note": "Prefer the **barista** blend — the oat one at the corner shop.",
      "created": "2026-06-02T08:30:00.000Z",
      "updated": "2026-06-02T08:30:00.000Z"
    }
  ],
  "page": 0,
  "page_size": 50,
  "has_more": false
}

Create a thing

write:things
POST/api/v1/things

Creates a thing inside one of your lists. Target the list with `list_id` (from list_lists/search) or `list` (a list name, or "today"/"tomorrow"/an ISO date). Pick a `type` — `todo` (checkbox), `note`/`none` (plain), `dash`, `number`, or `bullet` — or omit it to use your default thing type. Add a `url` to make it a hyperlink, `tags` to categorize it, `list_ref` to link another list, or `created` to backdate it. To create many at once, send an `items` array to Bulk-create things instead.

Request body

list_idstringThe id of the list to add the thing to. Omit if you pass `list`.
liststringAlternative to list_id: a list name, or "today"/"yesterday"/"tomorrow"/an ISO date (the day list, created if needed).
textstringrequiredThe thing’s text, in markdown (**bold**, *italic*, ~~strike~~, `code`, [label](url)) — the same dialect reads return, so text round-trips. An unpaired marker stays literal, and a code:true thing is never parsed.
type"todo" | "note" | "dash" | "number" | "bullet" | "none" | "photo"Thing type: `todo` (checkbox), `note`/`none` (plain, no marker), `dash`, `number` (numbered), or `bullet`. Omit to use your default thing type.
urlstringA hyperlink to attach to the thing.
list_refstringThe id of another list to reference (a navigable list link). Pass a list id, not a URL.
tagsstring[]Tag names (with or without a leading #).
photosstring[]Hosted image URLs to attach (must be http(s); max 10). The API does not host images — upload them elsewhere and pass the URLs.
completedbooleanWhether the thing starts completed.
createdstringISO 8601 creation time. Use it to preserve original capture-date order when importing; omit to stamp now.
tabsintegerIndent level: 0 = top level (default); 1+ nests this thing under the preceding one. Create the parent first, then the indented children.
boldbooleanBold the whole thing.
italicbooleanItalicize the whole thing.
underlinebooleanUnderline the whole thing.
headerbooleanMake the thing a heading (H1). Mutually exclusive with `subheader`; also bolds it unless you pass `bold` explicitly.
subheaderbooleanMake the thing a subheading (H2). Mutually exclusive with `header`; also bolds it unless you pass `bold` explicitly.
quotebooleanStyle the thing as a blockquote. Mutually exclusive with `code`.
codebooleanStyle the thing as a code block. Mutually exclusive with `quote`.
notestringA long-form note attached to the thing, in **markdown**. Shown behind a note icon in the app, never inline with `text`. Pass `""` to remove it. Reads return the same markdown, so a note round-trips unchanged.

Request

curl -X POST "https://writethingsdown.com/api/v1/things" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"list_id":"64f0c2a1e4b0a1234567890a","text":"Buy oat milk","type":"todo","tags":["errands"]}'

Response · 201

{
  "thing": {
    "id": "64f0c2a1e4b0a1234567890b",
    "list_id": "64f0c2a1e4b0a1234567890a",
    "list_ref": null,
    "text": "Buy oat milk",
    "type": "todo",
    "url": "",
    "tags": [
      "errands"
    ],
    "tabs": 0,
    "photos": [],
    "completed": false,
    "canceled": false,
    "favorited": false,
    "clone_of": null,
    "has_clones": false,
    "bold": false,
    "italic": false,
    "underline": false,
    "header": false,
    "subheader": false,
    "quote": false,
    "code": false,
    "note": "Prefer the **barista** blend — the oat one at the corner shop.",
    "created": "2026-06-02T08:30:00.000Z",
    "updated": "2026-06-02T08:30:00.000Z"
  }
}

Bulk-create things

write:things
POST/api/v1/things/bulk

Create many things in one call (one ordered insert) — use this instead of looping for imports or building a list. Send an `items` array; each item takes the same fields as Create a thing (`text`, optional `type`, `url`, `list_ref`, `tags`, `photos`, `completed`, `tabs`, `created`). Set a top-level `list_id` as the default target, or give each item its own. `skip_duplicates: true` skips items already present (by url, else text), making re-runs safe. Returns per-list counts, not the items. Max 500 per call.

Request body

itemsobject[]requiredThe things to create (max 500). Each item: { text, list_id?, list?, type?, url?, list_ref?, tags?, photos?, completed?, tabs?, created?, and formatting: bold?, italic?, underline?, header?, subheader?, quote?, code? }.
list_idstringDefault list for items that don’t set their own list_id.
liststringAlternative default target: a list name, or "today"/"tomorrow"/an ISO date.
skip_duplicatesbooleanSkip items already in the target list (by url, else text).

Request

curl -X POST "https://writethingsdown.com/api/v1/things/bulk" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"list_id":"64f0c2a1e4b0a1234567890a","items":[{"text":"Oat milk","type":"todo"},{"text":"Sourdough","type":"todo"}]}'

Response · 201

{
  "created": 2,
  "skipped": 0,
  "by_list": [
    {
      "list_id": "64f0c2a1e4b0a1234567890a",
      "created": 2,
      "skipped": 0
    }
  ]
}

Bulk-update things

write:things
PATCH/api/v1/things/bulk

Apply one change set to many things at once — check them all off, move them to a list, star them. Send `ids` (the thing ids) and `set` (fields to apply to each, same shape as Update a thing). A move target (`list_id` or `list` name) is resolved once. Ids you no longer own are skipped, not errors. Max 500 ids per call.

Request body

idsstring[]requiredIds of the things to change (max 500).
setobjectrequiredFields to apply to every listed thing: { completed?, canceled?, favorited?, type?, tags?, tabs?, url?, list_ref?, list_id?, list?, and formatting: bold?, italic?, underline?, header?, subheader?, quote?, code? }.

Request

curl -X PATCH "https://writethingsdown.com/api/v1/things/bulk" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids":["64f0c2a1e4b0a1234567890b"],"set":{"completed":true}}'

Response · 200

{
  "updated": 1,
  "skipped": 0
}

Bulk-delete things

write:things
DELETE/api/v1/things/bulk

Permanently delete many things at once by id. This cannot be undone. Returns how many were deleted (unknown ids are ignored). Max 500 ids per call.

Request body

idsstring[]requiredIds of the things to delete (max 500).

Request

curl -X DELETE "https://writethingsdown.com/api/v1/things/bulk" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ids":["64f0c2a1e4b0a1234567890b"]}'

Response · 200

{
  "deleted": 1
}

Append markdown to a list

write:things
POST/api/v1/things/markdown

Parse a markdown block into things and append them to a list in one call. Checkboxes (`- [ ]`/`- [x]`) become todos, `1.` numbered items, `-`/`*` bullets, `>` notes, `##` headings, `###` subheadings; leading indentation nests items. Target the list with `list_id` or `list` (name / "today"). `skip_duplicates: true` makes a re-run safe.

Request body

markdownstringrequiredThe markdown to parse into things.
list_idstringThe list to append to.
liststringAlternative to list_id: a list name, or "today"/"tomorrow"/an ISO date.
skip_duplicatesbooleanSkip items already present (by url, else text).

Request

curl -X POST "https://writethingsdown.com/api/v1/things/markdown" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"list_id":"64f0c2a1e4b0a1234567890a","markdown":"- [ ] Oat milk\n- [ ] Sourdough"}'

Response · 201

{
  "created": 2,
  "skipped": 0,
  "by_list": [
    {
      "list_id": "64f0c2a1e4b0a1234567890a",
      "created": 2,
      "skipped": 0
    }
  ]
}

Get a thing

read:things
GET/api/v1/things/{id}

Returns a single thing you own, plus its reminder (or null).

Parameters

idstring · pathrequiredThe thing id.

Request

curl "https://writethingsdown.com/api/v1/things/64f0c2a1e4b0a1234567890b" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "thing": {
    "id": "64f0c2a1e4b0a1234567890b",
    "list_id": "64f0c2a1e4b0a1234567890a",
    "list_ref": null,
    "text": "Buy oat milk",
    "type": "todo",
    "url": "",
    "tags": [
      "errands"
    ],
    "tabs": 0,
    "photos": [],
    "completed": false,
    "canceled": false,
    "favorited": false,
    "clone_of": null,
    "has_clones": false,
    "bold": false,
    "italic": false,
    "underline": false,
    "header": false,
    "subheader": false,
    "quote": false,
    "code": false,
    "note": "Prefer the **barista** blend — the oat one at the corner shop.",
    "created": "2026-06-02T08:30:00.000Z",
    "updated": "2026-06-02T08:30:00.000Z"
  },
  "reminder": null
}

Update a thing

write:things
PATCH/api/v1/things/{id}

Updates a thing. Only the fields you send change. Set `completed`/`canceled` to complete or cancel a todo, `list_id` to move it to another list, or `list_ref` to reference another list. Content edits keep the thing’s position in its list — only moving it (new `list_id`) changes its order.

Path & query

idstring · pathrequiredThe thing id.

Request body

textstringNew text for the thing, in markdown (same dialect as reads, so a read-modify-write keeps its formatting).
type"todo" | "note" | "dash" | "number" | "bullet" | "none" | "photo"Change the thing type: `todo`, `note`/`none`, `dash`, `number`, or `bullet`.
urlstringSet or clear the hyperlink.
list_refstringSet a referenced list id (a navigable list link), or pass an empty string to clear it.
tagsstring[]Replace the thing’s tags.
photosstring[]Replace the attached image URLs (http(s); max 10). Pass [] to remove all photos.
completedbooleanMark complete or incomplete.
canceledbooleanCancel or un-cancel the thing.
favoritedbooleanStar or unstar the thing.
tabsintegerIndent level: 0 = top level; 1+ nests under the preceding thing.
list_idstringMove the thing to another list you own.
liststringAlternative to list_id for a move: a list name, or "today"/"tomorrow"/an ISO date.
boldbooleanBold the whole thing.
italicbooleanItalicize the whole thing.
underlinebooleanUnderline the whole thing.
headerbooleanMake the thing a heading (H1). Mutually exclusive with `subheader`; also bolds it unless you pass `bold` explicitly.
subheaderbooleanMake the thing a subheading (H2). Mutually exclusive with `header`; also bolds it unless you pass `bold` explicitly.
quotebooleanStyle the thing as a blockquote. Mutually exclusive with `code`.
codebooleanStyle the thing as a code block. Mutually exclusive with `quote`.
notestringA long-form note attached to the thing, in **markdown**. Shown behind a note icon in the app, never inline with `text`. Pass `""` to remove it. Reads return the same markdown, so a note round-trips unchanged.

Request

curl -X PATCH "https://writethingsdown.com/api/v1/things/64f0c2a1e4b0a1234567890b" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'

Response · 200

{
  "thing": {
    "id": "64f0c2a1e4b0a1234567890b",
    "list_id": "64f0c2a1e4b0a1234567890a",
    "list_ref": null,
    "text": "Buy oat milk",
    "type": "todo",
    "url": "",
    "tags": [
      "errands"
    ],
    "tabs": 0,
    "photos": [],
    "completed": true,
    "canceled": false,
    "favorited": false,
    "clone_of": null,
    "has_clones": false,
    "bold": false,
    "italic": false,
    "underline": false,
    "header": false,
    "subheader": false,
    "quote": false,
    "code": false,
    "note": "Prefer the **barista** blend — the oat one at the corner shop.",
    "created": "2026-06-02T08:30:00.000Z",
    "updated": "2026-06-02T08:30:00.000Z"
  }
}

Delete a thing

write:things
DELETE/api/v1/things/{id}

Permanently deletes a thing. Idempotent.

Path & query

idstring · pathrequiredThe thing id.

Request

curl -X DELETE "https://writethingsdown.com/api/v1/things/64f0c2a1e4b0a1234567890b" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "success": true
}

Set a reminder

write:things
PUT/api/v1/things/{id}/reminder

Set or replace the reminder on a thing. A thing has at most one reminder.

Path & query

idstring · pathrequiredThe thing id.

Request body

atstringrequiredWhen to remind — ISO 8601. Include a timezone offset or Z for an exact time (e.g. 2026-06-03T17:00:00-04:00). A naive time (no offset) is interpreted in the user’s timezone.
all_daybooleanTreat it as an all-day reminder.
duration_minutesintegerHow long the calendar block lasts, in minutes. 0 gives it no end time. Defaults to your "Default reminder duration" setting. Cannot be combined with end or all_day.
endstringWhen the block ends — ISO 8601, an alternative to duration_minutes. Must use the same form as at: both with a timezone offset, or both without.
repeat"none" | "daily" | "weekly" | "monthly" | "yearly"Recurrence. Defaults to "none".
everyintegerRepeat every N of the chosen interval, so repeat "monthly" with every 3 is every 3 months. Defaults to 1. Cannot be combined with days.
repeat_untilstringStop repeating after this date — ISO 8601. Must be after at.
daysstring[]For weekly reminders, the weekdays it repeats on — names ("monday") or 0–6 (0=Sunday). Providing days makes it weekly.
alert_minutesintegerNotify this many minutes before. Defaults to your reminder settings. 0 means no advance notice.
second_alert_minutesintegerA second, independent notification this many minutes before — e.g. 1440 for a day before, alongside a 60-minute first alert. 0 means none.
colorstringHex color. Defaults to your default reminder color.

Request

curl -X PUT "https://writethingsdown.com/api/v1/things/64f0c2a1e4b0a1234567890b/reminder" \
  -H "Authorization: Bearer twos_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"at":"2026-06-03T17:00:00Z","duration_minutes":30,"repeat":"monthly","every":3,"alert_minutes":60,"second_alert_minutes":1440}'

Response · 200

{
  "reminder": {
    "id": "64f0c2a1e4b0a1234567890c",
    "thing_id": "64f0c2a1e4b0a1234567890b",
    "list_id": "64f0c2a1e4b0a1234567890a",
    "text": "Buy oat milk",
    "at": "2026-06-03T17:00:00.000Z",
    "all_day": false,
    "repeat": "none",
    "every": 1,
    "repeat_until": null,
    "days": [],
    "end": "2026-06-03T18:00:00.000Z",
    "duration_minutes": 60,
    "alert_minutes": 10,
    "second_alert_minutes": null,
    "color": "#FF9500"
  }
}

Remove a reminder

write:things
DELETE/api/v1/things/{id}/reminder

Remove the reminder from a thing. Idempotent.

Path & query

idstring · pathrequiredThe thing id.

Request

curl -X DELETE "https://writethingsdown.com/api/v1/things/64f0c2a1e4b0a1234567890b/reminder" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "success": true
}

List reminders

read:things
GET/api/v1/reminders

Your upcoming reminders (future first), up to 50.

Request

curl "https://writethingsdown.com/api/v1/reminders" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "reminders": [
    {
      "id": "64f0c2a1e4b0a1234567890c",
      "thing_id": "64f0c2a1e4b0a1234567890b",
      "list_id": "64f0c2a1e4b0a1234567890a",
      "text": "Buy oat milk",
      "at": "2026-06-03T17:00:00.000Z",
      "all_day": false,
      "repeat": "none",
      "every": 1,
      "repeat_until": null,
      "days": [],
      "end": "2026-06-03T18:00:00.000Z",
      "duration_minutes": 60,
      "alert_minutes": 10,
      "second_alert_minutes": null,
      "color": "#FF9500"
    }
  ]
}

Fetch a document

read:things
GET/api/v1/fetch

Fetch one resource (a list or a thing) by id and return it as a single document — `{ id, title, text, url, metadata }`. For a list, `text` is its things rendered as plain text. Pairs with search (search returns ids, fetch retrieves full content). Primarily for AI deep-research clients.

Parameters

idstringrequiredA list or thing id.

Request

curl "https://writethingsdown.com/api/v1/fetch?id=64f0c2a1e4b0a1234567890b" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "id": "64f0c2a1e4b0a1234567890b",
  "title": "Buy oat milk",
  "text": "Buy oat milk",
  "url": "https://writethingsdown.com/list/5f9a1b2c3d4e5f6a7b8c9d0e",
  "metadata": {
    "kind": "thing",
    "list_id": "5f9a1b2c3d4e5f6a7b8c9d0e",
    "type": "todo",
    "tags": []
  }
}

List tags

read:things
GET/api/v1/tags

The tags you use across your things, with usage counts.

Request

curl "https://writethingsdown.com/api/v1/tags" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Response · 200

{
  "tags": [
    {
      "name": "errands",
      "count": 12,
      "color": "#66cc99"
    }
  ]
}

Connect to AI (MCP)

NewTwos runs a Model Context Protocol server so AI clients like Claude and ChatGPT can read and write your NewTwos. It’s the same data and the same scoped API key — point your client at the endpoint below and authenticate with Authorization: Bearer twos_.

Endpointhttps://writethingsdown.com/mcp

NewTwos is on ChatGPT

No setup needed in ChatGPT — NewTwos is a published app. Open it, sign in to connect, and start managing your lists and things right from a conversation.

Open in ChatGPT

Set it up

  1. Create an API key in Settings → API Keys, granting the scopes you want the assistant to have (read-only is safest to start). Copy it — it’s shown once.
  2. In a client that supports custom headers (the config-file clients below), add NewTwos as a remote MCP server using the endpoint above and an Authorization: Bearer header with your key:
    {
      "mcpServers": {
        "twos": {
          "url": "https://writethingsdown.com/mcp",
          "headers": { "Authorization": "Bearer twos_YOUR_KEY" }
        }
      }
    }
  3. Restart the client (or reload its MCP servers). NewTwos’ tools will appear; ask it to “add a to-do to my Groceries list” to test.

Config-file clients (work today with the Bearer key)

Claude Desktop

Settings → Developer → Edit Config, paste the JSON above into claude_desktop_config.json, then restart Claude.

Cursor / Cline / Windsurf

Add the same server block to ~/.cursor/mcp.json (or the editor’s MCP settings) and reload.

In-app connectors (no API key needed — sign in instead)

In ChatGPT, just open the published NewTwos app — no endpoint to paste. In Claude → Settings → Connectors → Add custom connector (or any other MCP client), paste the endpoint https://writethingsdown.com/mcp and click Add. There’s no API key to enter — you’ll be sent to NewTwos to sign in and approve the scopes, then you’re connected. Leave the OAuth Client ID/Secret blank; NewTwos registers the client automatically.

Note: ChatGPT’s deep-research connector additionally requires search and fetch tools (NewTwos provides both).

Tools

list_listsList your lists.read:lists
get_listGet a list and its things (in the app’s display order).read:lists
get_todayGet/create the day list for today, yesterday, tomorrow, or a date.read:lists
create_listCreate a list (optionally filled with `things` in one call).write:lists
import_markdownCreate a new list from a markdown document.write:lists
update_listRename, star, or archive a list.write:lists
delete_listDelete a list and its things.write:lists
list_thingsList things newest-first (filter by list, completion, tag).read:things
create_thingCreate a todo or note (target a list by id or name).write:things
create_thingsBulk-create many things in one call (returns per-list counts).write:things
append_markdownAppend a markdown block to a list as things.write:things
get_thingGet a thing.read:things
update_thingEdit, complete, cancel, or move a thing.write:things
update_thingsApply one change to many things (check off / move / star).write:things
delete_thingDelete a thing.write:things
delete_thingsDelete many things at once by id.write:things
set_reminderSet or replace a reminder on a thing.write:things
remove_reminderRemove a thing’s reminder.write:things
list_remindersList your reminders, optionally within a date range.read:things
searchSearch your lists and things.search
fetchFetch one list or thing by id as a document.read:things
list_tagsList your tags with counts.read:things

Each tool requires the scope shown — grant the matching scopes when you create the key, and the AI can only do what you allow.

Automations

Connect NewTwos to automation platforms with your API key — no code required. Each request authenticates with Authorization: Bearer twos_.

Zapier

Triggers — New Thing, Thing Completed, New List — and actions — Create Thing, Create List, Set Reminder, Complete Thing, Delete Thing, plus Find List. Paste a NewTwos API key to connect.

Open NewTwos on Zapier

Raycast

Capture and search from the Raycast launcher — Add Thing, Search Things, Create List. Paste a NewTwos API key in the extension preferences to connect.

Install the Raycast extension

Other platforms

Make, n8n, and Pipedream can call the API directly — use an HTTP request with the Authorization header, or import the OpenAPI spec to generate modules.

For polling triggers, list things newest-first and dedupe on each item’s id, or pass a since cursor:

# new things since a timestamp
curl "https://writethingsdown.com/api/v1/things?sort=created&since=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer twos_YOUR_KEY"

# recently completed things
curl "https://writethingsdown.com/api/v1/things?completed=true&sort=updated" \
  -H "Authorization: Bearer twos_YOUR_KEY"

Errors

Errors use standard HTTP status codes and return a JSON body of the form { "error": "…" }.

400Bad RequestA required field or parameter is missing or malformed.
401UnauthorizedThe API key is missing, invalid, or has been revoked.
403ForbiddenThe key is valid but lacks the scope required for this endpoint.
404Not FoundThe referenced list or thing does not exist or is not owned by you.
429Too Many RequestsThe rate limit of 1000 requests per hour has been exceeded.

Troubleshooting

“Couldn’t connect” when adding the connector
Use the exact URL https://writethingsdown.com/mcp, and leave the OAuth Client ID and Secret blank — the server registers the client automatically. When redirected, sign in to NewTwos and approve.
401 / the connector stopped working
Your access was revoked or the token expired. Remove and re-add the connector (or, for API keys, check the key isn’t revoked in Settings → API Keys) and sign in again.
A tool says it’s missing a scope
The connection wasn’t granted that permission. Reconnect and approve the needed scopes, or create an API key that includes them. Each tool’s required scope is listed in Tools.
429 — rate limited
The limit is 1,000 requests per hour per key. Check the X-RateLimit-* headers and retry after the window resets.
The assistant can’t find a list or thing
Ask it to search first to get the id — list/thing ids are required for get, update, delete, and reminder tools.
Still stuck?
Email help@twosapp.com and we’ll help.