Status: Scaffolded 2026-09-24
All routes live under /api on https://api.vegangrove.org; health is /healthz. Auth is Authorization: Bearer <session token>. Every error is { error: { code, message } } (error handling). Every list is { items, nextCursor } with an opaque cursor over _id; there is no page or skip, and a bad cursor is a 400 invalid_cursor.
Auth levels: public (no session), member (any session), organizer (grove organizer or organization admin for the host), admin (role: admin, checked in the database per request).
Scaffold status: auth, me, places, admin place approval, healthz, and stats are implemented with tests. Every other route is mounted, validates its input, and returns 501 { error: { code: 'not_implemented' } } until its milestone (milestones).
Auth and account (implemented)
| Route | Auth | Note |
|---|
POST /api/auth/register { email, password, handle } | public | returns { token, user }, rate limited |
POST /api/auth/login { email, password } | public | rate limited |
POST /api/auth/magic-link { email } | public | always 202, no account enumeration |
POST /api/auth/magic-link/verify { token } | public | single use, 15 minute TTL |
POST /api/auth/apple { identityToken, nonce } | public | email from the token only, 501 in scaffold |
POST /api/auth/google { idToken } | public | 501 in scaffold |
POST /api/auth/logout | member | deletes the session |
GET, PATCH, DELETE /api/me | member | delete is the hard delete from ADR 0010 |
GET /api/me/sessions, DELETE /api/me/sessions/:id | member | |
GET /api/me/notification-preferences, PUT | member | 501 |
POST, DELETE /api/push-tokens | member | 501 |
Health and stats (implemented)
| Route | Auth | Note |
|---|
GET /healthz | public | { ok: true } after a DB ping, else 503 |
GET /api/stats | public | aggregate counters, cached 5 minutes |
Places (implemented)
| Route | Auth | Note |
|---|
GET /api/places?bbox=w,s,e,n&type=&q=&cursor= | public | approved places only |
GET /api/places/:slug | public | |
POST /api/places | member | created as pending |
GET, POST /api/places/:id/reviews | public, member | 501 |
GET, POST /api/place-lists, PATCH, DELETE /api/place-lists/:id | member | private by default, 501 |
Events, groves, organizations
| Route | Auth | Note |
|---|
GET /api/events?from=&to=&area=&groveId=&cursor= | public | visibility-filtered by caller |
GET /api/events/:slug | public | |
POST /api/events, PATCH /api/events/:id | organizer | |
POST, DELETE /api/events/:id/rsvp | member | |
GET /api/events/:id/attendees | organizer | everyone else sees counts only |
GET /api/groves, GET /api/groves/:slug | public | |
POST /api/groves/:id/join, DELETE /api/groves/:id/leave | member | |
GET /api/organizations, GET /api/organizations/:slug | public | adminUserIds never in the response |
Friends, feed, posts
| Route | Auth | Note |
|---|
GET /api/friends, GET /api/friends/requests | member | |
POST /api/friends/invites | member | { code, expiresAt } |
POST /api/friends/invites/:code/accept | member | |
DELETE /api/friends/:userId | member | |
GET /api/feed?scope=friends|grove:<id>|public&cursor= | member | |
POST /api/posts, GET, DELETE /api/posts/:id | member | a post the caller may not see is 404 |
POST, DELETE /api/posts/:id/reactions | member | |
GET, POST /api/posts/:id/comments | member | |
POST, DELETE /api/posts/:id/save | member | |
GET /api/handles/:handle/posts | public | public posts only, per ADR 0004 |
POST /api/reports | member | |
| Route | Auth | Note |
|---|
POST /api/uploads/image/presign { contentType, purpose } | member | { url, key }, S3 PUT |
POST /api/uploads/video/create { title } | member | { videoId, tusEndpoint, authorization }, Bunny |
GET, POST /api/conversations | member | |
GET, POST /api/conversations/:id/messages | member | encrypted at rest, 90 day TTL |
Socket.IO /messages | member | rooms user:<id>, conversation:<id> |
GET /api/media?kind=&tag=&cursor=, GET /api/media/:slug | public | |
GET /api/guides?category=, GET /api/guides/:slug | public | |
GET, POST /api/actions, DELETE /api/actions/:id | member | private log |
POST /api/companion/chat { conversationId?, message } | member | SSE stream, rate limited |
GET /api/companion/conversations, POST .../:id/pin, DELETE .../:id | member | |
Admin
| Route | Auth | Note |
|---|
GET /api/admin/places/pending, PUT /api/admin/places/:id/approve, .../reject | admin | implemented |
CRUD /api/admin/media, CRUD /api/admin/guides | admin | 501 |
GET /api/admin/reports, PUT /api/admin/reports/:id | admin | 501 |