{
  "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" }
              }
            }
          },
          "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 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"]
                  }
                }
              }
            }
          }
        },
        "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,
                      "devices_notified": 2,
                      "users_notified": 1,
                      "timestamp": "2025-08-12T12:00:00.000Z"
                    }
                  },
                  "channel_targeted": {
                    "value": {
                      "success": true,
                      "devices_notified": 2,
                      "users_notified": 1,
                      "effective_channel_slugs": ["devs", "ops"],
                      "timestamp": "2025-08-12T12:00:00.000Z"
                    }
                  },
                  "tagged": {
                    "value": {
                      "success": true,
                      "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"
                    }
                  }
                }
              }
            }
          },
          "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": "No recipients found for 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_devices": {
                    "value": {
                      "error": "No recipients found",
                      "message": "No devices matched the effective channel target",
                      "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"
                    }
                  }
                }
              }
            }
          },
          "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."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No recipients found for 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_devices": {
                    "value": {
                      "error": "No recipients found",
                      "message": "No Live Activity devices matched the effective channel target",
                      "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,
                      "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 for stream_key: prod-web-1"
                    }
                  }
                }
              }
            }
          },
          "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"
                    }
                  }
                }
              }
            }
          },
          "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."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No recipients found for 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_devices": {
                    "value": {
                      "error": "No recipients found",
                      "message": "No Live Activity devices matched the effective channel target",
                      "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."
            }
          }
        }
      }
    },
    "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" }
        },
        "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
    },
    "PushNotificationRequest": {
        "type": "object",
        "required": ["title"],
        "properties": {
          "metadata": { "$ref": "#/components/schemas/Metadata" },
          "title": { "type": "string" },
          "message": { "type": "string" },
          "subtitle": { "type": "string" },
          "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",
        "properties": {
          "success": { "type": "boolean" },
          "devices_notified": { "type": "integer" },
          "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", "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" }
        },
        "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" },
          "timestamp": { "type": "string", "format": "date-time" }
        },
        "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" }
        },
        "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
      },
      "NoRecipientsError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "message": { "type": "string" },
          "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" }
        },
        "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" }
        },
        "required": ["success", "activity_id", "timestamp"],
        "additionalProperties": false
      }
    }
  }
}
