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

Mobile APP

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 documented here, except viewing a service, 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
"👋"

The default token in these examples belongs to the "Appio Docs" service.
Reads work, but every PATCH and DELETE, and any POST on data sources or widget templates, returns 403.
Use your own token for those.

API Services

View service details

Returns the details of a single service.
The {id} path parameter names the service, so no X-Service-Id header is needed.

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

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.

Pagination parameters

limit
optional
Integer. Default: 50. Min: 1. Values above 100 are treated as 100.
Maximum number of items to return.
after
optional
String. ID of the last item of the previous page.
Items are listed newest first, so this returns older items.

pagination.next is the path of the next page, or null on the last page.

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": "iPhone 13",
            "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": "iPhone 13",
    "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.

Pagination parameters

limit
optional
Integer. Default: 50. Min: 1. Values above 100 are treated as 100.
Maximum number of items to return.
after
optional
String. ID of the last item of the previous page.
Items are listed newest first, so this returns older items.

pagination.next is the path of the next page, or null on the last page.


List notifications

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": "2025-03-24T14:23:37.016526Z"
        },
        ...
    ],
    "pagination": {
        "next": "/v1/notifications?after=ntf_01jmpmgb6my0s57c960q1s862v&limit=50"
    }
}

List delivered notifications

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": "2025-03-24T14:23:37.016526Z"
        },
        ...
    ],
    "pagination": {
        "next": "/v1/notifications?after=ntf_01jmpmgb6my0s57c960q1s862v&limit=50&status=completed"
    }
}

List notifications for a device

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": "2025-03-24T14:23:37.016526Z"
        },
        ...
    ],
    "pagination": {
        "next": "/v1/notifications?after=ntf_01jmpmgb6my0s57c960q1s862v&device_id=dvc_01jmpmh9fvxgyym44sqanjr9hs&limit=50"
    }
}

List notifications for a user

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": "2025-03-24T14:23:37.016526Z"
        },
        ...
    ],
    "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": "2025-03-24T14:23:37.016526Z",
    "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 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.
Only sending a notification (POST /v1/notifications), and saving or previewing a data source whose URL host is not localhost, 127.0.0.1 or api.appio.so, require one. Subscribe at the URL in data.url of the error response, my.appio.so/pricing.
403
Forbidden
The API key does not exist, 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

LLM-friendly content for AI agents is available at /llms.txt.
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.
The sections above document the subset most integrations need.