AdScaled LinkBoard API
Read a temporary LinkBoard room (texts, links, files) that a person shared with you, and follow it live.
AdScaled LinkBoard is a temporary board for moving links, text and files between devices. A person creates a room in the browser (https://linkboard.adscaled.media/new), gets a link such as https://linkboard.adscaled.media/r/J7K4-P2 and everything sent to the room appears on every device. Rooms expire after 1 hour, 6 hours, 24 hours or 7 days.
With this API an agent can read a room the person gave it: the room state with all items (getRoom), the stored files and previews (getRoomItemAsset) and new items as they arrive (streamRoomEvents, Server-Sent Events). Creating rooms and sending items is done in the web interface only: write requests must come from LinkBoard pages (same-site check) and answer 403 forbidden_origin to other clients.
No account or API key exists. Every room request needs an X-Device-Id header (any random id you keep). A room with a password additionally needs its room token in X-Room-Token (or ?t=); the token is issued only to the web interface after the password is entered, so password rooms are normally out of reach for agents.
Rate limits per client IP (IPv6 per /64): 300 API requests per minute (flood) and 40 unknown room codes per 10 minutes (room-lookup). Responses carry the IETF RateLimit-Policy and RateLimit headers (draft-ietf-httpapi-ratelimit-headers); a 429 also carries Retry-After in seconds. At most 20 live streams per room and 30 per IP. Errors are always { "error": { "code", "message" } } (application/json).
Versioning and deprecation
The API is versioned in the path: every documented operation lives under /api/v1/ and info.version follows semantic versioning (a minor version adds fields or operations, a patch version fixes descriptions). Within v1 changes are only additive: new optional fields, new event types, new operations. Clients must ignore unknown fields and events. A breaking change (removing or renaming a field, changing a type, a status code or the meaning of a value) gets a new path version (/api/v2/); the old version keeps working until the announced Sunset date. Deprecated operations are announced in this document (deprecated: true), in /api/docs and in responses with the Deprecation header (RFC 9745) and the Sunset header with the removal date (RFC 8594), plus a Link with rel="deprecation" to the notice. A deprecated operation stays available for at least 90 days after the deprecation is announced. Nothing is deprecated today. The unversioned paths (/api/health, /api/rooms/...) are the same v1 handlers, used by the web interface, and stay as aliases of the current version.
- API version:
1.1.0, base path https://linkboard.adscaled.media/api/v1/ (see Versioning and deprecation) - OpenAPI 3.1 specification: https://linkboard.adscaled.media/api/openapi.json
- API catalog (RFC 9727): https://linkboard.adscaled.media/.well-known/api-catalog
- Agent guide: https://linkboard.adscaled.media/llms.txt
- Web interface: https://linkboard.adscaled.media/ (create a room: https://linkboard.adscaled.media/new)
Endpoints
GET /api/v1/health (getHealth)
Check API availability. Returns { "ok": true } while the service is running. Needs no headers.
Example:
curl https://linkboard.adscaled.media/api/v1/health
Responses:
200: The service is running.429:rate_limited: a rate limit was hit; waitRetry-Afterseconds.
GET /api/v1/rooms/{code} (getRoom)
Read a room and all its items. Returns the room settings, every item (oldest first), the connected device numbers and your own device number. Text items carry the full text; link items the URL and a preview once the server has fetched it; file and image items the file metadata (download them with getRoomItemAsset). Opening a room registers your X-Device-Id as a device of that room.
| Parameter | In | Required | Description |
|---|---|---|---|
code | path | yes | Room code from the room link (https://linkboard.adscaled.media/r/J7K4-P2). Case-insensitive, with or without the dash; 6 characters of ABCDEFGHJKMNPQRSTUVWXYZ23456789. |
X-Device-Id | header | yes | Random id of the client (16 to 64 characters of A-Z a-z 0-9 _ -). Generate one and reuse it: the room numbers devices by it ("Device 2"). It is not a secret and is never shown to other devices. |
Example:
curl -H 'X-Device-Id: agent-0123456789abcdef' https://linkboard.adscaled.media/api/v1/rooms/J7K4-P2
Responses:
200: The room state.400:invalid_input:X-Device-Idis missing or invalid.401:password_required: the room has a password and the request has no valid room token.404:room_not_found: no such room, or it has expired or was deleted. Unknown codes count against theroom-lookuplimit.429:rate_limited: a rate limit was hit; waitRetry-Afterseconds.
GET /api/v1/rooms/{code}/items/{id}/{asset} (getRoomItemAsset)
Download a stored file or preview image. file = the uploaded file of a file or image item (images are served inline, everything else as an attachment; add download=1 to force an attachment); thumb = WebP preview of an image item; icon and image = WebP favicon and picture of a link item preview (see preview.hasIcon / preview.hasImage). Supports HTTP Range requests.
| Parameter | In | Required | Description |
|---|---|---|---|
code | path | yes | Room code from the room link (https://linkboard.adscaled.media/r/J7K4-P2). Case-insensitive, with or without the dash; 6 characters of ABCDEFGHJKMNPQRSTUVWXYZ23456789. |
id | path | yes | Item id from getRoom. |
asset | path | yes | Which stored bytes to return. |
download | query | no | 1 = send Content-Disposition: attachment for images too. |
t | query | no | Room token of a password-protected room, for URLs that cannot carry headers (EventSource, links). Same value as X-Room-Token. |
Example:
curl -o file.bin https://linkboard.adscaled.media/api/v1/rooms/J7K4-P2/items/ITEM_ID/file
Responses:
200: The bytes.Content-Typeis the type sniffed from the bytes when the file was stored (FileMeta.mime); HTML, SVG, XML and scripts are sent asapplication/octet-stream. Previews (thumb,icon,image) are alwaysimage/webp. The listed media types are the common ones; any other stored type is returned as it is.206: Partial content: the requested byte range of a Range request.304: Not modified (If-Modified-Since); no body.401:password_required: the room has a password and the request has no valid room token.404:room_not_foundoritem_not_found: the room or item does not exist, or the item has no such asset.416:invalid_input: the Range header asks for bytes outside the file.429:rate_limited: a rate limit was hit; waitRetry-Afterseconds.
GET /api/v1/rooms/{code}/events (streamRoomEvents)
Follow a room live (Server-Sent Events). A text/event-stream that starts with retry: 3000 and a hello event (the same RoomState as getRoom), then sends item, item_updated, item_deleted, items_cleared, room, presence and room_deleted events with JSON data; a : ping comment every 25 s keeps the connection open. While the stream is open your device counts as online in the room. The stream ends with room_deleted when the room expires or is deleted.
| Parameter | In | Required | Description |
|---|---|---|---|
code | path | yes | Room code from the room link (https://linkboard.adscaled.media/r/J7K4-P2). Case-insensitive, with or without the dash; 6 characters of ABCDEFGHJKMNPQRSTUVWXYZ23456789. |
device | query | yes | Your device id (same rules as X-Device-Id). |
t | query | no | Room token of a password-protected room, for URLs that cannot carry headers (EventSource, links). Same value as X-Room-Token. |
Example:
curl -N "https://linkboard.adscaled.media/api/v1/rooms/J7K4-P2/events?device=agent-0123456789abcdef"
Responses:
200: The event stream (text/event-stream; charset=utf-8,Cache-Control: no-cache, no-store, no-transform). Every event isevent: <type>plus onedata:line with JSON; the JSON of each event type is described by theRoomEventschema.400:invalid_input:deviceis missing or invalid.401:password_required: the room has a password and the request has no valid room token.404:room_not_found: no such room, or it has expired or was deleted. Unknown codes count against theroom-lookuplimit.429:rate_limited: a rate limit was hit; waitRetry-Afterseconds.
Rate limits
Responses carry RateLimit-Policy and RateLimit headers (draft-ietf-httpapi-ratelimit-headers). Each policy is listed by name:
flood: 300 requests per 60 s per client IP, shared by all/apipaths.room-lookup: 40 unknown room codes per 600 s per client IP (opening an existing room does not count).write,room-create,unlock: limits of the web interface (120 writes per 60 s, 30 new rooms per 3600 s, 10 wrong passwords per 900 s).
Example: RateLimit-Policy: "flood";q=300;w=60, "room-lookup";q=40;w=600 and RateLimit: "flood";r=297;t=41, "room-lookup";r=40;t=600 (q quota, w window in seconds, r remaining, t seconds until the quota resets). A 429 response adds Retry-After in seconds. Live streams: at most 20 per room and 30 per IP (429 with Retry-After: 30).
Authentication
None: no accounts and no API keys. Access to a room means knowing its code. Send any random X-Device-Id you keep for the session. Password rooms need the room token (X-Room-Token or ?t=), which only the web interface receives after the password is entered. Write requests (creating rooms, sending items, uploads, settings) are accepted only from LinkBoard pages and answer 403 forbidden_origin to other clients.