API
Endpoints
NeoDB has a set of API endpoints mapping to its functions, like marking a book or listing collections. They can be found in the Swagger-based API documentation at /developer/ of your running instance; a version of it is available on our flagship instance.
NeoDB also supports a subset of Mastodon API, details can be found in Mastodon API documentation.
Both sets of APIs can be accessed with the same access token.
How to authorize
Create an application
You must have at least one URL included in the Redirect URIs field, e.g. https://example.org/callback, or use urn:ietf:wg:oauth:2.0:oob if you don't have a callback URL.
curl https://neodb.social/api/v1/apps \
-d client_name=MyApp \
-d redirect_uris=https://example.org/callback \
-d website=https://my.site
and save the client_id and client_secret returned in the response:
{
"client_id": "CLIENT_ID",
"client_secret": "CLIENT_SECRET",
"name": "MyApp",
"redirect_uri": "https://example.org/callback",
"vapid_key": "PUSH_KEY",
"website": "https://my.site"
}
Guide your user to open this URL
https://neodb.social/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https://example.org/callback&scope=read+write
Once authorized by the user, it will redirect to https://example.org/callback with a code parameter:
https://example.org/callback?code=AUTH_CODE
Obtain access token with the following POST request:
curl https://neodb.social/oauth/token \
-d "client_id=CLIENT_ID" \
-d "client_secret=CLIENT_SECRET" \
-d "code=AUTH_CODE" \
-d "redirect_uri=https://example.org/callback" \
-d "grant_type=authorization_code"
and an access token will be returned in the response:
{
"access_token": "ACCESS_TOKEN",
"token_type": "Bearer",
"scope": "read write"
}
Use the access token to access protected endpoints like /api/me
curl -H "Authorization: Bearer ACCESS_TOKEN" -X GET https://neodb.social/api/me
and the response will be returned accordingly:
{
"username": "xxx",
"url": "/users/xxx/",
"display_name": "XYZ",
"avatar": "https://neodb.social/xxx.gif",
"external_acct": "xxx@yyy.zzz",
"external_accounts": [{"platform": "mastodon", "handle": "xxx@yyy.zzz", "url": "https://yyy.zzz/@xxx"}],
"roles": []
}
url is relative to the site; external_acct is deprecated in favour of
external_accounts.
Webhooks
An application can register one webhook URL per user it is authorized by, to learn about changes without polling. With the user's access token:
curl -H "Authorization: Bearer ACCESS_TOKEN" -X PUT -H "Content-Type: application/json" \
-d '{"url": "https://example.org/hook"}' https://neodb.social/api/me/webhook
GET /api/me/webhook returns the current URL and whether it is disabled,
DELETE /api/me/webhook removes it. Setting it needs a token with both
write and push scopes. Only https URLs resolving to public addresses are
accepted, and a user can have at most 5 webhooks across applications.
Once the application holds no token with push scope for the user any more
(revoked from the account page, via /oauth/revoke, or by logging out
everywhere), its webhook is removed, at the latest when the next change
would have been delivered.
When the user's marks, reviews, notes, collections or articles change,
a JSON document is POSTed to the URL with Content-Type: application/json
and a User-Agent like NeoDB/1.0 (+https://neodb.social):
{
"version": 1,
"site": "https://neodb.social",
"time": "2026-09-07T02:22:47+00:00",
"username": "alice",
"changes": [
{
"type": "mark",
"action": "update",
"object": {
"shelf_type": "progress",
"visibility": 0,
"item": {"uuid": "4upSY7ttUqa5kjvcnXflWt", "title": "Item Title", "...": "..."},
"comment_text": "...",
"rating_grade": 8,
"tags": ["fiction"],
"...": "..."
}
}
]
}
versionis bumped on incompatible changes to this document.usernameis the account whose content changed, as in/api/me.changesis a list. Today each delivery carries one entry; consumers should nonetheless loop over it. A ping from the developer console sends the same document with an empty list.typeis one ofmark,review,note,collection,article;actioniscreate,updateordelete.- On
createandupdate,objectis exactly what the API returns for the piece (as inGET /api/me/shelf/item/{uuid},/api/me/note/item/{uuid}/,/api/review/{uuid},/api/collection/{uuid},/api/article/{uuid}), minus fields the API documents as deprecated. - On
delete,objectonly identifies the piece:{"uuid": "..."}, or{"item": {"uuid": "..."}}for a mark, since marks are addressed by item.
One delivery is sent per change: editing a mark's shelf, comment, rating and
tags together yields one mark update. Delivery is one attempt, without
retry or signature. After 100 consecutive failures (counted over a week) the
webhook is disabled until it is set again.
Users can see which of their authorized applications have a webhook on the account page, and set one for the Dev Console token on the developer page.