Skip to content
Omicron

Reference

HTTP API

The JSON API under /api — authentication, posts, users, feeds, tags, lists, and notifications.

The backend serves a JSON API under /api. The web app is its first consumer: the frontend reverse-proxies /api/* to the backend, so the browser talks to one origin, there is no CORS configuration, and the session cookie flows naturally.

Authentication

Session-based. Signing in sets an httpOnly cookie backed by a row in the Postgres sessions table. Send that cookie with subsequent requests.

Conventions

  • Cursor pagination. Listing endpoints accept a cursor and return the next one. There is no offset.
  • Rate limiting. Writes are throttled per signed-in user, or per IP when anonymous; reads are not throttled by the general limiter. A limited request gets 429 with Retry-After.
  • Errors are JSON, always the same shape — {"error": "…"} — with an appropriate status code. A request body that fails validation gets 400 and a message naming the field, for example {"error":"displayName: Invalid input: expected string, received number"}. A body that is not valid JSON gets 400 too, not 500.

Health

Method Path Purpose
GET /healthz {"status":"ok"}
GET /version Name, version, federation flag

These sit at the root, not under /api.

Auth — /api/auth

Method Path Purpose
POST /register Create an account
POST /login Sign in, setting the session cookie
POST /logout End the session
GET /me The current user
DELETE /me Delete the account
POST /password/forgot Send a reset email
POST /password/reset Complete a reset with a token
POST /password/change Change password while signed in
POST /email/verify Verify an address with a token
POST /email/resend Resend the verification email

Posts — /api/posts

Method Path Purpose
GET / List posts — the Global timeline, assembled with per-author diversity (see Reading and following)
POST / Create a post (draft, scheduled, or published)
GET /drafts The current user’s drafts
GET /mine?status= The current user’s posts in one state — draft, scheduled, or published
GET /mine/counts How many the current user holds in each state
GET /trending Trending posts
GET /by/:username/:slug A single post by its public URL
GET /:id A single post
PATCH /:id Update a post
DELETE /:id Delete a post
POST / DELETE /:id/like Like / unlike
POST / DELETE /:id/recommend Recommend / un-recommend (federates as Announce/Undo(Announce))
GET /:id/comments List comments
POST /:id/comments Add a comment
PATCH / DELETE /:id/comments/:commentId Edit / delete a comment
POST / DELETE /:id/comments/:commentId/like Like / unlike a comment

Feed, search, dashboard

Method Path Purpose
GET /api/feed The personalised feed
GET /api/search Posts, accounts, and tags
GET /api/dashboard Writer dashboard figures

Users — /api/users

Method Path Purpose
PATCH /me Update the profile
PATCH /me/privacy Update privacy settings
POST /me/custom-section/preview Render the custom About Markdown
POST / DELETE /me/avatar Upload / remove the avatar
GET /me/follow-requests Pending follow requests
POST /me/follow-requests/:id/approve Approve a request
POST /me/follow-requests/:id/reject Reject a request
GET /me/muted, /me/blocked Muted / blocked accounts
DELETE /me/followers/:identifier Remove a follower
GET /suggested Suggested accounts
GET /:username A profile
GET /:username/followers, /:username/following, /:username/posts Their collections
GET /:username/recommendations Posts they’ve recommended
POST / DELETE /:username/follow Follow / unfollow
POST / DELETE /:username/mute Mute / unmute
POST / DELETE /:username/block Block / unblock

Remote actors — /api/remote

Federated equivalents, keyed by handle (user@domain):

Method Path Purpose
GET /users/:handle Resolve and fetch a remote profile
GET /users/:handle/posts Their posts, as cached locally
GET /users/:handle/recommendations Posts they’ve recommended (recorded from inbound Announce)
POST / DELETE /users/:handle/follow Follow / unfollow
POST / DELETE /users/:handle/mute Mute / unmute
POST / DELETE /users/:handle/block Block / unblock

Tags — /api/tags

Method Path Purpose
GET / List tags
GET /following Tags the user follows
GET /:slug One tag
GET /:slug/posts Posts under a tag
POST / DELETE /:slug/follow Follow / unfollow a tag

Lists — /api/lists

Method Path Purpose
GET / The user’s lists
POST / Create a list
GET /read-later The built-in read-later list
GET /user/:username A user’s public lists
GET /for-post/:postId Which of your lists hold this post
GET /:id One list
GET /:id/items Its posts
PATCH / DELETE /:id Update / delete the list
POST /:id/items Add a post
DELETE /:id/items/:postId Remove a post

Notifications — /api/notifications

Method Path Purpose
GET / List notifications
GET /unread-count Unread badge count
POST /read Mark all as read
POST /:id/read Mark one as read

Media — /api/uploads

Method Path Purpose
POST / Upload a file
GET /:file Serve an uploaded file
GET /og/:id.jpg A JPEG copy of an upload, for link-preview crawlers

/og/:id.jpg takes the id of an existing upload — whatever its stored format — and returns it as a JPEG fitted inside 1200×630, generated on first request and cached on disk. It exists because uploads are stored as WebP, which several preview crawlers refuse, and because a share image has to be a format every one of them reads. Unauthenticated by necessity: the caller is a crawler with no session.

Share cards — /api/og

Method Path Purpose
GET /posts/:id.jpg The card drawn for a post that has no image of its own

A 1200×630 JPEG carrying the post’s title, its author, and the instance — generated on first request and cached on disk. What a link-preview crawler fetches as og:image when the post has neither a chosen banner nor an image in its body. Unauthenticated, like the media route above, and built from the same visibility check a signed-out reader gets: a draft’s card is a 404, exactly like the draft.

Answers 302 to the instance’s brand image when no card can be drawn — a federated copy of someone else’s post, an untitled one, or a title in a script the bundled font has no glyphs for. A redirect rather than a 404 because an og:image that fails puts no picture on the card at all.

Its own prefix rather than a path under /api/posts/ so robots.txt can allow it by name — see Discoverability. Nothing but share images belongs here.

Photos — /api/photos

Free-photo search for the editor’s banner picker. Signed in only — one provider spends an instance-wide quota.

Method Path Purpose
GET /providers Which providers this instance can search, in order
GET /search?provider=&q=&page= Search one provider
POST /use Tell a provider one of its photos was used

/providers is never empty: Openverse needs no credentials, so it is always present. Unsplash joins it only when an admin has configured a key. Results from either provider come back in one shape, carrying the attribution to store with the post.

/use exists for Unsplash’s API terms, which require a per-photo call when an image is actually used; it is a no-op for providers that ask for nothing. Search carries its own per-user rate limit on top of the general one, because a GET is not covered by the write throttle and both providers are shared resources.

Webhooks — /api/webhooks

Method Path Auth Purpose
POST /content Token Publish a document from an external CMS
GET /tokens Session Your live publishing tokens
POST /tokens Session Mint one (plaintext returned once)
DELETE /tokens/:id Session Revoke one

/content is the one endpoint that does not use the session cookie: it authenticates with a bearer credential in X-Webhook-Secret or Authorization: Bearer, and publishes as that credential’s owner. Managing tokens is an ordinary session-authenticated call, so a token cannot mint or revoke tokens. Full reference in Content webhook.

Reports, instance, setup, SEO

Method Path Purpose
POST /api/reports Report a post or actor
GET /api/instance Public instance metadata
GET /api/instance/tls-check Whether a hostname may be issued a certificate
POST /api/setup Complete the first-run wizard
POST /api/setup/test-email Send a test email during setup
GET /api/seo Indexing flag and verification tokens (the IndexNow key is withheld)
GET /api/seo/sitemap-entries Profiles, tags, and public lists for the sitemap, plus the post count
GET /api/seo/sitemap-posts?page=N One page of published local posts for the sitemap
GET /api/seo/indexnow-key/:key Whether key is this instance’s IndexNow key

Administrative endpoints are documented separately in Admin API.

Found a mistake?Edit this page on GitHub.