# Cotask documentation

Cotask is the simplest task manager for one person — and for the AI agents that work with them.
It runs as a native app on iPhone and Mac, with a home- and lock-screen widget, and it exposes the
same account to any AI assistant through an [MCP server](#agents-and-mcp) and a
[REST API](#rest-api).

These docs cover every feature: what it does, how to use it, and how to configure it. The whole
product is also one Markdown document, on purpose — [read it as Markdown](/docs.md) if you are a
language model or feeding one.

- **Apps:** iPhone (iOS 26), Mac (macOS 26), iOS widget.
- **Account:** email and password. Everything is scoped to you; nothing is shared.
- **Plans:** Free, and [Cotask Pro](#cotask-pro) for delegation, reminders, notifications, custom
  filters, dependencies and 5 GB of files.
- **Agent endpoint:** `https://usecotask.com/mcp`
- **Changelog:** [/changelog](/changelog) — also readable as JSON at [/api/changelog](/api/changelog).

## Quick start

1. **Install and sign in.** Open the app, create an account with an email and a password, and confirm
   it from the email you get. The same account signs you in on the Mac and feeds the widget. A new
   account gets a five-screen tour after its first sign-in — once per account, on whichever device
   you open first, and it picks up where you left it if you quit halfway.
2. **Add a task.** Tap `+` on iPhone, or press `⌃Space` anywhere on the Mac, type a title and press
   return. That is the whole ritual — everything else on a task is optional.
3. **Complete it.** Tap the circle at the left of a row. A small toast slides up with **Undo** for four
   seconds.
4. **Connect an assistant** (optional). Settings → Agents → copy the MCP server URL, then add it as a
   custom connector in ChatGPT or Claude and sign in to approve. See [Agents and MCP](#agents-and-mcp).

## Concepts

### Tasks

A task is one line of text with optional detail hung off it. Nothing but the title is required.

| Property | What it is | Values |
|---|---|---|
| `title` | One line: what needs doing | Any text |
| `notes` | The detail | Plain text with [light Markdown](#notes-formatting) |
| `status` | Where it is in your process | `backlog` · `draft` · `todo` · `doing` · `done` |
| `project` | Optional container | One project, or none |
| `due_date` | A calendar day, not a time | `YYYY-MM-DD`, or none |
| `priority` | How important it is | `low` · `medium` · `high`, or none |
| `recurrence` | How it repeats | `daily` · `weekdays` · `weekly` · `monthly` · `yearly`, or none |
| `remind_at` | The exact moment it rings | An ISO 8601 timestamp, or none |
| `reminder_kind` | How loudly it rings | `notify` · `alarm` (urgent), or none |
| `blocked_by` | The one task that must be done first | A task id, or none |
| `attachments` | Links and files | Any number of either |

Tasks also carry who created them — you or an agent — which is what makes
[drafts](#drafts-what-agents-create) and the `Added by an agent` filter possible.

### The five statuses

| Status | Colour and mark | Means |
|---|---|---|
| **Backlog** | Dashed circle | Someday. Out of the way but not forgotten. |
| **Draft** | Dotted circle | A suggestion — almost always from an agent — that you have not committed to. |
| **Todo** | Empty circle | Committed work. |
| **Doing** | Half-filled circle, yellow | In progress right now. |
| **Done** | Filled checkmark | Complete. |

Lists show groups in that reading order: Todo, Doing, Drafts, Backlog, Done. "Open" work — what the
**Active** filter shows and what every count adds up — is Todo + Doing + Drafts.

### Projects

Projects are optional containers, nothing more: a name, a colour and an order. A task belongs to one
project or to none; tasks with none live in the general **Tasks** list. Deleting a project keeps its
tasks — they simply stop having a project.

Create one from the sidebar's `+` on iPhone, or with `⌘K → New project` on the Mac. Rename, recolour
or delete from the same places.

### Drafts: what agents create

Anything an assistant creates lands in **Drafts** rather than in your real list. A draft shows the
logo of the assistant that suggested it (✦ when the client is unknown) and an **Accept** button.

- **Accept** → the task becomes Todo and is yours.
- **Dismiss** → swipe to delete it.

Agents may delete or edit their own drafts. Once you have accepted a task, an agent will not delete
it — it has to ask you. This is the one rule that makes it safe to let a model plan your week.

### Attachments: links and files

A task takes two kinds of attachment, and they behave the same everywhere:

- **Links** — a page on the web. Cotask fetches the page once and stores its title and the site's
  favicon, so a link reads as "Meeting notes — Notion" rather than as a URL.
- **Files** — a photo, a video, a PDF, a CSV, anything. Stored in Cotask's own storage, shown with a
  thumbnail (or a glyph for its type) and its size.

Both carry a plain `https` URL that needs no credentials, so a link you attached and a report an agent
produced are equally fetchable by the next agent that picks the task up. Attachments live in the task
editor, never in the list.

See [Files](#files) for limits and housekeeping.

### Blocked by

A task names at most one task that has to be done first. That is deliberately narrower than a
dependency graph: one blocker, in one direction, which is enough for "print the badges after sending
the invitations" and never turns into project-management software.

A blocked task shows a hand mark in the list. With
[dependency chains](#dependency-chains) on — the default — it also sits indented under its blocker, so
a run of dependent work reads as one thread. The task editor shows both sides: what blocks this task,
and the tasks this one blocks.

### Repeats

Set a repeat and completing the task files a **done copy** for the record and moves the original
forward to its next occurrence. Your history keeps every instance; your list keeps one live task.

### Reminders, and the urgent one

A due date is a *day*. A **reminder** is a *moment*: set one and the task rings at it.

There are two kinds, and they are not the same thing wearing different labels.

| Kind | What happens |
|---|---|
| **Remind me** | A notification. A banner, the usual sound, silenced by Focus like anything else. |
| **Urgent** | An **alarm**. It takes over the screen and rings through the silent switch and Focus, until you stop it — exactly like the alarm in the Clock app. |

Urgent uses **AlarmKit**, so it is an iPhone alarm in the real sense, not a louder notification. It
asks for its own permission the first time you choose it (Settings → Notifications → *Urgent
reminders* shows where that stands, and there is nothing to switch on for ordinary reminders beyond
notifications themselves). Say no and urgent reminders still arrive — as ordinary notifications.

The alarm carries the task's title and its project, shows on the Lock Screen and in the Dynamic
Island while it rings, and offers **Done** next to Stop: one press completes the task and ends the
ring together.

Nothing on a server fires a reminder. Your iPhone schedules it from the synced task, so it rings with
no network and no push, and it un-schedules itself the moment the task is done, the time is cleared,
or the reminder has passed. Reminders ring on the iPhone; the Mac sets them and they sync across.
Reminders, urgent ones and blockers are part of [Pro](#cotask-pro).

An agent can set either kind for you — "remind me in 45 minutes", "wake me at 6 tomorrow, urgent" —
through `set_reminder`; see [Reminders, for agents](#reminders-for-agents).

## iPhone

### The list

One flowing list with light group labels per status. Tap a label to collapse the group, or its `+` to
add a task straight into that status.

- **Tap the circle** — completes an open task, accepts a draft, reopens a done one. A glass toast
  slides up from the bottom with **Undo** for four seconds.
- **Tap the row** — opens the task editor.
- **Swipe right** — Done (or back to Todo, on a done task).
- **Swipe left** — Delete, plus Accept on a draft or Backlog on anything else.
- **Long-press a row** — a menu with every status, and Delete.
- **Long-press and drag** — reorder. Dropping a task under another group's label changes its status.
- **Pull down** — refresh.

Reordering by hand is available when the filter is grouped and sorted manually; a filter that sorts by
due date or priority orders itself.

### The filter bar

Under the title sit the filter pills — **Active**, **Backlog**, **All** and any filter you have made,
in the order you chose. Opening a list lands on the first filter in that order, so the view you want
first is a matter of dragging it to the top in Settings. Tap a pill and the list stays there.

See [Filters](#filters) to build your own.

### Search

The magnifying glass in the toolbar. The field takes the filter bar's place and the list shows every
match in the current list, whatever its status and whichever filter is selected — titles and notes
both count, open work comes before done, and the most recently touched is first. **Cancel** brings
the bar back.

### The sidebar, and switching lists fast

The leading toolbar button — or a swipe in from the left edge — opens a floating drawer: **Tasks**
(everything), **[Notifications](#notifications)**, **[Analytics](#analytics)**, every project with its
count of open work, `+` to add a project, and your account (Settings, Agents, sign out).

Faster, once you know it: **press and hold the list's title** ("Tasks", or the project's name) and a
menu comes up with Tasks and every project, the current one checked — and the
[view](#compact-or-clean), Compact or Clean. One gesture instead of three.

### Select several tasks

**⋯ → Select Tasks**, or **Select** in a task's long-press menu (which starts with that task picked).
Circles become checkmarks and a bar along the bottom changes every selected task at once: **Status**,
**Project**, **Due** (today, tomorrow, next week, a date, or cleared), **Priority**, or **Delete**.

Whatever one action touched, one **Undo** in the toast takes back. **Select All** takes what is on
screen; changing the filter or the search drops anything no longer shown. **Done** leaves without
changing anything. Swipes, dragging and the long-press menu wait until you are out.

### Compact or Clean

**Where:** iPhone → Settings → **Appearance**, or press and hold the list's title → **View**.

- **Compact** (the default) — everything on one line, the most tasks on screen.
- **Clean** — the title leads and priority, project, due date and marks sit under it in one muted
  line; larger type, more space between rows, and a roomier task editor.

The choice is kept on that iPhone, not synced. The Mac has one look.

### Quick add

The `+` button opens a small sheet: a title, the property chips, return to save. Adding from inside a
project — or from a filter pinned to a single project — pre-fills that project. Adding from a group's
`+` gives the task that group's status.

The due date starts on **today**, unless the filter you are looking at asks for another day: a filter
for tomorrow dates the task tomorrow, and one for tasks with no date — or for overdue ones — leaves it
undated, as does adding straight into Backlog. Clear or change it on the chip like any other.

### The task editor

Everything about one task, saving as you type:

- **Title**, multi-line if it needs to be.
- **Property chips** — status, priority, project, due date, [remind](#reminders-and-the-urgent-one),
  repeat, blocked by. Each opens a small menu; each can be cleared. The remind chip carries the quick
  times, a picker for an exact one, and the choice between an ordinary reminder and an urgent alarm —
  which turns the chip red.
- **Notes** — rendered by default; tap to edit. Line breaks survive exactly as typed, `- ` bullets and
  `1.` lists render (and continue when you press return), and links are tappable. See
  [Notes formatting](#notes-formatting).
- **Attachments** — **Add link** pastes one; **Attach file** takes a photo or video from the library, or
  anything from Files. An upload shows as a row that fills while the file is compressed and sent.
  Long-press an attachment to copy the link, share it or remove it.
- **Blocks** — the tasks waiting on this one.
- **Suggested by** — which assistant proposed it, when it came from an agent.
- **Delegate to AI** — send this task to one of your [routes](#delegate-to-ai) — a webhook, or Claude
  Code / Codex on your Mac. Once sent, the task shows **Delegated to …** while the agent works, and
  the action becomes **Follow up**. With [Schedule delegate](#send-it-later) on, the same button can
  also give it a time.
- **Copy prompt** — the toolbar button; see [Hand a task to a model](#hand-a-task-to-a-model).
- **Delete task**.

## Widget

Add **Tasks** to the home screen or the lock screen. It comes in every size: small, medium, large,
extra large on iPad, and the lock-screen circular, rectangular and inline.

- **Which list it shows** — long-press → **Edit Widget** → pick any filter, built-in or your own. Each
  widget on the screen can show a different one.
- **Tapping a circle** completes the task — or accepts a draft — right in the widget, without opening
  the app.
- **Tapping a row** opens that task in the app; tapping the header opens that list.

The widget reads the app's cache, so it is populated the moment the app has synced once, and it writes
with your session — no separate sign-in.

## Siri and Shortcuts

**Where:** iPhone → Settings → **Siri & Shortcuts** lists every command with the phrase to say.

Nothing to set up — each phrase names the app, and each acts on your tasks the way the app does: the
change is on screen at once and syncs everywhere. Siri answers with a card drawn like Cotask's own
rows, and the circles in it complete the task (or accept a draft) right there.

- **Add Task** — "Add a task in Cotask". Siri asks what the task is; a Shortcut can also set the
  project, due date, priority, status and notes.
- **What's on My List** — "What's on my list in Cotask". What's in progress and what's due today or
  overdue, read out, with up to six rows to check off.
- **Review Drafts** — "Review my drafts in Cotask". What agents suggested, each with **Accept**.
- **Delegate to AI** — "Delegate a task with Cotask". Hands a task to a route (the one used last unless
  you pick) and starts it, as the button in the task does. Pro.
- **Complete Task**, **Start Task**, **Open Task** — "Mark *buy milk* done in Cotask", "Start *the
  deck* in Cotask", "Open *the deck* in Cotask". A repeating task rolls forward when completed; a
  note of completion can be added from a Shortcut (Pro).
- **Show List** — "Show Backlog in Cotask": the app opens on a filter or a project.

In the Shortcuts app there is also **Update Task** (status, due date, priority, project — accepting a
draft is setting it to Todo), **Delete Task** (Siri confirms first) and **Find Tasks**, with filters on
title, status, priority, project, due date and whether an agent drafted it, and sorting.

**Apple Intelligence.** On iOS 27 Cotask speaks Siri's own vocabulary for to-dos: a task is a
reminder and a project is a list, so "add a reminder in Cotask to call Ana tomorrow", "move it to
Friday", "flag it" (high priority), "make a list called Website" and "search Cotask for the launch"
work without a phrase being taught. While a task is open, Siri knows which one is on screen — "mark
this done", "what's this task about". Tags, sections and location-based reminders aren't kept.

**Spotlight.** Your open tasks, and the ones finished in the last week, are in Spotlight: type a title
and open the task. Signing out takes them out again.

## Mac

A launcher, not a file browser: one window, one column, no sidebar, no inspector. The search field is
always focused — type to filter, or type something new and press `⏎` to add it. What you add lands in
the filter you are on, with the same due date the iPhone's quick add would give it — today, unless the
filter asks for another day.

`⌃Space` summons the panel over whatever you are doing and `⌃Space` hides it again. It is a fixed
720×480 floating panel that always lands in the same place, opens cleared and focused, and does not
dismiss when you click away — `esc` only clears the field. To move it, press and hold its background
for a moment, then drag; it snaps to the middle of the screen, and `⌃Space` brings it back to its
place next time.

With the [Delegate Listener](#the-delegate-listener-mac) on (Settings → **Delegate Listener**, off by
default), the Mac also runs tasks you delegate to Claude Code or Codex, and shows it in the menu bar.

Settings on the Mac: **Account** (your plan, sign out, delete the account), **Lists**, **Files**,
**Filters**, **Agents**, **Delegate Listener**, **Usage Limits** (once a Mac listens — see
[Usage limits](#usage-limits)), **Keyboard Shortcuts**, **History** and **About** (the version, updates,
and what's new).

> `⌃Space` is macOS's default "select previous input source" shortcut. If the panel does not appear,
> free it under System Settings → Keyboard → Keyboard Shortcuts → Input Sources.

### Install and updates

The Mac app is in open beta: [download it](/download), open the DMG and drag Cotask into
Applications. It is signed with Apple's Developer ID and notarized, so it opens like any other app.

Cotask checks for a new build a few times a day and installs it when you quit. **⌘K → Check for
Updates**, **Cotask → Check for Updates…** in the menu bar and **Settings → About** check right away;
About is also where automatic checks and automatic installs are switched off. Updates come through
[Sparkle](https://sparkle-project.org): each one, and the list that announces it, is signed with
Cotask's key and verified before anything installs.

### Shortcuts

| Keys | What |
|---|---|
| `⌃Space` | Show / hide Cotask — from anywhere, over any app |
| `⌘N` | Clear the field and start a new task · `⌥⌘N` show Cotask |
| `⏎` | Add what you typed · **accept** a draft · open anything else |
| `⇥` | Cycle Active → Backlog → All → each project (`⇧⇥` reverses) |
| `↑` `↓` | Move through the list |
| `⌥↑` `⌥↓` | Move the selected task up or down inside its group |
| `⌘⏎` | Toggle done |
| `⇧⏎` | Complete, then write a [note of completion](#note-of-completion) — `esc` skips it, `⌘⏎` saves it. On a done task, add or edit its note |
| `⌘K` | Command palette |
| `⌘P` | Jump to a project |
| `⇧⌘T` · `⇧⌘I` · `⇧⌘D` · `⇧⌘B` · `⇧⌘K` | Move to Todo · Doing · Draft · Backlog · Done |
| `⌘1` `⌘2` `⌘3` | Active · Backlog · All |
| `⌘F` | Back to the search field · `esc` clears it |
| `⌘X` | Delete the selected task (`⌘⌫` clears the search field) |
| `⌘Z` · `⇧⌘Z` | Undo · redo |
| `⌘R` · `⌘,` | Refresh · Settings |

Every one of these is in the menu bar too, so nothing is hidden.

These are the defaults, and every one can be changed. **Settings → Keyboard Shortcuts** lists them all —
with Previous category (`⇧⇥`) and Copy to prompt, which has none to begin with. Click a shortcut and press
the new keys: `esc` cancels, `⌫` leaves the action without one. A keystroke another action already has
moves over, and that action is left without one. A letter needs `⌘`, `⌥` or `⌃`, and `⏎` `↑` `↓` `esc` stay
the list's own. The arrow beside a changed shortcut puts it back, and **Restore Defaults** resets them all.
Shortcuts are kept per Mac.

### The command palette

`⌘K` acts on whatever is selected, and is also where projects are managed. It nests, and `esc` steps
back out one level at a time rather than closing everything.

- **Task** — accept a draft, complete or reopen, complete with a note, move to another status, reschedule to one of the
  usual days, set priority, set a repeat, pick the task it is blocked by, delegate to AI, add to or
  remove from a project, delete.
- **New project** — type the name, pick a colour, `⏎` creates it.
- **Edit project** — rename, recolour or delete. Deleting keeps its tasks.
- **Go to project** — the same as `⌘P`.
- **Refresh**, **Settings**, **Check for Updates** — always at the end of the list.

### The task card

`⏎` on a task opens a card over the list: title, notes (rendered; click to edit), attachments, status,
project and due date. **Add link** or **Attach file** — or drop a link from the browser or a file from
the Finder straight onto the card. `esc` closes it, `⌘⏎` completes the task from inside it.

## Filters

A filter is a saved, named set of rules that decides which tasks a list shows and how they are laid
out. Three are built in — **Active** (all open work), **Backlog**, **All** — and with
[Pro](#cotask-pro) you can build as many as you like. They sync, so a filter made on the Mac is on
the phone and available to the widget.

**Where:** iPhone → Settings → **Filters**. Mac → Settings → **Filters**.

### What a filter can say

| Criterion | Options |
|---|---|
| **Status** | Any combination of Backlog, Draft, Todo, Doing, Done |
| **Project** | Any project · no project at all · only the projects you pick |
| **Due** | Any time · today · tomorrow · today or tomorrow · today or overdue · overdue · next 7 days · next 30 days · has a due date · no due date |
| **Priority** | Any · high · medium · low · high or medium · no priority |
| **Dependencies** | Blocked or not · blocked · not blocked |
| **Hide dependency chains** | Show only the first task you can act on in each chain |
| **Added by** | Anyone · added by me · added by an agent |
| **Sort** | Manual · priority · due date · newest first · recently updated · title |
| **Group by status** | Grouped under status labels, or, off, one flat list |

Each filter shows its own rules as a sentence — *"Todo, Doing · Due today or overdue · High or medium
· By due date"* — under its name in Settings, so a list of filters reads as a list of intentions.

Sorting by anything other than **Manual**, or turning grouping off, turns off reordering by hand in
that view — dragging on the iPhone, `⌥↑` / `⌥↓` on the Mac: the filter is already deciding the order.

**Hide dependency chains** turns a chain into a queue. With A blocking B blocking C, the list shows
A; finish it and B takes its place, then C. A blocker counts even when it sits outside the filter —
another project, another status — and done tasks are left out of such a filter, so the chain empties
as you work through it. With it on, **Blocked** and **Not blocked** mean *still* blocked: a task
whose blocker is done counts as free. The summary reads *"First in each chain"*, and the widget
follows the same rule.

### Ordering, hiding, and which one opens first

- **Drag to reorder.** The order in Settings is the order of the pills, and **the first one is what a
  list opens on**. Put "Today" first and Cotask opens on today, everywhere.
- **Hide a built-in** with the eye button if you never use it. Built-ins cannot be deleted, only
  hidden, and at least one always stays visible.
- **Delete a custom filter** by swiping it away. Any list sitting on it falls back to the first filter.

### Recipes

- **Today** — Status: Todo + Doing · Due: today or overdue · Sort: priority.
- **Inbox from my agents** — Status: Draft · Added by: an agent.
- **Waiting on something** — Dependencies: blocked · Sort: due date.
- **Next up** — Status: Todo + Doing · Hide dependency chains · Sort: priority.
- **This week at work** — Project: Work · Due: next 7 days · Grouped off.
- **Nothing scheduled** — Status: Todo · Due: no due date · Sort: newest first.

## Dependency chains

**Where:** iPhone → Settings → **Workflow** → *Dependency chains*. Mac → Settings → **Workflow** →
*Dependency chains*.

On (the default), a blocked task sits indented under the task that blocks it, with a little elbow, so
a chain of dependent work reads as one thread instead of three unrelated lines. Off, every task sits
at the same level and blocked tasks are marked only by their hand icon.

The setting is per account and syncs, and is part of [Pro](#cotask-pro) — on Free it reads off. It
changes layout only — never which tasks a filter includes; for that, a filter can
[hide dependency chains](#what-a-filter-can-say).

## Files

**Where:** iPhone → Settings → **Files**. Mac → Settings → **Files**.

The screen shows a **Storage** meter — how much of your plan's space the files take, how many there
are and what is left, turning orange at 90% and red when full — and carries one switch.

- **Delete files when done** (on by default) — a task's files are deleted 48 hours after it is marked
  done. The task, its notes and its links stay; only the stored bytes go. Tidying runs in the
  background about once a day, so a file can outlive the window by a little, and moving a task out of
  Done before then keeps its files.
- **Links are never touched.** They cost nothing and always stay.

**Limits.** 100 MB for the whole account on Free, 5 GB with [Pro](#cotask-pro); an upload that
would go over is refused and offers the upgrade. 25 MB per file after compression. The apps compress before uploading: images are scaled to
2048 px on the longest edge and re-encoded, video is re-exported at 720p, anything else goes up as it
is — and is refused rather than silently mangled if it is over. An agent sending bytes inline is capped
at 8 MB; bigger files go through a `source_url` for Cotask to download.

**Reads are public by design.** Every file gets a long, unguessable URL that needs no credentials, so
an agent handed a task — or a webhook receiving one — can fetch the file with nothing to configure.
Nothing is listable: reaching a file means already knowing the whole path. Removing an attachment
through the API deletes the stored file too.

## Analytics

The third row of the sidebar: one page of what you have actually finished. Everything on it counts the
same thing — a task that is **done** — so every card adds up to the number at the top.

**Day by day.** A square for every day, weeks running left to right, darker where the day was busier. It
starts at the week you finished your first task and grows with you, up to a full year, and scrolls sideways
once it is longer than the screen. **Tap a square** and the card lists what you closed that day — each line
marked with the assistant's logo, or a person when it was you — and tapping a line opens the task.

**Your numbers.** Under the total: what you have finished this week, your current streak, your longest
streak, and your busiest day. A streak is days in a row with something finished; today being still empty
does not break it.

**The last fortnight.** A column a day for two weeks, each column split into the part you finished and the
part an agent did. Tap a column to name the day and read the split.

**Who did the work.** One bar for the whole account — you against every agent — then a line for you and
one for each assistant that has closed something, with its share. This is the page that answers "how much
of this did I actually do myself".

**Where the work went.** Finished tasks by project, biggest first; past the sixth they fold into "Other".

**Your week.** Which weekday your work lands on.

Who finished a task is read from your [history](#history-and-undo) — the entry that moved it to Done knows
whether it was you or an assistant, and which one. Tasks finished before your history reaches back read as
yours.

## Notifications

**Where:** iPhone → Settings → **Notifications**. The feed is the **Notifications** row of the sidebar.

Every change Cotask records can notify you. Pick which ones, whether it only counts when an agent
does it, and whether it goes to the feed, to your lock screen, or both.

| Event | When |
|---|---|
| **A task is drafted** | An agent suggests a new task — always agents only |
| **A task is added** | Any new task, whoever adds it |
| **A task changes status** | Any move, including completion |
| **A task is completed** | A task is marked done |
| **A task is edited** | Title, notes, project, due date, priority, repeat or blocker |
| **A link or file is attached** | An attachment is added to a task |
| **A task is delegated** | A task is sent to one of your [routes](#delegate-to-ai) |
| **A task is deleted** | A task is removed |
| **A project is created · edited · deleted** | The same, for projects |

Each event you switch on has three choices: **By anyone**, **By agents** or **By you**, and **Feed**
and **Push** — at least one of the two stays on; the row's switch is what turns an event off. Out of
the box three are on, by agents, to both: drafted, changes status, completed. "Agents" means anything
that talks to Cotask through the API — Claude, ChatGPT, Codex, a script with an agent key — and
every line says which one, with its logo.

**The feed** groups lines by day, each with the agent's logo (or the device, when it was you), what
happened, the task and its project. Unread lines carry a dot, and their count is the app icon's
badge. Tap a line to read it and open the task; swipe right for **Read**, left for **Delete**; the ⋯
menu has **Mark all as read** and **Clear all**. A push opens the task too, and Back returns to the
feed.

Pushes go to the iPhone; the screen shows whether iOS lets them through, with a way to fix it. The
Mac has no feed — its only notifications are the [Delegate Listener's](#the-delegate-listener-mac).
Notifications are part of [Pro](#cotask-pro); a Free account keeps the lines it has but gets no new
ones.

## History and undo

Every add, edit, status change and deletion of a task or a project is recorded — by the apps, with the
device it came from (Mac, iPhone, iPad, and whether it was the widget), and by the agent API, with the
agent's name.

**Settings → History** shows the timeline, each entry marked with a device icon or the assistant's
logo, so "who moved this to Done" always has an answer.

On the Mac, `⌘Z` and `⇧⌘Z` undo and redo those changes — task edits, status moves, deletes and
restores, links, projects. While you are editing a text field, that field's own undo runs instead.
On iPhone, completing a task offers **Undo** in the toast for four seconds.

## Agents and MCP

Cotask exposes one endpoint that any AI assistant can use as a tool. It speaks **MCP** (the Model
Context Protocol) and plain **REST**, at the same base, with the same permissions.

```
https://usecotask.com/mcp
```

Two ways to authorize, and the endpoint accepts either:

- **Sign in (OAuth 2.1)** — for ChatGPT, Claude and any MCP client with a connector UI. Nothing to
  paste; you approve on Cotask's own consent screen.
- **An agent key** — for terminals, scripts and Claude Code. A `pl_…` key you create in the app.

Whatever an agent creates lands in **Drafts** with the assistant's name and logo on it, until you
accept it.

### Connect ChatGPT or Claude (no key)

1. In the app: **Settings → Agents** → copy the **MCP server URL**.
2. In ChatGPT or Claude: **Settings → Connectors → Add custom connector** (the wording differs a
   little per client) and paste the URL.
3. The client discovers everything and sends you to Cotask's consent screen. Sign in with your
   Cotask account and approve.
4. Ask for something: *"look at my Cotask tasks and plan my week."* Suggestions arrive in Drafts.

Access tokens last an hour and refresh tokens rotate on every use, so the connection keeps working
without asking you again. Revoking is a matter of removing the connector in the client.

### Connect Claude Code, a terminal or a script

1. **Settings → Agents → New agent key**, name it after the tool ("Claude Code"), and copy the key —
   `pl_…`, shown once and never again.
2. Add the server:

```sh
claude mcp add --transport http cotask \
  https://usecotask.com/mcp \
  --header "Authorization: Bearer pl_your_key"
```

Any MCP client that takes a JSON config wants the same three things — an HTTP transport, the URL, and
the header:

```json
{
  "mcpServers": {
    "cotask": {
      "type": "http",
      "url": "https://usecotask.com/mcp",
      "headers": { "Authorization": "Bearer pl_your_key" }
    }
  }
}
```

Keys are stored hashed — Cotask cannot show you one again. Each key lists its prefix and when it was
last used, so an unfamiliar one is easy to spot and swipe away. Deleting a key cuts that agent off
immediately; nothing it created is touched.

### The tools

| Tool | Does | Takes |
|---|---|---|
| `list_tasks` | Lists tasks with their attachments | `status`, `project` (id or name), `priority`, `reminder` (`any` · `urgent` · `notify` · `none`), `query` (title substring), `limit` (default 200, max 500) |
| `get_task` | One task, with notes, reminder and attachments | `id` |
| `create_task` | Creates a task — **a draft by default** | `title` (required), `notes`, `status`, `project`, `due_date`, `priority`, `recurrence`, `remind_at`, `remind_in`, `urgent`, `reminder_kind`, `time_zone`, `blocked_by`, `attachments` |
| `update_task` | Changes only the fields passed | `id` (required), plus any of `title`, `notes`, `status`, `project`, `due_date`, `priority`, `recurrence`, `remind_at`, `remind_in`, `urgent`, `reminder_kind`, `time_zone`, `blocked_by` — `null` clears the optional ones. Also `completion_note`, the [note of completion](#note-of-completion) |
| `complete_task` | Marks a task done, with an optional [note of completion](#note-of-completion) — `notes` are never touched | `id` (required), `note` (Markdown; left out, `null` or blank completes with no note) |
| `set_reminder` | Sets, moves or clears when a task rings — and how loudly | `task_id` (required), `remind_at`, `remind_in`, `urgent`, `reminder_kind`, `time_zone` |
| `delete_task` | Retracts a suggestion — **drafts only** | `id` |
| `add_attachment` | Attaches a link; the title and favicon are resolved server-side | `task_id`, `url`, `title` (optional) |
| `attach_file` | Attaches a file — bytes or a URL to fetch | `task_id`, one of `data` (base64, ≤ 8 MB) or `source_url` (≤ 25 MB), `file_name`, `content_type`, `title` |
| `remove_attachment` | Removes a link or file (and deletes the stored file) | `id` |
| `list_projects` | The user's projects | — |
| `create_project` | Creates a project | `name`, `color` (gray, red, orange, yellow, green, teal, blue, indigo, purple, pink) |
| `list_delegate_routes` | Your [Delegate to AI](#delegate-to-ai) routes — never where a webhook posts | — |
| `delegate_task` | Delegates a task through a route, like the button: it starts (Doing, due today); a Mac route answers with its `dispatch_id` and whether a Mac is listening | `task_id`, `route` (id or name), `message`, `params` (add only), `send_at` / `send_in` |
| `get_delegation` | How a delegation is going: a run's `status`, `stage`, `error` and `result`, or a send still waiting | `id` (`dispatch_id` or `scheduled.id`) |

Every tool carries MCP annotations — `readOnlyHint` and `openWorldHint` on all of them, plus
`destructiveHint` and `idempotentHint` on the ones that write — and a description on every property,
so a model knows what is safe without being told in a prompt.

Delegating is there for when you ask for it — "send this to Claude Code on my Mac" — and only
through the routes you set up, as you set them up. See [Delegate to AI](#delegate-to-ai).

### Reminders, for agents

An agent asked to remind you of something has no clock of yours and no idea where you are, so it is
met more than half way. `set_reminder` is the direct way in — the same fields also live on
`create_task` and `update_task`.

| Field | What it takes |
|---|---|
| `remind_at` | `2026-09-01T09:00:00-03:00`, or `2026-09-01T09:00` **read on your own clock**, or `2026-09-01` for 09:00 that morning, or a delay: `45m`, `2h`, `1h30m`, `3d`. `null` clears the reminder |
| `remind_in` | Just the delay: `45m`, `2h`. For "in an hour" — needs no clock and no time zone. Wins over `remind_at` |
| `urgent` | `true` for the alarm that rings through silent mode and Focus; `false` (the default) for an ordinary notification |
| `reminder_kind` | The long form of `urgent`: `notify` or `alarm` |
| `time_zone` | An IANA name, to read a zone-less `remind_at` somewhere else — "9am in Tokyo" is `Asia/Tokyo` |

Your iPhone records its own time zone to your account, which is what a timestamp with no zone is read
on; until a phone has signed in, that is UTC. Two things are refused rather than written and never
rung: a kind with no moment to ring at, and a moment that has already gone by — and the refusal says
what time it is where you are, which is usually what the agent was missing.

Every task comes back with a `reminder` object as well as the raw columns:

```json
"reminder": {
  "at": "2026-09-01T12:00:00.000Z",
  "kind": "alarm",
  "urgent": true,
  "local_time": "Tue, 1 Sep 2026, 09:00 (America/Sao_Paulo)",
  "relative": "in 2 days",
  "has_passed": false
}
```

Tasks with no reminder have `"reminder": null`. An agent should only reach for urgent when you asked
for something urgent; see [Reminders](#reminders-and-the-urgent-one).

**What agents may not do:** delete a task you accepted, remove an attachment you added to a task you
accepted, delegate a task — to any route, now or later — create a Mac route or change how one runs,
see or change a route's token or secret, or delete your account. Those are yours, and need you
signed in to the app: holding an agent key yourself does not count.

**Pro, for agents.** An agent works inside your plan. Setting a reminder, a blocker or a note of
completion, or uploading past your storage, on a Free account answers `402` —
*"… requires Cotask Pro. Upgrade in the app to continue."* Everything already there stays readable,
and clearing a value is always allowed.

### Notes formatting

Notes are plain text with light Markdown. The apps render:

- line breaks, exactly as written
- `- ` bullets and `1.` numbered lists
- `[text](https://…)` links, and bare `https://…` URLs
- inline `**bold**`, `*italic*` and `code` in backticks

They do **not** render headings, block quotes, code fences, tables or images. This spec is repeated in
the MCP server's instructions and on the `notes` property of `create_task` and `update_task`, so an
agent finds it wherever it looks.

For sources, attach the page instead of pasting a long URL into the notes: an attachment shows the
page title and favicon and is fetchable later. For things an agent produced — a report, a chart, a
CSV — use `attach_file`.

### Note of completion

An agent that finishes a task can say what it did and where the result is. `complete_task` is the
direct way — it marks the task done and leaves the note in one call:

```json
{ "id": "…", "note": "Completed, appended to doc: [Q3 plan](https://…)" }
```

`update_task` does the same with `status` and `completion_note`:

```json
{ "id": "…", "status": "done", "completion_note": "Completed, appended to doc: [Q3 plan](https://…)" }
```

- It is a field of its own, `completion_note` — separate from `notes`, which stay your brief and are
  never touched by it. Every task comes back with it (`null` when there is none).
- It is Markdown, stored exactly as written — nothing is trimmed, shortened or rewritten.
- Agents can always leave one. **Completion notes** — iPhone → Settings → **Delegate to AI**, Mac →
  Settings → **Agents**; off by default — only decides whether the apps show it: both tools offer the
  field whatever it says, and a note written while it is off is kept and appears once it is turned
  on. Writing one is part of [Pro](#cotask-pro).
- You can leave one too, with it on: on the Mac, `⇧⏎` completes the selected task and asks for its
  note (`esc` skips, `⌘⏎` saves; on a task that is already done it opens the note to edit); on the
  iPhone, press and hold a task's circle.
- It belongs to the completion: `update_task` accepts it together with `status: "done"` or on a task
  that is already done, and refuses it on any other. Reopening the task clears it, whoever reopens
  it and from wherever — that is done in the database.
- Each `complete_task` call is a completion of its own: with no `note`, a note left by an earlier
  completion is cleared; `null` or a blank string clears it explicitly.
- On a done task `update_task` changes it on its own: pass a new `completion_note` to replace it,
  `null` (or a blank string) to clear it, or leave it out to keep it.

The same field is accepted by `PATCH /tasks/:id` on the REST API.

## REST API

Same base, same auth, for anything that is not an MCP client.

```
BASE=https://usecotask.com/api/agent
AUTH="Authorization: Bearer pl_your_key"
```

| Method and path | Does |
|---|---|
| `GET /` | Service info: the MCP URL, the REST base, how to authorize |
| `GET /tasks` | List tasks — `?status=&project=&priority=&reminder=&query=&limit=` |
| `POST /tasks` | Create a task (a draft unless you pass `status`) |
| `GET /tasks/:id` | One task with its attachments |
| `PATCH /tasks/:id` | Update the fields you pass (`PUT` behaves the same) |
| `DELETE /tasks/:id` | Delete — drafts only, for agents |
| `GET /tasks/:id/attachments` | The task's links and files |
| `POST /tasks/:id/attachments` | Attach a link: `{url, title?}` |
| `DELETE /tasks/:id/attachments/:attachmentId` | Remove an attachment |
| `POST /tasks/:id/files` | Attach a file: `{data}` base64 or `{source_url}` |
| `GET /projects` · `POST /projects` | List · create projects |
| `GET /preview?url=…` | Resolve a page's title and favicon without attaching it |
| `POST /uploads` | A signed upload URL for one object — the apps' three-step upload |
| `PUT /tasks/:id/reminder` · `DELETE /tasks/:id/reminder` | Set when a task rings and how loudly · take the reminder off |
| `POST /delegate` | Send a task to one of your routes — app session only. See [Delegate through the API](#delegate-through-the-api) |
| `GET /delegations` · `DELETE /delegations/:id` | Scheduled sends (`?status=` — waiting by default, or `all`) · call one off while it waits |
| `GET /webhooks` · `POST /webhooks` | List · create routes: `{name, url, params?}` for a webhook, `{name, kind: "mac", runner, model?, effort?, params?}` for a Mac route (app session only) |
| `PATCH /webhooks/:id` · `DELETE /webhooks/:id` | Change · remove a route. `params` replaces the whole set |
| `POST /webhooks/:id/token` · `PUT /webhooks/:id/secret` · `DELETE /webhooks/:id/token` | A Mac route's inbound token (new or rotated) · a webhook's signing secret · revoke either — app session only, shown once |
| `POST /inbound/:id` · `GET /inbound/:id/:dispatch` | Queue work for a Mac route · how it went — authorized by the route's `prt_` token, not your key |
| `POST /storage/gc` | Sweep files no attachment points at — app session only |
| `DELETE /account` | Delete the account and everything in it — app session only |

```sh
curl "$BASE/tasks?status=todo" -H "$AUTH"

curl -X POST "$BASE/tasks" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"title":"Review the Q3 plan","project":"Work","due_date":"2026-09-01","priority":"high"}'

curl -X PUT "$BASE/tasks/<id>/reminder" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"remind_at":"2026-09-01T09:00","urgent":true}'   # 9am your time, as an alarm

curl -X PUT "$BASE/tasks/<id>/reminder" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"remind_in":"45m"}'                              # in three quarters of an hour

curl -X PATCH "$BASE/tasks/<id>" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"status":"doing"}'

curl -X POST "$BASE/tasks/<id>/attachments" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"url":"https://www.notion.so/Meeting-notes-…"}'

curl -X POST "$BASE/tasks/<id>/files" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"source_url":"https://example.com/q3.pdf"}'
```

Responses are JSON: `{"task": …}`, `{"tasks": […]}`, `{"attachment": …}`. Errors are
`{"error": "…"}` with a real status code — `400` bad input, `401` missing or expired credentials
(with a `WWW-Authenticate` header pointing an MCP client at the OAuth flow), `402` a
[Pro](#cotask-pro) feature, or storage full, on a Free account, `403` something only you may do,
`404` not found, `409` a Mac route with nothing to run, `413` a file or a body over the limit, `429`
too many requests, `502` a webhook that could not be reached or did not answer `2xx`.

Every task comes back with its `attachments`: `id`, `title`, `url`, `kind` (`link` or `file`),
`created_by`, plus `content_type` and `byte_size` on files. `url` is always fetchable with no
credentials.

### OAuth endpoints

Implemented for MCP clients that discover them automatically. You never call these by hand.

| Endpoint | Purpose |
|---|---|
| `GET /.well-known/oauth-protected-resource` | RFC 9728 — points at the authorization server |
| `GET /.well-known/oauth-authorization-server` | RFC 8414 — endpoints and `S256` PKCE |
| `POST /register` | RFC 7591 dynamic client registration |
| `GET /authorize` | Redirects to Cotask's hosted consent screen |
| `POST /authorize` | Verifies your credentials and mints a single-use code |
| `POST /token` | `authorization_code` (PKCE-verified) and rotating `refresh_token` |

Only SHA-256 hashes of codes and tokens are stored.

## Delegate to AI

MCP lets an assistant come to Cotask. **Delegate to AI** is the other direction: you push one task
out to an agent you run. Each place a task can go is a **route**, and there are two kinds:

- **Claude Code or Codex on your Mac** — nothing to host. Cotask keeps the endpoint; the Cotask
  Mac app's [Delegate Listener](#the-delegate-listener-mac) picks the task up and runs it through the
  CLI. See [Run it on your Mac](#run-it-on-your-mac).
- **A webhook URL** — an n8n flow, a Lambda, a bridge of your own, anything that accepts a POST.

You set routes up; delegating through them is yours, and — when you ask it to — an assistant's you
connected: `delegate_task` over [MCP](#the-tools) (or an agent key on `POST /agent/delegate`) is the
same hand-over as the button. An assistant uses a route exactly as you set it up: it can add
instructions and custom parameters, never change the route's model, effort, folder or access. So a
route with full access to a folder is one any assistant you connected can start — keep full access
for routes you'd hand to them. Delegate to AI is part of [Pro](#cotask-pro).

### Configure a webhook

**Where:** iPhone → Settings → **Delegate to AI** → **New route** → **Webhook URL**. Mac → Settings →
**Agents** → **New Route** → **Webhook URL…**.

1. **Name it after the agent behind it** — Claude, ChatGPT, n8n… The name picks the logo, exactly like
   an agent key, so a list of routes reads as a list of agents. Left blank on the Mac, it takes the
   URL's host.
2. Paste the URL Cotask should POST to. It has to be public — `localhost`, private addresses,
   `.local` and `.internal` are refused. Each route shows where it goes and when it was last used.
3. Optionally add [parameters](#parameters-and-secrets) and a [signing secret](#parameters-and-secrets)
   — tap the route on the iPhone, or its slider button on the Mac, to edit it.

### Run it on your Mac

A Mac route hands the task to **Claude Code** or **Codex** on your own Mac, from the command line,
with no server of yours in between.

**Where:** iPhone → Settings → **Delegate to AI** → **New route** → **Claude Code or Codex on my Mac**.
Mac → Settings → **Agents** → **New Route** → **Claude Code or Codex on This Mac…**. Both apps edit the
same routes with the same editor — CLI, model, effort, access, folder, parameters and the inbound
token; the Mac lists each as *"Claude Code · opus · high on your Mac"*, with a slider button to edit it.

1. **Pick the CLI** — Claude Code or Codex.
2. **Pick the model and effort.** The lists are not written into the app: they are what the CLI on
   your Mac says it offers, read when its listener last checked (Claude Code through its SDK
   handshake, Codex through its app server), so a new model shows up the day your CLI has it. Each
   model offers only the efforts it takes. When your Mac hasn't reported yet, or a CLI can't list its
   models, type a model id instead — it is checked here, again on the Mac, and the CLI has the last
   word. Leave both blank for the CLI's own defaults.
3. **Access** — how much it may do:
   - **Read only** — reads and answers, changes nothing. Claude Code gets only its Read, Glob and
     Grep tools, ignores settings files (and the permissions and hooks in them) and starts no MCP
     server (`--restricted --tools Read,Glob,Grep --strict-mcp-config --permission-mode dontAsk`).
     Codex runs in its read-only sandbox without your `~/.codex/config.toml`, so none of its MCP
     servers start either (`--sandbox read-only --ignore-user-config`). Read only stops changes; it
     doesn't hide files — Codex's sandbox can still read outside the folder. With no MCP server it
     also can't [mark the task done](#what-the-cli-is-told) — its answer is in the run's log.
   - **Edit the folder** (default) — edits files in its folder; anything that would need approval is
     refused (`acceptEdits` · `workspace-write`).
   - **Full access** — runs anything without asking (`--dangerously-skip-permissions` ·
     `danger-full-access`). Only for folders you would hand to it unattended.
4. **Folder** — where it runs, like `~/Projects/app` (**Choose…** on the Mac). Blank uses the Mac's
   **Default folder** (Settings → Delegate Listener; your home folder unless you choose another). It
   must exist on the Mac and sit inside your home folder once links are followed; absolute or `~`
   paths only, no `..`.

**Project folders** (optional) — iPhone → Settings → **Delegate to AI** → **Project folders**; Mac →
Settings → **Agents** → **Project folders**. Give a Cotask project a folder, and its tasks run there
whichever Claude Code or Codex route they're sent to. The order, most specific first: a folder typed
for one send → the task's project folder → the route's folder → the Mac's default folder. Leave a
project blank to keep using the route's.

Through the API, a Mac route's folder and access are two of its parameters — `cwd`, and `access` as
`read_only`, `workspace_write` or `full_access`.

The iPhone shows your Mac above the routes: whether it is listening, and each CLI's version, whether it
is signed in, and how many models it offers.

Delegating to a Mac route queues the task. The toast says **Sent to Claude Code** when a Mac is
listening — its listener checked in within the last three minutes — and **Queued for Claude Code — it
runs when your Mac is listening** when none is. Work nobody picks up within a day expires rather than
starting late and by surprise. Only you, signed in to the app, can create a Mac route or change how
it runs; an agent key can rename one, not give it more access.

### The Delegate Listener (Mac)

**Where:** Mac → Settings → **Delegate Listener** → *Listen for delegated tasks*. **Off by default.**

Switched on, Cotask sits in the menu bar and listens — Realtime for the moment a task arrives, and a
check every 20 seconds so nothing is missed across sleep or a dropped connection. It checks in every
minute, which is what the iPhone reads as *listening*. For each task it:

1. **claims it** — one conditional update, queued → running, that only one Mac can win, so a task
   never runs twice, even with Cotask open on two Macs;
2. **posts a notification** — "Sent to Claude Code", with the route, the model and the task's title
   (never its parameters);
3. **runs the CLI** — started directly with an argument list (no shell), the
   [prompt](#what-the-cli-is-told) piped to its standard input, in the route's folder, with the
   route's model, effort and access:
   - `claude -p --input-format stream-json --output-format stream-json --verbose --replay-user-messages <access> [--model …] [--effort …]`
   - `codex exec --json --skip-git-repo-check --color never --cd <folder> <access> [--model …] [-c model_reasoning_effort="…"] -`

   Both stream their events as JSON lines, so the run's log is its transcript — what **Get summary**
   reads. Claude Code's input stays open while it works, so a [follow-up](#follow-ups-on-the-mac) can
   be written into it.
4. **records how it went** — done, or failed with the end of what it printed (anything shaped like a
   key or token is blanked out), and a notification either way: "Claude Code finished", "Claude Code
   failed" with the reason, or "Couldn't start Claude Code" when the model, effort or folder is
   refused before anything runs.

That record is the run's, not the task's: the listener never completes a task or writes on it. The
agent does that itself, through the Cotask MCP server, when it has one — which is what the prompt
asks of it. A run that fails leaves the task in Doing, and says so on the Mac.

Up to **five runs at once** by default, Claude Code and Codex together, oldest first — **Runs at
once** in the pane changes it (1 to 20). When that many are going, the rest wait in the queue until one
finishes. A run is stopped after two hours (*"Stopped after 120 minutes."*). **Switched off**, it
takes nothing new — no claim, no run — and runs already going finish unless you stop them.
**Quitting** Cotask, or **signing out**, stops them as well, and they are recorded as canceled.

The menu-bar item shows what it is doing, each run in progress with **Stop This Run** (and **Stop All
Runs** when there are several), each CLI's health, the last five runs, **Check CLIs Again** and **Stop
Listening**. Removing the icon from the menu bar switches the listener off.

The pane also shows the health of each CLI, checked natively: installed, version, signed in
(`claude auth status --json`, `codex login status`), and the models it offers. **Check again**
re-reads them, and a path can be set by hand when a CLI lives somewhere unusual — otherwise Cotask
looks in your login shell's `PATH` and the usual install folders (Homebrew, npm, bun, Volta,
`~/.local/bin`, `~/.claude/local`). **Running now** lists the runs in progress, each with **Stop**;
**Recent runs** lists the last eight with their errors, and a button to show each run's log in the
Finder. Each CLI also has a **Summary model** — what **Get summary** asks for (below); blank uses
`sonnet` for Claude Code and `gpt-6-luna` for Codex.

Each run's full output is logged under `~/Library/Logs/Cotask/Delegate Listener/<dispatch id>.log`,
readable by you only. The log is the raw output — only the error and the notifications are scrubbed.

### Follow-ups on the Mac

A follow-up goes back into the conversation the task already has, instead of starting Claude Code or
Codex over with the whole task:

- **While the run is still going**, it is **steered** in. Claude Code gets it on its input and reads it
  mid-task — at its next step, without stopping. Codex can't take anything once it is going, so the
  step it is on is interrupted and the same thread resumed at once with the follow-up; it keeps
  everything it had done and said. Either way the follow-up needs no free slot, the run's two hours
  start again, and both runs end together, with the same summary. **Cancel run** on either stops the
  conversation.
- **Once the run has ended** — done, failed or stopped — the follow-up **resumes** its session
  (`claude --resume <session>`, `codex exec resume <thread>`), with the route's access as before.
- Only when the session isn't on this Mac — the run was on another Mac, the session was deleted, or
  the route now uses the other CLI — does it start **a new session** with the whole prompt.

The Mac reports each session's id as the CLI names it, and the follow-up's *working* line in the
timeline says which it was: *Sent into the run that was already going*, *Continued the earlier
session*, or *Started a new session*. A follow-up whose run is going on another Mac is left for that
Mac. Steering and resuming share the run's log, so **Get summary** on a follow-up reads the whole
conversation.

The CLI inherits the app's environment, plus `PARALLEL_DISPATCH_ID`, `PARALLEL_ROUTE_ID`,
`PARALLEL_TASK_ID` (when there is a task), and every route parameter as `PARALLEL_PARAM_<NAME>` —
`PARALLEL_PARAM_CWD` and `PARALLEL_PARAM_ACCESS` included.

### More than one Mac

With one Mac nothing here appears. Once two or more Macs have listened in the last 30 days:

- A Mac route's editor gets **Runs on**: *Any Mac* (the default — the first Mac listening takes the
  run) or one Mac by name. Only that Mac can take the route's runs; moving the route moves its queued
  runs with it, and a waiting run says which Mac it is waiting for.
- **Project folders** get **Folders on**: a folder for every Mac, and a folder for one Mac that wins
  on that Mac.
- Settings shows each Mac's status, and a route's editor shows its Mac's CLIs and models.

Over the API, a route's `listener_id` names its Mac, and `list_delegate_routes` reports each route's
`device`.

### Usage limits

**Where:** iPhone → Settings → **Delegate to AI** → **Usage limits**. Mac → Settings → **Usage Limits**. Both appear once a
Mac listens for Mac routes; with none, there is nothing to show and neither does.

How much of your Claude Code and Codex plans is used, Mac by Mac — each Mac is signed in to its own
CLIs, so with two Macs on two accounts you see both. For each CLI: the plan (*Max*, *Pro Lite*) and a
bar per limit — the current 5-hour session, the week, and the week for a single model when the plan
has one — with how much is used and when it resets. Claude Code's bars are in Claude's orange, Codex's
in white; any bar turns warning orange at 80% and red at 95%. The Usage limits row shows the tightest
limit of each CLI across your Macs: *Claude 64% · Codex 38%*.

The Mac asks each CLI itself, and it costs nothing — no prompt is sent: Claude Code's `get_usage`
request over `--input-format stream-json`, and Codex's `account/rateLimits/read` over `codex
app-server`. It asks when the listener starts, every five minutes, and after every run, and
publishes only the plan, the percentages and the reset times — never the account behind them. Each
Mac's card says when it was last updated, or *Offline* and when it was last heard from; a window whose
reset time has passed reads 0% until the Mac checks again. A CLI signed in with an API key has no plan
limits, and says so. On the Mac, **Check This Mac Now** asks again at once.

### What the CLI is told

The prompt is written by Cotask's server, once, so every Mac gets the same words:

```
You have been handed a task from Cotask (the user's task manager) through the "Claude Code" route.

Task: Draft the release notes
Task id: …
Status: doing
Project: Cotask
Due: 2026-09-01
Priority: high

Notes:
…

Attachments (public URLs you can fetch):
- Spec — Notion: https://…

Instructions from the user:
Match the tone of the last one.

Parameters:
- branch: main
- access: workspace_write

When you are done, reply with a short summary of what you did and where the result is. If the
Cotask MCP server is available to you, mark the task done with that summary as its completion note.
```

Lines with nothing to say are left out. Work queued with a `prompt` and no task gets the request in
place of the task. The whole prompt is capped at 60,000 characters.

A [follow-up](#follow-ups-on-the-mac) that goes back into the session is told only what is new — the
agent already has the rest:

```
A follow-up from the user on the task they handed you from Cotask through the "Claude Code" route.

Also update the changelog.

The task as it stands now:
Task: Draft the release notes
…
```

With nothing typed, it says the task was sent again and asks the agent to carry on where it stands. A
follow-up that has to start a new session gets the whole prompt above, its instructions included.

To have the agent close the loop, give the CLI the [Cotask MCP server](#connect-claude-code-a-terminal-or-a-script)
in its own configuration: it then calls `complete_task` with a [note of completion](#note-of-completion)
and the task leaves Doing on every device.

### Parameters and secrets

A route can carry **parameters** — up to 20 names with text values, like `branch: main` or `board: ops`.
A name is `snake_case`, starts with a letter and is at most 40 characters; a value is at most 1,000
characters, on one line. A webhook receives them as `params`; a Mac route puts them in the prompt and
the environment. They are visible wherever the route is, so a name that *contains* a credential word —
`secret`, `token`, `password`, `passwd`, `api_key`, `apikey`, `bearer`, `credential`, `private_key` —
is refused, `max_tokens` included: a credential belongs in the route's **secret**, which is kept apart
and never shown again after the moment it is created.

- **A webhook's signing secret** — generate one (`pws_…`), or use your own: 16–200 printable
  characters, no spaces. Every delegate is then sent with `Authorization: Bearer <secret>` and
  `X-Cotask-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">`, so the receiver
  can check it is Cotask, and that the body is fresh and untouched.
- **A Mac route's inbound token** — `prt_…`, lets something other than the app queue work for it.
  See [Queue work from anywhere](#queue-work-from-anywhere).

Either is shown once — copy it then — and afterwards only as its last four characters. Generating a new
one replaces the old at once; removing it revokes it.

A single delegate can add or override parameters, and pick another model or effort for a Mac route —
see [Delegate a task](#delegate-a-task). Through the API: `params`, `model` and `effort` on
`POST /delegate`. On a send made now, a parameter set to `null` drops the route's for that send.

### Queue work from anywhere

A Mac route with an inbound token has its own endpoint, hosted by Cotask — a script, a Shortcut or an
n8n flow can send work to your Mac with it:

```sh
curl -X POST "https://usecotask.com/api/agent/inbound/<route id>" \
  -H "Authorization: Bearer prt_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: nightly-2026-09-26" \
  -d '{"prompt":"Run the test suite and summarise the failures."}'
# 202 {"queued":true,"dispatch_id":"…","dispatch_status":"queued","listener":"online"}
```

The token also goes in an `X-Cotask-Token` header, for tools that can't set `Authorization`.

The body is JSON, at most 64 KB, with only these fields: `prompt` (up to 20,000 characters) and/or
`task_id` (one of your tasks — its title, notes and attachments go into the prompt), and optionally
`message` (up to 4,000), `params` and `follow_up_of`. Anything else is refused.

`follow_up_of` is the `dispatch_id` of an earlier request to the same route: the new one is a
[follow-up](#follow-ups-on-the-mac), steered into that run while it goes or resuming its session after
— `{"follow_up_of":"…","message":"Now only the ones touching auth."}` is enough. A request with a
`task_id` needs none: it follows up the task's latest run on the route by itself. The answer names
the run it follows (`"follow_up_of"`); another route's run, or none, is `404`. The token only works for its own
route; only its SHA-256 is stored, and a wrong token, another route's, or a webhook's id all get the
same `401`.

A token queues work; it doesn't decide how it runs. The route's model, effort, folder (`cwd`) and
access are yours, set on the route: a request that sends `model` or `effort`, or a `cwd` or `access`
parameter — to change it or to clear it — is refused with `403`, and one that tries to clear any of the
route's parameters with `400`. Other parameters are added to the route's, or override them.

A route takes at most 10 new requests a minute (`429` past that), whatever their `Idempotency-Key`.
Repeating a key that already queued work returns that work (`"duplicate": true`) and queues nothing —
even over the limit. A key is remembered per route for as long as its run is — seven days after it
finishes. `GET …/inbound/<route id>/<dispatch id>` with the same token says how it went — `status`
(`queued`, `running`, `done`, `failed`, `canceled`, `expired`), `exit_code`, `error`, the model and
effort, when it was claimed and finished, and for a follow-up the run it follows (`follow_up_of`) and
how it went in (`continuation`: `steered`, `resumed` or `fresh`) — never the prompt.

### Delegate a task

Open the task → **Delegate to AI** → pick the route. You can type a line of instructions with it,
for when the task alone says too little. Sending starts the task: the app moves it to **Today** and
to **Doing**, and the send is recorded in History. The toast says where it went — **Sent to Claude**,
or for a Mac route, **Queued for …** when no Mac is listening.

While it runs, the task shows the agent's mark and **Delegated to Claude Code**, shimmering — a task
you moved to Doing yourself never does, because Cotask remembers who a task was handed to, not just
that it is in progress. That memory is the device's: a task sent from the iPhone reads as delegated
on the iPhone, and a scheduled send on every device.

From then on the action reads **Follow up** and goes back to the same agent: same send, but for a task
already being worked on — say what changed, what to correct, or what to do next. It returns to
**Delegate to AI** once the task is done.

A follow-up continues the same conversation rather than starting another. On a Mac route, Claude Code
or Codex gets it **while it is still working** — steered into the run, not queued behind it — or, if
the run has finished, in **the same session**, with everything it did before. See
[Follow-ups on the Mac](#follow-ups-on-the-mac). A webhook is told the send is a follow-up and which
conversation it belongs to (see [What arrives](#what-arrives)).

**Options** change one send without touching the route: other parameters (for any route), and another
model or effort (for Claude Code or Codex — picked from what your Mac's CLI reported, or typed). On the
iPhone they're under **Options** in the delegate sheet — the plain alert has **Options…** to open it.
Names that look like credentials are refused, as they are on the route.

On the Mac: `⌘K → Delegate to AI` (`Follow up`, once it has been sent) — pick the agent, type
the line of instructions, ⏎ sends. The same field opens under the buttons on a task's card, where the
slider button next to **Send** opens this send's options: a model and an effort field — with menus of
what your Mac's CLI reported — and parameters as one line, `branch=main, ticket=PAR-7` (so a value
can't hold a comma).

With no route yet, delegating says where to add one: Settings → Delegate to AI on the iPhone,
Settings → Agents on the Mac.

### Watch, stop or summarise a run

A task sent to a Mac route shows where its run is — waiting for your Mac, received, working,
finished — and tapping that line opens the run: its steps, how it ended, earlier attempts. While it
runs:

- **Cancel run** stops it. Before a Mac has taken it, it's simply called off; once one has, the Mac
  hears the request at once, stops the CLI, and records the run as *Canceled from Cotask.* Either
  way the task goes back to To Do.
- **Get summary** asks the Mac running it how it's going. The Mac reads the run's transcript so far
  (its log), boils it down to what the agent said and did, and hands that to a small model through
  the same CLI — Sonnet for Claude Code, GPT-6 Luna for Codex, or the **Summary model** set in the
  Delegate Listener pane — in a session of its own, with no tools, settings, hooks or MCP servers, so
  the run itself is never touched. A few seconds later **Progress so far** shows the summary;
  **Update summary** asks again. Only that Mac has the transcript, so it needs to be listening.

### Send it later

**Where:** iPhone → Settings → **Delegate to AI** → **Schedule delegate**. Mac → Settings → **Agents**.
Off by default: Send means now, as it always has.

Turned on, delegating asks one more thing — when. **Now** is still the first answer and the one it
opens on; next to it are the moments worth a single tap (in an hour, this evening at 18:00 — until
it has passed, tomorrow morning and next week at 09:00) and a picker for anything else. Pick one and
the button reads **Schedule**.

The waiting is not the app's. Cotask keeps the send on the server and makes it at the moment you
chose, so it goes out with the app closed, the phone asleep, or neither to hand. Until then the task
is untouched — it isn't started and nobody has it. It shows a line under its chips:

> Going to Claude Code · Tomorrow 09:00 — **Send now** · **Cancel**

**Send now** stops the waiting: the send goes out at once, exactly as it would have at its moment.
Cancel while it is still waiting and nothing is sent. When the moment comes, everything a delegate
does happens at once: the POST (or, for a Mac route, the queue), the history line, **Sending to
Claude Code**, and — as soon as an app syncs — Today, Doing and **Delegated to Claude Code**. A send
that fails — the webhook refused it, or couldn't be reached — says so on the task with the reason
and a **Dismiss**, and is not retried: a webhook that starts an agent on a task would start it twice.
iPhone Settings counts the sends still waiting.

### Delegate through the API

`POST /delegate` is the same send, for a script of yours signed in as you — the apps use it. Agent
keys and connected assistants get `403`.

| Field | What it takes |
|---|---|
| `task_id` | The task (required) |
| `webhook_id` | The route — webhook or Mac (required) |
| `message` | The line of instructions — trimmed, and cut at 4,000 characters |
| `params` | Parameters for this send, added to the route's or overriding them |
| `model` · `effort` | Another model or effort for this send — Mac routes only, `400` on a webhook |
| `send_at` · `send_in` | Send later — read on your own clock exactly like a reminder: `"2h"`, `"2026-09-01T21:00"`, or a timestamp with its own zone. A bare number in `send_in` is minutes. Needs [Schedule delegate](#send-it-later) on; a moment more than a minute gone is refused |

What comes back:

```json
{ "delegated": true, "webhook": "Claude", "status": 200, "follow_up": false }
{ "delegated": true, "webhook": "Claude Code", "status": 202, "queued": true,
  "dispatch_id": "…", "dispatch_status": "queued", "follow_up_of": "…", "listener": "online" }
{ "scheduled": { "id": "…", "status": "pending", "send_at": "…", "local_time": "…", "relative": "in 2 hours",
  "message": "…", "webhook": { "id": "…", "name": "Claude" } } }
```

— a webhook (`status` is the receiver's; `follow_up` says whether the task had gone there before), a
Mac route (`follow_up_of` is the run it follows up, when it does), and a send for later. The server sends and
records; moving the task to Today and Doing is the apps' part, so a script that wants that sets it with
`PATCH /tasks/:id`.

`GET /delegations` lists the sends still waiting, each with its `status`, `send_at`, `local_time`,
`relative`, `message`, `webhook`, and `sent_at` and `error` once it has gone; `?status=all` includes the
ones that went. `DELETE /delegations/:id` calls off one that is still waiting.

### What arrives

This is what a webhook receives (a Mac route gets the same task, [written as a prompt](#what-the-cli-is-told)).
A POST with `Content-Type: application/json`, `User-Agent: Cotask/1.0 (delegate)`, an
`X-Cotask-Event: task.delegated` header and an `X-Cotask-Delivery` id — plus `Authorization` and
`X-Cotask-Signature` when the route has a [signing secret](#parameters-and-secrets). A scheduled send
is the same POST, made at the moment you picked:

```json
{
  "event": "task.delegated",
  "webhook": { "id": "…", "name": "Claude" },
  "task": {
    "id": "…",
    "title": "Draft the release notes",
    "notes": "…",
    "status": "todo",
    "project": { "id": "…", "name": "Cotask" },
    "due_date": "2026-09-01",
    "priority": "high",
    "recurrence": null,
    "blocked_by": null,
    "reminder": null,
    "completion_note": null,
    "attachments": [
      { "id": "…", "kind": "link", "title": "Spec — Notion", "url": "https://…", "created_by": "user" },
      { "id": "…", "kind": "file", "title": "shot.png", "url": "https://…", "created_by": "user",
        "content_type": "image/png", "byte_size": 184320 }
    ],
    "…": "…"
  },
  "message": "Match the tone of the last one.",
  "params": { "board": "ops" },
  "delegated_at": "2026-08-26T21:14:00.000Z"
}
```

`task` is the same object `get_task` returns — with its reminder, who created it, and when — and
`project` is `null` when it has none. `message` is `null` when you typed nothing. `params` is there only
when the route (or that delegate) has parameters, so a webhook set up before them receives exactly
what it always has.

**Follow-ups.** When the task has gone to this webhook before — a **Follow up**, a retry, or a
scheduled send — the payload also carries `follow_up`, so the receiver can put it in the conversation
it already has instead of starting another. The event is still `task.delegated`, so a receiver that
ignores it keeps working as it did:

```json
"follow_up": {
  "sequence": 2,
  "previous_delivery_id": "…",
  "previous_delegated_at": "2026-08-26T21:14:00.000Z",
  "thread_id": "conv_42"
}
```

`sequence` counts the sends of this task to this webhook (2 is the first follow-up), and
`previous_delivery_id` is the earlier send's `X-Cotask-Delivery`. `thread_id` is yours: answer any
send with JSON carrying a `thread_id` — `{"thread_id": "conv_42"}`, up to 200 characters — and
Cotask keeps it and sends it back with every follow-up of that task (`null` until you do). `task.id`
stays the same throughout, so keying on it works too. A first send has no `follow_up` at all.

To check a signature, recompute it over the raw body:

```js
const [, t, v1] = req.headers["x-parallel-signature"].match(/^t=(\d+),v1=([0-9a-f]{64})$/);
const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${rawBody}`).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
  && Math.abs(Date.now() / 1000 - Number(t)) < 300;
```

Every attachment `url` is fetchable with no credentials, so the receiving agent can read the brief and
its files immediately. Cotask waits up to 20 seconds for a response and reports a failure back to
you — a webhook that answers anything other than `2xx` shows as an error rather than failing silently.

Give the agent an agent key too, and the loop closes: it receives the task, works, and writes results
back onto the same task with `complete_task`, `update_task` or `attach_file`.

## Hand a task to a model

The **Copy prompt** button in a task's toolbar copies one line:

```
Work on my Cotask task <task id>.
Look it up with the Cotask MCP server (get_task) — it holds the brief and its attachments.
```

It carries the id and nothing else, on purpose. Any model connected to Cotask reads the task
itself, so it sees the notes and the attachments as they are now rather than as they were when you
copied — and anything it produces can go straight back onto the same task.

## Cotask Pro

**Where:** iPhone → Settings → the **Cotask Pro** card at the top. Mac → Settings → **Account**.

Cotask is free for the essentials — tasks, projects, the built-in filters, links and files, the
widget, history, analytics, and every agent through MCP and REST. **Cotask Pro** is one
subscription, monthly or yearly, that adds:

| Feature | What it unlocks |
|---|---|
| **More file storage** | 5 GB instead of 100 MB — see [Files](#files) |
| **Delegate to AI** | Routes, the Mac's Delegate Listener, and Schedule delegate — see [Delegate to AI](#delegate-to-ai) |
| **Notifications and reminders** | The feed, pushes and reminders — see [Notifications](#notifications) |
| **Urgent alerts** | Reminders that ring as an alarm — see [Reminders](#reminders-and-the-urgent-one) |
| **Completion notes** | A note of what was done, left by you or an agent — see [Note of completion](#note-of-completion) |
| **Custom filter views** | Filters of your own, in the filter bar and the widget — see [Filters](#filters) |
| **Task dependencies** | Blocked by, and dependency chains — see [Blocked by](#blocked-by) |

You subscribe on the iPhone, where the card opens the plans and, once you have it, reads *Active*
with **Manage Subscription**. The Mac shows your plan — *Cotask Pro · 5 GB* or *Free · 100 MB* —
and picks the subscription up by itself; reaching for a Pro feature there says to upgrade on the
iPhone.

When Pro ends nothing is taken away: tasks, files, filters, notes and reminders you already have stay
readable, and clearing any of them is always allowed. What stops is setting new ones. Agents meet the
same rule, as a `402` — see [Pro, for agents](#reminders-for-agents).

## Sync, offline and your account

**Local-first.** Every change lands in the app instantly and syncs in the background. Changes made
elsewhere stream in over a realtime connection, so a task completed on the Mac disappears from the
phone's list as you watch.

**Offline.** The apps keep a full local cache and read from it at launch, so the list is there before
the network is, and every edit shows immediately. A change that cannot reach the server is not
silently swallowed: the app tells you and puts the list back to what the server has. Pull to refresh
(or `⌘R`) forces a sync.

**Signing in.** Email and password. New accounts get a confirmation email. The iPhone app shares its
session with the widget through a keychain group, so the widget is signed in whenever the app is.

**Deleting your account.** iPhone → Settings → **Delete Account**; Mac → Settings → **Account** →
**Delete account…**. It
permanently deletes the account and everything in it — tasks, projects, links, files, history,
filters, agent keys and any OAuth grants you approved. It cannot be undone.

**Privacy.** No analytics, no trackers, no ads. See the [privacy policy](/privacy) and the
[terms](/terms).

## Troubleshooting

**`⌃Space` does nothing on the Mac.** macOS uses it for "select previous input source". Free it under
System Settings → Keyboard → Keyboard Shortcuts → Input Sources.

**An assistant says it cannot delete a task.** By design — agents only delete their own drafts. Delete
accepted tasks yourself.

**A connector will not connect.** Check the URL ends in `/mcp`, and that you are pasting it as a
*custom connector*, not as an API key. If a key is what your client wants, create one under
Settings → Agents and send it as `Authorization: Bearer pl_…`.

**A task an agent created is not in my list.** It is in **Drafts**. Use the Active filter, or a filter
with Status: Draft, and accept it.

**My files disappeared from a finished task.** Files are deleted 48 hours after a task is marked done.
Turn that off in Settings → Files; links are never affected.

**The widget is empty.** Open the app once so it syncs, and check the widget's list under long-press →
Edit Widget — a filter that matches nothing shows nothing.

**A list opens on the wrong filter.** Lists open on the *first* filter in your order. Reorder them in
Settings → Filters.

**A task sent to a Mac route says Queued and nothing happens.** No Mac is listening. On the Mac:
Settings → **Delegate Listener** → switch it on, and keep Cotask running (it lives in the menu bar
while listening). Queued work waits a day, then expires.

**The Mac says Claude Code or Codex is not found, or signed out.** Cotask looks in your login shell's
`PATH` and the usual install folders; set the path by hand in Settings → Delegate Listener if yours is
elsewhere. Sign in once in Terminal — `claude auth login` or `codex login` — then **Check again**.

**My route's model list is empty.** The Mac hasn't reported yet (listener off), or that CLI couldn't
list its models. Type a model id instead — `opus`, `gpt-6-sol` — or leave it blank for the CLI's
default.

**A task sent to my Mac ran, but it is still in Doing.** The listener doesn't complete tasks; the agent
does, through the Cotask MCP server. Give the CLI that server in its own configuration — and note
that a **Read only** route starts none. What the run said is in Settings → Delegate Listener →
**Recent runs**, and its full output in the log beside it.

**A delegated task shows "Delegated to …" on one device only.** Who a task went to is remembered by the
device that sent it; the other device shows the task in Doing. Scheduled sends are the exception.

**An agent or a script gets `402`.** The account is on Free and the call sets something that is part
of [Pro](#cotask-pro) — a reminder, a blocker, a note of completion — or the files are over 100 MB.

**A parameter is refused.** Its name contains a credential word — `token`, `secret`, `password`, `key`
as in `api_key`… — even inside another word, like `max_tokens`. Rename it, or put the credential in the
route's signing secret.

## For language models

This page is available as plain Markdown at **[/docs.md](/docs.md)**, and as
[/llms.txt](/llms.txt) for the [llms.txt](https://llmstxt.org) convention. Both are served with CORS
open, so they can be fetched from anywhere.

- Everything a user can do, an agent can do through [MCP](#agents-and-mcp) or the
  [REST API](#rest-api), except what is the user's alone: delegating a task, setting up or arming a
  Mac route, route tokens and secrets, deleting accepted tasks, and deleting the account.
- A `402` means the user's plan doesn't include what you tried. Tell them; don't retry.
- If you were handed a task by a Cotask route, finish with `complete_task` and a `note` — that is
  what moves it out of Doing. See [What the CLI is told](#what-the-cli-is-told).
- Create tasks as **drafts** unless the user explicitly asked for something else.
- Attach sources as links and outputs as files rather than pasting URLs into notes.
- Notes take [light Markdown only](#notes-formatting).
- When you finish a task, use `complete_task` and say what you did in `note` — never in the task's
  `notes`. See [Note of completion](#note-of-completion).
- To make a task ring, use `set_reminder` — `remind_in: "45m"` when the user spoke in delays,
  `urgent: true` only when they asked for something urgent. See
  [Reminders, for agents](#reminders-for-agents).
- Release notes are machine-readable at [/api/changelog](/api/changelog).
