API reference
Every route the API serves, who may call it, and how sign-in, permissions, errors and pagination work.
For the Full Stack package
Base URL and response format
Every route is served under the /api prefix. Locally the base URL is http://localhost:8000/api. On a server it is your API's address followed by /api, the same value the dashboard reads from NEXT_PUBLIC_API_BASE_URL (Environment variables).
Members and staff use the same sign-in and the same token; the roles on the account decide which routes answer. Request bodies are JSON, except file uploads. Every answer comes in the same envelope:
{
"success": true,
"data": { },
"message": ""
}data holds the result. message is a short sentence some writes fill in, translated to the request's language, and empty otherwise.
The health check is the one route outside the envelope. It needs no token and is not rate limited:
curl http://localhost:8000/api/health{"status":"ok"}It answers 503 with {"status":"unavailable"} while the database cannot be reached or holds no tables, so a platform can use it as a readiness probe.
Errors
A failed request answers with the matching HTTP status and the same envelope, with success: false, data: null, and for validation the problems per field:
{
"success": false,
"data": null,
"message": "<the first field's problem>",
"errors": {
"email": ["<the problem with email>"]
}
}| Status | When |
|---|---|
400 | A field failed validation, or the body holds a field the route does not accept: unknown fields are refused, not ignored. Also an upload with no bucket configured. |
401 | The token is missing or expired, a failed sign-in (one answer whether the email has no account or the password is wrong), or a missing or unknown ingest key. |
403 | The account's roles do not grant the route's permission: "You need one of these permissions: …". |
404 | No such record, or a record that is not the caller's. |
409 | A delete that would break something still using the record, such as exercise_in_use. |
429 | More than 10 attempts in a minute from one address on the sign-in, registration or account-deletion route. The Retry-After header says how many seconds to wait. |
503 | The health check before the database is ready. |
500 | An unexpected failure. The message stays generic and the details go to the API's log. |
Many errors carry a key the dashboard translates, such as member_not_found or event_full, in message.
Pagination
Lists take page, from 1, and page_count, the page size. A page holds at most 100 rows: a larger size is read as 100, a smaller one as 1, and a missing or unreadable one falls back to the list's default. A list answers in one shape:
{
"success": true,
"data": { "data": [ ], "page": 1, "limit": 15, "total": 35, "totalPages": 3 },
"message": ""
}| List | Default page size |
|---|---|
| Members, the content catalogues | 15 |
| Coaches, roles, settings | 10 |
The content lists sort by updated_at, newest first by default (order=asc or desc), with the id as a tiebreaker, and take search and their own filters.
Language
Send the reader's language in the Accept-Language header: en or ar as shipped. ar-SA counts as Arabic, and any language the API does not have is answered in English. Error messages and the message of a successful write are translated; content stored in both languages comes back as { "en": "...", "ar": "..." }.
Authentication
- Sign in at
POST /api/auth/login. The answer holdsaccess_token, a JWT signed withJWT_SECRET, and the account with its roles and permissions. Send it asAuthorization: Bearer <token>. - A token lasts for
JWT_EXPIRATION,7dby default. There is no refresh route: when a token expires, the next request answers401and the client signs in again. - Sign-in, registration and account deletion accept 10 attempts a minute from one address, counted per route, then answer
429. The count is kept in the API's memory, so it starts again on a restart. Behind a proxy, seeTRUST_PROXY(Troubleshooting).
Sign in as the seeded Head Coach
Terminalcurl -X POST http://localhost:8000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"headcoach@example.com","password":"Coach@123"}'Response{ "success": true, "data": { "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "Bearer", "expires_in": "7d", "account": { "email": "headcoach@example.com", "roles": [ ], "permissions": [ ] } }, "message": "..." }Call a protected route with the token
Terminalcurl "http://localhost:8000/api/users?page=1&page_count=5" \ -H "Authorization: Bearer <access_token>"Expected result: The first five members, in the list shape.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/auth/login | Anyone | Signs in any account. Rate-limited. |
POST | /api/auth/register | Anyone | Creates a member account (first_name, last_name, email, password of 8 characters or more, optional username) and signs it in. Rate-limited. |
GET | /api/auth/me | Any signed-in account | The account with its roles and permissions. |
POST | /api/auth/delete-account | Any signed-in account | Closes the caller's own account after checking password. Rate-limited. |
PATCH | /api/coaches/profile | Any signed-in account | Updates the caller's own profile. |
PATCH | /api/coaches/profile/password | Any signed-in account | Changes the caller's own password. |
There is no password-reset route. The dashboard's forgot-password and reset-password pages are the screens only: no reset is sent. A staff account with members.update sets a member's password with PATCH /api/users/:username/change-password.
Change the seeded passwords
The demo accounts use published passwords: Coach@123, Member@123, Trainer@123, staff123 for the other staff and password123 for the other members. Change them, or start from empty tables, before anything goes live.
Permissions
A route checks the token first, then the permission it names. An account holds every permission of every role it has; where a route names several, any one is enough. In the tables below, a permission name in Who can call it means an account whose roles grant it.
Members
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/users | members.view | A page of members (order, search, email, phone, country_id, username, first_name, last_name, from_date, to_date, verified). |
GET | /api/users/statistic | members.view | Member counts for the Gym screen. |
GET | /api/users/:username | members.view | One member. |
POST | /api/users | members.create | Creates a member. |
PATCH | /api/users/:username | members.update | Updates a member. |
PATCH | /api/users/:username/change-password | members.update | Sets a member's password. |
POST | /api/users/:username/resend-verification-email | members.update | Answers success; no email is sent, as no mail transport ships. |
POST | /api/users/:username/make-verified | members.verify | Marks the email verified. |
POST | /api/users/:username/make-unverified | members.verify | Marks it unverified. |
DELETE | /api/users/:username | members.delete | Moves the member to the deleted list. |
GET | /api/users/deleted | members.view | A page of deleted members. |
GET | /api/users/deleted/:username | members.view | One deleted member. |
POST | /api/users/deleted/:username/restore | members.restore | Restores one. |
Coaches and roles
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/coaches | coaches.view | A page of staff accounts (email, name, phone). |
GET | /api/coaches/statistics | coaches.view | Staff counts. |
GET | /api/coaches/management/roles/select | coaches.view or coaches.assign_roles | The roles a staff form can pick from. |
GET | /api/coaches/:id | coaches.view | One staff account, by id or username. |
POST | /api/coaches | coaches.create | Creates one. |
PATCH | /api/coaches/:id | coaches.edit | Updates one. |
PATCH | /api/coaches/:id/roles | coaches.assign_roles | Sets its roles. |
DELETE | /api/coaches/:id | coaches.delete | Deletes one. |
GET | /api/roles | roles.view | The roles (page, page_count, name, guard_name, created_from, created_to); every role without page_count. |
GET | /api/roles/statistics | roles.view | Role counts. |
GET | /api/roles/select | roles.view | The roles for a dropdown. |
GET | /api/roles/permissions | roles.view | Every permission, by module. |
GET | /api/roles/:id | roles.view | One role with its permissions. |
POST | /api/roles | roles.create | Creates a role. |
PUT | /api/roles/:id | roles.edit | Renames it. |
POST | /api/roles/:id/permissions | roles.assign_permissions | Sets its permissions. |
DELETE | /api/roles/:id | roles.delete | Deletes it. |
App settings
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/settings | settings.view | A page of settings (search, category, type). |
GET | /api/settings/:key | settings.view | One setting. |
PATCH | /api/settings/:key | settings.edit | Changes its value. |
DELETE | /api/settings/:key | settings.edit | Deletes it. |
A member's own data
Every route here answers for the account on the token and nobody else. A staff token answers 403, because only the Member role holds fitness.view and fitness.edit. Each write answers with the whole refreshed page.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/fitness/overview | fitness.view | The dashboard's day: rings, calories, sleep, heart rate, workouts, steps. |
GET | /api/fitness/activity | fitness.view | The activity page (granularity days, weeks or months). |
GET | /api/fitness/activity/:slug | fitness.view | One activity. |
GET | /api/fitness/nutrition | fitness.view | The nutrition page. |
POST | /api/fitness/nutrition/meals | fitness.edit | Logs a catalogue dish into today. |
PATCH | /api/fitness/nutrition/hydration | fitness.edit | Sets today's glasses. |
GET | /api/fitness/nutrition/planner | fitness.view | One day's planner (date). |
POST | /api/fitness/nutrition/planner | fitness.edit | Logs the member's own meal. |
DELETE | /api/fitness/nutrition/planner/:key | fitness.edit | Removes one. |
GET | /api/fitness/sleep | fitness.view | The sleep page. |
POST | /api/fitness/sleep/nights | fitness.edit | Logs a night. |
DELETE | /api/fitness/sleep/nights/:date | fitness.edit | Deletes one. |
GET | /api/fitness/health | fitness.view | The health page. |
POST | /api/fitness/health/readings | fitness.edit | Logs a day's vitals. |
DELETE | /api/fitness/health/readings/:date | fitness.edit | Deletes them. |
GET | /api/fitness/progress | fitness.view | The progress page. |
GET | /api/fitness/progress/photos | fitness.view | The photos page (before, after). |
POST | /api/fitness/progress/photos | fitness.edit | Adds a checkpoint. |
PATCH | /api/fitness/progress/photos/notes | fitness.edit | Saves the journal. |
PATCH | /api/fitness/progress/goal | fitness.edit | Sets the weight goal. |
POST | /api/fitness/progress/readings | fitness.edit | Logs a body reading. |
DELETE | /api/fitness/progress/readings/:date | fitness.edit | Deletes one. |
GET | /api/fitness/community | fitness.view | The community page. |
GET | /api/fitness/community/members/:username | fitness.view | A member's profile. |
GET | /api/fitness/community/groups/:slug | fitness.view | A group. |
GET | /api/fitness/community/discussions/:slug | fitness.view | A discussion. |
POST | /api/fitness/community/groups/:slug/join | fitness.edit | Joins or leaves a group. |
POST | /api/fitness/community/challenges/:slug/join | fitness.edit | Joins or leaves a challenge. |
POST | /api/fitness/community/events/:slug/attend | fitness.edit | Attends an event or not. |
POST | /api/fitness/community/groups/:slug/posts | fitness.edit | Writes a post. |
POST | /api/fitness/community/groups/:slug/posts/:postKey/like | fitness.edit | Likes or unlikes it. |
POST | /api/fitness/community/groups/:slug/posts/:postKey/comments | fitness.edit | Comments. |
POST | /api/fitness/community/discussions/:slug/replies | fitness.edit | Replies. |
GET | /api/fitness/billing | fitness.view | The plan and its invoices. Read only. |
GET | /api/fitness/settings-hub | settings.view | The settings page's figures, alerts, privacy and appearance. |
PATCH | /api/fitness/settings-hub/notifications | fitness.edit | The four alert switches. |
PATCH | /api/fitness/settings-hub/privacy | fitness.edit | Sharing activity. |
PATCH | /api/fitness/settings-hub/appearance | settings.view | Theme, accent and motion. |
GET | /api/fitness/settings-hub/export | fitness.view | Everything the member recorded, as JSON. |
GET | /api/fitness/devices | fitness.view | The devices and the account's connection to each. |
POST | /api/fitness/devices/:key/toggle | fitness.edit | Connects or disconnects one. |
POST | /api/fitness/devices/:key/sync | fitness.edit | Always 400 device_sync_unavailable. |
GET | /api/fitness/devices/keys | fitness.view | The account's ingest keys. |
POST | /api/fitness/devices/keys | fitness.edit | Creates one; the full key is in this answer only. |
DELETE | /api/fitness/devices/keys/:id | fitness.edit | Revokes one. |
Workouts, exercises and the session
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/fitness/workouts | fitness.view | The plan page. |
GET | /api/fitness/workouts/programs | fitness.view | The programs and the active one. |
POST | /api/fitness/workouts/programs/:slug/enroll | fitness.edit | Starts or stops a program. |
GET | /api/fitness/workouts/:slug | fitness.view | One workout. |
POST | /api/fitness/workouts/:slug/save | fitness.edit | Saves or unsaves it. |
GET | /api/fitness/exercises | fitness.view | The library (muscle, equipment, difficulty). |
GET | /api/fitness/exercises/:slug | fitness.view | One movement; records a view. |
POST | /api/fitness/exercises/:slug/save | fitness.edit | Saves or unsaves it. |
POST | /api/fitness/exercises/:slug/log-set | fitness.edit | Logs sets, reps and weight. |
GET | /api/fitness/workouts/session | fitness.view | The open session, opened from today's plan when none is. |
POST | /api/fitness/workouts/session/sets/:set | fitness.edit | Logs a set (weightKg, reps, rpe). |
POST | /api/fitness/workouts/session/rest/skip | fitness.edit | Skips the rest. |
PATCH | /api/fitness/workouts/session/notes | fitness.edit | Saves the note. |
POST | /api/fitness/workouts/session/exercises | fitness.edit | Adds a movement (slug). |
POST | /api/fitness/workouts/session/exercises/:slug/select | fitness.edit | Makes it current. |
DELETE | /api/fitness/workouts/session/exercises/:slug | fitness.edit | Removes it. |
POST | /api/fitness/workouts/session/finish | fitness.edit | Finishes the session. |
POST | /api/fitness/workouts/session/restart | fitness.edit | Discards it and starts again. |
Coaching
The roster routes answer for the coach making the request: another coach's client is a 404.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/fitness/coaching/clients | coaching.clients | The caller's roster. |
GET | /api/fitness/coaching/clients/:username | coaching.clients | One client. |
GET | /api/fitness/coaching/available-members | coaching.clients | Members with no coach. |
POST | /api/fitness/coaching/clients | coaching.clients | Takes a member on (username). |
GET | /api/fitness/coaching/workouts | coaching.clients | The workouts a coach assigns from. |
GET | /api/fitness/coaching/roster/assigned-work | coaching.clients | Today's work across the roster. |
GET | /api/fitness/coaching/roster/check-ins | coaching.clients | This week's check-ins, unanswered first. |
GET | /api/fitness/coaching/clients/:username/plan | coaching.clients | A client's assigned work. |
POST | /api/fitness/coaching/clients/:username/plan | coaching.clients | Assigns a workout (workoutSlug, startsOn, note). |
DELETE | /api/fitness/coaching/plan/:id | coaching.clients | Removes an assignment. |
GET | /api/fitness/coaching/clients/:username/messages | coaching.clients | The thread with a client. |
POST | /api/fitness/coaching/clients/:username/messages | coaching.clients | Writes to them (body, attachments). |
GET | /api/fitness/coaching/clients/:username/check-ins | coaching.clients | A client's check-ins. |
POST | /api/fitness/coaching/check-ins/:id/reply | coaching.clients | Answers one (body). |
GET | /api/fitness/coaching/my-coach | fitness.view | The member's coach, or null. |
GET | /api/fitness/coaching/my-plan | fitness.view | What the coach assigned. |
GET | /api/fitness/coaching/messages | fitness.view | The member's thread. |
POST | /api/fitness/coaching/messages | fitness.edit | Writes to the coach. |
GET | /api/fitness/coaching/check-ins | fitness.view | The member's check-ins. |
POST | /api/fitness/coaching/check-ins | fitness.edit | Files this week's (energy, weightKg, notes). |
GET | /api/fitness/coaching/coaches | coaching.assign | The accounts holding the Coach role, with their client counts. |
GET | /api/fitness/coaching/assignments/:username | coaching.assign | Who coaches a member. |
POST | /api/fitness/coaching/assignments | coaching.assign | Moves a member to a coach (coachUsername, memberUsername). |
DELETE | /api/fitness/coaching/assignments/:username | coaching.assign | Takes a member off every roster. |
The content catalogues
Each catalogue answers the same five routes under /api/fitness/manage/<catalogue>, and every one needs fitness.manage. The catalogues are exercises, workouts, programs, dishes, groups, events, challenges, achievements and badges.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/fitness/manage/<catalogue> | fitness.manage | A page of records (page, page_count, search, order and the catalogue's own filters). |
GET | /api/fitness/manage/<catalogue>/:id | fitness.manage | One record. |
POST | /api/fitness/manage/<catalogue> | fitness.manage | Creates one; a slug or key left out is made from the English name. Answers 201. |
PATCH | /api/fitness/manage/<catalogue>/:id | fitness.manage | Changes the fields sent; lists inside a record are replaced whole. |
DELETE | /api/fitness/manage/<catalogue>/:id | fitness.manage | Deletes it and answers 200, or 409 while something still uses it. |
POST | /api/fitness/manage/exercises/:id/feature | fitness.manage | Makes this movement the featured one. |
An unknown id is 404 record_not_found. Names and texts are sent as { "en": "…", "ar": "…" }; a dish's calories are never sent, they are worked out from its grams.
Ingest
The door for devices and aggregators. These routes take an ingest key in X-Api-Key, never a bearer token, and write for the key's account only.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/ingest/whoami | An ingest key | The key's account and name. |
POST | /api/ingest/daily-metrics | An ingest key | Upserts days of movement figures (days). |
POST | /api/ingest/sessions | An ingest key | Records finished workouts (sessions). |
Search, uploads, notifications and countries
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/search | Any signed-in account | Hits for a term across the kinds the caller may open (q, locale, limit). |
POST | /api/helpers/upload | Any signed-in account | Uploads one file (file, up to 150 MB) to the bucket; 400 with no bucket configured. |
POST | /api/helpers/upload-chunk | Any signed-in account | One part of a larger file (up to 16 MB a part). |
GET | /api/notifications | Any signed-in account | The caller's notifications. |
PATCH | /api/notifications/read-all | Any signed-in account | Marks them all read. |
PATCH | /api/notifications/:id/read | Any signed-in account | Marks one read. |
DELETE | /api/notifications/:id | Any signed-in account | Deletes one. |
GET | /api/helpers/countries | Anyone | Countries for a dropdown, labelled in the request's language. |
Two Socket.IO namespaces on the same server push live updates: /notifications delivers each new notification, and /auth tells a signed-in dashboard that its permissions changed. They accept the origins in FRONTEND_URL, or CORS_ORIGIN when it is unset.
AI assistant
Included with your purchase. Sign in to read, or open it in your download.
The assistant's chat, model, opener and chat-history routes.
MCP server
Included with your purchase. Sign in to read, or open it in your download.
The route a coding agent uses, and the key it sends.
Demo mode routes
Included with your purchase. Sign in to read, or open it in your download.
The routes a public demo uses to describe itself and to give each visitor an account.