Skip to main content

Admin Routes

Admin-only endpoints powering /admin.

Every route in /api/admin/* requires:

  1. Authsupabase.auth.getUser() returns a user
  2. Admin flagisAdmin() helper returns true (checks users.is_admin)

If either check fails, the route returns 403 Forbidden.

Rate-limited with the admin tier.

Projects

GET /api/admin/projects

List all projects including pending, rejected, deleted. Supports the same query params as /api/projects plus:

  • status=all — include everything
  • q — substring search on name, url

PUT /api/admin/projects/[id]/approve

Flip status pending → live. Fires webhookEvents.projectApproved() and sends the approval email.

PUT /api/admin/projects/[id]/reject

Flip status pending → rejected.

Body:

{ "reason": "Optional reason shown to the submitter" }

Sends the rejection email with the reason attached.

PUT /api/admin/projects/[id]

General admin edit. Can change any field (status, category assignments, pricing, etc.) bypassing ownership checks.

DELETE /api/admin/projects/[id]

Hard-delete. Irrecoverable. For soft-delete, use PUT with { "deleted_at": "..." }.

Users

GET /api/admin/users

List users with aggregated stats (project count, comment count, join date).

Query params: role, q, page, limit.

PUT /api/admin/users/[id]

Update is_admin, role, is_banned, ban_reason.

Remember: is_admin and role must be kept in sync when granting/revoking admin.

Categories

POST /api/admin/categories

Create a new category.

PUT /api/admin/categories/reorder

Persist drag-and-drop ordering. Body is an array of { id, sort_order, sphere }.

DELETE /api/admin/categories/[id]

Delete a category. Projects referencing it are not removed — they just lose that category in their categories array.

Promotions

GET /api/admin/promotions

List active + expired promotions.

PUT /api/admin/promotions/[id]

Approve, extend, revoke, change end date.

Analytics

GET /api/admin/analytics

Returns the aggregated data shown on /admin/analytics:

  • Traffic by day / week / month
  • Top projects by views
  • Top countries, browsers, devices
  • Submission funnel conversion

Query params:

ParamDefaultNotes
range30d7d / 30d / 90d / all
groupBydayday / week / month

Settings

GET /api/admin/settings

Current values of DB-backed site settings.

PUT /api/admin/settings

Update settings. Body: any subset of { site_banner, maintenance_mode, custom_theme, ... }.

CSV import

POST /api/admin/projects/import

Import projects from a CSV file. Multipart request with:

  • file — the CSV blob
  • mapping — JSON describing how CSV columns map to fields