Overview
Appio helps businesses add widgets and notifications to their products, without building or maintaining mobile apps, hiring developers, or dealing with app stores.
Learn more about Appio, try our interactive demo, or explore our case studies.
Found an issue or have feedback? Let us know!
Getting Started
Before getting started, you'll need to create an account and obtain your service_id.
There are two integration options for Appio, both functionally identical.
Option 1
Preview
Install code
<script src="https://cdn.appio.so/v1/appio.js"></script>
<script>
const appio = Appio({
service: "svc_00dddddd000000ccccccssssss"
})
function mobileApp() {
appio.open({
user: "23d0e9848fe0ad06272dea39a03679ff"
})
}
</script>
<a href="#" onclick="mobileApp()">Mobile APP</a>Appio
- service
- Use your service ID, which was assigned to you during registration.
Replace svc_**YOUR*SERVICE*ID** with your service id. - eventCallback
- optional
Callback function to receive events from Appio. function (type, data)
Appio.open
- user
- A unique identifier for your user. For security, use a non-sequential, non-predictable string.
Suggestion: Hash the customer’s unique ID and registration date using a function like SHA-256 or MD5. - service
- optional
Use your service ID, which was assigned to you during registration.
Option 2
Preview
Install code
<script src="https://cdn.appio.so/v1/appio.js"></script>
<a
data-appio="on"
data-service="svc_00dddddd000000ccccccssssss"
data-user="23d0e9848fe0ad06272dea39a03679ff"
href="#"
>
Mobile APP
</a>- data-appio
- Activates Appio. The field is required, but its value is optional and can be omitted.
- data-service
- Use your service ID, which was assigned to you during registration.
Replace svc_**YOUR*SERVICE*ID** with your service id. - data-user
- A unique identifier for your user. For security, use a non-sequential, non-predictable string.
Suggestion: Hash the customer’s unique ID and registration date using a function like SHA-256 or MD5.
API Overview
All commands in this documentation are live and ready for testing.
NOTE:
Every 15 minutes the seeded data is restored and anything created more than 30 minutes earlier is removed.
This also removes the "Appio Docs" service from your device after 30 minutes.
Authentication
All API requests must include an API key in the Authorization: Bearer <api-key> header.
Keys created in the dashboard start with prod_, the key in these examples with docs_.
Every /v1 endpoint, unless stated otherwise, also requires the X-Service-Id header, set to a service the key has access to.
You can validate your token by calling the testing endpoint:
Request
curl https://api.appio.so/hi \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A"Response
HTTP Code: 200"👋"NOTE:
The examples in this documentation use custom API key. It can read everything, but calls that change or delete data are blocked and return 403.
Use your own API key for full access.
Requests
Request bodies are JSON and are decoded strictly: a field the endpoint does not know returns 400.
Text limits count UTF-8 bytes, not characters, so emoji and accented text hit them early.
Pagination
Every list endpoint returns its items in data, newest first, and accepts the same parameters:
- limit
- optional
Integer. Default: 50. Min: 1. Values above 100 are treated as 100.
Maximum number of items to return. A value below 1 or not an integer returns 400. - after
- optional
String. ID of the last item of the previous page.
Items are listed newest first, so this returns older items. A malformed ID returns 400.
pagination.next is the path of the next page, or null on the last page.
API Services
View service details
Returns the details of a single service.
The {id} path parameter names the service.
No X-Service-Id header is required.
Path
GET /v1/services/{id}Request
curl -X GET https://api.appio.so/v1/services/svc_00dddddd000000ccccccssssss \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A"Response
HTTP Code: 200{
"id": "svc_00dddddd000000ccccccssssss",
"title": "Appio Docs",
"description": "Try out the functionality of Appio by downloading the app.",
"logo_url": "https://cdn.appio.so/app/docs.appio.so/logo.png",
"banner_url": "https://cdn.appio.so/app/docs.appio.so/banner.jpg",
"url": "https://docs.appio.so/",
"text_color": "",
"background_color": "",
"accent_color": ""
}List services
Returns a paginated list of the active services.
Accepts the limit and after pagination parameters.
No X-Service-Id header is required.
Path
GET /v1/servicesRequest
curl -X GET https://api.appio.so/v1/services \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A"Response
HTTP Code: 200{
"data": [
{
"id": "svc_00dddddd000000ccccccssssss",
"title": "Appio Docs",
"description": "Try out the functionality of Appio by downloading the app.",
"logo_url": "https://cdn.appio.so/app/docs.appio.so/logo.png",
"banner_url": "https://cdn.appio.so/app/docs.appio.so/banner.jpg",
"url": "https://docs.appio.so/",
"text_color": "",
"background_color": "",
"accent_color": ""
}
],
"pagination": {
"next": null
}
}Service fields
Body of create and update requests. A response contains every field; one that was never set is an empty string.
- title
- String. 1 to 255 bytes. Required for create.
- description
- optional
String. Max 1000 bytes. - logo_url
- optional
String. Max 512 bytes. Absolute http(s) URL, or "" to clear it. - banner_url
- optional
String. Max 512 bytes. Absolute http(s) URL, or "" to clear it. - url
- optional
String. Max 512 bytes. Absolute http(s) URL, or "" to clear it. - text_color
- optional
String. Color in format #RRGGBB, or "" to clear it. A color set by an update comes back lowercase. - background_color
- optional
String. Color in format #RRGGBB, or "" to clear it. - accent_color
- optional
String. Color in format #RRGGBB, or "" to clear it.
Create service
No X-Service-Id header is required.
Use your own API key.
Path
POST /v1/servicesRequest
curl -X POST https://api.appio.so/v1/services \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-d '{"title":"My Service","description":"A service for my application"}'Request body
{
"title": "My Service",
"description": "A service for my application"
}Response
HTTP Code: 201{
"id": "svc_01jv7938vhvsccakmeq7c1hcy8"
}Update service
Every field is optional, but at least one must be present: a body with none returns 400.
No X-Service-Id header is required.
Use your own API key.
Path
PATCH /v1/services/{id}Request
curl -X PATCH https://api.appio.so/v1/services/svc_00dddddd000000ccccccssssss \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-d '{"title":"Updated Service Name","accent_color":"#FF5733"}'Request body
{
"title": "Updated Service Name",
"accent_color": "#FF5733"
}Response
HTTP Code: 200{
"id": "svc_00dddddd000000ccccccssssss"
}Deactivate service
No X-Service-Id header is required.
Use your own API key.
Path
DELETE /v1/services/{id}Request
curl -X DELETE https://api.appio.so/v1/services/svc_00dddddd000000ccccccssssss \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A"Response
HTTP Code: 200{
"id": "svc_00dddddd000000ccccccssssss"
}API Devices
List devices subscribed to service
Returns a paginated list of devices subscribed to the service.
platform is one of ios, android or watchos.
Accepts the limit and after pagination parameters.
Path
GET /v1/devicesRequest
curl -X GET https://api.appio.so/v1/devices \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"data": [
{
"id": "dvc_01jmpmh9fvxgyym44sqanjr9hs",
"user_id": "23d0e9848fe0ad06272dea39a03679ff",
"name": "Custom user name",
"platform": "ios",
"os_version": "18.3",
"model": "iPhone",
"device_token": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"notifications_enabled": true,
"device_identifier": "iPhone14,5",
"marketing_name": "iPhone 13"
},
...
],
"pagination": {
"next": "/v1/devices?after=dvc_01jmpmh9fvxgyym44sqanjr9hs&limit=50"
}
}View device details
Path
GET /v1/devices/{id}Request
curl -X GET https://api.appio.so/v1/devices/dvc_01jmpmh9fvxgyym44sqanjr9hs \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "dvc_01jmpmh9fvxgyym44sqanjr9hs",
"user_id": "23d0e9848fe0ad06272dea39a03679ff",
"name": "Custom user name",
"platform": "ios",
"os_version": "18.3",
"model": "iPhone",
"device_token": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"notifications_enabled": true,
"device_identifier": "iPhone14,5",
"marketing_name": "iPhone 13"
}Deactivate device
Deactivate a device when it should no longer receive notifications or when its associated user has been deactivated.
Path
DELETE /v1/devices/{id}Request
curl -X DELETE https://api.appio.so/v1/devices/dvc_01jmpmh9fvxgyym44sqanjr9hs \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "dvc_01jmpmh9fvxgyym44sqanjr9hs"
}Deactivate all user devices
Deactivate all of a user’s devices when they should no longer receive notifications or when the user has been deactivated.
The devices are unlinked from this service only; their links to other services are left alone.
Returns an empty array when the user has no devices.
Path
DELETE /v1/devices?user_id={user_id}Request
curl -X DELETE https://api.appio.so/v1/devices?user_id=23d0e9848fe0ad06272dea39a03679ff \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200[
{
"id": "dvc_01jmpmh9fvxgyym44sqanjr9hs"
},
...
]API Notifications
Notification request
- payload
- Notification payload
- scheduled_at
- optional
String. RFC 3339 format.
Specifies the date and time to schedule the notification for future delivery, up to 30 days in advance. Must be in the future.
Omit it for immediate delivery; the notification then reports its creation time as scheduled_at.
Notification payload
- title
- String. Max 50 bytes.
- message
- String. Max 200 bytes.
- link
- optional
String. Valid URL.
Returned as null when not set. - image_url
- optional
String. Image url.
Image dimension ratio 16/9, up to 3MB.
Allowed formats: .gif, .jpg, .png, .webp
Returned as null when not set.
Text limits count bytes, not characters, so emoji and accented text hit them early.
Notification status
- created
- The notification exists but hasn’t been assigned to any devices yet.
- queued
- All device deliveries exist and are waiting to be sent.
- completed
- The notification has been processed and no delivery is pending.
- failed
- The notification could not be processed.
- skipped
- The notification was not sent out.
Filter options
- status
- optional
String. Filter notifications by status: created, queued, completed, failed, skipped.
Any other value returns 400. - device_id
- optional
String. Filter notifications for a specific device. - user_id
- optional
String. Filter notifications for a specific user.
If both device_id and user_id are provided, only device_id is used.
List notifications
Returns a paginated list of notifications.
Accepts the limit and after pagination parameters.
Path
GET /v1/notificationsRequest
curl -X GET https://api.appio.so/v1/notifications \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"data": [
{
"id": "ntf_01jmpmgb6my0s57c960q1s862v",
"service_id": "svc_00dddddd000000ccccccssssss",
"status": "completed",
"payload": {
"link": "https://docs.appio.so/",
"title": "Notification",
"message": "Hello from Appio Docs",
"image_url": "https://cdn.appio.so/app/docs.appio.so/banner.jpg"
},
"scheduled_at": "2027-01-01T12:34:56.789000Z"
},
...
],
"pagination": {
"next": "/v1/notifications?after=ntf_01jmpmgb6my0s57c960q1s862v&limit=50"
}
}List delivered notifications
Returns a paginated list of delivered notifications.
Accepts the limit and after pagination parameters.
Path
GET /v1/notifications?status=completedRequest
curl -X GET https://api.appio.so/v1/notifications?status=completed \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"data": [
{
"id": "ntf_01jmpmgb6my0s57c960q1s862v",
"service_id": "svc_00dddddd000000ccccccssssss",
"status": "completed",
"payload": {
"link": "https://docs.appio.so/",
"title": "Notification",
"message": "Hello from Appio Docs",
"image_url": "https://cdn.appio.so/app/docs.appio.so/banner.jpg"
},
"scheduled_at": "2027-01-01T12:34:56.789000Z"
},
...
],
"pagination": {
"next": "/v1/notifications?after=ntf_01jmpmgb6my0s57c960q1s862v&limit=50&status=completed"
}
}List notifications for a device
Returns a paginated list of notifications.
Accepts the limit and after pagination parameters.
Path
GET /v1/notifications?device_id={device_id}Request
curl -X GET https://api.appio.so/v1/notifications?device_id=dvc_01jmpmh9fvxgyym44sqanjr9hs \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"data": [
{
"id": "ntf_01jmpmgb6my0s57c960q1s862v",
"service_id": "svc_00dddddd000000ccccccssssss",
"status": "completed",
"payload": {
"link": "https://docs.appio.so/",
"title": "Notification",
"message": "Hello from Appio Docs",
"image_url": "https://cdn.appio.so/app/docs.appio.so/banner.jpg"
},
"scheduled_at": "2027-01-01T12:34:56.789000Z"
},
...
],
"pagination": {
"next": "/v1/notifications?after=ntf_01jmpmgb6my0s57c960q1s862v&device_id=dvc_01jmpmh9fvxgyym44sqanjr9hs&limit=50"
}
}List notifications for a user
Returns a paginated list of notifications.
Accepts the limit and after pagination parameters.
Path
GET /v1/notifications?user_id={user_id}Request
curl -X GET https://api.appio.so/v1/notifications?user_id=23d0e9848fe0ad06272dea39a03679ff \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"data": [
{
"id": "ntf_01jmpmgb6my0s57c960q1s862v",
"service_id": "svc_00dddddd000000ccccccssssss",
"status": "completed",
"payload": {
"link": "https://docs.appio.so/",
"title": "Notification",
"message": "Hello from Appio Docs",
"image_url": "https://cdn.appio.so/app/docs.appio.so/banner.jpg"
},
"scheduled_at": "2027-01-01T12:34:56.789000Z"
},
...
],
"pagination": {
"next": "/v1/notifications?after=ntf_01jmpmgb6my0s57c960q1s862v&limit=50&user_id=23d0e9848fe0ad06272dea39a03679ff"
}
}View notification details
View a notification's details, including its sending status.
Each notification may be delivered to multiple devices.
Path
GET /v1/notifications/{id}Request
curl -X GET https://api.appio.so/v1/notifications/ntf_01jmpmgb6my0s57c960q1s862v \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "ntf_01jmpmgb6my0s57c960q1s862v",
"service_id": "svc_00dddddd000000ccccccssssss",
"status": "completed",
"payload": {
"link": "https://docs.appio.so/",
"title": "Notification",
"message": "Hello from Appio Docs",
"image_url": "https://cdn.appio.so/app/docs.appio.so/banner.jpg"
},
"scheduled_at": "2027-01-01T12:34:56.789000Z",
"delivery_stats": {
"total": 1,
"created": 0,
"queued": 0,
"completed": 1,
"failed": 0,
"skipped": 0
}
}Target options
The audience of POST /v1/notifications is chosen by two optional query parameters:
- device_id
- optional
String. Deliver to that single device. - user_id
- optional
String. Deliver to all devices of that user.
Omitting both broadcasts the notification to every device of the service. This is the default behaviour.
If both are given, device_id is used and user_id is ignored.
Notification is delivered only to eligible devices: devices that are linked to the service, have notifications enabled, and have a push token.
Targeted notification (device_id / user_id) sent to an empty audience returns 404 and creates nothing.
A broadcast to a service with no eligible devices is still created and completes with zero deliveries.
Send a notification to all subscribed devices
Sent to every eligible device linked to the service.
A broadcast is fanned out by the queueing cron, which does not pick the notification up before scheduled_at has passed. Until then it reports status: created and has no deliveries; it becomes queued once every per-device delivery exists.
Path
POST /v1/notificationsRequest
curl -X POST https://api.appio.so/v1/notifications \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"payload":{"title":"Notification","message":"Hello from Appio Docs"}}'Request body
{
"payload": {
"title": "Notification",
"message": "Hello from Appio Docs"
}
}Response
HTTP Code: 201{
"id": "ntf_01jv7938vhvsccakmeq7c1hcy8"
}Send a scheduled notification to a user
Sends a notification to all eligible devices belonging to this user.
Returns 404, and creates nothing, if the user is unknown to the service or none of their devices can receive a push.
The per-device deliveries are created immediately, so the notification reports status: queued from creation onwards; the deliveries are simply held back until scheduled_at.
Path
POST /v1/notifications?user_id={user_id}Request
curl -X POST https://api.appio.so/v1/notifications?user_id=23d0e9848fe0ad06272dea39a03679ff \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"payload":{"title":"Notification","message":"Hello from Appio Docs"},"scheduled_at":""}'Request body
{
"payload": {
"title": "Notification",
"message": "Hello from Appio Docs"
},
"scheduled_at": ""
}Response
HTTP Code: 201{
"id": "ntf_01jv7akwwbj47rf3je11jgpvvf"
}Send a notification to a single device
Sends a notification to one device.
Returns 404, and creates nothing, if the device is unknown to the service or cannot receive a push (notifications disabled, no push token).
The delivery is created immediately, so the notification reports status: queued from creation onwards.
Path
POST /v1/notifications?device_id={device_id}Request
curl -X POST https://api.appio.so/v1/notifications?device_id=dvc_01jmpmh9fvxgyym44sqanjr9hs \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"payload":{"title":"Notification","message":"Hello from Appio Docs"}}'Request body
{
"payload": {
"title": "Notification",
"message": "Hello from Appio Docs"
}
}Response
HTTP Code: 201{
"id": "ntf_01jmpmgb6my0s57c960q1s862v"
}API Widgets
A widget shows the data of a data source, laid out by a widget template, on the user's device.
Widget fields
- name
- String. Max 255 bytes. Required for create.
- template_id
- String. ID of a widget template. Required for create.
- data_source_id
- optional
String. ID of a data source.
Send an explicit null to clear it; an empty string is rejected as a malformed ID. - published
- optional
Boolean. Whether the widget is published. Default: false.
List widgets
Returns a paginated list of the widgets of the service.
Accepts the limit and after pagination parameters.
Path
GET /v1/widgetsRequest
curl -X GET https://api.appio.so/v1/widgets \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"data": [
{
"id": "wgt_01jmpr3vt1008txv1jehgdeny7",
"service_id": "svc_00dddddd000000ccccccssssss",
"published": true,
"name": "Doc Widget",
"data_source_id": "ds_01k784vqqpassea0bfq2v2qps3",
"template_id": "wt_01k77dkvgnen72bk0xvvpvbq3a"
}
],
"pagination": {
"next": null
}
}View widget details
Path
GET /v1/widgets/{id}Request
curl -X GET https://api.appio.so/v1/widgets/wgt_01jmpr3vt1008txv1jehgdeny7 \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "wgt_01jmpr3vt1008txv1jehgdeny7",
"service_id": "svc_00dddddd000000ccccccssssss",
"published": true,
"name": "Doc Widget",
"data_source_id": "ds_01k784vqqpassea0bfq2v2qps3",
"template_id": "wt_01k77dkvgnen72bk0xvvpvbq3a"
}Create widget
Creates a new widget for the service. This request works with the docs key.
Path
POST /v1/widgetsRequest
curl -X POST https://api.appio.so/v1/widgets \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"name":"User Stats Widget","data_source_id":"ds_01k784vqqpassea0bfq2v2qps3","template_id":"wt_01k77dkvgnen72bk0xvvpvbq3a","published":true}'Request body
{
"name": "User Stats Widget",
"data_source_id": "ds_01k784vqqpassea0bfq2v2qps3",
"template_id": "wt_01k77dkvgnen72bk0xvvpvbq3a",
"published": true
}Response
HTTP Code: 201{
"id": "wgt_01jv7938vhvsccakmeq7c1hcy8"
}Update widget
Every field is optional, but at least one of name, data_source_id, template_id or published must be present: a body with none of them returns 400.
An empty name counts as absent.
Use your own API key.
Path
PATCH /v1/widgets/{id}Request
curl -X PATCH https://api.appio.so/v1/widgets/wgt_01jmpr3vt1008txv1jehgdeny7 \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"published":false}'Request body
{
"published": false
}Response
HTTP Code: 200{
"id": "wgt_01jmpr3vt1008txv1jehgdeny7"
}Delete widget
Use your own API key.
Path
DELETE /v1/widgets/{id}Request
curl -X DELETE https://api.appio.so/v1/widgets/wgt_01jmpr3vt1008txv1jehgdeny7 \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "wgt_01jmpr3vt1008txv1jehgdeny7"
}API Widget Templates
A widget template defines how a widget looks on each platform.
Widget template fields
- name
- String. Max 255 bytes. Required for create. Unique within the service: a duplicate returns 409.
- template_ios
- String. iOS template content. Required for create.
- template_android
- String. Android template content. Required for create.
- template_watchos
- String. watchOS template content. Required for create.
Responses also contain id, service_id, created_at and updated_at.
List widget templates
Returns a paginated list of the widget templates of the service.
Accepts the limit and after pagination parameters.
Path
GET /v1/templatesRequest
curl -X GET https://api.appio.so/v1/templates \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"data": [
{
"id": "wt_01k77dkvgnen72bk0xvvpvbq3a",
"service_id": "svc_00dddddd000000ccccccssssss",
"name": "Docs Widget Template",
"template_ios": "{\"variants\": [{\"properties\": {\"version\": \"1.0\", \"supportedFamilies\": [\"systemMedium\"]}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}",
"template_android": "{\"variants\": [{\"properties\": {\"version\": \"1.0\"}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}",
"template_watchos": "{\"variants\": [{\"properties\": {\"version\": \"1.0\", \"supportedFamilies\": [\"accessoryRectangular\"]}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}",
"created_at": "2026-11-02T16:50:31.160387Z",
"updated_at": "2026-11-02T16:50:31.160387Z"
}
],
"pagination": {
"next": null
}
}View widget template details
Path
GET /v1/templates/{id}Request
curl -X GET https://api.appio.so/v1/templates/wt_01k77dkvgnen72bk0xvvpvbq3a \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "wt_01k77dkvgnen72bk0xvvpvbq3a",
"service_id": "svc_00dddddd000000ccccccssssss",
"name": "Docs Widget Template",
"template_ios": "{\"variants\": [{\"properties\": {\"version\": \"1.0\", \"supportedFamilies\": [\"systemMedium\"]}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}",
"template_android": "{\"variants\": [{\"properties\": {\"version\": \"1.0\"}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}",
"template_watchos": "{\"variants\": [{\"properties\": {\"version\": \"1.0\", \"supportedFamilies\": [\"accessoryRectangular\"]}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}",
"created_at": "2026-11-02T16:50:31.160387Z",
"updated_at": "2026-11-02T16:50:31.160387Z"
}Create widget template
Use your own API key.
Path
POST /v1/templatesRequest
curl -X POST https://api.appio.so/v1/templates \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"name":"My Widget Template","template_ios":"{\"variants\": [{\"properties\": {\"version\": \"1.0\", \"supportedFamilies\": [\"systemMedium\"]}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}","template_android":"{\"variants\": [{\"properties\": {\"version\": \"1.0\"}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}","template_watchos":"{\"variants\": [{\"properties\": {\"version\": \"1.0\", \"supportedFamilies\": [\"accessoryRectangular\"]}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}"}'Request body
{
"name": "My Widget Template",
"template_ios": "{\"variants\": [{\"properties\": {\"version\": \"1.0\", \"supportedFamilies\": [\"systemMedium\"]}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}",
"template_android": "{\"variants\": [{\"properties\": {\"version\": \"1.0\"}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}",
"template_watchos": "{\"variants\": [{\"properties\": {\"version\": \"1.0\", \"supportedFamilies\": [\"accessoryRectangular\"]}, \"elements\": [{\"type\": \"text\", \"properties\": {\"text\": \"Demo\"}}]}]}"
}Response
HTTP Code: 201{
"id": "wt_01jv7938vhvsccakmeq7c1hcy8"
}Update widget template
Every field is optional, but at least one of name, template_ios, template_android or template_watchos must be present and non-empty: a body with none of them returns 400.
A successful update also sends a silent push that makes the service's devices refresh their widgets.
It does not appear in GET /v1/notifications.
Use your own API key.
Path
PATCH /v1/templates/{id}Request
curl -X PATCH https://api.appio.so/v1/templates/wt_01k77dkvgnen72bk0xvvpvbq3a \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"name":"Renamed Widget Template"}'Request body
{
"name": "Renamed Widget Template"
}Response
HTTP Code: 200{
"id": "wt_01k77dkvgnen72bk0xvvpvbq3a"
}Delete widget template
Use your own API key.
Path
DELETE /v1/templates/{id}Request
curl -X DELETE https://api.appio.so/v1/templates/wt_01k77dkvgnen72bk0xvvpvbq3a \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "wt_01k77dkvgnen72bk0xvvpvbq3a"
}API Data Sources
A data source tells Appio where to fetch the data a widget shows. Appio requests it for each user when their widget is rendered.
Data source fields
- name
- String. Max 255 bytes. Required for create. Unique within the service: a duplicate returns 409.
- type
- String. url or demo. Required for create.
Any other non-empty value is saved, but a widget using it fails to render. - definition
- Object. Required for create. See below.
Responses also contain id, service_id, created_at and updated_at.
Definition for type url
- url
- String. URL to fetch the data from. It must return JSON.
- headers
- optional
Object of strings. Custom headers sent with the request. - query_param
- optional
Boolean. Default: false. Send the user's ID as the user_id query parameter. - header
- optional
Boolean. Default: false. Send the user's ID in the X-User-Id header.
For type: demo the definition holds url, color, bg and tint. Other keys are stored as they are.
Creating, updating or previewing a data source with any other host returns 402.
List data sources
Returns a paginated list of the data sources of the service.
Accepts the limit and after pagination parameters.
Path
GET /v1/datasourcesRequest
curl -X GET https://api.appio.so/v1/datasources \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"data": [
{
"id": "ds_01k784vqqpassea0bfq2v2qps3",
"service_id": "svc_00dddddd000000ccccccssssss",
"name": "Docs Data Source",
"type": "url",
"definition": {
"url": "https://demo.appio.so/data/random/1/99"
},
"created_at": "2026-11-02T16:50:31.160387Z",
"updated_at": "2026-11-02T16:50:31.160387Z"
}
],
"pagination": {
"next": null
}
}View data source details
Path
GET /v1/datasources/{id}Request
curl -X GET https://api.appio.so/v1/datasources/ds_01k784vqqpassea0bfq2v2qps3 \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "ds_01k784vqqpassea0bfq2v2qps3",
"service_id": "svc_00dddddd000000ccccccssssss",
"name": "Docs Data Source",
"type": "url",
"definition": {
"url": "https://demo.appio.so/data/random/1/99"
},
"created_at": "2026-11-02T16:50:31.160387Z",
"updated_at": "2026-11-02T16:50:31.160387Z"
}Create data source
Use your own API key.
Path
POST /v1/datasourcesRequest
curl -X POST https://api.appio.so/v1/datasources \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"name":"My Data Source","type":"url","definition":{"url":"https://demo.appio.so/data/random/1/99","headers":{"Authorization":"Bearer 1234567890"},"query_param":true}}'Request body
{
"name": "My Data Source",
"type": "url",
"definition": {
"url": "https://demo.appio.so/data/random/1/99",
"headers": {
"Authorization": "Bearer 1234567890"
},
"query_param": true
}
}Response
HTTP Code: 201{
"id": "ds_01jv7938vhvsccakmeq7c1hcy8"
}Update data source
Every field is optional, but at least one of name, type or definition must be present: a body with none of them returns 400.
An empty name or type counts as absent.
Use your own API key.
Path
PATCH /v1/datasources/{id}Request
curl -X PATCH https://api.appio.so/v1/datasources/ds_01k784vqqpassea0bfq2v2qps3 \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"name":"Renamed Data Source"}'Request body
{
"name": "Renamed Data Source"
}Response
HTTP Code: 200{
"id": "ds_01k784vqqpassea0bfq2v2qps3"
}Delete data source
Use your own API key.
Path
DELETE /v1/datasources/{id}Request
curl -X DELETE https://api.appio.so/v1/datasources/ds_01k784vqqpassea0bfq2v2qps3 \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "ds_01k784vqqpassea0bfq2v2qps3"
}Preview data source URL
Tests a data source URL before saving it. Appio requests the URL with the given user ID and headers and returns what it received.
- name
- String. Max 255 bytes.
- url
- String. URL to fetch the data from.
- user_id
- String. User ID to test with.
- query_param
- optional
Boolean. Default: false. Append user_id as a query parameter to the URL. - header
- optional
Boolean. Default: false. Send user_id in the X-User-Id header. - headers
- optional
Object of strings. Custom headers sent with the request.
At least one of query_param or header must be true.
The response is 200 whatever the URL answered:
- code
- Integer. HTTP status code the URL answered with.
- body
- String. Response body of the URL.
- type
- String. Always JSON, the only format a widget can render.
- valid
- Boolean. Whether the body parses as JSON. An XML, HTML or plain text body is reported as false.
A URL that cannot be reached, does not answer within 10 seconds, or returns a body over 10 MB returns 500.
Use your own API key.
Path
POST /v1/datasources/previewRequest
curl -X POST https://api.appio.so/v1/datasources/preview \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"name":"Test Preview","url":"https://demo.appio.so/data/random/1/99","user_id":"23d0e9848fe0ad06272dea39a03679ff","query_param":true}'Request body
{
"name": "Test Preview",
"url": "https://demo.appio.so/data/random/1/99",
"user_id": "23d0e9848fe0ad06272dea39a03679ff",
"query_param": true
}Response
HTTP Code: 200{
"code": 200,
"body": "{\"value\": 37}",
"type": "JSON",
"valid": true
}API Data Source Snapshots
A snapshot is the last response of a data source stored for one user. There is one snapshot per data source and user.
View data source snapshot
Returns the snapshot of a data source for the user in the required user_id query parameter.
A missing user_id returns 400; a user without a snapshot returns 404.
Path
GET /v1/datasources/{id}/snapshots?user_id={user_id}Request
curl -X GET https://api.appio.so/v1/datasources/ds_01k784vqqpassea0bfq2v2qps3/snapshots?user_id=23d0e9848fe0ad06272dea39a03679ff \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss"Response
HTTP Code: 200{
"id": "dss_01k784w2m3q8zr5t6y7n8b9c0d",
"data_source_id": "ds_01k784vqqpassea0bfq2v2qps3",
"user_id": "23d0e9848fe0ad06272dea39a03679ff",
"snapshot": "{\"value\": 37}",
"created_at": "2026-11-02T16:50:31.160387Z",
"updated_at": "2026-11-02T16:50:31.160387Z"
}Store data source snapshot
Stores the snapshot of a data source for a user, replacing the one already stored for that user (which keeps its id).
A later widget render for that user replaces it again.
- user_id
- String. ID of the user the snapshot belongs to.
- snapshot
- String. The data source response to store.
Use your own API key.
Path
POST /v1/datasources/{id}/snapshotsRequest
curl -X POST https://api.appio.so/v1/datasources/ds_01k784vqqpassea0bfq2v2qps3/snapshots \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "Content-Type: application/json" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-d '{"user_id":"23d0e9848fe0ad06272dea39a03679ff","snapshot":"{\"value\": 37}"}'Request body
{
"user_id": "23d0e9848fe0ad06272dea39a03679ff",
"snapshot": "{\"value\": 37}"
}Response
HTTP Code: 201{
"id": "dss_01k784w2m3q8zr5t6y7n8b9c0d",
"data_source_id": "ds_01k784vqqpassea0bfq2v2qps3",
"user_id": "23d0e9848fe0ad06272dea39a03679ff",
"snapshot": "{\"value\": 37}",
"created_at": "2026-11-02T16:50:31.160387Z",
"updated_at": "2026-11-02T16:50:31.160387Z"
}API Images
Upload image
Uploads an image for the service and returns its public URL, e.g. to use as a notification image_url.
Send it as multipart/form-data in the image field.
Allowed formats: JPEG, PNG, GIF, WebP. Max 3MB. SVG is not accepted.
The Content-Type of the part is verified against the file's content; if they contradict each other the upload returns 400.
The file name in the returned URL is generated by Appio, and its extension (.jpg, .png, .gif, .webp) comes from the verified format.
With the docs key nothing is stored and the same placeholder URL is always returned.
Use your own API key for full access.
Path
POST /v1/imagesRequest
curl -X POST https://api.appio.so/v1/images \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A" \
-H "X-Service-Id: svc_00dddddd000000ccccccssssss" \
-F "image=@banner.png"Response
HTTP Code: 201{
"url": "https://cdn-r2.appio.so/svc_00dddddd000000ccccccssssss/img_00dddddd000000ccccccssssss.png"
}API Billing
View billing status
Returns whether the organization has active subscription.
entitled is true when at least one subscription is active, trialing or past due, and false otherwise.
No X-Service-Id header is required.
Path
GET /v1/billing/statusRequest
curl -X GET https://api.appio.so/v1/billing/status \
-H "Authorization: Bearer docs_g3psUMsuKZ7NGGJvuk1csf47pvJfukz97cS5ZrOuHnY98yhY5A"Response
HTTP Code: 200{
"entitled": true
}API Errors
- 400
- Bad Request
The request contains invalid or missing data.
Please double-check the API endpoint URL and ensure the request body is correctly formatted and complete.
This includes a missing or malformed X-Service-Id header, and any request body field the endpoint does not know. - 401
- Unauthorized
Authentication is required.
The Authorization header is missing, or is not Bearer <api-key>. - 402
- Payment Required
Your organization has no subscription that is active, trialing or past due.
Subscribe at the URL in data.url of the error response. - 403
- Forbidden
The API key does not exist, has been deactivated, is bound to a service that has been deactivated, or has no access to this service or endpoint.
On the "Appio Docs" service, every PATCH and DELETE, and any POST on data sources or widget templates, returns Not allowed on the Appio Docs service, use your production API key. - 404
- Not Found
The requested entity doesn't exist.
Verify that the resource identifier is correct, exists, and is currently active. - 409
- Conflict
A data source or widget template with the same name already exists in this service. - 429
- Too Many Requests
The per-IP rate limit was exceeded, or too many failed attempts were made with the same API key.
After exceeding the rate limit, wait the number of seconds in the Retry-After header before retrying. - 500
- Internal Error
Something went wrong while processing your request.
Try again later or contact support if the issue persists.
Error response
Every error uses the same body shape.
{
"error": {
"message": "Invalid input data",
"data": {
"doc_url": "https://docs.appio.so/#api-services",
"entity": "service",
"validation_errors": [
{
"field": "title",
"reason": "title is required"
}
]
}
}
}- message
- String. Human-readable description of the error.
- data
- optional
Object. Extra context when available, e.g. doc_url, entity, validation_errors, url.
AI Instructions
OpenAPI definition is available at /openapi-v1.yaml.
It covers the whole API: services, devices, notifications, widgets, widget templates, data sources and their snapshots, image upload and billing status.