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#
{
"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#
{ "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.
{
"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.subtitleis the title that the agent gives.alert.bodyis the body that the agent gives.task_idis there only if the agent gave one. If that task isurgent,interruption-levelistime-sensitive.
Question notifications#
New question#
{
"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.
{ "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:
{ "taskID": "tsk_01jb2m9x4e7q", "keyLabel": "Claude Code" }Content state:
{
"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"] }
}progressandnotecan benull.questionis the oldest pending question of the task. It isnullwhen 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.
{ "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.