This request updates the content of an activity that was started via /start.
Sends an update push to the target devices; the device's live activity is re-rendered with the new content.
The activity state does not change (STARTED → STARTED). The activity keeps living until /end is called.
It can be called unlimited times. You can send updates as often as your user experience requires (apart from rate limits).
Endpoint and headers
POST /api/v1/live-activity/update
Headers
Header | Value |
|---|---|
X-Api-Key |
Body parameters
Below are the parameters for the request body.
Parameter | Description | Data Type | Required |
|---|---|---|---|
activity_id | The ID returned by /register | String | Yes |
activity_type | Same as in register; iOS ActivityAttributes class name | String | Yes |
content | Live activity content | Object | Yes |
title | Live activity title | String | Yes |
message | Live activity body/secondary message | String | Yes |
content_state | Key/value map that exactly matches the ContentState struct in the iOS application | Object | Yes |
When target_devices is present, pushes are sent only to devices matching the (insider_id, udid) pairs in the list. Omit this field to push to the whole segment.
activity_id cannot be empty.
activity_type cannot be empty.
Sample body
Below is a sample request body.
{
"activity_id": "550e8400-e29b-41d4-a716-446655440000",
"activity_type": "OrderTrackingAttributes",
"content": {
"title": "Your order is being prepared",
"message": "Estimated delivery: 14:30",
"content_state": {
"status": "preparing",
"eta": "2026-05-20T14:30:00Z"
}
}
}Body examples
/update body examples are for the three activities in the SDK demo app. The attributes block is often omitted on /update calls. A content_state change is sufficient.
Delivery stage transitions
// Courier set off
{
"activity_id": "<activity_id>",
"activity_type": "DeliveryActivityAttributes",
"content": {
"content_state": { "status": "in_transit", "etaMinutes": 30 }
}
}.png)
// Out for delivery
{
"activity_id": "<activity_id>",
"activity_type": "DeliveryActivityAttributes",
"content": {
"content_state": { "status": "out_for_delivery", "etaMinutes": 10 }
}
}.png)
// Almost at your door
{
"activity_id": "<activity_id>",
"activity_type": "DeliveryActivityAttributes",
"content": {
"content_state": { "status": "out_for_delivery", "etaMinutes": 1 }
}
}Workout phase transitions
// Warmup → active
{
"activity_id": "<activity_id>",
"activity_type": "WorkoutActivityAttributes",
"content": {
"content_state": { "phase": "active", "elapsedSeconds": 600, "calories": 80 }
}
}
// Active → paused
{
"activity_id": "<activity_id>",
"activity_type": "WorkoutActivityAttributes",
"content": {
"content_state": { "phase": "paused", "elapsedSeconds": 900, "calories": 120 }
}
}
// Paused → active again
{
"activity_id": "<activity_id>",
"activity_type": "WorkoutActivityAttributes",
"content": {
"content_state": { "phase": "active", "elapsedSeconds": 1200, "calories": 170 }
}
}// Cooldown
{
"activity_id": "<activity_id>",
"activity_type": "WorkoutActivityAttributes",
"content": {
"content_state": { "phase": "cooldown", "elapsedSeconds": 1650, "calories": 230 }
}
}
Match’s score/period changes
// 1-0 (35th minute)
{
"activity_id": "<activity_id>",
"activity_type": "MatchActivityAttributes",
"content": {
"content_state": { "homeScore": 1, "awayScore": 0, "period": "first_half", "minute": 35 }
}
}
// Half time
{
"activity_id": "<activity_id>",
"activity_type": "MatchActivityAttributes",
"content": {
"content_state": { "homeScore": 1, "awayScore": 0, "period": "half_time", "minute": 45 }
}
}// 2-1 (70th minute)
{
"activity_id": "<activity_id>",
"activity_type": "MatchActivityAttributes",
"content": {
"content_state": { "homeScore": 2, "awayScore": 1, "period": "second_half", "minute": 70 }
}
}Every time /update is called, the live activity is re-rendered on the device.
If any field or enum value inside content_state does not match the ContentState schema in the iOS application, Apple's decoder on the device cannot update the existing activity, and the activity remains stuck in a loading state. For example, the field name might be misspelled, an enum case might be botched in camelCase/snake_case conversion, or a type might not match.

Insider One does not detect this situation. Your API call still returns 200 OK, and the sessions_triggered counter increases, but the activity does not update on the device. The error only surfaces on the device, visually to the user.
Mitigations:
Match content_state field names and enum cases to the iOS classes exactly (case-sensitive).
Test every possible content_state variant on a real device before going to production.
Critical rules
SDK sync gate
After the push is sent with /start, the live activity opens on the device, and Apple produces a new push token for that activity. The Insider SDK synchronizes this token in the background. Until this rotation completes, /update messages cannot reach the device.
Behavior is as follows:
Situation | Result |
|---|---|
All target devices are synced. | Pushes go to all of them. 200 OK, skipped_not_synced is not returned. |
Some are synced, some are not. | Pushes go to the synced ones. 200 OK + skipped_not_synced: N |
All target devices are unsynced. | 412 PUSH_TOKEN_NOT_SYNCED. No push is sent. |
// All targets unsynced
{
"error": "push tokens are not synced yet",
"code": "PUSH_TOKEN_NOT_SYNCED"
}Timing of /update after /start: Do not send /update immediately after /start. Apple's token rotation and the SDK's sync can take several seconds. Typical buffer 2-5 seconds, but it can be longer on backgrounded devices.
Unlimited updates
There is no upper bound on /update calls for the same activity_id (apart from rate limits). As long as the activity remains in STARTED state (i.e., until /end is called), you can send as many updates as you like.
Content responsibility
Same rules as /start apply.
The content field is entirely under customer control; Insider One acts as a proxy.
content_state enum case names must match the iOS values exactly (case-sensitive).
activity_type must match the Swift class name on the device exactly.
The activity keeps living
/update does not end the activity. The state remains STARTED; the activity_id is unchanged; the record on Insider One's end is not deleted. To end the activity, call /end.
Update on a non-started activity
Sending /update to an activity that was /register-ed but not yet /start-ed is not a 404. The activity exists on Insider One's end. However, because no live activity has been opened on the devices yet, the push does not reach them: targets that have not become visible on-device are counted in skipped_not_synced; if all are in this state, 412 PUSH_TOKEN_NOT_SYNCED is returned.
Correct flow: /start first, then /update. Trying /update without /start is meaningless.
Sample responses
Success response 200
{
"status": "success",
"sessions_triggered": 845,
"skipped_not_synced": 5
}Below are the fields that return in the response.
Field | Type | Description |
|---|---|---|
status | String | Always "success" |
sessions_triggered | Int | The number of push messages published to Apple (= number of devices notified) |
skipped_not_synced | Int (omitempty) | Number of devices skipped because the SDK has not yet synced the token. Only returned when > 0. |
sessions_triggered indicates the push has been handed off to Apple.
Error responses
Refer to Error Codes for details.
Limitations
The rate limit is 1,000 requests per minute.
Update frequency: Apple applies a budget to very frequent updates. Standard usage of a few updates per hour is ideal; sending at a very high frequency may be throttled by Apple.
You do not need to push this request if content_state hasn't changed. Spamming updates with the same content wastes your rate limit quota and triggers re-orders on the user side.
Targeted updates via target_devices: To send different content to specific users, make multiple /update calls on the same activity_id, each with a different target_devices list.
Stale activity: If you call /update on an activity whose ends_at has expired, the validity period may be over → 404 NOT_FOUND.
No wait in SDK-first flow: If the device application started the Live Activity itself and notified the Insider SDK (SDK-First flow, iOS 16.1+), the token is already on the device; /update requires no extra wait.