# Replace Push Notification

Replaces a Push Notification you already sent, using the `notification_id` from the send response. The replacement takes the original's place on the Lock Screen and in Notification Center instead of appearing as a second notification. It is delivered silently, with no sound or screen wake, to the same recipients as the original. Notification history is updated in place.

```http
PUT https://activitysmith.com/api/push-notification/{notification_id}
```

## Authentication

Use your [API key](/docs/api-keys) in the request header.

```http
Authorization: Bearer $YOUR_API_KEY
```

## Parameters

### notification_id

path parameter. Required.

The `notification_id` returned when the Push Notification was sent.

Type: string

**Min Length**: `1`

## Request examples

```bash cURL
curl -X PUT "https://activitysmith.com/api/push-notification/Xk3pQ8mV2nR7tL9wB4yZ" \
  -H "Authorization: Bearer $YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "✅ checkout-api recovered",
    "message": "Error rate is back to normal. Incident resolved."
  }'
```

## Body

Content type: `application/json`

### Schema: PushNotificationReplaceRequest

Replacement content. title, subtitle, message, redirection, media, icon, actions, and payload fully replace the original values. Omit metadata or tags to keep the stored values; send {} or [] to clear them.

| Field | Type | Presence | Description | Constraints |
| --- | --- | --- | --- | --- |
| `metadata` | `Metadata` | Optional | Optional. Omit to keep the stored metadata; send {} to clear it. |  |
| `title` | string | Required |  |  |
| `message` | string | Optional |  |  |
| `subtitle` | string | Optional |  |  |
| `icon` | `string<uri>` | Optional | Optional HTTPS image URL without credentials for a custom notification icon. If the image cannot be loaded, the app icon is used. iOS may omit subtitle when displaying a custom icon. | **Pattern**: `^https://` |
| `media` | `string<uri>` | Optional | Optional HTTPS URL for an image, audio file, or video that users can preview or play when they expand the notification. If `redirection` is omitted, tapping the notification opens this URL. Cannot be combined with `actions`. | **Pattern**: `^https://` |
| `redirection` | `string<uri>` | Optional | Optional HTTP, HTTPS, Shortcuts, or installed app URL opened when the user taps the notification body. Custom schemes such as spotify:// and spotify:track:123 require iOS 1.13.4 build 2 or later and an installed handler; no web fallback is provided. Internal and executable schemes are blocked. Overrides the default tap target from media. | **Pattern**: `^[A-Za-z][A-Za-z0-9+.-]*:`; **Max Length**: `2048` |
| `actions` | array of `PushNotificationAction` | Optional | Optional interactive actions shown when users expand the notification. Cannot be combined with `media`. | **Max Items**: `4` |
| `payload` | object | Optional |  | **Additional Properties**: `true` |
| `badge` | integer | Optional |  |  |
| `tags` | `TagSlugs` | Optional | Optional. Omit to keep the stored tags; send [] to clear them. |  |

**Additional Properties**: `false`

**default**

```json
{
  "title": "✅ checkout-api recovered",
  "message": "Error rate is back to normal. Incident resolved."
}
```

## Responses

### 200

Push notification replaced

Content type: `application/json`

### Schema: PushNotificationReplaceResponse

Apple APNs acceptance result for the replacement. Acceptance does not confirm device delivery or presentation.

| Field | Type | Presence | Description | Constraints |
| --- | --- | --- | --- | --- |
| `success` | boolean | Required | True when APNs accepts the replacement for at least one device. |  |
| `notification_id` | string | Required | ID of the replaced Push Notification. It stays the same, so you can replace it again. |  |
| `devices_notified` | integer | Optional | Number of device tokens for which APNs accepted the notification, without confirming on-device delivery. |  |
| `users_notified` | integer | Optional |  |  |
| `effective_channel_slugs` | array of string or null | Optional |  |  |
| `tags` | `TagSlugs` | Optional |  |  |
| `timestamp` | `string<date-time>` | Required |  |  |

**Additional Properties**: `false`

**default**

```json
{
  "success": true,
  "notification_id": "Xk3pQ8mV2nR7tL9wB4yZ",
  "devices_notified": 2,
  "users_notified": 1,
  "effective_channel_slugs": null,
  "timestamp": "2025-08-12T12:05:00.000Z"
}
```

### 400

Bad request (invalid payload or channel targeting input)

Content type: `application/json`

### Schema: BadRequestError

| Field | Type | Presence | Description | Constraints |
| --- | --- | --- | --- | --- |
| `error` | string | Required |  |  |
| `message` | string | Required |  |  |

**Additional Properties**: `true`

**missing title**

```json
{
  "error": "Missing title",
  "code": "missing_title",
  "message": "title is required in the request body."
}
```

**invalid tags**

```json
{
  "error": "Invalid tags",
  "code": "invalid_tags",
  "message": "tags must use 1-64 lowercase letters, numbers, dashes, underscores, periods, or colons"
}
```

### 402

Billing access required

Content type: `application/json`

### Schema: BillingBlockedError

| Field | Type | Presence | Description | Constraints |
| --- | --- | --- | --- | --- |
| `error` | string | Required |  | **Enum**: `["trial_expired","trial_exhausted","billing_overage_cap_reached"]` |
| `message` | string | Required |  |  |
| `trial_period` | object | Optional |  |  |
| `trial_period.started_at` | `string<date-time>` | Required |  |  |
| `trial_period.ends_at` | `string<date-time>` | Required |  |  |
| `upgrade_url` | `string<uri>` | Required |  |  |

**Additional Properties**: `true`

### 403

Forbidden (API key scope or channel assignment violation)

Content type: `application/json`

### Schema: ForbiddenError

| Field | Type | Presence | Description | Constraints |
| --- | --- | --- | --- | --- |
| `error` | string | Required |  |  |
| `message` | string | Required |  |  |

**Additional Properties**: `true`

**channel scope violation**

```json
{
  "error": "Invalid channel targeting",
  "message": "This API key can only target assigned channels. Requested channels include channels outside this key scope."
}
```

### 404

Push notification not found

Content type: `application/json`

### Schema: NotFoundError

| Field | Type | Presence | Description | Constraints |
| --- | --- | --- | --- | --- |
| `error` | string | Required |  |  |
| `message` | string | Required |  |  |

**Additional Properties**: `true`

**not found**

```json
{
  "error": "Push notification not found",
  "code": "push_notification_not_found",
  "message": "No push notification found with id: Xk3pQ8mV2nR7tL9wB4yZ"
}
```

### 409

Push notification was sent before replacement support

Content type: `application/json`

Schema: [NotFoundError](#schema-notfounderror)

**not replaceable**

```json
{
  "error": "Push notification cannot be replaced",
  "code": "push_notification_not_replaceable",
  "message": "This push notification was sent before replacement was supported and cannot be replaced. Send a new push notification instead."
}
```

### 422

No recipients of the original Push Notification are still available

Content type: `application/json`

### Schema: NoRecipientsError

| Field | Type | Presence | Description | Constraints |
| --- | --- | --- | --- | --- |
| `error` | string | Required |  |  |
| `code` | string | Optional | Machine-readable reason. no_paired_devices: the account has no active paired iOS devices; pair an iPhone with the ActivitySmith app. no_recipients_found: devices are paired, but none matched the targeted channels. Treat unknown values as a generic no-delivery error. |  |
| `message` | string | Required |  |  |
| `app_store_url` | `string<uri>` | Optional | ActivitySmith App Store link. Present when code is no_paired_devices. |  |
| `effective_channel_slugs` | array of string or null | Optional |  |  |

**Additional Properties**: `true`

**no paired devices**

```json
{
  "error": "No paired devices",
  "code": "no_paired_devices",
  "message": "No active iOS devices are paired yet. Download ActivitySmith from the App Store and pair your device.",
  "effective_channel_slugs": null,
  "app_store_url": "https://apps.apple.com/us/app/activitysmith/id6752254835"
}
```

**no channel recipients**

```json
{
  "error": "No recipients found",
  "code": "no_recipients_found",
  "message": "No active iOS devices matched the targeted channels.",
  "effective_channel_slugs": [
    "marketing"
  ]
}
```

### 429

Rate limit exceeded

Content type: `application/json`

**oneOf**

Schema: `RateLimitError`

Schema: `LiveActivityLimitError`

**rate limited**

```json
{
  "error": "Rate limit exceeded",
  "message": "Too many requests, please try again later."
}
```

[Download the OpenAPI specification](/openapi.json)
