{
  "openapi": "3.1.0",
  "info": {
    "title": "ActivitySmith API",
    "description": "Send push notifications and Live Activities to your own devices via a single API key.",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://activitysmith.com/api"
    }
  ],
  "security": [
    {
      "apiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "PushNotifications",
      "description": "Send push notifications to paired devices."
    },
    {
      "name": "AppIconBadges",
      "description": "Update App Icon Badge Counts on paired devices."
    },
    {
      "name": "LiveActivities",
      "description": "Start, update, stream, and end Live Activities."
    },
    {
      "name": "Metrics",
      "description": "Update metric values shown in ActivitySmith widgets."
    }
  ],
  "paths": {
    "/badge": {
      "post": {
        "tags": ["AppIconBadges"],
        "summary": "Set App Icon Badge Count",
        "description": "Shows a number on the ActivitySmith app icon.",
        "operationId": "updateAppIconBadgeCount",
        "x-codeSamples": [
          {
            "lang": "javascript",
            "label": "Node",
            "source": "await activitysmith.badgeCount(8333);"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "activitysmith.badge_count(8333)"
          },
          {
            "lang": "go",
            "label": "Go",
            "source": "activitysmith.BadgeCount(8333)"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "$activitysmith->badgeCount(8333);"
          },
          {
            "lang": "ruby",
            "label": "Ruby",
            "source": "activitysmith.badge_count(8333)"
          },
          {
            "lang": "bash",
            "label": "CLI",
            "source": "activitysmith badge 8333"
          },
          {
            "lang": "bash",
            "label": "cURL",
            "source": "curl -X POST \"https://activitysmith.com/api/badge\" \\\n  -H \"Authorization: Bearer $ACTIVITYSMITH_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"badge\": 8333\n  }'"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/AppIconBadgeCountUpdateRequest" },
              "examples": {
                "set_count": {
                  "value": {
                    "badge": 8333
                  }
                },
                "clear_count": {
                  "value": {
                    "badge": 0
                  }
                },
                "channel_targeted": {
                  "value": {
                    "badge": 3,
                    "target": {
                      "channels": ["sales", "customer-success"]
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "App Icon Badge Count updated",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; r=599; t=60, \"account:badges\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; q=600; w=60, \"account:badges\"; q=60; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AppIconBadgeCountUpdateResponse" },
                "examples": {
                  "default": {
                    "value": {
                      "success": true,
                      "badge": 8333,
                      "devices_updated": 2,
                      "users_updated": 1,
                      "effective_channel_slugs": null,
                      "timestamp": "2026-07-10T12:00:00.000Z"
                    }
                  },
                  "channel_targeted": {
                    "value": {
                      "success": true,
                      "badge": 3,
                      "devices_updated": 2,
                      "users_updated": 1,
                      "effective_channel_slugs": ["sales", "customer-success"],
                      "timestamp": "2026-07-10T12:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; r=599; t=60, \"account:badges\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; q=600; w=60, \"account:badges\"; q=60; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestError" }
              }
            }
          },
          "402": {
            "description": "Billing access required",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BillingBlockedError" }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; r=599; t=60, \"account:badges\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; q=600; w=60, \"account:badges\"; q=60; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ForbiddenError" }
              }
            }
          },
          "422": {
            "description": "No matching devices found, or a targeted device needs to reconnect",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; r=599; t=60, \"account:badges\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; q=600; w=60, \"account:badges\"; q=60; w=60"}
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/NoRecipientsError" },
                    { "$ref": "#/components/schemas/AppIconBadgeCountUpdateError" }
                  ]
                }
              }
            }
          },
          "502": {
            "description": "App Icon Badge Count could not be updated",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; r=599; t=60, \"account:badges\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; q=600; w=60, \"account:badges\"; q=60; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AppIconBadgeCountUpdateError" }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; r=599; t=60, \"account:badges\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:badges\"; q=600; w=60, \"account:badges\"; q=60; w=60"},
              "Retry-After": {"description": "Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.", "schema": {"type": "integer", "minimum": 0}, "example": 30}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RateLimitError" }
              }
            }
          }
        }
      }
    },
    "/push-notification": {
      "post": {
        "tags": ["PushNotifications"],
        "summary": "Send a push notification",
        "description": "Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional `icon` shows a custom image, such as a logo or avatar, next to a smaller app icon. Optional `interruption_level` sets how urgently iOS presents the notification. Optional tags to organize and filter notification history.",
        "operationId": "sendPushNotification",
        "x-codeSamples": [
          {
            "lang": "javascript",
            "label": "Node",
            "source": "import ActivitySmith from \"activitysmith\";\n\nconst activitysmith = new ActivitySmith({\n  apiKey: process.env.ACTIVITYSMITH_API_KEY,\n});\n\nawait activitysmith.notifications.send({\n  title: \"New subscription 💸\",\n  message: \"Customer upgraded to Pro plan\",\n});"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import os\nfrom activitysmith import ActivitySmith\n\nactivitysmith = ActivitySmith(api_key=os.environ[\"ACTIVITYSMITH_API_KEY\"])\n\nactivitysmith.notifications.send({\n    \"title\": \"New subscription 💸\",\n    \"message\": \"Customer upgraded to Pro plan\",\n})"
          },
          {
            "lang": "go",
            "label": "Go",
            "source": "package main\n\nimport (\n\t\"log\"\n\n\tactivitysmithsdk \"github.com/ActivitySmithHQ/activitysmith-go\"\n)\n\nfunc main() {\n\tactivitysmith, err := activitysmithsdk.New(\"YOUR-API-KEY\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t_, err = activitysmith.Notifications.Send(activitysmithsdk.PushNotificationInput{\n\t\tTitle:   \"New subscription 💸\",\n\t\tMessage: \"Customer upgraded to Pro plan\",\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n\nuse ActivitySmith\\ActivitySmith;\n\n$activitysmith = new ActivitySmith($_ENV['ACTIVITYSMITH_API_KEY']);\n\n$activitysmith->notifications->send([\n    'title' => 'New subscription 💸',\n    'message' => 'Customer upgraded to Pro plan',\n]);"
          },
          {
            "lang": "ruby",
            "label": "Ruby",
            "source": "require \"activitysmith\"\n\nactivitysmith = ActivitySmith::Client.new(api_key: ENV.fetch(\"ACTIVITYSMITH_API_KEY\"))\n\nactivitysmith.notifications.send(\n  title: \"New subscription 💸\",\n  message: \"Customer upgraded to Pro plan\"\n)"
          },
          {
            "lang": "bash",
            "label": "cURL",
            "source": "curl -X POST \"https://activitysmith.com/api/push-notification\" \\\n  -H \"Authorization: Bearer $ACTIVITYSMITH_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"title\": \"New subscription 💸\",\n    \"message\": \"Customer upgraded to Pro plan\"\n  }'"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/PushNotificationRequest" },
              "examples": {
                "default": {
                  "value": {
                    "title": "New subscription 💸",
                    "message": "Customer upgraded to Pro plan"
                  }
                },
                "channel_targeted": {
                  "value": {
                    "title": "New subscription 💸",
                    "message": "Customer upgraded to Pro plan",
                    "target": {
                      "channels": ["devs", "ops"]
                    }
                  }
                },
                "redirection": {
                  "value": {
                    "title": "Incident Resolved ✅",
                    "message": "All systems operational",
                    "redirection": "https://status.example.com/incidents/abc-123"
                  }
                },
                "media_preview": {
                  "value": {
                    "title": "Homepage ready",
                    "message": "Your agent finished the redesign.",
                    "media": "https://cdn.example.com/output/homepage.png"
                  }
                },
                "media_preview_with_redirection": {
                  "value": {
                    "title": "Homepage ready",
                    "message": "Your agent finished the redesign.",
                    "media": "https://cdn.example.com/output/homepage.png",
                    "redirection": "https://github.com/acme/web/pull/482"
                  }
                },
                "actions_open_url": {
                  "value": {
                    "title": "Release Candidate Ready 🚀",
                    "message": "v2.14.0-rc.1 deployed to staging",
                    "actions": [
                      {
                        "title": "View CI Run",
                        "type": "open_url",
                        "url": "https://github.com/activitysmithhq/backend/actions/runs/1234567890"
                      },
                      {
                        "title": "Release Notes",
                        "type": "open_url",
                        "url": "https://github.com/activitysmithhq/backend/releases/tag/v2.14.0-rc.1"
                      }
                    ]
                  }
                },
                "actions_webhook": {
                  "value": {
                    "title": "Build Failed 🚨",
                    "message": "Tap and hold for remediation actions",
                    "redirection": "https://ci.example.com/builds/8842",
                    "actions": [
                      {
                        "title": "Retry Build",
                        "type": "webhook",
                        "url": "https://hooks.example.com/activitysmith/build-retry",
                        "method": "POST",
                        "body": {
                          "build_id": "8842",
                          "source": "push_action"
                        }
                      }
                    ]
                  }
                },
                "tagged": {
                  "summary": "Organize notification history with tags",
                  "value": {
                    "title": "New subscription 💸",
                    "message": "Customer upgraded to Pro plan",
                    "tags": ["user:382", "billing"]
                  }
                },
                "custom_icon": {
                  "summary": "Show a custom icon",
                  "value": {
                    "title": "MRR just crossed $10,000 🎉",
                    "message": "Up 18% this month from 31 new subscriptions.",
                    "icon": "https://cdn.activitysmith.com/integrations/icons/stripe.png"
                  }
                },
                "time_sensitive": {
                  "summary": "Deliver as Time Sensitive",
                  "value": {
                    "title": "🚨 checkout-api is down",
                    "message": "500 errors on 62% of requests. Customers can't complete payments.",
                    "interruption_level": "time-sensitive"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Push notification sent",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PushNotificationResponse" },
                "examples": {
                  "default": {
                    "value": {
                      "success": true,
                      "notification_id": "Xk3pQ8mV2nR7tL9wB4yZ",
                      "devices_notified": 2,
                      "users_notified": 1,
                      "timestamp": "2025-08-12T12:00:00.000Z"
                    }
                  },
                  "channel_targeted": {
                    "value": {
                      "success": true,
                      "notification_id": "Xk3pQ8mV2nR7tL9wB4yZ",
                      "devices_notified": 2,
                      "users_notified": 1,
                      "effective_channel_slugs": ["devs", "ops"],
                      "timestamp": "2025-08-12T12:00:00.000Z"
                    }
                  },
                  "tagged": {
                    "value": {
                      "success": true,
                      "notification_id": "Xk3pQ8mV2nR7tL9wB4yZ",
                      "devices_notified": 2,
                      "users_notified": 1,
                      "effective_channel_slugs": null,
                      "tags": ["user:382", "billing"],
                      "timestamp": "2025-08-12T12:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid payload or channel targeting input)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestError" },
                "examples": {
                  "invalid_target": {
                    "value": {
                      "error": "Invalid channel targeting",
                      "message": "target.channels must be an array of channel slugs"
                    }
                  },
                  "invalid_tags": {
                    "value": {
                      "error": "Invalid tags",
                      "code": "invalid_tags",
                      "message": "tags must use 1-64 lowercase letters, numbers, dashes, underscores, periods, or colons"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Billing access required",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BillingBlockedError" }
              }
            }
          },
          "403": {
            "description": "Forbidden (API key scope or channel assignment violation)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ForbiddenError" },
                "examples": {
                  "channel_scope_violation": {
                    "value": {
                      "error": "Invalid channel targeting",
                      "message": "This API key can only target assigned channels. Requested channels include channels outside this key scope."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "No paired devices on the account, or no devices matched the effective channel target",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NoRecipientsError" },
                "examples": {
                  "no_paired_devices": {
                    "value": {
                      "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": {
                    "value": {
                      "error": "No recipients found",
                      "code": "no_recipients_found",
                      "message": "No active iOS devices matched the targeted channels.",
                      "effective_channel_slugs": ["marketing"]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"},
              "Retry-After": {"description": "Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.", "schema": {"type": "integer", "minimum": 0}, "example": 30}
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/RateLimitError" },
                    { "$ref": "#/components/schemas/LiveActivityLimitError" }
                  ]
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "Too many requests, please try again later."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/push-notification/{notification_id}": {
      "put": {
        "tags": [
          "PushNotifications"
        ],
        "summary": "Replace a push notification",
        "description": "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.",
        "operationId": "replacePushNotification",
        "x-codeSamples": [
          {
            "lang": "bash",
            "label": "cURL",
            "source": "curl -X PUT \"https://activitysmith.com/api/push-notification/Xk3pQ8mV2nR7tL9wB4yZ\" \\\n  -H \"Authorization: Bearer $ACTIVITYSMITH_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"title\": \"✅ checkout-api recovered\",\n    \"message\": \"Error rate is back to normal. Incident resolved.\"\n  }'"
          }
        ],
        "parameters": [
          {
            "name": "notification_id",
            "in": "path",
            "required": true,
            "description": "The `notification_id` returned when the Push Notification was sent.",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PushNotificationReplaceRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "title": "✅ checkout-api recovered",
                    "message": "Error rate is back to normal. Incident resolved."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Push notification replaced",
            "headers": {
              "RateLimit": {
                "description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"
              },
              "RateLimit-Policy": {
                "description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PushNotificationReplaceResponse"
                },
                "examples": {
                  "default": {
                    "value": {
                      "success": true,
                      "notification_id": "Xk3pQ8mV2nR7tL9wB4yZ",
                      "devices_notified": 2,
                      "users_notified": 1,
                      "effective_channel_slugs": null,
                      "timestamp": "2025-08-12T12:05:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid payload or channel targeting input)",
            "headers": {
              "RateLimit": {
                "description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"
              },
              "RateLimit-Policy": {
                "description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BadRequestError"
                },
                "examples": {
                  "missing_title": {
                    "value": {
                      "error": "Missing title",
                      "code": "missing_title",
                      "message": "title is required in the request body."
                    }
                  },
                  "invalid_tags": {
                    "value": {
                      "error": "Invalid tags",
                      "code": "invalid_tags",
                      "message": "tags must use 1-64 lowercase letters, numbers, dashes, underscores, periods, or colons"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Billing access required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingBlockedError"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (API key scope or channel assignment violation)",
            "headers": {
              "RateLimit": {
                "description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"
              },
              "RateLimit-Policy": {
                "description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenError"
                },
                "examples": {
                  "channel_scope_violation": {
                    "value": {
                      "error": "Invalid channel targeting",
                      "message": "This API key can only target assigned channels. Requested channels include channels outside this key scope."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Push notification not found",
            "headers": {
              "RateLimit": {
                "description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"
              },
              "RateLimit-Policy": {
                "description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                },
                "examples": {
                  "not_found": {
                    "value": {
                      "error": "Push notification not found",
                      "code": "push_notification_not_found",
                      "message": "No push notification found with id: Xk3pQ8mV2nR7tL9wB4yZ"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Push notification was sent before replacement support",
            "headers": {
              "RateLimit": {
                "description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"
              },
              "RateLimit-Policy": {
                "description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                },
                "examples": {
                  "not_replaceable": {
                    "value": {
                      "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": {
            "description": "No recipients of the original Push Notification are still available",
            "headers": {
              "RateLimit": {
                "description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"
              },
              "RateLimit-Policy": {
                "description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NoRecipientsError"
                },
                "examples": {
                  "no_paired_devices": {
                    "value": {
                      "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": {
                    "value": {
                      "error": "No recipients found",
                      "code": "no_recipients_found",
                      "message": "No active iOS devices matched the targeted channels.",
                      "effective_channel_slugs": [
                        "marketing"
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "RateLimit": {
                "description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; r=599; t=60, \"account:notifications\"; r=59; t=60"
              },
              "RateLimit-Policy": {
                "description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.",
                "schema": {
                  "type": "string"
                },
                "example": "\"public-api-ip:notifications\"; q=600; w=60, \"account:notifications\"; q=60; w=60"
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                },
                "example": 30
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RateLimitError"
                    },
                    {
                      "$ref": "#/components/schemas/LiveActivityLimitError"
                    }
                  ]
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "Too many requests, please try again later."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/live-activity/stream/{stream_key}": {
      "put": {
        "tags": ["LiveActivities"],
        "summary": "Start a new Live Activity or update an existing one",
        "description": "Use a stable stream_key for each ongoing thing you want to show as a Live Activity. Send the latest content_state whenever it changes, and ActivitySmith will keep the Live Activity in sync. For timer streams, send duration_seconds to start or reset the timer; omit duration_seconds on later updates to preserve the existing timer window. Use secondary_action for a second button on alert, progress, segmented_progress, and value Live Activities. Optional tags to organize and filter notification history. On later stream updates, omit tags to keep the current tags, send tags again to replace them, or send an empty array to clear them.",
        "operationId": "reconcileLiveActivityStream",
        "x-activitysmith-lifecycle": "recommended_managed",
        "x-codeSamples": [
          {
            "lang": "javascript",
            "label": "Node",
            "source": "import ActivitySmith from \"activitysmith\";\n\nconst activitysmith = new ActivitySmith({\n  apiKey: process.env.ACTIVITYSMITH_API_KEY,\n});\n\nawait activitysmith.liveActivities.stream(\"search-reindex\", {\n  content_state: {\n    title: \"Search Reindex\",\n    subtitle: \"catalog-v2\",\n    type: \"progress\",\n    percentage: 42,\n  },\n});"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import os\nfrom activitysmith import ActivitySmith\n\nactivitysmith = ActivitySmith(api_key=os.environ[\"ACTIVITYSMITH_API_KEY\"])\n\nactivitysmith.live_activities.stream(\n    \"search-reindex\",\n    {\n        \"content_state\": {\n            \"title\": \"Search Reindex\",\n            \"subtitle\": \"catalog-v2\",\n            \"type\": \"progress\",\n            \"percentage\": 42,\n        },\n    },\n)"
          },
          {
            "lang": "go",
            "label": "Go",
            "source": "package main\n\nimport (\n\t\"log\"\n\n\tactivitysmithsdk \"github.com/ActivitySmithHQ/activitysmith-go\"\n)\n\nfunc main() {\n\tactivitysmith, err := activitysmithsdk.New(\"YOUR-API-KEY\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t_, err = activitysmith.LiveActivities.Stream(\"search-reindex\", activitysmithsdk.LiveActivityStreamInput{\n\t\tTitle:      \"Search Reindex\",\n\t\tSubtitle:   \"catalog-v2\",\n\t\tType:       \"progress\",\n\t\tPercentage: 42,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n\nuse ActivitySmith\\ActivitySmith;\n\n$activitysmith = new ActivitySmith($_ENV['ACTIVITYSMITH_API_KEY']);\n\n$activitysmith->liveActivities->stream('search-reindex', [\n    'content_state' => [\n        'title' => 'Search Reindex',\n        'subtitle' => 'catalog-v2',\n        'type' => 'progress',\n        'percentage' => 42,\n    ],\n]);"
          },
          {
            "lang": "ruby",
            "label": "Ruby",
            "source": "require \"activitysmith\"\n\nactivitysmith = ActivitySmith::Client.new(api_key: ENV.fetch(\"ACTIVITYSMITH_API_KEY\"))\n\nactivitysmith.live_activities.stream(\n  \"search-reindex\",\n  content_state: {\n    title: \"Search Reindex\",\n    subtitle: \"catalog-v2\",\n    type: \"progress\",\n    percentage: 42\n  }\n)"
          },
          {
            "lang": "bash",
            "label": "cURL",
            "source": "curl -X PUT \"https://activitysmith.com/api/live-activity/stream/search-reindex\" \\\n  -H \"Authorization: Bearer $ACTIVITYSMITH_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"content_state\": {\n      \"title\": \"Search Reindex\",\n      \"subtitle\": \"catalog-v2\",\n      \"type\": \"progress\",\n      \"percentage\": 42\n    }\n  }'"
          }
        ],
        "parameters": [
          {
            "name": "stream_key",
            "in": "path",
            "required": true,
            "description": "Stable identifier for one ongoing thing. Allowed characters: letters, numbers, underscores, and hyphens.",
            "schema": {
              "type": "string",
              "maxLength": 255,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LiveActivityStreamRequest" },
              "examples": {
                "metrics": {
                  "value": {
                    "content_state": {
                      "title": "Server Health",
                      "subtitle": "prod-web-1",
                      "type": "metrics",
                      "metrics": [
                        {
                          "label": "CPU",
                          "value": 27,
                          "unit": "%"
                        },
                        {
                          "label": "MEM",
                          "value": 64,
                          "unit": "%"
                        }
                      ]
                    }
                  }
                },
                "channel_targeted": {
                  "value": {
                    "channels": ["ops"],
                    "content_state": {
                      "title": "Server Health",
                      "subtitle": "prod-web-1",
                      "type": "metrics",
                      "metrics": [
                        {
                          "label": "CPU",
                          "value": 27,
                          "unit": "%"
                        },
                        {
                          "label": "MEM",
                          "value": 64,
                          "unit": "%"
                        }
                      ]
                    }
                  }
                },
                "timer": {
                  "value": {
                    "content_state": {
                      "title": "Benchmark Run",
                      "subtitle": "sampling",
                      "type": "timer",
                      "duration_seconds": 300,
                      "color": "cyan"
                    }
                  }
                },
                "approval_secondary_actions": {
                  "value": {
                    "content_state": {
                      "title": "Approval Needed",
                      "message": "Should I send the follow-up email to Brightlane?",
                      "type": "alert",
                      "color": "cyan"
                    },
                    "action": {
                      "title": "Send",
                      "type": "webhook",
                      "url": "https://example.com/hooks/approvals/send",
                      "method": "POST",
                      "body": {
                        "approval_id": "appr_123"
                      }
                    },
                    "secondary_action": {
                      "title": "Deny",
                      "type": "webhook",
                      "url": "https://example.com/hooks/approvals/deny",
                      "method": "POST",
                      "body": {
                        "approval_id": "appr_123"
                      }
                    }
                  }
                },
                "tagged": {
                  "summary": "Organize notification history with tags",
                  "value": {
                    "content_state": {
                      "title": "Search Reindex",
                      "subtitle": "catalog-v2",
                      "type": "progress",
                      "percentage": 42
                    },
                    "tags": ["user:382", "search-reindex"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stream reconciled",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LiveActivityStreamPutResponse" },
                "examples": {
                  "started": {
                    "value": {
                      "success": true,
                      "operation": "started",
                      "stream_key": "prod-web-1",
                      "activity_id": "h8QmSyYFTuwOIF6Wh3YcZlzHLhUcr034",
                      "devices_notified": 2,
                      "timestamp": "2025-08-12T12:00:00.000Z"
                    }
                  },
                  "noop": {
                    "value": {
                      "success": true,
                      "operation": "noop",
                      "stream_key": "prod-web-1",
                      "activity_id": "h8QmSyYFTuwOIF6Wh3YcZlzHLhUcr034",
                      "timestamp": "2025-08-12T12:05:00.000Z"
                    }
                  },
                  "paused": {
                    "value": {
                      "success": true,
                      "operation": "paused",
                      "stream_key": "prod-web-1",
                      "activity_id": null,
                      "timestamp": "2025-08-12T12:10:00.000Z"
                    }
                  },
                  "tagged": {
                    "value": {
                      "success": true,
                      "operation": "started",
                      "stream_key": "search-reindex",
                      "activity_id": "h8QmSyYFTuwOIF6Wh3YcZlzHLhUcr034",
                      "devices_notified": 2,
                      "tags": ["user:382", "search-reindex"],
                      "timestamp": "2025-08-12T12:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid stream_key, payload, action, or channel targeting input)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestError" },
                "examples": {
                  "invalid_stream_key": {
                    "value": {
                      "error": "Invalid stream_key",
                      "message": "stream_key must contain only letters, numbers, underscores, and hyphens"
                    }
                  },
                  "invalid_target": {
                    "value": {
                      "error": "Invalid channel targeting",
                      "message": "channels must contain at least one channel"
                    }
                  },
                  "invalid_tags": {
                    "value": {
                      "error": "Invalid tags",
                      "code": "invalid_tags",
                      "message": "tags must contain at most 20 unique values"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Trial expired, trial allowance exhausted, or paid usage cap reached",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BillingBlockedError" },
                "examples": {
                  "trial_expired": {
                    "value": {
                      "error": "trial_expired",
                      "message": "Your free trial has ended. Start a subscription or lifetime membership to resume using ActivitySmith.",
                      "trial_period": {
                        "started_at": "2026-09-01T00:00:00.000Z",
                        "ends_at": "2026-09-08T00:00:00.000Z"
                      },
                      "upgrade_url": "https://activitysmith.com/app/account?billing=upgrade"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (API key scope or channel assignment violation)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ForbiddenError" },
                "examples": {
                  "channel_scope_violation": {
                    "value": {
                      "error": "Invalid channel targeting",
                      "message": "This API key can only target assigned channels. Requested channels include channels outside this key scope."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "No paired devices on the account, or no devices matched the effective channel target",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NoRecipientsError" },
                "examples": {
                  "no_paired_devices": {
                    "value": {
                      "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": {
                    "value": {
                      "error": "No recipients found",
                      "code": "no_recipients_found",
                      "message": "No active iOS devices matched the targeted channels.",
                      "effective_channel_slugs": ["ops"]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"},
              "Retry-After": {"description": "Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.", "schema": {"type": "integer", "minimum": 0}, "example": 30}
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/RateLimitError" },
                    { "$ref": "#/components/schemas/LiveActivityLimitError" }
                  ]
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "Too many requests, please try again later."
                    }
                  },
                  "concurrent_live_activity_limit_reached": {
                    "value": {
                      "error": "Live Activity limit reached",
                      "message": "iOS supports up to 5 concurrent Live Activities per app on a device. End an active Live Activity before starting another.",
                      "limit": 5,
                      "active": 5
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": ["LiveActivities"],
        "summary": "End a stream",
        "description": "Use this endpoint when the process you are tracking is finished and you no longer want the Live Activity on your devices. ActivitySmith ends the current Live Activity for this stream and dismisses it from devices. If you need direct lifecycle control, use /live-activity/start, /live-activity/update, and /live-activity/end instead. Use secondary_action for a second button on alert, progress, segmented_progress, and value Live Activities.",
        "operationId": "endLiveActivityStream",
        "x-codeSamples": [
          {
            "lang": "javascript",
            "label": "Node",
            "source": "import ActivitySmith from \"activitysmith\";\n\nconst activitysmith = new ActivitySmith({\n  apiKey: process.env.ACTIVITYSMITH_API_KEY,\n});\n\nawait activitysmith.liveActivities.endStream(\"prod-web-1\", {\n  content_state: {\n    title: \"Server Health\",\n    subtitle: \"prod-web-1\",\n    type: \"metrics\",\n    metrics: [\n      { label: \"CPU\", value: 7, unit: \"%\" },\n      { label: \"MEM\", value: 38, unit: \"%\" },\n    ],\n  },\n});"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import os\nfrom activitysmith import ActivitySmith\n\nactivitysmith = ActivitySmith(api_key=os.environ[\"ACTIVITYSMITH_API_KEY\"])\n\nactivitysmith.live_activities.end_stream(\n    \"prod-web-1\",\n    {\n        \"content_state\": {\n            \"title\": \"Server Health\",\n            \"subtitle\": \"prod-web-1\",\n            \"type\": \"metrics\",\n            \"metrics\": [\n                {\"label\": \"CPU\", \"value\": 7, \"unit\": \"%\"},\n                {\"label\": \"MEM\", \"value\": 38, \"unit\": \"%\"},\n            ],\n        },\n    },\n)"
          },
          {
            "lang": "go",
            "label": "Go",
            "source": "package main\n\nimport (\n\t\"log\"\n\n\tactivitysmithsdk \"github.com/ActivitySmithHQ/activitysmith-go\"\n\t\"github.com/ActivitySmithHQ/activitysmith-go/generated\"\n)\n\nfunc main() {\n\tactivitysmith, err := activitysmithsdk.New(\"YOUR-API-KEY\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t_, err = activitysmith.LiveActivities.EndStream(\"prod-web-1\", activitysmithsdk.LiveActivityStreamEndInput{\n\t\tTitle:    \"Server Health\",\n\t\tSubtitle: \"prod-web-1\",\n\t\tType:     \"metrics\",\n\t\tMetrics: []generated.ActivityMetric{\n\t\t\t{Label: \"CPU\", Value: 7, Unit: generated.PtrString(\"%\")},\n\t\t\t{Label: \"MEM\", Value: 38, Unit: generated.PtrString(\"%\")},\n\t\t},\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n\nuse ActivitySmith\\ActivitySmith;\n\n$activitysmith = new ActivitySmith($_ENV['ACTIVITYSMITH_API_KEY']);\n\n$activitysmith->liveActivities->endStream('prod-web-1', [\n    'content_state' => [\n        'title' => 'Server Health',\n        'subtitle' => 'prod-web-1',\n        'type' => 'metrics',\n        'metrics' => [\n            ['label' => 'CPU', 'value' => 7, 'unit' => '%'],\n            ['label' => 'MEM', 'value' => 38, 'unit' => '%'],\n        ],\n    ],\n]);"
          },
          {
            "lang": "ruby",
            "label": "Ruby",
            "source": "require \"activitysmith\"\n\nactivitysmith = ActivitySmith::Client.new(api_key: ENV.fetch(\"ACTIVITYSMITH_API_KEY\"))\n\nactivitysmith.live_activities.end_stream(\n  \"prod-web-1\",\n  {\n    content_state: {\n      title: \"Server Health\",\n      subtitle: \"prod-web-1\",\n      type: \"metrics\",\n      metrics: [\n        { label: \"CPU\", value: 7, unit: \"%\" },\n        { label: \"MEM\", value: 38, unit: \"%\" }\n      ]\n    }\n  }\n)"
          },
          {
            "lang": "bash",
            "label": "cURL",
            "source": "curl -X DELETE \"https://activitysmith.com/api/live-activity/stream/prod-web-1\" \\\n  -H \"Authorization: Bearer $ACTIVITYSMITH_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"content_state\": {\n      \"title\": \"Server Health\",\n      \"subtitle\": \"prod-web-1\",\n      \"type\": \"metrics\",\n      \"metrics\": [\n        { \"label\": \"CPU\", \"value\": 7, \"unit\": \"%\" },\n        { \"label\": \"MEM\", \"value\": 38, \"unit\": \"%\" }\n      ]\n    }\n  }'"
          }
        ],
        "parameters": [
          {
            "name": "stream_key",
            "in": "path",
            "required": true,
            "description": "Stable identifier for one ongoing thing. Allowed characters: letters, numbers, underscores, and hyphens.",
            "schema": {
              "type": "string",
              "maxLength": 255,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LiveActivityStreamDeleteRequest" },
              "examples": {
                "default": {
                  "value": {}
                },
                "with_content_state": {
                  "value": {
                    "content_state": {
                      "title": "Server Health",
                      "subtitle": "prod-web-1",
                      "type": "metrics",
                      "metrics": [
                        {
                          "label": "CPU",
                          "value": 0,
                          "unit": "%"
                        },
                        {
                          "label": "MEM",
                          "value": 0,
                          "unit": "%"
                        }
                      ],
                      "auto_dismiss_minutes": 2
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Managed stream ended",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LiveActivityStreamDeleteResponse" },
                "examples": {
                  "ended": {
                    "value": {
                      "success": true,
                      "operation": "ended",
                      "stream_key": "prod-web-1",
                      "activity_id": "h8QmSyYFTuwOIF6Wh3YcZlzHLhUcr034",
                      "devices_queued": 1,
                      "delivered": true,
                      "timestamp": "2025-08-12T12:10:00.000Z"
                    }
                  },
                  "not_delivered_after_ios_limit": {
                    "value": {
                      "success": true,
                      "operation": "ended",
                      "stream_key": "prod-web-1",
                      "activity_id": "h8QmSyYFTuwOIF6Wh3YcZlzHLhUcr034",
                      "forced_completion": true,
                      "delivered": false,
                      "warning": "This Live Activity started more than 8 hours ago, so iOS had already ended it and no device received the end. It stays on the Lock Screen until iOS removes it (up to 4 hours later) or someone dismisses it.",
                      "timestamp": "2025-08-12T12:10:00.000Z"
                    }
                  },
                  "already_inactive": {
                    "value": {
                      "success": true,
                      "operation": "ended",
                      "stream_key": "prod-web-1",
                      "activity_id": null,
                      "timestamp": "2025-08-12T12:10:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid stream_key or action)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestError" },
                "examples": {
                  "invalid_stream_key": {
                    "value": {
                      "error": "Invalid stream_key",
                      "message": "stream_key must contain only letters, numbers, underscores, and hyphens"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Managed stream not found",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundError" },
                "examples": {
                  "not_found": {
                    "value": {
                      "error": "Live Activity stream not found",
                      "message": "No Live Activity stream found in this account for stream_key: prod-web-1. The stream may have already ended or been deleted."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"},
              "Retry-After": {"description": "Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.", "schema": {"type": "integer", "minimum": 0}, "example": 30}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RateLimitError" },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "Too many requests, please try again later."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/live-activity/start": {
      "post": {
        "tags": ["LiveActivities"],
        "summary": "Start a Live Activity (legacy manual lifecycle)",
        "description": "Legacy manual lifecycle endpoint. For new integrations, use PUT /live-activity/stream/{stream_key} so ActivitySmith can manage start, update, rotation, and end state for you. This endpoint remains supported for existing integrations and advanced lifecycle control. Starts a Live Activity on devices matched by API key scope and optional target channels. Supports segmented_progress, progress, metrics, stats, alert, timer, and value activity types. For segmented_progress activities, number_of_steps can be changed later during update or end calls if the workflow changes. Use secondary_action for a second button on alert, progress, segmented_progress, and value Live Activities. Optional tags to organize and filter notification history.",
        "operationId": "startLiveActivity",
        "x-activitysmith-lifecycle": "legacy_manual",
        "x-activitysmith-recommended-alternative": "PUT /live-activity/stream/{stream_key}",
        "x-codeSamples": [
          {
            "lang": "javascript",
            "label": "Node",
            "source": "import ActivitySmith from \"activitysmith\";\n\nconst activitysmith = new ActivitySmith({\n  apiKey: process.env.ACTIVITYSMITH_API_KEY,\n});\n\nconst start = await activitysmith.liveActivities.start({\n  content_state: {\n    title: \"Nightly database backup\",\n    subtitle: \"create snapshot\",\n    number_of_steps: 3,\n    current_step: 1,\n    type: \"segmented_progress\",\n    color: \"yellow\",\n  },\n});\n\nconst activityId = start.activity_id;"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import os\nfrom activitysmith import ActivitySmith\n\nactivitysmith = ActivitySmith(api_key=os.environ[\"ACTIVITYSMITH_API_KEY\"])\n\nstart = activitysmith.live_activities.start({\n    \"content_state\": {\n        \"title\": \"Nightly database backup\",\n        \"subtitle\": \"create snapshot\",\n        \"number_of_steps\": 3,\n        \"current_step\": 1,\n        \"type\": \"segmented_progress\",\n        \"color\": \"yellow\",\n    },\n})\n\nactivity_id = start.activity_id"
          },
          {
            "lang": "go",
            "label": "Go",
            "source": "package main\n\nimport (\n\t\"log\"\n\n\tactivitysmithsdk \"github.com/ActivitySmithHQ/activitysmith-go\"\n)\n\nfunc main() {\n\tactivitysmith, err := activitysmithsdk.New(\"YOUR-API-KEY\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tstart, err := activitysmith.LiveActivities.Start(activitysmithsdk.LiveActivityStartInput{\n\t\tTitle:         \"Nightly database backup\",\n\t\tSubtitle:      \"create snapshot\",\n\t\tNumberOfSteps: 3,\n\t\tCurrentStep:   1,\n\t\tType:          \"segmented_progress\",\n\t\tColor:         \"yellow\",\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tactivityID := start.GetActivityId()\n\t_ = activityID\n}"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n\nuse ActivitySmith\\ActivitySmith;\n\n$activitysmith = new ActivitySmith($_ENV['ACTIVITYSMITH_API_KEY']);\n\n$start = $activitysmith->liveActivities->start([\n    'content_state' => [\n        'title' => 'Nightly database backup',\n        'subtitle' => 'create snapshot',\n        'number_of_steps' => 3,\n        'current_step' => 1,\n        'type' => 'segmented_progress',\n        'color' => 'yellow',\n    ],\n]);\n\n$activityId = $start->getActivityId();"
          },
          {
            "lang": "ruby",
            "label": "Ruby",
            "source": "require \"activitysmith\"\n\nactivitysmith = ActivitySmith::Client.new(api_key: ENV.fetch(\"ACTIVITYSMITH_API_KEY\"))\n\nstart = activitysmith.live_activities.start(\n  content_state: {\n    title: \"Nightly database backup\",\n    subtitle: \"create snapshot\",\n    number_of_steps: 3,\n    current_step: 1,\n    type: \"segmented_progress\",\n    color: \"yellow\"\n  }\n)\n\nactivity_id = start.activity_id"
          },
          {
            "lang": "bash",
            "label": "cURL",
            "source": "curl -X POST \"https://activitysmith.com/api/live-activity/start\" \\\n  -H \"Authorization: Bearer $ACTIVITYSMITH_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"content_state\": {\n      \"title\": \"Nightly database backup\",\n      \"subtitle\": \"create snapshot\",\n      \"number_of_steps\": 3,\n      \"current_step\": 1,\n      \"type\": \"segmented_progress\",\n      \"color\": \"yellow\"\n    }\n  }'"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LiveActivityStartRequest" },
              "examples": {
                "segmented_progress": {
                  "value": {
                    "content_state": {
                      "title": "Nightly database backup",
                      "subtitle": "create snapshot",
                      "number_of_steps": 3,
                      "current_step": 1,
                      "type": "segmented_progress",
                      "color": "yellow"
                    }
                  }
                },
                "progress": {
                  "value": {
                    "content_state": {
                      "title": "EV Charging",
                      "subtitle": "Added 30 mi range",
                      "percentage": 15,
                      "type": "progress",
                      "color": "lime"
                    }
                  }
                },
                "metrics": {
                  "value": {
                    "content_state": {
                      "title": "Server Health",
                      "subtitle": "prod-web-1",
                      "type": "metrics",
                      "metrics": [
                        {
                          "label": "CPU",
                          "value": 21,
                          "unit": "%"
                        },
                        {
                          "label": "MEM",
                          "value": 64,
                          "unit": "%"
                        }
                      ]
                    }
                  }
                },
                "open_url_action": {
                  "value": {
                    "content_state": {
                      "title": "Deploying payments-api",
                      "subtitle": "Running database migrations",
                      "number_of_steps": 5,
                      "current_step": 3,
                      "type": "segmented_progress"
                    },
                    "action": {
                      "title": "Open Workflow",
                      "type": "open_url",
                      "url": "https://github.com/acme/payments-api/actions/runs/1234567890"
                    }
                  }
                },
                "segmented_progress_channel_targeted": {
                  "value": {
                    "content_state": {
                      "title": "Nightly database backup",
                      "subtitle": "create snapshot",
                      "number_of_steps": 3,
                      "current_step": 1,
                      "type": "segmented_progress",
                      "color": "yellow"
                    },
                    "target": {
                      "channels": ["devs", "ops"]
                    }
                  }
                },
                "value": {
                  "summary": "Current revenue with a badge and footer button",
                  "value": {
                    "content_state": {
                      "type": "value", "title": "Current revenue", "subtitle": "Today",
                      "value": "$1,240", "color": "cyan", "icon": "dollarsign.circle",
                      "badge": { "title": "Live", "color": "green" }
                    },
                    "action": { "title": "Dashboard", "type": "open_url", "url": "https://example.com/dashboard" }
                  }
                },
                "timer": {
                  "value": {
                    "content_state": {
                      "title": "Benchmark Run",
                      "subtitle": "sampling",
                      "type": "timer",
                      "duration_seconds": 300,
                      "color": "cyan"
                    }
                  }
                },
                "approval_secondary_actions": {
                  "value": {
                    "content_state": {
                      "title": "Approval Needed",
                      "message": "Should I send the follow-up email to Brightlane?",
                      "type": "alert",
                      "color": "cyan"
                    },
                    "action": {
                      "title": "Send",
                      "type": "webhook",
                      "url": "https://example.com/hooks/approvals/send",
                      "method": "POST",
                      "body": {
                        "approval_id": "appr_123"
                      }
                    },
                    "secondary_action": {
                      "title": "Deny",
                      "type": "webhook",
                      "url": "https://example.com/hooks/approvals/deny",
                      "method": "POST",
                      "body": {
                        "approval_id": "appr_123"
                      }
                    }
                  }
                },
                "tagged": {
                  "summary": "Organize notification history with tags",
                  "value": {
                    "content_state": {
                      "title": "Nightly database backup",
                      "subtitle": "create snapshot",
                      "number_of_steps": 3,
                      "current_step": 1,
                      "type": "segmented_progress",
                      "color": "yellow"
                    },
                    "tags": ["user:382", "database-backup"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Live Activity started",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LiveActivityStartResponse" },
                "examples": {
                  "segmented_progress": {
                    "value": {
                      "success": true,
                      "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                      "devices_notified": 2,
                      "users_notified": 1,
                      "timestamp": "2026-01-28T09:57:22.929Z"
                    }
                  },
                  "segmented_progress_channel_targeted": {
                    "value": {
                      "success": true,
                      "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                      "devices_notified": 2,
                      "users_notified": 1,
                      "effective_channel_slugs": ["devs", "ops"],
                      "timestamp": "2026-01-28T09:57:22.929Z"
                    }
                  },
                  "tagged": {
                    "value": {
                      "success": true,
                      "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                      "devices_notified": 2,
                      "users_notified": 1,
                      "tags": ["user:382", "database-backup"],
                      "timestamp": "2026-01-28T09:57:22.929Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid payload or channel targeting input)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestError" },
                "examples": {
                  "invalid_target": {
                    "value": {
                      "error": "Invalid channel targeting",
                      "message": "target.channels must be an array of channel slugs"
                    }
                  },
                  "invalid_tags": {
                    "value": {
                      "error": "Invalid tags",
                      "code": "invalid_tags",
                      "message": "tags must use 1-64 lowercase letters, numbers, dashes, underscores, periods, or colons"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Billing access required",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BillingBlockedError" }
              }
            }
          },
          "403": {
            "description": "Forbidden (API key scope or channel assignment violation)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ForbiddenError" },
                "examples": {
                  "channel_scope_violation": {
                    "value": {
                      "error": "Invalid channel targeting",
                      "message": "This API key can only target assigned channels. Requested channels include channels outside this key scope."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "No paired devices on the account, or no devices matched the effective channel target",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NoRecipientsError" },
                "examples": {
                  "no_paired_devices": {
                    "value": {
                      "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": {
                    "value": {
                      "error": "No recipients found",
                      "code": "no_recipients_found",
                      "message": "No active iOS devices matched the targeted channels.",
                      "effective_channel_slugs": ["marketing"]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"},
              "Retry-After": {"description": "Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.", "schema": {"type": "integer", "minimum": 0}, "example": 30}
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/RateLimitError" },
                    { "$ref": "#/components/schemas/LiveActivityLimitError" }
                  ]
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "Too many requests, please try again later."
                    }
                  },
                  "concurrent_live_activity_limit_reached": {
                    "value": {
                      "error": "Live Activity limit reached",
                      "message": "iOS supports up to 5 concurrent Live Activities per app on a device. End an active Live Activity before starting another.",
                      "limit": 5,
                      "active": 5
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/live-activity/update": {
      "post": {
        "tags": ["LiveActivities"],
        "summary": "Update a Live Activity (legacy manual lifecycle)",
        "description": "Legacy manual lifecycle endpoint. For new integrations, use PUT /live-activity/stream/{stream_key} so ActivitySmith can manage start, update, rotation, and end state for you. This endpoint remains supported for existing integrations and advanced lifecycle control. Updates an existing Live Activity. If the per-activity token is not registered yet, the update is queued. Supports segmented_progress, progress, metrics, stats, alert, timer, and value activity types. For segmented_progress activities, you can increase or decrease number_of_steps here as the workflow changes. For timer activities, send duration_seconds only when you want to reset the timer window; omit it to keep the current timer running. Use secondary_action for a second button on alert, progress, segmented_progress, and value Live Activities.",
        "operationId": "updateLiveActivity",
        "x-activitysmith-lifecycle": "legacy_manual",
        "x-activitysmith-recommended-alternative": "PUT /live-activity/stream/{stream_key}",
        "x-codeSamples": [
          {
            "lang": "javascript",
            "label": "Node",
            "source": "import ActivitySmith from \"activitysmith\";\n\nconst activitysmith = new ActivitySmith({\n  apiKey: process.env.ACTIVITYSMITH_API_KEY,\n});\n\nawait activitysmith.liveActivities.update({\n  activity_id: \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n  content_state: {\n    title: \"Nightly database backup\",\n    subtitle: \"upload archive\",\n    number_of_steps: 3,\n    current_step: 2,\n  },\n});"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import os\nfrom activitysmith import ActivitySmith\n\nactivitysmith = ActivitySmith(api_key=os.environ[\"ACTIVITYSMITH_API_KEY\"])\n\nactivitysmith.live_activities.update({\n    \"activity_id\": \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n    \"content_state\": {\n        \"title\": \"Nightly database backup\",\n        \"subtitle\": \"upload archive\",\n        \"number_of_steps\": 3,\n        \"current_step\": 2,\n    },\n})"
          },
          {
            "lang": "go",
            "label": "Go",
            "source": "package main\n\nimport (\n\t\"log\"\n\n\tactivitysmithsdk \"github.com/ActivitySmithHQ/activitysmith-go\"\n)\n\nfunc main() {\n\tactivitysmith, err := activitysmithsdk.New(\"YOUR-API-KEY\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t_, err = activitysmith.LiveActivities.Update(activitysmithsdk.LiveActivityUpdateInput{\n\t\tActivityID:    \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n\t\tTitle:         \"Nightly database backup\",\n\t\tSubtitle:      \"upload archive\",\n\t\tNumberOfSteps: 3,\n\t\tCurrentStep:   2,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n\nuse ActivitySmith\\ActivitySmith;\n\n$activitysmith = new ActivitySmith($_ENV['ACTIVITYSMITH_API_KEY']);\n\n$activitysmith->liveActivities->update([\n    'activity_id' => 'pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW',\n    'content_state' => [\n        'title' => 'Nightly database backup',\n        'subtitle' => 'upload archive',\n        'number_of_steps' => 3,\n        'current_step' => 2,\n    ],\n]);"
          },
          {
            "lang": "ruby",
            "label": "Ruby",
            "source": "require \"activitysmith\"\n\nactivitysmith = ActivitySmith::Client.new(api_key: ENV.fetch(\"ACTIVITYSMITH_API_KEY\"))\n\nactivitysmith.live_activities.update(\n  activity_id: \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n  content_state: {\n    title: \"Nightly database backup\",\n    subtitle: \"upload archive\",\n    number_of_steps: 3,\n    current_step: 2\n  }\n)"
          },
          {
            "lang": "bash",
            "label": "cURL",
            "source": "curl -X POST \"https://activitysmith.com/api/live-activity/update\" \\\n  -H \"Authorization: Bearer $ACTIVITYSMITH_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"activity_id\": \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n    \"content_state\": {\n      \"title\": \"Nightly database backup\",\n      \"subtitle\": \"upload archive\",\n      \"number_of_steps\": 3,\n      \"current_step\": 2\n    }\n  }'"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LiveActivityUpdateRequest" },
              "examples": {
                "segmented_progress": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "Nightly database backup",
                      "subtitle": "upload archive",
                      "number_of_steps": 3,
                      "current_step": 2
                    }
                  }
                },
                "progress": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "EV Charging",
                      "subtitle": "Added 120 mi range",
                      "percentage": 60
                    }
                  }
                },
                "metrics": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "Server Health",
                      "subtitle": "prod-web-1",
                      "type": "metrics",
                      "metrics": [
                        {
                          "label": "CPU",
                          "value": 25,
                          "unit": "%"
                        },
                        {
                          "label": "MEM",
                          "value": 67,
                          "unit": "%"
                        }
                      ]
                    }
                  }
                },
                "webhook_action": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "Reindexing product search",
                      "subtitle": "Shard 7 of 12",
                      "number_of_steps": 12,
                      "current_step": 7
                    },
                    "action": {
                      "title": "Pause Reindex",
                      "type": "webhook",
                      "url": "https://ops.example.com/hooks/search/reindex/pause",
                      "method": "POST",
                      "body": {
                        "job_id": "reindex-2026-03-19",
                        "requested_by": "activitysmith_live_activity",
                        "reason": "paused_from_ios_live_activity"
                      }
                    }
                  }
                },
                "timer_preserve_window": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "Benchmark Run",
                      "subtitle": "checking p95 latency",
                      "type": "timer"
                    }
                  }
                },
                "timer_reset_window": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "Benchmark Run",
                      "subtitle": "retrying sample",
                      "type": "timer",
                      "duration_seconds": 180
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Live Activity updated (or queued)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LiveActivityUpdateResponse" },
                "examples": {
                  "sent": {
                    "value": {
                      "success": true,
                      "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                      "devices_notified": 2,
                      "devices_queued": 0,
                      "timestamp": "2026-01-28T09:57:26.056Z"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (activity not owned by this API key account)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ForbiddenError" },
                "examples": {
                  "forbidden": {
                    "value": {
                      "error": "Forbidden",
                      "message": "This API key cannot update this Live Activity"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"},
              "Retry-After": {"description": "Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.", "schema": {"type": "integer", "minimum": 0}, "example": 30}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RateLimitError" },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "Too many requests, please try again later."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/live-activity/end": {
      "post": {
        "tags": ["LiveActivities"],
        "summary": "End a Live Activity (legacy manual lifecycle)",
        "description": "Legacy manual lifecycle endpoint. For new integrations, use DELETE /live-activity/stream/{stream_key} to end a managed Live Activity stream. This endpoint remains supported for existing integrations and advanced lifecycle control. Ends a Live Activity and archives its lifecycle. Supports segmented_progress, progress, metrics, stats, alert, timer, and value activity types. For segmented_progress activities, you can send the latest number_of_steps here if the workflow changed after start. Use secondary_action for a second button on alert, progress, segmented_progress, and value Live Activities.",
        "operationId": "endLiveActivity",
        "x-activitysmith-lifecycle": "legacy_manual",
        "x-activitysmith-recommended-alternative": "DELETE /live-activity/stream/{stream_key}",
        "x-codeSamples": [
          {
            "lang": "javascript",
            "label": "Node",
            "source": "import ActivitySmith from \"activitysmith\";\n\nconst activitysmith = new ActivitySmith({\n  apiKey: process.env.ACTIVITYSMITH_API_KEY,\n});\n\nawait activitysmith.liveActivities.end({\n  activity_id: \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n  content_state: {\n    title: \"Nightly database backup\",\n    subtitle: \"verify restore\",\n    number_of_steps: 3,\n    current_step: 3,\n    auto_dismiss_minutes: 2,\n  },\n});"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import os\nfrom activitysmith import ActivitySmith\n\nactivitysmith = ActivitySmith(api_key=os.environ[\"ACTIVITYSMITH_API_KEY\"])\n\nactivitysmith.live_activities.end({\n    \"activity_id\": \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n    \"content_state\": {\n        \"title\": \"Nightly database backup\",\n        \"subtitle\": \"verify restore\",\n        \"number_of_steps\": 3,\n        \"current_step\": 3,\n        \"auto_dismiss_minutes\": 2,\n    },\n})"
          },
          {
            "lang": "go",
            "label": "Go",
            "source": "package main\n\nimport (\n\t\"log\"\n\n\tactivitysmithsdk \"github.com/ActivitySmithHQ/activitysmith-go\"\n)\n\nfunc main() {\n\tactivitysmith, err := activitysmithsdk.New(\"YOUR-API-KEY\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t_, err = activitysmith.LiveActivities.End(activitysmithsdk.LiveActivityEndInput{\n\t\tActivityID:         \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n\t\tTitle:              \"Nightly database backup\",\n\t\tSubtitle:           \"verify restore\",\n\t\tNumberOfSteps:      3,\n\t\tCurrentStep:        3,\n\t\tAutoDismissMinutes: 2,\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n\nuse ActivitySmith\\ActivitySmith;\n\n$activitysmith = new ActivitySmith($_ENV['ACTIVITYSMITH_API_KEY']);\n\n$activitysmith->liveActivities->end([\n    'activity_id' => 'pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW',\n    'content_state' => [\n        'title' => 'Nightly database backup',\n        'subtitle' => 'verify restore',\n        'number_of_steps' => 3,\n        'current_step' => 3,\n        'auto_dismiss_minutes' => 2,\n    ],\n]);"
          },
          {
            "lang": "ruby",
            "label": "Ruby",
            "source": "require \"activitysmith\"\n\nactivitysmith = ActivitySmith::Client.new(api_key: ENV.fetch(\"ACTIVITYSMITH_API_KEY\"))\n\nactivitysmith.live_activities.end(\n  activity_id: \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n  content_state: {\n    title: \"Nightly database backup\",\n    subtitle: \"verify restore\",\n    number_of_steps: 3,\n    current_step: 3,\n    auto_dismiss_minutes: 2\n  }\n)"
          },
          {
            "lang": "bash",
            "label": "cURL",
            "source": "curl -X POST \"https://activitysmith.com/api/live-activity/end\" \\\n  -H \"Authorization: Bearer $ACTIVITYSMITH_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"activity_id\": \"pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW\",\n    \"content_state\": {\n      \"title\": \"Nightly database backup\",\n      \"subtitle\": \"verify restore\",\n      \"number_of_steps\": 3,\n      \"current_step\": 3,\n      \"auto_dismiss_minutes\": 2\n    }\n  }'"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/LiveActivityEndRequest" },
              "examples": {
                "segmented_progress": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "Nightly database backup",
                      "subtitle": "verify restore",
                      "number_of_steps": 3,
                      "current_step": 3,
                      "auto_dismiss_minutes": 2
                    }
                  }
                },
                "progress": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "EV Charging",
                      "subtitle": "Added 200 mi range",
                      "percentage": 100,
                      "auto_dismiss_minutes": 2
                    }
                  }
                },
                "metrics": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "Server Health",
                      "subtitle": "prod-web-1",
                      "type": "metrics",
                      "metrics": [
                        {
                          "label": "CPU",
                          "value": 0,
                          "unit": "%"
                        },
                        {
                          "label": "MEM",
                          "value": 0,
                          "unit": "%"
                        }
                      ],
                      "auto_dismiss_minutes": 2
                    }
                  }
                },
                "open_url_action": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "Deploying payments-api",
                      "subtitle": "Production rollout complete",
                      "number_of_steps": 5,
                      "current_step": 5,
                      "auto_dismiss_minutes": 10
                    },
                    "action": {
                      "title": "Open Workflow",
                      "type": "open_url",
                      "url": "https://github.com/acme/payments-api/actions/runs/1234567890"
                    }
                  }
                },
                "timer": {
                  "value": {
                    "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                    "content_state": {
                      "title": "Benchmark Run",
                      "subtitle": "complete",
                      "type": "timer",
                      "auto_dismiss_minutes": 2
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Live Activity ended",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LiveActivityEndResponse" },
                "examples": {
                  "segmented_progress": {
                    "value": {
                      "success": true,
                      "activity_id": "pLAr-Hnq9ZFW4sxlk43Lhbuok4GLh7UW",
                      "devices_notified": 2,
                      "devices_queued": 0,
                      "timestamp": "2026-01-28T09:57:29.258Z"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (activity not owned by this API key account)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ForbiddenError" },
                "examples": {
                  "forbidden": {
                    "value": {
                      "error": "Forbidden",
                      "message": "This API key cannot end this Live Activity"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; r=599; t=60, \"account:live-activities\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:live-activities\"; q=600; w=60, \"account:live-activities\"; q=300; w=60"},
              "Retry-After": {"description": "Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.", "schema": {"type": "integer", "minimum": 0}, "example": 30}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RateLimitError" },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "Too many requests, please try again later."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/metrics/{key}/value": {
      "post": {
        "tags": ["Metrics"],
        "summary": "Update a widget metric value",
        "description": "Updates the latest value for a metric displayed in ActivitySmith widgets. Create the metric in the web app first, then update its value using the key. Metric updates have an independent 300-requests-per-minute account allowance. They do not consume the Push Notification, badge, or Live Activity allowances.",
        "operationId": "updateMetricValue",
        "x-codeSamples": [
          {
            "lang": "javascript",
            "label": "Node",
            "source": "import ActivitySmith from \"activitysmith\";\n\nconst activitysmith = new ActivitySmith({\n  apiKey: process.env.ACTIVITYSMITH_API_KEY,\n});\n\nawait activitysmith.metrics.update(\"deploy.success_rate\", 99.9);"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "import os\nfrom activitysmith import ActivitySmith\n\nactivitysmith = ActivitySmith(api_key=os.environ[\"ACTIVITYSMITH_API_KEY\"])\n\nactivitysmith.metrics.update(\"deploy.success_rate\", 99.9)"
          },
          {
            "lang": "go",
            "label": "Go",
            "source": "package main\n\nimport (\n\t\"log\"\n\n\tactivitysmithsdk \"github.com/ActivitySmithHQ/activitysmith-go\"\n)\n\nfunc main() {\n\tactivitysmith, err := activitysmithsdk.New(\"YOUR-API-KEY\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t_, err = activitysmith.Metrics.Update(\"deploy.success_rate\", 99.9)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n\nuse ActivitySmith\\ActivitySmith;\n\n$activitysmith = new ActivitySmith($_ENV['ACTIVITYSMITH_API_KEY']);\n\n$activitysmith->metrics->update('deploy.success_rate', 99.9);"
          },
          {
            "lang": "ruby",
            "label": "Ruby",
            "source": "require \"activitysmith\"\n\nactivitysmith = ActivitySmith::Client.new(api_key: ENV.fetch(\"ACTIVITYSMITH_API_KEY\"))\n\nactivitysmith.metrics.update(\"deploy.success_rate\", 99.9)"
          },
          {
            "lang": "bash",
            "label": "cURL",
            "source": "curl -X POST \"https://activitysmith.com/api/metrics/deploy.success_rate/value\" \\\n  -H \"Authorization: Bearer $ACTIVITYSMITH_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"value\": 99.9 }'"
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "description": "Metric key configured in the web app. Lowercase letters, numbers, dots, underscores, and dashes are allowed.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "pattern": "^[a-z0-9][a-z0-9_.-]{0,63}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MetricValueUpdateRequest" },
              "example": {
                "value": 42
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Metric value updated",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:metrics\"; r=599; t=60, \"account:metrics\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:metrics\"; q=600; w=60, \"account:metrics\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MetricValueUpdateResponse" },
                "examples": {
                  "success": {
                    "value": {
                      "success": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (invalid key or value)",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:metrics\"; r=599; t=60, \"account:metrics\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:metrics\"; q=600; w=60, \"account:metrics\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MetricError" },
                "examples": {
                  "invalid_value": {
                    "value": {
                      "error": "value must be a finite number"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Metric not found",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:metrics\"; r=599; t=60, \"account:metrics\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:metrics\"; q=600; w=60, \"account:metrics\"; q=300; w=60"}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MetricError" },
                "examples": {
                  "not_found": {
                    "value": {
                      "error": "Metric not found"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "RateLimit": {"description": "Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.", "schema": {"type": "string"}, "example": "\"public-api-ip:metrics\"; r=599; t=60, \"account:metrics\"; r=299; t=60"},
              "RateLimit-Policy": {"description": "Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.", "schema": {"type": "string"}, "example": "\"public-api-ip:metrics\"; q=600; w=60, \"account:metrics\"; q=300; w=60"},
              "Retry-After": {"description": "Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.", "schema": {"type": "integer", "minimum": 0}, "example": 30}
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RateLimitError" },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "Rate limit exceeded",
                      "message": "Too many requests, please try again later."
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API Key",
        "description": "Required. Include `Authorization: Bearer ask_123456789` in every request. Replace `ask_123456789` with your API key."
      },
      "mcpOAuth": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorization for the ActivitySmith MCP server at https://mcp.activitysmith.com/mcp. These tokens are scoped to MCP tools and are not used by the REST API operations in this document.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://activitysmith.com/oauth/authorize",
            "tokenUrl": "https://activitysmith.com/oauth/token",
            "scopes": {
              "mcp:read": "List and read ActivitySmith account data through read-only MCP tools.",
              "mcp:write": "Create or change ActivitySmith account data through write-capable MCP tools.",
              "api_keys:write": "List, create, rename, and delete ActivitySmith API keys through MCP tools. Granted only when the user ticks Manage API keys on the approval screen."
            }
          }
        }
      }
    },
    "schemas": {
      "ContentStateStart": {
        "type": "object",
        "description": "Start payload requires title and type. For segmented_progress include number_of_steps and current_step. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For value include a string or number in value; strings preserve currency, units, and other formatting. For alert include message. For timer include duration_seconds for countdowns, or set counts_down false without duration_seconds for an open-ended elapsed timer. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, segmented_progress, and value. For segmented_progress, number_of_steps is not locked and can be changed in later update or end calls.",
        "required": ["title", "type"],
        "properties": {
          "title": { "type": "string" },
          "subtitle": { "type": "string" },
          "number_of_steps": {
            "type": "integer",
            "minimum": 1,
            "description": "Total number of steps. Use for type=segmented_progress. This value can be increased or decreased later when updating or ending the same activity."
          },
          "current_step": {
            "type": "integer",
            "minimum": 0,
            "description": "Current completed step count. Use for type=segmented_progress. Set 0 when the activity has started but no segment is complete yet. Must be less than or equal to number_of_steps."
          },
          "percentage": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Progress percentage (0–100). Use for type=progress. Takes precedence over value/upper_limit if both are provided."
          },
          "value": {
            "$ref": "#/components/schemas/LiveActivityValue",
            "description": "For type=value, the required prominent readout (string or finite number); strings preserve exact formatting. For type=progress, a numeric progress value used with upper_limit."
          },
          "upper_limit": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Maximum progress value. Use with value for type=progress."
          },
          "duration_seconds": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Timer duration in seconds. For type=timer countdowns, required on start when counts_down is true or omitted."
          },
          "counts_down": {
            "type": "boolean",
            "default": true,
            "description": "Use with type=timer. When true or omitted, the timer counts down from duration_seconds. Set false for an elapsed timer; omit duration_seconds for an open-ended elapsed timer."
          },
          "is_running": {
            "type": "boolean",
            "default": true,
            "description": "Use with type=timer. Defaults to true. Set false to pause/freeze via API; set true on a paused timer to resume."
          },
          "metrics": {
            "type": "array",
            "description": "Use for type=metrics or type=stats.",
            "minItems": 1,
            "maxItems": 8,
            "items": { "$ref": "#/components/schemas/ActivityMetric" }
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "description": "Required for type=alert."
          },
          "icon": {
            "$ref": "#/components/schemas/LiveActivityAlertIcon",
            "description": "Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, stats, timer, and value."
          },
          "badge": {
            "$ref": "#/components/schemas/LiveActivityAlertBadge",
            "description": "Optional badge. Supported by alert, progress, segmented_progress, and value."
          },
          "type": {
            "type": "string",
            "enum": ["segmented_progress", "progress", "metrics", "stats", "alert", "timer", "value"]
          },
          "color": {
            "type": "string",
            "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"],
            "description": "Optional. Accent color for progress, segmented_progress, metrics, timer, and value Live Activities. For Alert Live Activities, this tints action and secondary_action buttons when included."
          },
          "step_color": {
            "type": "string",
            "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"],
            "description": "Optional. Overrides color for the current step. Only applies to type=segmented_progress."
          },
          "step_colors": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"]
            },
            "description": "Optional. Colors for completed steps. When used with segmented_progress, the array length should match current_step."
          }
        },
        "dependentRequired": { "upper_limit": ["value"] },
        "allOf": [
          {
            "if": { "properties": { "type": { "const": "value" } }, "required": ["type"] },
            "then": { "required": ["value"] }
          },
          {
            "if": { "properties": { "type": { "const": "progress" } }, "required": ["type"] },
            "then": {
              "properties": { "value": { "type": "number" } },
              "anyOf": [{ "required": ["percentage"] }, { "required": ["value", "upper_limit"] }]
            }
          }
        ],
        "additionalProperties": false
      },
      "ContentStateUpdate": {
        "type": "object",
        "description": "Update payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For value include a string or number in value; strings preserve currency, units, and other formatting. For alert include message. For timer, omit duration_seconds to preserve the current timer window or send duration_seconds to reset the timer from the update request time. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, segmented_progress, and value. Type is optional when updating an existing activity. You can increase or decrease number_of_steps during updates.",
        "required": ["title"],
        "properties": {
          "title": { "type": "string" },
          "subtitle": { "type": "string" },
          "number_of_steps": {
            "type": "integer",
            "minimum": 1,
            "description": "Total number of steps. Use for type=segmented_progress. Optional on update, and safe to change if the workflow gains or loses steps."
          },
          "current_step": {
            "type": "integer",
            "minimum": 0,
            "description": "Current completed step count. Use for type=segmented_progress. Set 0 when no segment is complete yet. Must be less than or equal to number_of_steps when number_of_steps is provided."
          },
          "percentage": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Progress percentage (0–100). Use for type=progress. Takes precedence over value/upper_limit if both are provided."
          },
          "value": {
            "$ref": "#/components/schemas/LiveActivityValue",
            "description": "For type=value, the required prominent readout (string or finite number); strings preserve exact formatting. For type=progress, a numeric progress value used with upper_limit."
          },
          "upper_limit": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Maximum progress value. Use with value for type=progress."
          },
          "duration_seconds": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Timer duration in seconds. For type=timer, sending duration_seconds resets the timer window from the update request time; omit it to preserve the existing timer window."
          },
          "counts_down": {
            "type": "boolean",
            "default": true,
            "description": "Use with type=timer. When true or omitted, the timer counts down from duration_seconds. Set false for an elapsed timer; omit duration_seconds for an open-ended elapsed timer."
          },
          "is_running": {
            "type": "boolean",
            "default": true,
            "description": "Use with type=timer. Defaults to true. Set false to pause/freeze via API; set true on a paused timer to resume."
          },
          "metrics": {
            "type": "array",
            "description": "Use for type=metrics or type=stats.",
            "minItems": 1,
            "maxItems": 8,
            "items": { "$ref": "#/components/schemas/ActivityMetric" }
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "description": "Alert message. Use for type=alert."
          },
          "icon": {
            "$ref": "#/components/schemas/LiveActivityAlertIcon",
            "description": "Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, stats, timer, and value."
          },
          "badge": {
            "$ref": "#/components/schemas/LiveActivityAlertBadge",
            "description": "Optional badge. Supported by alert, progress, segmented_progress, and value."
          },
          "type": {
            "type": "string",
            "enum": ["segmented_progress", "progress", "metrics", "stats", "alert", "timer", "value"],
            "description": "Optional. When omitted, the API uses the existing Live Activity type."
          },
          "color": {
            "type": "string",
            "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"],
            "description": "Optional. Accent color for progress, segmented_progress, metrics, timer, and value Live Activities. For Alert Live Activities, this tints action and secondary_action buttons when included."
          },
          "step_color": {
            "type": "string",
            "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"],
            "description": "Optional. Overrides color for the current step. Only applies to type=segmented_progress."
          },
          "step_colors": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"]
            },
            "description": "Optional. Colors for completed steps. When used with segmented_progress, the array length should match current_step."
          }
        },
        "dependentRequired": { "upper_limit": ["value"] },
        "allOf": [
          {
            "if": { "properties": { "type": { "const": "value" } }, "required": ["type"] },
            "then": { "required": ["value"] }
          },
          {
            "if": { "properties": { "type": { "const": "progress" } }, "required": ["type"] },
            "then": {
              "properties": { "value": { "type": "number" } },
              "anyOf": [{ "required": ["percentage"] }, { "required": ["value", "upper_limit"] }]
            }
          }
        ],
        "additionalProperties": false
      },
      "ContentStateEnd": {
        "type": "object",
        "description": "End payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For value include a string or number in value; strings preserve currency, units, and other formatting. For alert include message. For timer, omit duration_seconds to preserve and freeze the latest timer state. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, segmented_progress, and value. Type is optional when ending an existing activity. You can send an updated number_of_steps here if the workflow changed after start.",
        "required": ["title"],
        "properties": {
          "title": { "type": "string" },
          "subtitle": { "type": "string" },
          "number_of_steps": {
            "type": "integer",
            "minimum": 1,
            "description": "Total number of steps. Use for type=segmented_progress. Optional on end, and safe to change if the final workflow used more or fewer steps than originally planned."
          },
          "current_step": {
            "type": "integer",
            "minimum": 0,
            "description": "Current completed step count. Use for type=segmented_progress. Must be less than or equal to number_of_steps when number_of_steps is provided."
          },
          "percentage": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Progress percentage (0–100). Use for type=progress. Takes precedence over value/upper_limit if both are provided."
          },
          "value": {
            "$ref": "#/components/schemas/LiveActivityValue",
            "description": "For type=value, the required prominent readout (string or finite number); strings preserve exact formatting. For type=progress, a numeric progress value used with upper_limit."
          },
          "upper_limit": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Maximum progress value. Use with value for type=progress."
          },
          "duration_seconds": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Timer duration in seconds. For type=timer, omit duration_seconds on end to preserve and freeze the latest timer state."
          },
          "counts_down": {
            "type": "boolean",
            "default": true,
            "description": "Use with type=timer. When true or omitted, the timer counts down from duration_seconds. Set false for an elapsed timer; omit duration_seconds for an open-ended elapsed timer."
          },
          "is_running": {
            "type": "boolean",
            "default": true,
            "description": "Use with type=timer. Defaults to true. Set false to pause/freeze via API; set true on a paused timer to resume."
          },
          "metrics": {
            "type": "array",
            "description": "Use for type=metrics or type=stats.",
            "minItems": 1,
            "maxItems": 8,
            "items": { "$ref": "#/components/schemas/ActivityMetric" }
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "description": "Alert message. Use for type=alert."
          },
          "icon": {
            "$ref": "#/components/schemas/LiveActivityAlertIcon",
            "description": "Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, stats, timer, and value."
          },
          "badge": {
            "$ref": "#/components/schemas/LiveActivityAlertBadge",
            "description": "Optional badge. Supported by alert, progress, segmented_progress, and value."
          },
          "type": {
            "type": "string",
            "enum": ["segmented_progress", "progress", "metrics", "stats", "alert", "timer", "value"],
            "description": "Optional. When omitted, the API uses the existing Live Activity type."
          },
          "color": {
            "type": "string",
            "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"],
            "description": "Optional. Accent color for progress, segmented_progress, metrics, timer, and value Live Activities. For Alert Live Activities, this tints action and secondary_action buttons when included."
          },
          "step_color": {
            "type": "string",
            "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"],
            "description": "Optional. Overrides color for the current step. Only applies to type=segmented_progress."
          },
          "step_colors": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"]
            },
            "description": "Optional. Colors for completed steps. When used with segmented_progress, the array length should match current_step."
          },
          "auto_dismiss_minutes": {
            "type": "integer",
            "minimum": 0,
            "description": "Optional. Minutes before the ended Live Activity is dismissed. Default 3. Set 0 for immediate dismissal. iOS will dismiss ended Live Activities after ~4 hours max.",
            "default": 3
          }
        },
        "dependentRequired": { "upper_limit": ["value"] },
        "allOf": [
          {
            "if": { "properties": { "type": { "const": "value" } }, "required": ["type"] },
            "then": { "required": ["value"] }
          },
          {
            "if": { "properties": { "type": { "const": "progress" } }, "required": ["type"] },
            "then": {
              "properties": { "value": { "type": "number" } },
              "anyOf": [{ "required": ["percentage"] }, { "required": ["value", "upper_limit"] }]
            }
          }
        ],
        "additionalProperties": false
      },
      "StreamContentState": {
        "type": "object",
        "description": "Current state for a managed Live Activity stream. Include type on the first PUT, and whenever the stream may need to start a fresh activity. Supports segmented_progress, progress, metrics, stats, alert, timer, and value types. For timer, send duration_seconds to start or reset a bounded timer; omit duration_seconds on later updates to preserve the existing timer window.",
        "required": ["title"],
        "properties": {
          "title": { "type": "string" },
          "subtitle": { "type": "string" },
          "number_of_steps": {
            "type": "integer",
            "minimum": 1,
            "description": "Use for segmented_progress."
          },
          "current_step": {
            "type": "integer",
            "minimum": 0,
            "description": "Use for segmented_progress. Set 0 when no segment is complete yet. Must be less than or equal to number_of_steps when number_of_steps is provided."
          },
          "percentage": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Use for progress. Takes precedence over value/upper_limit if both are provided."
          },
          "value": {
            "$ref": "#/components/schemas/LiveActivityValue",
            "description": "For type=value, the required prominent readout (string or finite number); strings preserve exact formatting. For progress, a numeric progress value used with upper_limit."
          },
          "upper_limit": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Maximum progress value. Use with value for progress."
          },
          "duration_seconds": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Timer duration in seconds. For type=timer, send duration_seconds to start or reset the timer window; omit it on later stream updates to preserve the existing timer window."
          },
          "counts_down": {
            "type": "boolean",
            "default": true,
            "description": "Use with type=timer. When true or omitted, the timer counts down from duration_seconds. Set false for an elapsed timer; omit duration_seconds for an open-ended elapsed timer."
          },
          "is_running": {
            "type": "boolean",
            "default": true,
            "description": "Use with type=timer. Defaults to true. Set false to pause/freeze via API; set true on a paused timer to resume."
          },
          "type": {
            "type": "string",
            "enum": ["segmented_progress", "progress", "metrics", "stats", "alert", "timer", "value"],
            "description": "Required on the first PUT or whenever the stream cannot infer the current activity type."
          },
          "color": {
            "type": "string",
            "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"],
            "description": "Optional. Accent color for progress, segmented_progress, metrics, timer, and value Live Activities. For Alert Live Activities, this tints action and secondary_action buttons when included."
          },
          "step_color": {
            "type": "string",
            "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"],
            "description": "Optional. Overrides color for the current step. Only applies to segmented_progress."
          },
          "step_colors": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"]
            },
            "description": "Optional. Colors for completed steps. When used with segmented_progress, the array length should match current_step."
          },
          "metrics": {
            "type": "array",
            "description": "Use for metrics and stats activities.",
            "minItems": 1,
            "maxItems": 8,
            "items": { "$ref": "#/components/schemas/ActivityMetric" }
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "description": "Required for type=alert."
          },
          "icon": {
            "$ref": "#/components/schemas/LiveActivityAlertIcon",
            "description": "Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, stats, timer, and value."
          },
          "badge": {
            "$ref": "#/components/schemas/LiveActivityAlertBadge",
            "description": "Optional badge. Supported by alert, progress, segmented_progress, and value."
          },
          "auto_dismiss_seconds": {
            "type": "integer",
            "minimum": 0,
            "description": "Optional. Seconds before the ended Live Activity is dismissed."
          },
          "auto_dismiss_minutes": {
            "type": "integer",
            "minimum": 0,
            "description": "Optional. Minutes before the ended Live Activity is dismissed."
          }
        },
        "dependentRequired": { "upper_limit": ["value"] },
        "allOf": [
          {
            "if": { "properties": { "type": { "const": "value" } }, "required": ["type"] },
            "then": { "required": ["value"] }
          },
          {
            "if": { "properties": { "type": { "const": "progress" } }, "required": ["type"] },
            "then": {
              "properties": { "value": { "type": "number" } },
              "anyOf": [{ "required": ["percentage"] }, { "required": ["value", "upper_limit"] }]
            }
          }
        ],
        "additionalProperties": false
      },
      "LiveActivityValue": {
        "description": "A formatted string or finite numeric Live Activity value. String formatting is preserved.",
        "oneOf": [{ "type": "string" }, { "type": "number" }]
      },
      "ActivityMetric": {
        "type": "object",
        "required": ["label", "value"],
        "properties": {
          "label": { "type": "string", "minLength": 1 },
          "value": {
            "oneOf": [
              { "type": "number", "minimum": 0 },
              { "type": "string", "minLength": 1, "maxLength": 64 }
            ]
          },
          "unit": { "type": "string" },
          "color": {
            "type": "string",
            "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"],
            "description": "Optional per-metric accent color for metrics and stats activities."
          }
        },
        "additionalProperties": false
      },
      "LiveActivityColor": {
        "type": "string",
        "enum": ["lime", "green", "cyan", "blue", "purple", "magenta", "red", "orange", "yellow", "gray"]
      },
      "LiveActivityAlertIcon": {
        "type": "object",
        "description": "Optional SF Symbol icon for Live Activities.",
        "required": ["symbol"],
        "properties": {
          "symbol": {
            "type": "string",
            "minLength": 1,
            "description": "Apple SF Symbol name."
          },
          "color": {
            "$ref": "#/components/schemas/LiveActivityColor",
            "description": "Optional icon color."
          }
        },
        "additionalProperties": false
      },
      "LiveActivityAlertBadge": {
        "type": "object",
        "description": "Optional badge for Live Activities.",
        "required": ["title"],
        "properties": {
          "title": { "type": "string", "minLength": 1 },
          "color": {
            "$ref": "#/components/schemas/LiveActivityColor",
            "description": "Optional badge color."
          }
        },
        "additionalProperties": false
      },
      "AlertPayload": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "body": { "type": "string" }
        },
        "additionalProperties": false
      },
      "ChannelTarget": {
        "type": "object",
        "properties": {
          "channels": {
            "type": "array",
            "items": { "type": "string" },
            "minItems": 1,
            "description": "Channel slugs. When omitted, API key scope determines recipients."
          }
        },
        "required": ["channels"],
        "additionalProperties": false
      },
      "PushNotificationActionType": {
        "type": "string",
        "enum": ["open_url", "webhook"]
      },
      "PushNotificationWebhookMethod": {
        "type": "string",
        "enum": ["GET", "POST"],
        "default": "POST"
      },
      "PushNotificationAction": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Button title displayed in iOS expanded notification UI."
          },
          "type": { "$ref": "#/components/schemas/PushNotificationActionType" },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Action URL. For open_url, use HTTP, HTTPS, Shortcuts, or an installed app’s custom URL scheme, such as spotify:// or spotify:track:123. Custom app schemes require iOS 1.13.4 build 2 or later; no web fallback is provided. Internal and executable schemes are blocked. For webhook, use an HTTPS URL called by the ActivitySmith backend."
          },
          "method": {
            "$ref": "#/components/schemas/PushNotificationWebhookMethod",
            "description": "Webhook HTTP method. Used only when type=webhook."
          },
          "body": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional webhook payload body. Used only when type=webhook."
          }
        },
        "required": ["title", "type", "url"],
        "allOf": [
          {
            "if": {
              "properties": {
                "type": {
                  "const": "open_url"
                }
              },
              "required": ["type"]
            },
            "then": {
              "properties": {
                "url": {
                  "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "type": {
                  "const": "webhook"
                }
              },
              "required": ["type"]
            },
            "then": {
              "properties": {
                "url": {
                  "pattern": "^https://"
                }
              }
            }
          }
        ],
        "additionalProperties": false
      },
      "LiveActivityActionType": {
        "type": "string",
        "enum": ["open_url", "webhook"]
      },
      "LiveActivityWebhookMethod": {
        "type": "string",
        "enum": ["GET", "POST"],
        "default": "POST"
      },
      "LiveActivityAction": {
        "type": "object",
        "description": "Optional action button shown in the Live Activity UI. Use action for the primary button, or secondary_action for a secondary button on alert, progress, segmented_progress, and value Live Activities.",
        "properties": {
          "title": {
            "type": "string",
            "description": "Button title displayed in the Live Activity UI."
          },
          "type": { "$ref": "#/components/schemas/LiveActivityActionType" },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Action URL. For open_url, use an HTTP or HTTPS URL or a shortcuts://run-shortcut?name=... URL that runs a specific iPhone Shortcut. For webhook, use an HTTPS URL called by the ActivitySmith backend."
          },
          "method": {
            "$ref": "#/components/schemas/LiveActivityWebhookMethod",
            "description": "Webhook HTTP method. Used only when type=webhook."
          },
          "body": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional webhook payload body. Used only when type=webhook."
          }
        },
        "required": ["title", "type", "url"],
        "allOf": [
          {
            "if": {
              "properties": {
                "type": {
                  "const": "open_url"
                }
              },
              "required": ["type"]
            },
            "then": {
              "properties": {
                "url": {
                  "pattern": "^(http|https|shortcuts)://"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "type": {
                  "const": "webhook"
                }
              },
              "required": ["type"]
            },
            "then": {
              "properties": {
                "url": {
                  "pattern": "^https://"
                }
              }
            }
          }
        ],
        "additionalProperties": false
      },
      "AppIconBadgeCountUpdateRequest": {
        "type": "object",
        "required": ["badge"],
        "properties": {
          "badge": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647,
            "description": "The count to show on the ActivitySmith app icon. Send 0 to clear it."
          },
          "target": { "$ref": "#/components/schemas/ChannelTarget" }
        },
        "additionalProperties": false
      },
      "AppIconBadgeCountUpdateResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "badge": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647
          },
          "devices_updated": {
            "type": "integer",
            "description": "Number of devices whose App Icon Badge Count was updated."
          },
          "users_updated": {
            "type": "integer",
            "description": "Number of account users with at least one updated device."
          },
          "devices_notified": {
            "type": "integer",
            "deprecated": true,
            "description": "Deprecated compatibility alias for devices_updated."
          },
          "users_notified": {
            "type": "integer",
            "deprecated": true,
            "description": "Deprecated compatibility alias for users_updated."
          },
          "effective_channel_slugs": {
            "type": ["array", "null"],
            "items": { "type": "string" }
          },
          "timestamp": { "type": "string", "format": "date-time" },
          "devices_targeted": { "type": "integer", "description": "Number of device tokens targeted." },
          "devices_deactivated": { "type": "integer", "description": "Number of device tokens deactivated because APNs reported them unregistered." },
          "devices_unregistered": { "type": "integer", "description": "Number of targeted device tokens APNs reported as unregistered." }
        },
        "required": ["success", "badge", "devices_updated", "users_updated", "effective_channel_slugs", "timestamp"],
        "additionalProperties": false
      },
      "AppIconBadgeCountUpdateError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "code": {
            "type": "string",
            "enum": ["badge_device_disconnected", "badge_update_failed"]
          },
          "message": { "type": "string" },
          "badge": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647
          },
          "devices_targeted": { "type": "integer" },
          "devices_updated": { "type": "integer" },
          "users_updated": { "type": "integer" },
          "devices_notified": {
            "type": "integer",
            "deprecated": true,
            "description": "Deprecated compatibility alias for devices_updated."
          },
          "effective_channel_slugs": {
            "type": ["array", "null"],
            "items": { "type": "string" }
          }
        },
        "required": ["error", "code", "message", "badge", "devices_updated"],
        "additionalProperties": true
      },
      "TagSlugs": {
        "type": "array",
        "description": "Optional tags to organize and filter notification history.",
        "items": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$"
        },
        "examples": [
          ["user:382", "environment:production"]
        ]
      },
      "Metadata": {
      "type": "object",
      "description": "Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.",
      "maxProperties": 50,
      "propertyNames": {
        "minLength": 1,
        "maxLength": 100,
        "pattern": "\\S",
        "not": {
          "const": "__proto__"
        }
      },
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string",
            "maxLength": 4000
          },
          {
            "type": "number",
            "format": "double"
          },
          {
            "type": "boolean"
          }
        ]
      },
      "x-max-json-bytes": 16384
    },
    "PushInterruptionLevel": {
      "type": "string",
      "enum": ["passive", "active", "time-sensitive"],
      "description": "How urgently iOS presents the notification. `passive` arrives quietly in the notification list without sound or lighting up the screen. `active` is normal delivery and the default when omitted. `time-sensitive` can break through Focus and Scheduled Summary when the user allows Time Sensitive Notifications for ActivitySmith."
    },
    "PushNotificationRequest": {
        "type": "object",
        "required": ["title"],
        "properties": {
          "metadata": { "$ref": "#/components/schemas/Metadata" },
          "title": { "type": "string" },
          "message": { "type": "string" },
          "subtitle": { "type": "string" },
          "icon": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "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."
          },
          "interruption_level": {
            "$ref": "#/components/schemas/PushInterruptionLevel",
            "description": "Optional. How urgently iOS presents the notification: `passive` (quiet, no sound or screen wake), `active` (default), or `time-sensitive` (can break through Focus and Scheduled Summary when allowed). Omit for normal delivery."
          },
          "media": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "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`."
          },
          "redirection": {
            "type": "string",
            "format": "uri",
            "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:",
            "maxLength": 2048,
            "description": "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."
          },
          "actions": {
            "type": "array",
            "maxItems": 4,
            "items": { "$ref": "#/components/schemas/PushNotificationAction" },
            "description": "Optional interactive actions shown when users expand the notification. Cannot be combined with `media`."
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          },
          "badge": { "type": "integer" },
          "sound": { "type": "string" },
          "target": { "$ref": "#/components/schemas/ChannelTarget" },
          "tags": { "$ref": "#/components/schemas/TagSlugs" }
        },
        "additionalProperties": false
      },
      "PushNotificationResponse": {
        "type": "object",
        "description": "Apple APNs acceptance result. Acceptance does not confirm device delivery or presentation. Alerts remain eligible for APNs delivery retries for 24 hours; device settings and APNs storage policies still apply.",
        "properties": {
          "success": { "type": "boolean", "description": "True when APNs accepts the notification for at least one targeted device." },
          "notification_id": { "type": "string", "description": "ID of this Push Notification. Pass it to `PUT /push-notification/{notification_id}` to replace the notification later." },
          "devices_notified": { "type": "integer", "description": "Number of device tokens for which APNs accepted the notification, without confirming on-device delivery." },
          "users_notified": { "type": "integer" },
          "effective_channel_slugs": {
            "type": ["array", "null"],
            "items": { "type": "string" }
          },
          "tags": { "$ref": "#/components/schemas/TagSlugs" },
          "timestamp": { "type": "string", "format": "date-time" },
          "devices_targeted": { "type": "integer", "description": "Number of device tokens targeted." },
          "devices_deactivated": { "type": "integer", "description": "Number of device tokens deactivated because APNs reported them unregistered." },
          "devices_unregistered": { "type": "integer", "description": "Number of targeted device tokens APNs reported as unregistered." },
          "credential_test": { "type": "boolean", "description": "True when the request was an integration connection test and no paired devices were available." },
          "message": { "type": "string" }
        },
        "required": ["success", "timestamp"],
        "additionalProperties": false
      },
      "PushNotificationReplaceRequest": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "metadata": {
            "$ref": "#/components/schemas/Metadata",
            "description": "Optional. Omit to keep the stored metadata; send {} to clear it."
          },
          "title": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "subtitle": {
            "type": "string"
          },
          "icon": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "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."
          },
          "media": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "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`."
          },
          "redirection": {
            "type": "string",
            "format": "uri",
            "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:",
            "maxLength": 2048,
            "description": "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."
          },
          "actions": {
            "type": "array",
            "maxItems": 4,
            "items": {
              "$ref": "#/components/schemas/PushNotificationAction"
            },
            "description": "Optional interactive actions shown when users expand the notification. Cannot be combined with `media`."
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          },
          "badge": {
            "type": "integer"
          },
          "tags": {
            "$ref": "#/components/schemas/TagSlugs",
            "description": "Optional. Omit to keep the stored tags; send [] to clear them."
          }
        },
        "additionalProperties": false,
        "description": "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."
      },
      "PushNotificationReplaceResponse": {
        "type": "object",
        "description": "Apple APNs acceptance result for the replacement. Acceptance does not confirm device delivery or presentation.",
        "properties": {
          "success": {
            "type": "boolean",
            "description": "True when APNs accepts the replacement for at least one device."
          },
          "notification_id": {
            "type": "string",
            "description": "ID of the replaced Push Notification. It stays the same, so you can replace it again."
          },
          "devices_notified": {
            "type": "integer",
            "description": "Number of device tokens for which APNs accepted the notification, without confirming on-device delivery."
          },
          "users_notified": {
            "type": "integer"
          },
          "effective_channel_slugs": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "$ref": "#/components/schemas/TagSlugs"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "success",
          "notification_id",
          "timestamp"
        ],
        "additionalProperties": false
      },
      "LiveActivityStartRequest": {
        "type": "object",
        "description": "Start a new Live Activity. The response includes activity_id for later update and end calls.",
        "required": ["content_state"],
        "properties": {
          "metadata": { "$ref": "#/components/schemas/Metadata" },
          "content_state": { "$ref": "#/components/schemas/ContentStateStart" },
          "action": { "$ref": "#/components/schemas/LiveActivityAction" },
          "secondary_action": {
            "$ref": "#/components/schemas/LiveActivityAction",
            "description": "Optional secondary action button. Supported for alert, progress, segmented_progress, and value Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action."
          },
          "alert": { "$ref": "#/components/schemas/AlertPayload" },
          "target": { "$ref": "#/components/schemas/ChannelTarget" },
          "tags": { "$ref": "#/components/schemas/TagSlugs" }
        },
        "additionalProperties": false
      },
      "LiveActivityStartResponse": {
        "type": "object",
        "description": "Returned after a Live Activity starts. Save activity_id and use it for all later updates and for the final end call.",
        "properties": {
          "success": { "type": "boolean" },
          "devices_notified": { "type": "integer" },
          "users_notified": { "type": "integer" },
          "activity_id": { "type": "string" },
          "effective_channel_slugs": {
            "type": ["array", "null"],
            "items": { "type": "string" }
          },
          "tags": { "$ref": "#/components/schemas/TagSlugs" },
          "timestamp": { "type": "string", "format": "date-time" },
          "ended_activity_id": { "type": "string", "description": "Present when starting this Live Activity ended an older one to stay within the device limit." },
          "message": { "type": "string" },
          "errors": { "type": "array", "items": {"type": "string"}, "description": "Per-device delivery errors when some devices could not be reached." }
        },
        "required": ["success", "activity_id", "timestamp"],
        "additionalProperties": false
      },
      "LiveActivityStreamRequest": {
        "type": "object",
        "description": "Send the latest state for a managed Live Activity stream. channels is the streamlined form for stream targeting. target.channels is also accepted for compatibility. If both are provided, they must match.",
        "required": ["content_state"],
        "properties": {
          "metadata": { "$ref": "#/components/schemas/Metadata" },
          "content_state": { "$ref": "#/components/schemas/StreamContentState" },
          "action": { "$ref": "#/components/schemas/LiveActivityAction" },
          "secondary_action": {
            "$ref": "#/components/schemas/LiveActivityAction",
            "description": "Optional secondary action button. Supported for alert, progress, segmented_progress, and value Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action."
          },
          "alert": { "$ref": "#/components/schemas/AlertPayload" },
          "channels": {
            "type": "array",
            "items": { "type": "string" },
            "minItems": 1,
            "description": "Channel slugs. When omitted, API key scope determines recipients."
          },
          "target": { "$ref": "#/components/schemas/ChannelTarget" },
          "tags": {
            "$ref": "#/components/schemas/TagSlugs",
            "description": "Optional tags to organize and filter notification history. Omit this field on later updates to keep the current tags, send tags again to replace them, or send an empty array to clear them."
          }
        },
        "additionalProperties": false
      },
      "LiveActivityStreamPutResponse": {
        "type": "object",
        "description": "Returned after a managed stream request is reconciled.",
        "properties": {
          "success": { "type": "boolean" },
          "operation": {
            "type": "string",
            "enum": ["started", "updated", "rotated", "noop", "paused"]
          },
          "stream_key": { "type": "string" },
          "activity_id": { "type": ["string", "null"] },
          "previous_activity_id": { "type": "string" },
          "devices_notified": { "type": "integer" },
          "devices_queued": { "type": "integer" },
          "users_notified": { "type": "integer" },
          "effective_channel_slugs": {
            "type": ["array", "null"],
            "items": { "type": "string" }
          },
          "tags": { "$ref": "#/components/schemas/TagSlugs" },
          "warning": { "type": "string", "description": "Present on `updated` responses when the current Live Activity has received more than 12 updates and has averaged more than one update every 2 minutes since it started. The update is still sent, but iOS may throttle Live Activities that update this often." },
          "timestamp": { "type": "string", "format": "date-time" },
          "ignored": { "type": "boolean", "description": "True when the request had no effect." },
          "remote_push_sent": { "type": "boolean" },
          "message": { "type": "string" },
          "errors": { "type": "array", "items": {"type": "string"}, "description": "Per-device delivery errors when some devices could not be reached." }
        },
        "required": ["success", "operation", "stream_key", "timestamp"],
        "additionalProperties": false
      },
      "LiveActivityStreamDeleteRequest": {
        "type": "object",
        "description": "Optional payload for ending a managed stream. When omitted, ActivitySmith ends the stream using the latest known state when possible.",
        "properties": {
          "metadata": { "$ref": "#/components/schemas/Metadata" },
          "tags": { "$ref": "#/components/schemas/TagSlugs", "description": "Omit to preserve Tags, supply a list to replace them, or [] to clear them in the final activity history." },
          "content_state": { "$ref": "#/components/schemas/StreamContentState" },
          "action": { "$ref": "#/components/schemas/LiveActivityAction" },
          "secondary_action": {
            "$ref": "#/components/schemas/LiveActivityAction",
            "description": "Optional secondary action button. Supported for alert, progress, segmented_progress, and value Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action."
          },
          "alert": { "$ref": "#/components/schemas/AlertPayload" }
        },
        "additionalProperties": false
      },
      "LiveActivityStreamDeleteResponse": {
        "type": "object",
        "description": "Returned after a managed stream is ended and removed.",
        "properties": {
          "success": { "type": "boolean" },
          "operation": {
            "type": "string",
            "enum": ["ended"]
          },
          "stream_key": { "type": "string" },
          "activity_id": { "type": ["string", "null"] },
          "devices_queued": { "type": "integer" },
          "devices_notified": { "type": "integer" },
          "timestamp": { "type": "string", "format": "date-time" },
          "ignored": { "type": "boolean", "description": "True when the stream was already ended and the request had no effect." },
          "message": { "type": "string" },
          "forced_completion": { "type": "boolean" },
          "delivered": { "type": "boolean", "description": "Present when the stream had a current Live Activity. True when the end was sent to at least one device, or queued for a device that has not registered yet. False when no device received it, so the Live Activity may stay on the Lock Screen until iOS removes it or someone dismisses it." },
          "warning": { "type": "string", "description": "Explains why the end may not have removed the Live Activity, for example because iOS had already ended a Live Activity that started more than 8 hours ago." },
          "errors": { "type": "array", "items": {"type": "string"}, "description": "Per-device delivery errors when some devices could not be reached." }
        },
        "required": ["success", "operation", "stream_key", "timestamp"],
        "additionalProperties": false
      },
      "MetricValueUpdateRequest": {
        "type": "object",
        "description": "Latest metric value to display in widgets.",
        "properties": {
          "value": {
            "oneOf": [{ "type": "number" }, { "type": "string", "minLength": 1, "maxLength": 64 }]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "Optional ISO timestamp for when the metric value was measured. Defaults to the server receive time."
          }
        },
        "required": ["value"],
        "additionalProperties": false
      },
      "MetricValueUpdateResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" }
        },
        "required": ["success"],
        "additionalProperties": false
      },
      "MetricError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "message": { "type": "string" }
        },
        "required": ["error"],
        "additionalProperties": true
      },
      "BadRequestError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "message": { "type": "string" }
        },
        "required": ["error", "message"],
        "additionalProperties": true
      },
      "ForbiddenError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "message": { "type": "string" }
        },
        "required": ["error", "message"],
        "additionalProperties": true
      },
      "BillingBlockedError": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "enum": ["trial_expired", "trial_exhausted", "billing_overage_cap_reached"] },
          "message": { "type": "string" },
          "trial_period": {
            "type": "object",
            "properties": {
              "started_at": { "type": "string", "format": "date-time" },
              "ends_at": { "type": "string", "format": "date-time" }
            },
            "required": ["started_at", "ends_at"]
          },
          "upgrade_url": { "type": "string", "format": "uri" }
        },
        "required": ["error", "message", "upgrade_url"],
        "additionalProperties": true
      },
      "NoRecipientsError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "code": {
            "type": "string",
            "description": "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": { "type": "string" },
          "app_store_url": {
            "type": "string",
            "format": "uri",
            "description": "ActivitySmith App Store link. Present when code is no_paired_devices."
          },
          "effective_channel_slugs": {
            "type": ["array", "null"],
            "items": { "type": "string" }
          }
        },
        "required": ["error", "message"],
        "additionalProperties": true
      },
      "NotFoundError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "message": { "type": "string" }
        },
        "required": ["error", "message"],
        "additionalProperties": true
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "enum": ["rate_limited"] },
          "error": { "type": "string" },
          "message": { "type": "string" }
        },
        "required": ["error", "message"],
        "additionalProperties": false
      },
      "LiveActivityLimitError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "message": { "type": "string" },
          "limit": { "type": "integer" },
          "active": {
            "type": "integer",
            "description": "Highest number of active Live Activities among the targeted devices."
          },
          "blocked_devices": {
            "type": "integer",
            "description": "Number of targeted devices that have reached the enforced iOS Live Activity concurrency threshold. Included only when targeted devices have mixed capacity."
          },
          "targeted_devices": {
            "type": "integer",
            "description": "Total number of targeted devices. Included only when targeted devices have mixed capacity."
          }
        },
        "required": ["error", "message", "limit", "active"],
        "additionalProperties": false
      },
      "LiveActivityUpdateRequest": {
        "type": "object",
        "description": "Update an existing Live Activity by activity_id.",
        "required": ["activity_id", "content_state"],
        "properties": {
          "metadata": { "$ref": "#/components/schemas/Metadata" },
          "activity_id": { "type": "string" },
          "tags": {
            "type": "array",
            "items": { "type": "string", "minLength": 1, "maxLength": 64 },
            "maxItems": 20,
            "description": "Tags for notification history. Omit to keep existing Tags, supply an array to replace them, or send an empty array to clear them."
          },
          "content_state": { "$ref": "#/components/schemas/ContentStateUpdate" },
          "action": { "$ref": "#/components/schemas/LiveActivityAction" },
          "secondary_action": {
            "$ref": "#/components/schemas/LiveActivityAction",
            "description": "Optional secondary action button. Supported for alert, progress, segmented_progress, and value Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action."
          }
        },
        "additionalProperties": false
      },
      "LiveActivityUpdateResponse": {
        "type": "object",
        "description": "Returned after a Live Activity update is sent or queued.",
        "properties": {
          "success": { "type": "boolean" },
          "activity_id": { "type": "string" },
          "devices_queued": { "type": "integer" },
          "devices_notified": { "type": "integer" },
          "timestamp": { "type": "string", "format": "date-time" },
          "users_notified": { "type": "integer" },
          "effective_channel_slugs": { "type": ["array", "null"], "items": {"type": "string"} },
          "tags": { "$ref": "#/components/schemas/TagSlugs" },
          "operation": { "type": "string", "description": "Present when the update started a new Live Activity for a managed stream." },
          "stream_key": { "type": "string" },
          "previous_activity_id": { "type": "string" },
          "errors": { "type": "array", "items": {"type": "string"}, "description": "Per-device delivery errors when some devices could not be reached." }
        },
        "required": ["success", "activity_id", "timestamp"],
        "additionalProperties": false
      },
      "LiveActivityEndRequest": {
        "type": "object",
        "description": "End an existing Live Activity by activity_id.",
        "required": ["activity_id", "content_state"],
        "properties": {
          "metadata": { "$ref": "#/components/schemas/Metadata" },
          "activity_id": { "type": "string" },
          "tags": {
            "type": "array",
            "items": { "type": "string", "minLength": 1, "maxLength": 64 },
            "maxItems": 20,
            "description": "Tags for notification history. Omit to keep existing Tags, supply an array to replace them, or send an empty array to clear them."
          },
          "content_state": { "$ref": "#/components/schemas/ContentStateEnd" },
          "action": { "$ref": "#/components/schemas/LiveActivityAction" },
          "secondary_action": {
            "$ref": "#/components/schemas/LiveActivityAction",
            "description": "Optional secondary action button. Supported for alert, progress, segmented_progress, and value Live Activities. Uses the same open_url, shortcuts://, and webhook shapes as action."
          }
        },
        "additionalProperties": false
      },
      "LiveActivityEndResponse": {
        "type": "object",
        "description": "Returned after a Live Activity end event is sent or queued.",
        "properties": {
          "success": { "type": "boolean" },
          "activity_id": { "type": "string" },
          "devices_queued": { "type": "integer" },
          "devices_notified": { "type": "integer" },
          "timestamp": { "type": "string", "format": "date-time" },
          "ignored": { "type": "boolean", "description": "True when the Live Activity had already ended and the request had no effect." },
          "message": { "type": "string" },
          "end_reason": { "type": ["string", "null"] },
          "forced_completion": { "type": "boolean", "description": "True when the Live Activity was marked complete without a confirmed APNs end delivery." },
          "warning": { "type": "string" },
          "errors": { "type": "array", "items": {"type": "string"}, "description": "Per-device delivery errors when some devices could not be reached." }
        },
        "required": ["success", "activity_id", "timestamp"],
        "additionalProperties": false
      }
    }
  }
}
