Reference
Docs.
One REST surface for items, one long-lived connection for everything that changes.
Authentication
The app issues a device token on first launch. Send it as a bearer token. There are no cookies and no session state on the server, so the token is the whole identity of a client. Without a valid one every path under /api/ answers 401.
curl https://tylerdyrden.online/api/v3/items \
-H "Authorization: Bearer $SS_DEVICE_TOKEN" \
-H "Accept: application/json"
Items
| Method | Path | What it does |
|---|---|---|
GET | /api/v3/items | Unread items, newest first. ?since= takes a cursor. |
POST | /api/v3/items | Save a URL. Returns the item once the title has resolved. |
POST | /api/v3/items/:id/spend | Mark as read. The item leaves at the next local midnight. |
DELETE | /api/v3/items/:id | Drop it now, without reading. |
Item shape
{
"id": "it_8Fq2vK",
"url": "https://slowmedia.blog/to-read-pile",
"title": "Why your to-read pile is a to-do list in disguise",
"saved_at": "2026-10-02T09:14:22Z",
"expires_at": "2026-11-01T09:14:22Z",
"state": "unread"
}
Errors
Errors are JSON with the same shape everywhere, so a client never has to guess what it is parsing:
{ "error": "unauthorized", "message": "Missing or invalid device token." }
Sync
Polling works, but clients are expected to hold one connection instead and take changes as they arrive:
POST /cdn/v3/sync/
Content-Type: application/octet-stream
Frames are length-prefixed and carry the same objects as the REST surface. A client that drops reconnects with its last cursor and is sent what it missed — nothing lost, nothing replayed twice. A plain GET on that path is rejected; it only speaks the frame protocol.
Why it lives under /cdn
Historical, mostly. Sync and the static assets were put behind one cache so a single hostname covers both, and moving it now would break every shipped client for no real gain. Treat the prefix as an address, not a promise about caching: sync responses are never cached.
Limits
| Limit | Value |
|---|---|
| Saves per hour | 120 per device |
| Item retention | 30 days unread; until midnight once read |
| Payload | 64 KB per request |
| Open sync connections | 4 per account |
Going over a limit returns 429 with Retry-After. Nothing is banned automatically; if something looks stuck, write to me.