FeedFabric docsGitHub

API reference

Every route the API serves, grouped by what it's for, with the parameters that matter and the error codes each can return.

Conventions

  • Base URL: wherever the API runs (http://localhost:5161 with make run). Member routes are under /userfeed, /follows, /favourites and /media; staff routes under /admin and /ranking.
  • Auth: every route needs Authorization: Bearer <token> (in Development, or X-Dev-User: <id>). Without one: 401 unauthorized. Routes marked moderator, ranking admin or staff also need that role outside Development: 403 forbidden without it. See Sign-in and users.
  • JSON in and out, camelCase, Content-Type: application/json on bodies (415 unsupported_media_type otherwise). IDs are UUID strings; times are ISO 8601 UTC.
  • Paging: limit (1–100, default 20) and cursor, the previous page’s nextCursor; null means the end.
  • Every write by a suspended member is 403 user.suspended, except the reading signals.
  • Every member route can return 404 user.not_synced if the caller’s user doesn’t exist (only when Auth:ProvisionUsers is off, or a first-request race), and 400 validation_failed for a malformed ID, body or out-of-range parameter. These aren’t repeated below.
  • Errors are Problem Details with a stable code: see Errors.

The OpenAPI document

In Development the API serves its OpenAPI document at /openapi/v1.json (http://localhost:5161/openapi/v1.json); import it into Postman, Bruno or a client generator. The admin panel’s API reference page (System → API reference) shows a saved copy, for environments where the live document isn’t served. These tables add what the document can’t say: what each route is for, and the error codes it returns.

Feeds

Route Purpose Query Errors
GET /userfeed/home Following: posts from accounts you follow and your own, newest first limit, cursor (a post ID)
GET /userfeed/foryou For You: ranked, each post with ranking limit, cursor (an offset), profile 404 profile.not_found
GET /userfeed/favourites Posts from your favourites, newest first limit, cursor
GET /userfeed/tags/trending Tags on the most recent posts, hashtags and inferred topics alike: [{ tag, posts }] hours (1–168, 24), limit (1–50, 10)
GET /userfeed/tags/{tag}/posts Posts with a tag, newest first limit, cursor
GET /userfeed/mentions Posts and comments that @mention you, newest first: { mentions, nextCursor } limit, cursor
GET /userfeed Your own posts, in the bare post shape limit, cursor, order (newest/oldest), gt, gte, lt, lte (post IDs)

Feed responses are { posts, nextCursor, source } (Following, favourites, tags) or { posts, nextCursor, ranker, profile, candidates } (For You). See Feeds and paging and the types.

Posts

Route Purpose Body Success Errors
POST /userfeed/posts Create a post { content, mediaIds? } 201 post 422 content.violation, 400 post.too_many_media, 404 media.not_found, 409 media.not_ready
PUT /userfeed/posts/{id} Edit the text, within 60 minutes { content } 200 post 404 post.not_found, 403 post.not_author, 409 post.edit_window_closed, 409 post.is_repost, 422 content.violation
DELETE /userfeed/posts/{id} Delete your post 204 404 post.not_found, 403 post.not_author

The post is { id, authorId, content, createdAt, editedAt, repostOfId, isHeld }. There’s no route to fetch one post.

Engagement

Route Purpose Success Errors
PUT /userfeed/posts/{id}/like Like; repeating is a no-op 204 404 post.not_found (also for a repost’s own ID)
DELETE /userfeed/posts/{id}/like Unlike; repeating is a no-op 204 404 post.not_found
PUT /userfeed/posts/{id}/repost Repost (the original or a repost of it) 204 404 post.not_found
DELETE /userfeed/posts/{id}/repost Undo your repost 204 404 post.not_found
PUT /follows/{externalUserId} Follow; repeating is a no-op 204 404 user.not_found, 400 follow.self
DELETE /follows/{externalUserId} Unfollow 204 404 user.not_found, 400 follow.self
GET /favourites Your favourites, newest first: [{ externalUserId, username, displayName, avatarUrl, addedAt }] 200
PUT /favourites/{externalUserId} Add a favourite (up to 30) 204 404 user.not_found, 400 favourite.self, 409 favourite.limit_reached
DELETE /favourites/{externalUserId} Remove a favourite 204 404 user.not_found, 400 favourite.self

Comments

Route Purpose Body / query Success Errors
GET /userfeed/posts/{id}/comments Top-level comments, oldest first limit, cursor 200 { comments, nextCursor } 404 post.not_found
GET /userfeed/comments/{id}/replies Direct replies to a comment limit, cursor 200 { comments, nextCursor } 404 comment.not_found
POST /userfeed/posts/{id}/comments Comment, or reply with parentCommentId { content, parentCommentId? } 201 comment 404 post.not_found, 404 comment.not_found (parent), 422 content.violation
DELETE /userfeed/comments/{id} Delete your comment 204 404 comment.not_found, 403 comment.not_author

Reports

Route Body Success Errors
POST /userfeed/posts/{id}/report { reason }: spam, harassment, hate, threat, sexual, self_harm or other 204 404 post.not_found (also for reposts)
POST /userfeed/comments/{id}/report { reason } 204 404 comment.not_found

Reporting twice counts once; three different reporters hold the item for review (Moderation).

Reading signals

Accepted from suspended members too. See Reading signals.

Route Purpose Body Success Errors
POST /userfeed/impressions Posts seen: at least half on screen for a second { postIds } (up to 100) 202 { counted }
POST /userfeed/dwell Time on screen per view, sent as each scrolls away (60 s cap per view) { views: [{ postId, ms }] } (up to 100) 202 { counted }
POST /userfeed/posts/{id}/open The member opened the post 202 { counted }
POST /userfeed/posts/{id}/not-interested Hide from For You for 30 days; less of its author and topics 204 404 post.not_found
POST /userfeed/posts/{id}/show-more More of its author and topics 204 404 post.not_found

Photos

Route Purpose Body Success Errors
POST /media/uploads An upload slot and signed URL { contentType, size } 201 { mediaId, uploadUrl, contentType, expiresAt } 415 media.type_not_allowed, 413 media.too_large
POST /media/{id}/complete The file is uploaded; start the checks 202 media 404 media.not_found, 409 media.wrong_state
GET /media/{id} Status of your upload: awaitingUpload, processing, ready or rejected 200 media 404 media.not_found
PUT /blobs/{key}?expires=&sig=&contentType= Development only: the signed upload URL itself (local storage) the file 200 403 bad signature or type, 413 too large
GET /blobs/{key}?expires=&sig= Development only: a signed read URL 200 the file 403, 404

The /blobs routes exist only with BlobStorage:Provider Local; you never build their URLs yourself, the API hands them out. See Photos and Composing with photos.

Ranking profiles (ranking admin)

Route Purpose Errors
GET /ranking/profiles Presets first, then saved profiles at their latest version
GET /ranking/profiles/{key} One profile: { key, version, preset, label, isDefault, settings } 404 profile.not_found
GET /ranking/profiles/{key}/versions A saved profile’s versions (empty for a preset) 404 profile.not_found
GET /ranking/profiles/{key}/versions/{n} One version 404 profile.not_found
PUT /ranking/profiles/{key} Create, or save a new version; body is the settings 409 profile.read_only, 400 profile.invalid_key, 400 profile.invalid
DELETE /ranking/profiles/{key} Delete a saved profile 409 profile.read_only, 404 profile.not_found
POST /ranking/preview?viewer=&limit= The For You these settings would give viewer (default you; limit up to 50), without saving or logging 400 profile.invalid, 404 user.not_found
GET /ranking/vocabulary Variables, functions, operators, goals, presets and defaults, for editors

See Ranking profiles.

Moderation (moderator)

Route Purpose Errors
GET /admin/moderation/overview What’s waiting; the last day’s blocks, holds, reports and decisions
GET /admin/moderation/queue Held and flagged posts and comments, oldest first
GET /admin/moderation/log?action=&limit= Latest events; action blocked, held, reported or reviewed; limit 1–500 (100)
GET /admin/moderation/reports?limit= Latest member reports, newest first; limit 1–500 (100)
POST /admin/moderation/posts/{id}/decision { "decision": "approved" | "removed" } 404 post.not_found
POST /admin/moderation/comments/{id}/decision { "decision": "approved" | "removed" } 404 comment.not_found
GET /admin/users?q=&status=&sort=&offset=&limit= Search users; status active/suspended; sort username, posts, reports or flags
GET /admin/users/{externalUserId} A user for staff 404 user.not_found
PUT /admin/users/{externalUserId}/status { "status": "suspended" | "active" }, recorded in the log 404 user.not_found
PUT /admin/users/{externalUserId}/verified { "verified": true } 404 user.not_found
GET /admin/posts?q=&author=&status=&reported=&offset=&limit= Posts in every state; status visible, held, removed or deleted
GET /admin/posts/{id} A post with its comments, reports and moderation history 404 post.not_found
GET /admin/media/{id} An upload with its nudity score and the line it’s judged against 404 media.not_found

Staff (moderator or ranking admin)

Route Purpose
GET /admin/me Any signed-in user: who they are and which roles they have (moderator, rankingAdmin, environment)
GET /admin/dashboard?minutes=60 Everything the admin panel’s overview shows, in one call
GET /admin/system Sign-in settings (no secrets), moderation thresholds, media limits, models, ranker, users by status, outbox and consumers
GET /admin/feed/{externalUserId}/following A member’s Following as they’d see it, without logging it
GET /admin/feed/{externalUserId}/foryou?profile= A member’s For You in a session of its own, without logging it

Other

Route Purpose
GET /me The caller’s externalUserId; a quick token check
GET /openapi/v1.json The OpenAPI document, Development only

Edit this page on GitHub

FeedFabric is MIT licensed and open source on GitHub. These pages are built fromweb/docs; the repository's own docs/ folder has the full configuration, data model and error lists. Found something wrong? Fix it oropen an issue.