Skip to main content
Nine controllers under /api/admin expose admin operations for users, models, providers, keys, comparisons, votes, threads and messages, plus dashboard metrics. Each one carries [Authorize(Policy = "AdminOnly")]. model_leaderboard, system_settings and telegram_sessions have no admin routes; change them in Supabase.

Access

Production is read-only while you develop. Point writes (battles, votes, admin changes) at http://localhost:5079 and a development Supabase project. Against https://api.dualmindlab.tech, stick to GETs on public endpoints.
  • 401: no token, or the token failed validation.
  • 403: valid token, but public.users.role isn’t admin.
  • GET /api/admin/dashboard/health is the cheap way to check both. The admin panel calls it right after sign-in.

Resources

Dashboard

Metrics, provider health, recent activity

Users

Users and roles

AI models

Catalog and status

Providers

Provider registry

API keys

Key pool, toggles, cooldowns

Comparisons

Battles

Votes

Votes and statistics

Threads

Threads and visibility

Messages

Thread messages

Lists and paging

Paginated routes (/users, /models, /keys, /comparisons, /votes, /threads and /messages) take page (from 1) and pageSize (1 to 500, default 50) and return the matching row count as total. /providers and /models/active are unpaged and return total only; active models are sorted by model_name.
Paginated results are newest first. search and the filters (role, status, provider, visibility, userId, modelId, comparisonId, threadId, isRevealed) combine with AND.

Behaviours to know

PUT /users/{id}, /models/{id}, /providers/{name} and /threads/{id} serialize the whole request DTO, so any field you leave out is stored as null. Read the record, change what you need, send it all back. Use the PATCH routes to change a single field (role, status, visibility, key state). PUT /keys/{keyId} is the exception: it only writes the fields you send.
Comparisons and votes return model1_id, model2_id, winner_model_id and user_id. Resolve names with GET /models and GET /users.
When PostgREST rejects a write (a foreign key, a duplicate) the route returns 400 with the PostgREST JSON as a string in error. Parse it if you want the message field.
Every write to /providers and /keys refreshes the API’s in-memory provider cache. Model changes don’t touch the model cache, which refreshes within five minutes.
POST /users and DELETE /users/{id} work on public.users only. Supabase Auth accounts are created by signing up and removed in Supabase.
POST /votes, /threads and /messages insert rows directly, without the arena’s checks. Use them for repairs and test data in non-production environments; the admin panel doesn’t expose them.
The dashboard’s health.redis is always offline; there is no Redis.

Outside this API

The admin panel’s Email page calls the Supabase Edge Function send-admin-email ({ emails, subject, title, html } → { sent, failed }), not this API. Feature flags can be read with GET /api/settings/feature-flag/{key} but are changed in the system_settings table.
Last modified on October 18, 2026