The same account-scoped API the web app itself is built on — frames, framesets, alerts, ambient effects, data sources, and media. Anything you can do in the web app, you can do here: build your own integration, script a custom trigger, or pull your device list into another tool.
(Looking for the simpler device-key API a device's own script can push directly to, no account needed? See /docs/device-content instead.)
Every endpoint below takes Authorization: Bearer <token>. The token can be
either your logged-in session token, or a personal API key — create one from the
API Keys page in the web app. Both work identically everywhere; an API key
just doesn't expire when you log out, which is what you want for a script or webhook.
curl https://api.glowboxlabs.com/v1/devices \ -H "Authorization: Bearer <your api key>"
| Endpoint | Notes |
|---|---|
GET /v1/devices | List devices claimed by your account. Each has device_id, name, device_type, claimed_at. |
POST /v1/devices/{deviceID}/claim | Claim a device you've connected to over its own WiFi setup portal. |
PUT /v1/devices/{deviceID}/name | Body: {"name": "Living Room"}. |
POST /v1/devices/{deviceID}/reset | Clears any assigned frameset, active alert, and ambient override in one call — back to the built-in clock/announcement/score-reel rotation. |
A frame is one screen — a set of positioned boxes (text, image, data, or solid color). A frameset is an ordered list of frames a device rotates through. Assigning a frameset to a device is what actually puts it on the panel.
| Endpoint | Notes |
|---|---|
POST /v1/frames | Create a frame. See body shape below. |
GET /v1/frames / /v1/frames/{id} | List / fetch. |
PUT /v1/frames/{id} / DELETE /v1/frames/{id} | Update / delete. |
POST /v1/frames/preview | Same body as create, returns the server-compiled bitmap preview without saving — what the web app's live editor uses. |
POST /v1/framesets | Body: {"name": "...", "items": [{"frame_id": 1, "duration_ms": 4000}]}. |
GET /v1/framesets / /v1/framesets/{id} | List / fetch. |
PUT /v1/framesets/{id} / DELETE /v1/framesets/{id} | Update (same body as create) / delete. |
PUT /v1/devices/{deviceID}/frameset | Body: {"frameset_id": 1}. Assigns a frameset as the device's normal rotation. |
{
"name": "Game night",
"kind": "custom",
"duration_ms": 4000,
"boxes": [
{
"x": 0, "y": 0, "width": 64, "height": 16,
"box_type": "text",
"background_color": "000000",
"effect": "static",
"text": { "content": "GAME NIGHT <color=FF0000>7PM</color>", "align": "center" }
},
{
"x": 20, "y": 16, "width": 24, "height": 16,
"box_type": "image",
"media_id": 12
}
]
}
| Field | Notes |
|---|---|
box_type | text | image | color_fill (data also still accepted, deprecated -- see below) |
text.content | May contain <color=RRGGBB>...</color> markup to color part of the string. Omit entirely (or leave empty) when data_source_id is set -- see below. |
media_id | An uploaded image/GIF's ID (see Media below) — only valid on an image box with no data_source_id, and must be media you own. |
data_source_id | A data source's ID (see Data sources below), valid on a text or image box: on text, its field value becomes the box's content instead of authored spans; on image, the field's value is fetched as an image URL instead of using media_id. Must be a source you own. |
data_field_path | Which field of data_source_id's payload to use (e.g. artist vs track off the same Spotify source) — omit to use that source's own default field. |
text_color | Hex, no # — a data-source-driven text box's color (an authored box colors itself via <color=...> markup instead). |
effect | static | scroll — per-box marquee scroll instead of a held card. |
The standalone data box_type is deprecated but still accepted for existing integrations: it behaves exactly like a text box with data_source_id set. New code should just use text (or image) with data_source_id directly -- a data source is a content source for text/image, not its own kind of box.
An alert temporarily interrupts a device's normal rotation — either an existing frameset, or a quick one-off message synthesized on the spot. This is the endpoint a webhook (Zapier, IFTTT, a game-day script) typically fires.
| Endpoint | Notes |
|---|---|
POST /v1/alerts | See body shape below. |
DELETE /v1/alerts/{deviceID} | Clears an active alert early, resuming normal rotation. |
curl -X POST https://api.glowboxlabs.com/v1/alerts \
-H "Authorization: Bearer <your api key>" \
-H "Content-Type: application/json" \
-d '{
"device_id": "AA:BB:CC:DD:EE:FF",
"text": "GOAL!",
"color": "FF0000",
"background_color": "000000",
"duration_ms": 10000
}'
| Field | Notes |
|---|---|
text / color / background_color | The quick-message path — mutually exclusive with frameset_id. |
frameset_id | Show an existing frameset as the alert instead of a synthesized message. |
duration_ms | Omit for a 10s default. 0 means indefinite — stays up until cleared via the DELETE above. |
A remote override of a device's ambient lighting effect (plasma/rainbow/sparkle/fire), independent of frame/frameset content.
| Endpoint | Notes |
|---|---|
PUT /v1/devices/{deviceID}/ambient | Body: {"effect": "fire", "palette": "heat"}. palette is optional. |
GET /v1/devices/{deviceID}/ambient | Current override, if any. |
DELETE /v1/devices/{deviceID}/ambient | Clears the override — falls back to the device's own local setting. |
Map a JSON API, RSS feed, or your connected Spotify now-playing status to one value you can drop
into a data box on any frame.
| Endpoint | Notes |
|---|---|
POST /v1/data-sources | Body: {"name": "...", "kind": "json", "url": "https://...", "field_path": "data.items.0.title"}. For kind: "spotify", omit url. |
GET /v1/data-sources / /v1/data-sources/{id} | List / fetch. |
PUT /v1/data-sources/{id} / DELETE /v1/data-sources/{id} | Update / delete. |
POST /v1/data-sources/inspect | Body: {"kind": "json", "url": "..."} (or just {"kind": "spotify"}). Returns discoverable {path, sample_value} pairs — what the web app's field picker uses instead of hand-typing a path. |
POST /v1/spotify/authorize | Returns {"url": "..."} — send the browser there to connect a Spotify account. |
GET /v1/spotify/status | {"connected": bool, "spotify_user_id": "..."}. |
DELETE /v1/spotify/connection | Disconnects. Doesn't delete any data source built on top of it. |
| Endpoint | Notes |
|---|---|
POST /v1/media | multipart/form-data: file (PNG/JPEG/GIF), width, height. An animated GIF is stored frame-by-frame automatically. |
GET /v1/media / /v1/media/{id} | List / fetch metadata. |
DELETE /v1/media/{id} | Refuses if a frame still references it. |
No hard rate limit today beyond generous request-size caps per endpoint. Every error is a plain 4xx/5xx with a short plain-text body — no envelope to parse.