Appio documentation

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

Mobile APP

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/services

Request

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/services

Request

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/devices

Request

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/notifications

Request

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=completed

Request

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/notifications

Request

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/widgets

Request

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/widgets

Request

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/templates

Request

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/templates

Request

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/datasources

Request

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/datasources

Request

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/preview

Request

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}/snapshots

Request

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/images

Request

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/status

Request

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.