Skip to Content
API reference

API reference

The FudFi backend is an Express + Postgres API. Every route is mounted under /api.

Production: https://backend-production-03de6.up.railway.app Local: http://localhost:3000 Health: GET /api/health Route index: GET /api/docs

The backend preserves legacy Mongo document shapes wherever possible, so many responses expose _id (the current uuid) alongside id. Treat uuids as the canonical id; legacy 24-hex ObjectIds are still accepted as inputs on most routes.

Authentication

LevelHeadersUsed by
Public—Reads, board/thread/post/reply feeds, articles, search, stats, votes/state.
Walletx-wallet-addressVoting, article creation, ownership-checked writes.
Signed walletx-wallet-address, x-signature, x-messageAdmin routes (verifyAdmin).
Ownershipx-wallet-addressDELETE on posts/replies/threads/articles (verifyOwnershipOrAdmin).

The x-wallet-address value may be a Solana base58 address or an EVM hex address; identity is resolved with userIdentityCondition (Solana + raw EVM + lowercased EVM).

Admin requests additionally require a signature for the wallet. The x-message string must end in a millisecond timestamp, signed with the wallet; it expires after 5 minutes, timestamps in the future are rejected, and signatures are single-use (replay-protected).

Auth

Base path /api/auth.

MethodPathAuthBody / query
POST/evm/challenge—{ address, chainId } → { message, nonce, chainId, expiresAt }
POST/evm/verify—{ address, message, signature, nonce } → { success, user, chainId }
POST/link-evm—{ userId, address, message, signature, nonce }
POST/unlink-evm—{ userId }
POST/link-wallet—{ userId, walletAddress, signature?, message? }
POST/unlink-wallet—{ userId }
GET/twitter—?link=true&userId= → { authUrl, state }
GET/twitter/callback—?code=&state=
POST/unlink-twitter—{ userId }
GET/me—?userId= → { user }

EVM chain IDs accepted: 4663 (Robinhood Chain) and 46630 (testnet). Nonces are single-use with a 10-minute TTL.

Users

Base path /api/users.

MethodPathAuthBody / query
GET/—?search= — list users (default order by points).
GET/wallet/:walletAddress—Resolves Solana or EVM address.
GET/:userId—Returns { walletAddress, username, points }.
POST/—Create a user from allowed fields.
POST/get-or-create—{ walletAddress, ...overrides } — Solana register/login.
POST/vote-counts—{ userIds: [] } → per-user totalVoteCount.
GET/:userId/stats—{ postsCount, repliesCount, totalContent, votesGiven, totalVotes }.
GET/:walletAddress/hidden—Hidden content with details.
POST/:walletAddress/hide—{ contentId, contentType }.
POST/:walletAddress/unhide—{ contentId }.

Boards

Base path /api/boards.

MethodPathAuthBody / query
GET/—?search= — active boards, pinned first.
GET/name/:name—Lookup by name (case-insensitive) or slug.
POST/Admin (signed){ name, title, description, color?, icon?, image?, fudPointsMultiplier?, votePointsMultiplier? }

Threads

Base path /api/threads.

MethodPathAuthBody / query
GET/—?page=&limit=&sort=hot|new|top&search= — approved only.
GET/board/:boardId—?page=&limit=&sort=&format=legacy|paginated.
GET/:threadId—Single thread.
POST/Wallet{ title, content, userId, boardId, image? }
DELETE/:threadIdOwner/AdminCascades replies, posts, votes.

Posts

Base path /api/posts (also mounted at /api/conversations).

MethodPathAuthBody / query
GET/top—?limit=&period=day|week|month|all&sort=fud|votes|hot|new|random.
GET/—?boardId=&page=&limit=&userId=&cursor=&sort=random.
GET/board/:boardId—?page=&limit=&userId=&cursor=.
GET/thread/:threadId—?page=&limit=&userId=&cursor=.
GET/search—?q=&page=&limit=.
GET/conversations/:shortPostId—?userId= — thread + posts + replies + votes.
GET/:postId—Single post.
GET/adminAdmin (signed)?page=&limit=&search=.
POST/Wallet{ content, userId, threadId?, articleId?, parentPostId?, parentReplyId?, image? }
DELETE/:postIdOwner/AdminSoft-deletes a post or reply.

Replies

Base path /api/replies.

MethodPathAuthBody / query
GET/—Requires one of threadId, postId, articleId — or countsOnly=true.
GET/post/:postId—Replies for a post.
GET/adminAdmin (signed)?page=&limit=.
DELETE/:replyIdOwner/AdminSoft delete.
PATCH/:replyId/points-statusAdmin (signed){ pointsStatus: 'pending'|'approved'|'rejected' }.
PATCH/:replyId/approve-vote-pointsAdmin (signed)Approves pending vote points.

Votes

Base path /api/votes.

MethodPathAuthBody / query
GET/—`?boardId=
GET/board/:boardId—Votes for a board.
GET/thread/:threadId—Votes for a thread.
GET/user/:userId—Votes by a user.
GET/counts—{ targetId: { total, upvotes, downvotes } }.
POST/state—{ userId, postIds: [] } → { voteState }.
POST/Wallet{ userId, postId, value: 1 | -1 }
DELETE/:userId/:postIdWalletSets the vote value to 0.

Articles

Base path /api/articles.

MethodPathAuthBody / query
GET/—?page=&limit=&status=published&userId=&tag=&viewerId=.
GET/:slug—?userId= — full content or paywall preview.
POST/Wallet{ title, subtitle?, content, contentRaw?, coverImage?, tags?, status?, accessType?, price?, scheduledAt? }
PUT/:idOwner/AdminAny of the create fields.
DELETE/:idOwner/AdminSoft delete.
MethodPathAuthQuery
GET/api/search—?q= (min 2 chars), type=all|threads|posts, boardId, limit (max 50), page

Uses weighted full-text search (plainto_tsquery + ts_rank_cd) with an ilike fallback.

Stats

Base path /api/stats.

MethodPathAuthQuery
GET/users/:walletAddress—Per-user points, statistics, and recent activity.
GET/leaderboard—?limit=50&sortBy=totalPoints|totalVoteScore|totalPointsEarned
GET/global—Platform totals + top users.
GET/trending-boards—?limit=5&period=1d|7d|30d|all
GET/top-posts—?limit=10&period=1d|7d|30d|all

Payments

Base path /api/payments.

MethodPathAuthBody / query
POST/verify—{ signature, articleId, userId } — verifies a Solana tx and records the purchase.
GET/status/:articleId—?userId= → { hasAccess }.

Admin

Base path /api/admin. All routes are Admin (signed).

MethodPathBody
POST/pin-board{ boardId }
POST/unpin-board{ boardId }
POST/pin-post{ postId }
POST/unpin-post{ postId }

Errors

Common status codes: 400 validation, 401 missing/invalid auth, 403 forbidden (ownership or admin), 404 not found, 409 duplicate, 429 rate limited.