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/docsThe 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
| Level | Headers | Used by |
|---|---|---|
| Public | — | Reads, board/thread/post/reply feeds, articles, search, stats, votes/state. |
| Wallet | x-wallet-address | Voting, article creation, ownership-checked writes. |
| Signed wallet | x-wallet-address, x-signature, x-message | Admin routes (verifyAdmin). |
| Ownership | x-wallet-address | DELETE 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.
| Method | Path | Auth | Body / 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.
| Method | Path | Auth | Body / 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.
| Method | Path | Auth | Body / 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.
| Method | Path | Auth | Body / 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 | /:threadId | Owner/Admin | Cascades replies, posts, votes. |
Posts
Base path /api/posts (also mounted at /api/conversations).
| Method | Path | Auth | Body / 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 | /admin | Admin (signed) | ?page=&limit=&search=. |
| POST | / | Wallet | { content, userId, threadId?, articleId?, parentPostId?, parentReplyId?, image? } |
| DELETE | /:postId | Owner/Admin | Soft-deletes a post or reply. |
Replies
Base path /api/replies.
| Method | Path | Auth | Body / query |
|---|---|---|---|
| GET | / | — | Requires one of threadId, postId, articleId — or countsOnly=true. |
| GET | /post/:postId | — | Replies for a post. |
| GET | /admin | Admin (signed) | ?page=&limit=. |
| DELETE | /:replyId | Owner/Admin | Soft delete. |
| PATCH | /:replyId/points-status | Admin (signed) | { pointsStatus: 'pending'|'approved'|'rejected' }. |
| PATCH | /:replyId/approve-vote-points | Admin (signed) | Approves pending vote points. |
Votes
Base path /api/votes.
| Method | Path | Auth | Body / 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/:postId | Wallet | Sets the vote value to 0. |
Articles
Base path /api/articles.
| Method | Path | Auth | Body / 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 | /:id | Owner/Admin | Any of the create fields. |
| DELETE | /:id | Owner/Admin | Soft delete. |
Search
| Method | Path | Auth | Query |
|---|---|---|---|
| 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.
| Method | Path | Auth | Query |
|---|---|---|---|
| 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.
| Method | Path | Auth | Body / 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).
| Method | Path | Body |
|---|---|---|
| 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.