Push

The server sends pushes through the Apple Push Notification service (APNs) to the devices of an account. A device registers its tokens with PUT /v1/devices/{device_id}. See REST API.

Overview of the pushes#

Push apns-push-type Token Topic
Visible notification alert apns_token The bundle ID
Silent notification background apns_token The bundle ID
Live Activity start liveactivity push_to_start_token The bundle ID plus .push-type.liveactivity
Live Activity update and end liveactivity The token from PUT /v1/tasks/{task_id}/live-activity The bundle ID plus .push-type.liveactivity
Widget reload widgets widget_push_token The bundle ID plus .push-type.widgets

A device with the platform watchos gets the topic of the watch app, if the server has a bundle ID for it.

The server sends a push after it sends the HTTP response. A push does not make a request slower.

Who gets which notification#

Change Notification
An agent creates, updates, or deletes a task with notify: true Visible
An agent changes only progress or status_note Silent. This is a progress tick, and an agent sends many of them.
An agent changes a task with notify: false Silent
The app (an account token) changes a task Silent, so that the other devices of the account update
An agent calls notify_user Visible
An agent asks a question Visible
The status of a question changes Silent

Each notification goes to each device of the account that has an apns_token.

Task notifications#

event is task.created, task.updated, or task.deleted.

Visible#

JSON
{
  "aps": {
    "alert": { "title": "Claude Code", "subtitle": "Task done", "body": "Add dark mode support" },
    "sound": "default",
    "thread-id": "key_01jb2k3v9d",
    "category": "TASK",
    "interruption-level": "active"
  },
  "event": "task.updated",
  "task_id": "tsk_01jb2m9x4e7q",
  "key_id": "key_01jb2k3v9d"
}
Field Value
alert.title The label of the key
alert.subtitle New task, Task started, Task done, Task cancelled, Task updated, or Task deleted
alert.body The title of the task
interruption-level time-sensitive if the priority of the task is urgent, else active
apns-collapse-id header The task ID. A newer notification for the same task replaces the older one.

Silent#

JSON
{ "aps": { "content-available": 1 }, "event": "task.updated", "task_id": "tsk_...", "key_id": "key_..." }

Message notifications#

A notify_user call sends this notification. It has no collapse ID.

JSON
{
  "aps": {
    "alert": { "title": "Claude Code", "subtitle": "Approve the deploy", "body": "The build is ready." },
    "sound": "default",
    "thread-id": "key_01jb2k3v9d",
    "category": "MESSAGE",
    "interruption-level": "active"
  },
  "event": "message",
  "key_id": "key_01jb2k3v9d",
  "task_id": "tsk_01jb2m9x4e7q"
}
  • alert.subtitle is the title that the agent gives. alert.body is the body that the agent gives.
  • task_id is there only if the agent gave one. If that task is urgent, interruption-level is time-sensitive.

Question notifications#

New question#

JSON
{
  "aps": {
    "alert": { "title": "Claude Code", "subtitle": "Question", "body": "Deploy to production now?" },
    "sound": "default",
    "thread-id": "key_01jb2k3v9d",
    "category": "QUESTION_CONFIRM",
    "interruption-level": "time-sensitive",
    "mutable-content": 1
  },
  "event": "question.created",
  "question_id": "qst_01jb2n4r8t",
  "kind": "confirm",
  "options": ["Yes", "No"],
  "task_id": "tsk_01jb2m9x4e7q",
  "key_id": "key_01jb2k3v9d"
}
Field Value
category QUESTION_CONFIRM, QUESTION_CHOICE, or QUESTION_TEXT, from the kind of the question
options The options of the question. [] for the kind text.
task_id The task of the question, or null
apns-collapse-id header The question ID
apns-expiration header The expires_at of the question. APNs does not deliver the notification after that time.

The app answers with POST /v1/questions/{question_id}/answer. See REST API.

Status change#

When a question is answered, expired, or cancelled, the server sends a silent notification with the same collapse ID. Each device then removes the delivered notification of that question.

JSON
{ "aps": { "content-available": 1 }, "event": "question.updated", "question_id": "qst_...", "key_id": "key_..." }

Live Activity#

The ActivityKit attributes type name is TaskActivityAttributes. The JSON keys below are exact, because ActivityKit decodes them with the default JSONDecoder.

Attributes:

JSON
{ "taskID": "tsk_01jb2m9x4e7q", "keyLabel": "Claude Code" }

Content state:

JSON
{
  "title": "Add dark mode support",
  "status": "in_progress",
  "progress": 40,
  "note": "Waiting for you",
  "updatedAt": 1760112300.9,
  "question": { "id": "qst_01jb2n4r8t", "prompt": "Deploy to production now?", "kind": "confirm", "options": ["Yes", "No"] }
}
  • progress and note can be null.
  • question is the oldest pending question of the task. It is null when the task has no pending question.

The server sends these pushes when the account setting live_activities is true.

Case Push
An agent moves a task to in_progress, or creates it with that status start to each device that has a push_to_start_token and no registered activity for that task
A task with a registered activity changes title, status, progress, or status_note update
A question for a task with a registered activity is created, answered, expired, or cancelled update

An update goes at once when the activity got none in the last 5 seconds. Else the server sends one update with the latest state of the task when those 5 seconds end. Thus an agent that sends many progress ticks gives one push in 5 seconds for each task, and the last change is never lost. Each Live Activity push has the APNs priority 10, because a push with the priority 5 can wait for a long time on a locked device. | The task becomes done or cancelled, or the task or its key is deleted | end, with the final content state and a dismissal date 15 minutes later |

Each payload has aps.timestamp, aps.event (start, update, or end), and aps.content-state. A start and an update also have stale-date (30 minutes after the push: the system then shows the activity as stale) and relevance-score (100 when the task has a pending question, 75 when the task is in_progress, else 50; the activity with the highest score goes first). A start also has attributes-type, attributes, and an alert with the key label as title and the task title as body, because ActivityKit needs one. An end also has dismissal-date.

Widget reload#

A device that registers a widget_push_token gets a WidgetKit push when a task or a question of the account changes. WidgetKit then reloads the widget timelines. The push carries no task data.

JSON
{ "aps": { "content-changed": true } }
  • A device gets one widget push in 15 seconds at most.
  • If more changes come in those 15 seconds, the device gets one more push when the 15 seconds end. Thus the last change is not lost.
  • The system also limits how often it delivers these pushes.

Tokens that are not valid#

If APNs answers 410, or 400 with the reason BadDeviceToken, the server sets that token of the device to null, or removes that Live Activity registration. The app registers a new token with the next PUT.

This page as Markdown