NewTwos API
v1.0.0A simple REST API to read and write your NewTwos lists and things. Everything is JSON, over HTTPS, authenticated with a personal API key.
Quickstart
- Create an API key in Settings → API Keys and copy it (it’s shown once).
- Send it on every request as an
Authorization: Bearer twos_…header. - 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_0f9e8d7c6b5a4938271605f4e3d2c1b0Keep 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: 1780000000Scopes
Each key is granted a set of scopes. An endpoint returns 403 if your key is missing the scope it requires.
read:lists | Read your lists. |
write:lists | Create, update, and delete lists. |
read:things | Read the things inside your lists, and your tags. |
write:things | Create, update, and delete things. |
search | Search 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:listsReturns your lists (non-hidden, non-archived), most recently modified first, 50 per page.
Parameters
page | integer | Zero-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:listsCreates 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
title | stringrequired | The list name. |
emoji | string | An emoji shown next to the list. |
things | object[] | 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:listsCreate 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
markdown | stringrequired | The markdown document to import. |
title | string | The new list’s name. Omit to use the markdown’s first "# Heading". |
emoji | string | An 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:listsReturns 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
id | string · pathrequired | The list id. |
max_text | string | Truncate 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:listsReturns 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
date | string | "today" (default), "yesterday", "tomorrow", or an ISO 8601 date (e.g. 2026-04-01). |
max_text | string | Truncate 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:listsUpdates a list. Only the fields you send are changed. Renaming cascades to the list’s things.
Path & query
id | string · pathrequired | The list id. |
Request body
title | string | Rename the list. |
emoji | string | Change the emoji. |
favorited | boolean | Star or unstar the list. |
archived | boolean | Archive 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:listsPermanently deletes a list and all of its things. Idempotent.
Path & query
id | string · pathrequired | The 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:thingsList 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_id | string | Only things in this list. |
completed | boolean | Filter by completion (true/false). |
type | string | Filter by type: todo, note, dash, number, or bullet. |
tag | string | Only things with this tag. |
since | string | Only things newer than this ISO 8601 time (by the chosen sort). |
sort | string | created (default) or updated. |
page | string | Zero-based page (50 per page). Page until has_more is false to read everything. |
max_text | string | Truncate 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:thingsCreates 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_id | string | The id of the list to add the thing to. Omit if you pass `list`. |
list | string | Alternative to list_id: a list name, or "today"/"yesterday"/"tomorrow"/an ISO date (the day list, created if needed). |
text | stringrequired | The 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. |
url | string | A hyperlink to attach to the thing. |
list_ref | string | The id of another list to reference (a navigable list link). Pass a list id, not a URL. |
tags | string[] | Tag names (with or without a leading #). |
photos | string[] | Hosted image URLs to attach (must be http(s); max 10). The API does not host images — upload them elsewhere and pass the URLs. |
completed | boolean | Whether the thing starts completed. |
created | string | ISO 8601 creation time. Use it to preserve original capture-date order when importing; omit to stamp now. |
tabs | integer | Indent level: 0 = top level (default); 1+ nests this thing under the preceding one. Create the parent first, then the indented children. |
bold | boolean | Bold the whole thing. |
italic | boolean | Italicize the whole thing. |
underline | boolean | Underline the whole thing. |
header | boolean | Make the thing a heading (H1). Mutually exclusive with `subheader`; also bolds it unless you pass `bold` explicitly. |
subheader | boolean | Make the thing a subheading (H2). Mutually exclusive with `header`; also bolds it unless you pass `bold` explicitly. |
quote | boolean | Style the thing as a blockquote. Mutually exclusive with `code`. |
code | boolean | Style the thing as a code block. Mutually exclusive with `quote`. |
note | string | A 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:thingsCreate 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
items | object[]required | The 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_id | string | Default list for items that don’t set their own list_id. |
list | string | Alternative default target: a list name, or "today"/"tomorrow"/an ISO date. |
skip_duplicates | boolean | Skip 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:thingsApply 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
ids | string[]required | Ids of the things to change (max 500). |
set | objectrequired | Fields 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:thingsPermanently 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
ids | string[]required | Ids 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:thingsParse 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
markdown | stringrequired | The markdown to parse into things. |
list_id | string | The list to append to. |
list | string | Alternative to list_id: a list name, or "today"/"tomorrow"/an ISO date. |
skip_duplicates | boolean | Skip 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:thingsReturns a single thing you own, plus its reminder (or null).
Parameters
id | string · pathrequired | The 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:thingsUpdates 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
id | string · pathrequired | The thing id. |
Request body
text | string | New 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`. |
url | string | Set or clear the hyperlink. |
list_ref | string | Set a referenced list id (a navigable list link), or pass an empty string to clear it. |
tags | string[] | Replace the thing’s tags. |
photos | string[] | Replace the attached image URLs (http(s); max 10). Pass [] to remove all photos. |
completed | boolean | Mark complete or incomplete. |
canceled | boolean | Cancel or un-cancel the thing. |
favorited | boolean | Star or unstar the thing. |
tabs | integer | Indent level: 0 = top level; 1+ nests under the preceding thing. |
list_id | string | Move the thing to another list you own. |
list | string | Alternative to list_id for a move: a list name, or "today"/"tomorrow"/an ISO date. |
bold | boolean | Bold the whole thing. |
italic | boolean | Italicize the whole thing. |
underline | boolean | Underline the whole thing. |
header | boolean | Make the thing a heading (H1). Mutually exclusive with `subheader`; also bolds it unless you pass `bold` explicitly. |
subheader | boolean | Make the thing a subheading (H2). Mutually exclusive with `header`; also bolds it unless you pass `bold` explicitly. |
quote | boolean | Style the thing as a blockquote. Mutually exclusive with `code`. |
code | boolean | Style the thing as a code block. Mutually exclusive with `quote`. |
note | string | A 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:thingsPermanently deletes a thing. Idempotent.
Path & query
id | string · pathrequired | The 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:thingsSet or replace the reminder on a thing. A thing has at most one reminder.
Path & query
id | string · pathrequired | The thing id. |
Request body
at | stringrequired | When 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_day | boolean | Treat it as an all-day reminder. |
duration_minutes | integer | How 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. |
end | string | When 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". |
every | integer | Repeat 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_until | string | Stop repeating after this date — ISO 8601. Must be after at. |
days | string[] | For weekly reminders, the weekdays it repeats on — names ("monday") or 0–6 (0=Sunday). Providing days makes it weekly. |
alert_minutes | integer | Notify this many minutes before. Defaults to your reminder settings. 0 means no advance notice. |
second_alert_minutes | integer | A second, independent notification this many minutes before — e.g. 1440 for a day before, alongside a 60-minute first alert. 0 means none. |
color | string | Hex 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:thingsRemove the reminder from a thing. Idempotent.
Path & query
id | string · pathrequired | The 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:thingsYour 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"
}
]
}Search
searchCase-insensitive search across your list titles and thing text. Returns up to 50 lists + 50 things; `has_more` is true when results were capped (narrow the query). Not a complete enumeration — page `/things` to read a whole list.
Parameters
query | stringrequired | The search query (the legacy alias `q` also works). |
Request
curl "https://writethingsdown.com/api/v1/search?query=milk" \
-H "Authorization: Bearer twos_YOUR_KEY"Response · 200
{
"query": "milk",
"lists": [],
"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"
}
],
"results": [
{
"id": "64f0c2a1e4b0a1234567890b",
"title": "Buy oat milk",
"url": "https://writethingsdown.com/list/5f9a1b2c3d4e5f6a7b8c9d0e"
}
],
"limit": 50,
"has_more": false,
"lists_has_more": false,
"things_has_more": false
}Fetch a document
read:thingsFetch 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
id | stringrequired | A 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": []
}
}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_….
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 ChatGPTSet it up
- 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.
- 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: Bearerheader with your key:{ "mcpServers": { "twos": { "url": "https://writethingsdown.com/mcp", "headers": { "Authorization": "Bearer twos_YOUR_KEY" } } } } - 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_lists | List your lists. | read:lists |
get_list | Get a list and its things (in the app’s display order). | read:lists |
get_today | Get/create the day list for today, yesterday, tomorrow, or a date. | read:lists |
create_list | Create a list (optionally filled with `things` in one call). | write:lists |
import_markdown | Create a new list from a markdown document. | write:lists |
update_list | Rename, star, or archive a list. | write:lists |
delete_list | Delete a list and its things. | write:lists |
list_things | List things newest-first (filter by list, completion, tag). | read:things |
create_thing | Create a todo or note (target a list by id or name). | write:things |
create_things | Bulk-create many things in one call (returns per-list counts). | write:things |
append_markdown | Append a markdown block to a list as things. | write:things |
get_thing | Get a thing. | read:things |
update_thing | Edit, complete, cancel, or move a thing. | write:things |
update_things | Apply one change to many things (check off / move / star). | write:things |
delete_thing | Delete a thing. | write:things |
delete_things | Delete many things at once by id. | write:things |
set_reminder | Set or replace a reminder on a thing. | write:things |
remove_reminder | Remove a thing’s reminder. | write:things |
list_reminders | List your reminders, optionally within a date range. | read:things |
search | Search your lists and things. | search |
fetch | Fetch one list or thing by id as a document. | read:things |
list_tags | List 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 ZapierRaycast
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 extensionOther 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": "…" }.
| 400 | Bad Request | A required field or parameter is missing or malformed. |
| 401 | Unauthorized | The API key is missing, invalid, or has been revoked. |
| 403 | Forbidden | The key is valid but lacks the scope required for this endpoint. |
| 404 | Not Found | The referenced list or thing does not exist or is not owned by you. |
| 429 | Too Many Requests | The 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.