API Reference
The Aivi API is intentionally small. You create and configure an activity or a widget once in the app, then drive it from anywhere with a single HTTP request. Anything that can make an HTTPS call can update a Live Activity or a widget. Some templates also talk back: presses on their buttons and toggles reach you as events.
Base URL
Section titled “Base URL”All requests go to:
https://api.getaivi.appActivities live under /activities and widgets under /widgets, each
addressed by its slug. The earlier singular paths, /activity/{slug} and
/widget/{slug}, answer the same way and stay supported, so automations
that already use them need no change.
Authentication
Section titled “Authentication”Every request must include your API token in the Authorization header, using
the Token scheme:
Authorization: Token YOUR_API_TOKENYou get your token in the app: open the Settings tab, sign in with your Apple account, and subscribe to a plan to unlock API access. The token stays the same until you rotate it.
Check your account
Section titled “Check your account”GET /accountReturns whether your subscription is active and until when. Unlike every other
endpoint, it answers 200 while the subscription is inactive, so you can check
a token, and whether the API will answer, in one call.
curl https://api.getaivi.app/account \ -H "Authorization: Token YOUR_API_TOKEN"subscription takes one of three shapes, told apart by state:
{ "subscription": { "state": "active", "expires_at": "2026-11-12T09:30:00Z", "renews": true } }{ "subscription": { "state": "expired", "expired_at": "2026-09-01T12:30:15Z" } }{ "subscription": { "state": "none" } }state | Meaning | Fields |
|---|---|---|
active | The API answers. | expires_at: when the paid period ends, or null if it does not end. renews: whether it renews at expires_at. |
expired | The subscription ended. Other endpoints answer 402. | expired_at: when it ended, the same date as in the 402 body. |
none | The account never had one. Other endpoints answer 402. | None. |
Update an activity
Section titled “Update an activity”PATCH /activities/{slug}Updates the activity identified by slug and pushes the new state to every
subscribed device. This is the only endpoint you need to drive Live
Activities.
Path parameters
Section titled “Path parameters”| Parameter | Type | Description |
|---|---|---|
slug | string | The unique ID you gave the activity in the app (e.g. dishwasher). |
Request body
Section titled “Request body”The body has two top-level fields: a lifecycle state and a content object
that describes what to render.
| Field | Type | Description | Required? |
|---|---|---|---|
state | State | Whether the activity should be live on devices. | Yes |
content | Content | The template payload to render. | Yes |
Activity states
Section titled “Activity states”The state field controls the lifecycle of the Live Activity on every
subscribed device. There are two values:
state | What it does |
|---|---|
ONGOING | The activity is live. The first ONGOING update starts it; later ones update it in place. |
IDLE | The activity is not live. Sending IDLE ends an active Live Activity and dismisses it. |
A typical flow sets state to ONGOING while a task is running, then to
IDLE once it finishes. How long a finished activity lingers on screen before
it disappears is controlled by the per-activity dismissal delay you configure
in the app.
Activity content
Section titled “Activity content”The content object is chosen by its template field, which decides both the
layout and the rest of the fields it accepts. Four templates are available:
template | Use it for | |
|---|---|---|
progress | Progress bars: cycles, charging, prints, downloads. | |
monitor | One or two columns of labeled live values. | |
remote | A device with buttons and toggles you press. | |
generic | The original simple progress layout. | Legacy |
Each template’s full field list, example payloads, and rendered previews live on the Live Activity Templates page. Shared building blocks such as colors, icons, value formatters, and the tap action URL are documented there too.
Example request
Section titled “Example request”curl https://api.getaivi.app/activities/{slug} \ -X PATCH \ -H "Content-Type: application/json" \ -H "Authorization: Token YOUR_API_TOKEN" \ --data-binary @- << 'EOF'{ "state": "ONGOING", "content": { "template": "progress", "icon": "washer", "progress": { "style": "simple", "value": 0.35 }, "header_left": { "value": "In progress", "text_color": "green" }, "header_right": { "value": "35 min" } }}EOFResponse
Section titled “Response”A successful update returns 200 OK and echoes the stored state and
content:
{ "state": "ONGOING", "content": { "template": "progress", "icon": "washer", "progress": { "style": "simple", "value": 0.35 }, "header_left": { "value": "In progress", "text_color": "green" }, "header_right": { "value": "35 min" } }}List activities
Section titled “List activities”GET /activitiesReturns the Live Activities on your account, oldest first, so a client can
offer them by name instead of asking for a slug. The list is
paginated. Activities shared with you from another account are
not listed.
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
limit | int | How many activities to return, from 1 to 50. Defaults to 50. |
cursor | string | The next_cursor from the previous page. Omit it for the first page. |
Example request
Section titled “Example request”curl https://api.getaivi.app/activities \ -H "Authorization: Token YOUR_API_TOKEN"Response
Section titled “Response”200 OK with a page of activities:
{ "items": [ { "slug": "dishwasher", "name": "Dishwasher", "template": "progress" }, { "slug": "printer", "name": "3D printer", "template": "monitor" } ], "next_cursor": null}| Field | Type | Description |
|---|---|---|
items[].slug | string | The activity’s ID, as in PATCH /activities/{slug}. |
items[].name | string | The name shown in the Aivi app. |
items[].template | string | The template the activity was created with. |
next_cursor | string | null | Pass it as cursor to get the next page; null on the last page. |
Update a widget
Section titled “Update a widget”PATCH /widgets/{slug}Updates the persistent widget identified by slug, stores the new content,
and notifies every device that displays it. Authentication is the same as for
activities.
Every PATCH /widgets/{slug} stores the content and pushes it, even when it
is the same as before. Aivi sends each widget
at most one update every 2 seconds and
delivers the last content you sent, so a burst of updates costs one push.
To save the devices’ reload budget, send an update when the value changes
rather than on a timer.
Path parameters
Section titled “Path parameters”| Parameter | Type | Description |
|---|---|---|
slug | string | The unique ID you gave the widget in the app (e.g. living-room). |
Request body
Section titled “Request body”| Field | Type | Description | Required? |
|---|---|---|---|
content | Content | The template payload to render. | Yes |
stale_after | int | null | Freshness threshold in seconds (60–604800). Content older than this is marked as stale on device. Omit the field to keep the widget’s current setting; send null to clear it. | No |
Widgets have no lifecycle state: unlike an activity, a widget is always
“on” and simply shows the latest content you sent.
Widget content
Section titled “Widget content”The content object is chosen by its template field. Thirteen templates are
available: Status, Progress, Gauge, Battery, Budget, Trend, Countdown, List,
Prices, Energy, Showcase, Switchboard, and Remote. Each update replaces the previous content as a whole;
there is no partial merge.
Every template’s schema, example payload, and rendered previews live on the Widget Templates page. Widget templates share the same building blocks as the activity templates: colors, icons, value formatters, and tap action URLs.
Example request
Section titled “Example request”curl https://api.getaivi.app/widgets/{slug} \ -X PATCH \ -H "Content-Type: application/json" \ -H "Authorization: Token YOUR_API_TOKEN" \ --data-binary @- << 'EOF'{ "content": { "template": "gauge", "value": 21.5, "min_value": 0, "max_value": 40, "unit": "°C", "label": "Living room", "icon": "thermometer.medium" }, "stale_after": 3600}EOFResponse
Section titled “Response”A successful update returns 200 OK and echoes the stored content along
with the widget’s effective stale_after, including when the request left
stale_after out to keep the existing setting:
{ "content": { "template": "gauge", "value": 21.5, "min_value": 0, "max_value": 40, "unit": "°C", "label": "Living room", "icon": "thermometer.medium" }, "stale_after": 3600}List widgets
Section titled “List widgets”GET /widgetsReturns the widgets on your account, oldest first, so a client can offer them
by name instead of asking for a slug. The list is
paginated. Widgets shared with you from another account are
not listed. Because a valid token always gets 200 OK, even with no widgets,
the first page doubles as a token check.
Query parameters
Section titled “Query parameters”| Parameter | Type | Description |
|---|---|---|
limit | int | How many widgets to return, from 1 to 50. Defaults to 50. |
cursor | string | The next_cursor from the previous page. Omit it for the first page. |
Example request
Section titled “Example request”curl "https://api.getaivi.app/widgets?limit=2" \ -H "Authorization: Token YOUR_API_TOKEN"Response
Section titled “Response”200 OK with a page of widgets:
{ "items": [ { "slug": "living-room", "name": "Living room", "template": "gauge" }, { "slug": "front-door", "name": "Front door", "template": "status" } ], "next_cursor": "WyIyMDI2LTA5LTE0VDA4OjIxOjA1LjQxMjAwMCswMDowMCIsIjAxOTk0NzNhLTJmMWMtN2IzZS05YTQxLTZkMmM4ZTVmMGIxNyJd"}| Field | Type | Description |
|---|---|---|
items[].slug | string | The widget’s ID, as in PATCH /widgets/{slug}. |
items[].name | string | The name shown in the Aivi app. |
items[].template | string | The template the widget was created with. |
next_cursor | string | null | Pass it as cursor to get the next page; null on the last page. |
Receive events
Section titled “Receive events”Presses on buttons and toggles come back to you as
widget.action.v1 and activity.action.v1 events.
Receiving Events documents the event envelope and both ways to
receive it:
| Request | Description |
|---|---|
GET /events | A server-sent event stream of your account’s events, filterable by type and subject. |
PUT /widgets/{slug}/webhooks | Sets the widget’s webhooks: one per action id, and "*" for the rest. |
GET /widgets/{slug}/webhooks | Returns the widget’s webhooks, redacted, with the time of each one’s last success and failure. |
DELETE /widgets/{slug}/webhooks | Removes the widget’s webhooks. |
PUT, GET, DELETE /activities/{slug}/webhooks | The same for an activity. |
To confirm what a press did, publish the new content with
PATCH /widgets/{slug} or
PATCH /activities/{slug}.
Pagination
Section titled “Pagination”GET /activities and GET /widgets return one page at a time:
{ "items": [ ... ], "next_cursor": "..." }To read everything, request the first page without a cursor, then pass each
response’s next_cursor back as the cursor query parameter until
next_cursor is null:
curl "https://api.getaivi.app/widgets?cursor=NEXT_CURSOR" \ -H "Authorization: Token YOUR_API_TOKEN"- Items come oldest first, in the order they were created. Something created while you page through appears on a later page, and something deleted never makes another item repeat or go missing.
limitis the page size, from1to50(the default). A page can hold fewer items thanlimitonly when it is the last.- Treat
next_cursoras an opaque string: pass it back unchanged and don’t parse it. Acursorthe API did not issue is refused with a validation422.
Errors
Section titled “Errors”Errors use standard HTTP status codes and return a JSON body with a detail
field describing what went wrong.
| Status | detail | Cause |
|---|---|---|
400 | Template mismatch | The content.template does not match the template the activity or widget was created with. |
401 | Could not validate credentials | The Authorization header is missing, malformed, or the token is unknown. |
402 | No active subscription was found | The token is valid but the account has no active subscription. See Subscription errors. |
404 | Activity not found | No activity with that slug exists on your account. |
404 | Widget not found | No widget with that slug exists on your account. |
422 | Too many ongoing activities | Starting this activity would exceed your concurrent-activity quota. |
422 | Invalid widget content | The widget content fails its template schema. |
422 | Content exceeds the 4 KiB limit | The widget content is larger than 4 KiB as compact JSON. |
422 | array of field errors | The request body or a query parameter is missing or invalid, such as a limit outside 1–50 or a cursor the API did not issue. |
422 | array of field errors | The activity content (with its name) would make a Live Activity push larger than Apple’s 4096-byte limit: type payload_too_large at content, with ctx.size and ctx.limit in bytes. |
429 | Too many event streams | Your account already has 4 /events streams open. |
Subscription errors
Section titled “Subscription errors”A 402 carries two more fields next to detail, so a client can tell the
user what happened without parsing the text:
{ "detail": "No active subscription was found", "code": "subscription_inactive", "expired_at": "2026-09-01T12:30:15Z"}| Field | Type | Description |
|---|---|---|
code | string | Always subscription_inactive. |
expired_at | string | null | When the account’s last subscription ended (ISO 8601), or null if it never had one. |
Shortly after a subscription ends, Aivi also ends the account’s ongoing Live Activities on every device, since API requests to end them would be refused from then on. Widgets keep their last content.
OpenAPI specification
Section titled “OpenAPI specification”A machine-readable OpenAPI document is generated from the API itself and served
at
https://api.getaivi.app/openapi.json.
Import it into an OpenAPI-compatible client such as Postman or Insomnia to
explore the schema and make test requests.
OpenAPI can’t describe the JSON inside a server-sent event, so the
GET /events response names the event envelope
schema, WidgetActionEvent, in an x-sse-data-schema extension next to the
stream.
Aivi: Live Activities and widgets over an API · Pricing · App Store