API Documentation
Everything Mastodon client developers need to integrate with ReadSync.
👋 You're a Mastodon client developer
This documentation explains how to authenticate and call the ReadSync API to store and retrieve read positions on behalf of your users.
Not a developer?
If you're a Mastodon user and your client already supports ReadSync, you just need to create an account and generate a token in your dashboard. Get started here.
Introduction
The ReadSync API lets Mastodon clients read and write a user's last-read timeline position. Any client that integrates with ReadSync can resume exactly where the user left off — on any device.
All API endpoints are under the base path:
https://your-instance.example.com/api/v1Replace your-instance.example.com with the domain of the ReadSync instance the user registered on. Users self-host or use a shared instance.
Authentication
Every request must include a Bearer token in the Authorization header. Personal Access Tokens are generated from the user's dashboard under Settings → Access Tokens.
GET /api/v1/read_position HTTP/1.1
Host: your-instance.example.com
Authorization: Bearer rs_live_xxxxxxxxxxxxxxxxxxxxRate limiting
Requests are rate-limited per API key and per IP address.
| 60 req / min | Per token |
| 100 req / min | Per IP address |
When a limit is exceeded the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait.
{
"error": {
"code": "too_many_requests",
"detail": "Rate limit exceeded. Retry after 30 seconds."
}
}Errors
All error responses share the same envelope:
{
"error": {
"code": "unauthorized",
"detail": "Invalid or revoked token"
}
}| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing, invalid, or revoked token |
| 404 | not_found | Resource not found (no position set yet, or unknown handle) |
| 422 | unprocessable_entity | Missing or invalid request parameter |
| 429 | too_many_requests | Rate limit exceeded |
/api/v1/read_position
Get last-read position
Returns the last-read toot ID for the authenticated user's Mastodon account. If the user has multiple linked accounts, the first is returned by default. Use the handle parameter to target a specific account.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| handle | string | optional | Full Mastodon handle. Accepted formats: @user@server.example or user@server.example |
Example request
GET /api/v1/read_position HTTP/1.1
Host: your-instance.example.com
Authorization: Bearer rs_live_xxxxxxxxxxxxxxxxxxxxExample request with handle
GET /api/v1/read_position?handle=@you@mastodon.social HTTP/1.1
Host: your-instance.example.com
Authorization: Bearer rs_live_xxxxxxxxxxxxxxxxxxxxResponse — 200 OK
{
"data": {
"last_read_id": "114262948242478592",
"mastodon_account": "@you@mastodon.social",
"updated_at": "2026-04-27T14:00:00Z"
},
"meta": { "version": "v1" }
}Response — 404 Not Found
Returned when no position has been written yet for this account.
{
"error": {
"code": "not_found",
"detail": "No read position found for this account"
}
}/api/v1/read_position
Update last-read position
Sets (or overwrites) the last-read toot ID. This is the primary write endpoint — call it whenever the user advances in their timeline. Use the handle query parameter to target a specific Mastodon account when the user has more than one linked.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| handle | string | optional | Full Mastodon handle to target. Defaults to the user's first linked account. |
Request body
Content-Type: application/json
| Parameter | Type | Required | Description |
|---|---|---|---|
| last_read_id | string | required | Mastodon snowflake ID of the last toot the user has seen. |
Example request
PUT /api/v1/read_position HTTP/1.1
Host: your-instance.example.com
Authorization: Bearer rs_live_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"last_read_id": "114262948242478592"
}Response — 200 OK
{
"data": {
"last_read_id": "114262948242478592",
"mastodon_account": "@you@mastodon.social",
"updated_at": "2026-04-29T09:45:00Z"
},
"meta": { "version": "v1" }
}Response — 422 Unprocessable Entity
Returned when last_read_id is missing.
{
"error": {
"code": "unprocessable_entity",
"detail": "param is missing or the value is empty: last_read_id"
}
}/api/v1/mastodon_accounts
List Mastodon accounts
Returns all Mastodon accounts linked to the authenticated ReadSync user, including their current read positions. Use this to let users pick which account to sync, or to display all positions in your UI. Results are sorted by server then username.
Example request
GET /api/v1/mastodon_accounts HTTP/1.1
Host: your-instance.example.com
Authorization: Bearer rs_live_xxxxxxxxxxxxxxxxxxxxResponse — 200 OK
{
"data": [
{
"handle": "@you@mastodon.social",
"username": "you",
"server": "mastodon.social",
"verified": true,
"last_read_id": "114262948242478592",
"last_read_at": "2026-04-27T14:00:00Z"
},
{
"handle": "@you@fosstodon.org",
"username": "you",
"server": "fosstodon.org",
"verified": false,
"last_read_id": null,
"last_read_at": null
}
],
"meta": {
"total": 2,
"version": "v1"
}
}ReadPosition object
| Field | Type | Description |
|---|---|---|
| last_read_id | string | Mastodon snowflake ID of the last toot read |
| mastodon_account | string | Full Mastodon handle (e.g. @you@mastodon.social) |
| updated_at | string (ISO 8601) | When the position was last updated |
MastodonAccount object
| Field | Type | Description |
|---|---|---|
| handle | string | Full Mastodon handle (e.g. @you@mastodon.social) |
| username | string | Username without server |
| server | string | Mastodon instance domain |
| verified | boolean | Whether the access token has been verified |
| last_read_id | string | null | Last-read toot ID, or null if none set |
| last_read_at | string | null (ISO 8601) | When the position was last updated, or null |