Skip to content
Get the appGet app

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. One kind of widget also talks back: presses on a switchboard reach you as events.

All requests go to:

https://api.getaivi.app

Activities 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.

Every request must include your API token in the Authorization header, using the Token scheme:

Authorization: Token YOUR_API_TOKEN

You 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.

GET /account

Returns 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.

Terminal window
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" } }
stateMeaningFields
activeThe API answers.expires_at: when the paid period ends, or null if it does not end. renews: whether it renews at expires_at.
expiredThe subscription ended. Other endpoints answer 402.expired_at: when it ended, the same date as in the 402 body.
noneThe account never had one. Other endpoints answer 402.None.
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.

ParameterTypeDescription
slugstringThe unique ID you gave the activity in the app (e.g. dishwasher).

The body has two top-level fields: a lifecycle state and a content object that describes what to render.

FieldTypeDescriptionRequired?
stateStateWhether the activity should be live on devices.Yes
contentContentThe template payload to render.Yes

The state field controls the lifecycle of the Live Activity on every subscribed device. There are two values:

stateWhat it does
ONGOINGThe activity is live. The first ONGOING update starts it; later ones update it in place.
IDLEThe 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.

The content object is chosen by its template field, which decides both the layout and the rest of the fields it accepts. Three templates are available:

templateUse it for
progressProgress bars: cycles, charging, prints, downloads.
monitorOne or two columns of labeled live values.
genericThe 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.

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" }
}
}
EOF

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" }
}
}
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.

ParameterTypeDescription
slugstringThe unique ID you gave the widget in the app (e.g. living-room).
FieldTypeDescriptionRequired?
contentContentThe template payload to render.Yes
stale_afterint | nullFreshness 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.

The content object is chosen by its template field. Eleven templates are available: Status, Progress, Gauge, Battery, Budget, Trend, Countdown, List, Prices, Energy, and Switchboard. 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.

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
}
EOF

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
}

Presses on an interactive widget, such as a switchboard, come back to you as widget.action.v1 events. Receiving Events documents the event envelope and both ways to receive it:

RequestDescription
GET /eventsA server-sent event stream of your account’s events, filterable by type and subject.
PUT /widgets/{slug}/webhooksSets the widget’s webhooks: one per action id, and "*" for the rest.
GET /widgets/{slug}/webhooksReturns the widget’s webhooks, redacted, with the time of each one’s last success and failure.
DELETE /widgets/{slug}/webhooksRemoves the widget’s webhooks.

To confirm what a press did, publish the widget’s new content with PATCH /widgets/{slug}.

Errors use standard HTTP status codes and return a JSON body with a detail field describing what went wrong.

StatusdetailCause
400Template mismatchThe content.template does not match the template the activity or widget was created with.
401Could not validate credentialsThe Authorization header is missing, malformed, or the token is unknown.
402No active subscription was foundThe token is valid but the account has no active subscription.
404Activity not foundNo activity with that slug exists on your account.
404Widget not foundNo widget with that slug exists on your account.
422Too many ongoing activitiesStarting this activity would exceed your concurrent-activity quota.
422Invalid widget contentThe widget content fails its template schema.
422Content exceeds the 4 KiB limitThe widget content is larger than 4 KiB as compact JSON.
422array of field errorsThe request body is missing fields or has invalid values.
422array of field errorsThe 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.
429Too many event streamsYour account already has 4 /events streams open.

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.