Skip to the article
Aniq-UI

KinoraAPI reference

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:

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:

Terminal
curl http://localhost:8000/api/health
Response
{"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:

Response (400)
{
  "success": false,
  "data": null,
  "message": "<the first field's problem>",
  "errors": {
    "email": ["<the problem with email>"]
  }
}
StatusWhen
400A 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.
401The 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.
403The account's roles do not grant the route's permission: "You need one of these permissions: …".
404No such record, or a record that is not the caller's.
409A delete that would break something still using the record, such as exercise_in_use.
429More 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.
503The health check before the database is ready.
500An 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:

Response
{
  "success": true,
  "data": { "data": [ ], "page": 1, "limit": 15, "total": 35, "totalPages": 3 },
  "message": ""
}
ListDefault page size
Members, the content catalogues15
Coaches, roles, settings10

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 holds access_token, a JWT signed with JWT_SECRET, and the account with its roles and permissions. Send it as Authorization: Bearer <token>.
  • A token lasts for JWT_EXPIRATION, 7d by default. There is no refresh route: when a token expires, the next request answers 401 and 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, see TRUST_PROXY (Troubleshooting).
  1. Sign in as the seeded Head Coach

    Terminal
    curl -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": "..."
    }
  2. Call a protected route with the token

    Terminal
    curl "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.

MethodPathWho can call itWhat it does
POST/api/auth/loginAnyoneSigns in any account. Rate-limited.
POST/api/auth/registerAnyoneCreates 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/meAny signed-in accountThe account with its roles and permissions.
POST/api/auth/delete-accountAny signed-in accountCloses the caller's own account after checking password. Rate-limited.
PATCH/api/coaches/profileAny signed-in accountUpdates the caller's own profile.
PATCH/api/coaches/profile/passwordAny signed-in accountChanges 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

MethodPathWho can call itWhat it does
GET/api/usersmembers.viewA page of members (order, search, email, phone, country_id, username, first_name, last_name, from_date, to_date, verified).
GET/api/users/statisticmembers.viewMember counts for the Gym screen.
GET/api/users/:usernamemembers.viewOne member.
POST/api/usersmembers.createCreates a member.
PATCH/api/users/:usernamemembers.updateUpdates a member.
PATCH/api/users/:username/change-passwordmembers.updateSets a member's password.
POST/api/users/:username/resend-verification-emailmembers.updateAnswers success; no email is sent, as no mail transport ships.
POST/api/users/:username/make-verifiedmembers.verifyMarks the email verified.
POST/api/users/:username/make-unverifiedmembers.verifyMarks it unverified.
DELETE/api/users/:usernamemembers.deleteMoves the member to the deleted list.
GET/api/users/deletedmembers.viewA page of deleted members.
GET/api/users/deleted/:usernamemembers.viewOne deleted member.
POST/api/users/deleted/:username/restoremembers.restoreRestores one.

Coaches and roles

MethodPathWho can call itWhat it does
GET/api/coachescoaches.viewA page of staff accounts (email, name, phone).
GET/api/coaches/statisticscoaches.viewStaff counts.
GET/api/coaches/management/roles/selectcoaches.view or coaches.assign_rolesThe roles a staff form can pick from.
GET/api/coaches/:idcoaches.viewOne staff account, by id or username.
POST/api/coachescoaches.createCreates one.
PATCH/api/coaches/:idcoaches.editUpdates one.
PATCH/api/coaches/:id/rolescoaches.assign_rolesSets its roles.
DELETE/api/coaches/:idcoaches.deleteDeletes one.
GET/api/rolesroles.viewThe roles (page, page_count, name, guard_name, created_from, created_to); every role without page_count.
GET/api/roles/statisticsroles.viewRole counts.
GET/api/roles/selectroles.viewThe roles for a dropdown.
GET/api/roles/permissionsroles.viewEvery permission, by module.
GET/api/roles/:idroles.viewOne role with its permissions.
POST/api/rolesroles.createCreates a role.
PUT/api/roles/:idroles.editRenames it.
POST/api/roles/:id/permissionsroles.assign_permissionsSets its permissions.
DELETE/api/roles/:idroles.deleteDeletes it.

App settings

MethodPathWho can call itWhat it does
GET/api/settingssettings.viewA page of settings (search, category, type).
GET/api/settings/:keysettings.viewOne setting.
PATCH/api/settings/:keysettings.editChanges its value.
DELETE/api/settings/:keysettings.editDeletes 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.

MethodPathWho can call itWhat it does
GET/api/fitness/overviewfitness.viewThe dashboard's day: rings, calories, sleep, heart rate, workouts, steps.
GET/api/fitness/activityfitness.viewThe activity page (granularity days, weeks or months).
GET/api/fitness/activity/:slugfitness.viewOne activity.
GET/api/fitness/nutritionfitness.viewThe nutrition page.
POST/api/fitness/nutrition/mealsfitness.editLogs a catalogue dish into today.
PATCH/api/fitness/nutrition/hydrationfitness.editSets today's glasses.
GET/api/fitness/nutrition/plannerfitness.viewOne day's planner (date).
POST/api/fitness/nutrition/plannerfitness.editLogs the member's own meal.
DELETE/api/fitness/nutrition/planner/:keyfitness.editRemoves one.
GET/api/fitness/sleepfitness.viewThe sleep page.
POST/api/fitness/sleep/nightsfitness.editLogs a night.
DELETE/api/fitness/sleep/nights/:datefitness.editDeletes one.
GET/api/fitness/healthfitness.viewThe health page.
POST/api/fitness/health/readingsfitness.editLogs a day's vitals.
DELETE/api/fitness/health/readings/:datefitness.editDeletes them.
GET/api/fitness/progressfitness.viewThe progress page.
GET/api/fitness/progress/photosfitness.viewThe photos page (before, after).
POST/api/fitness/progress/photosfitness.editAdds a checkpoint.
PATCH/api/fitness/progress/photos/notesfitness.editSaves the journal.
PATCH/api/fitness/progress/goalfitness.editSets the weight goal.
POST/api/fitness/progress/readingsfitness.editLogs a body reading.
DELETE/api/fitness/progress/readings/:datefitness.editDeletes one.
GET/api/fitness/communityfitness.viewThe community page.
GET/api/fitness/community/members/:usernamefitness.viewA member's profile.
GET/api/fitness/community/groups/:slugfitness.viewA group.
GET/api/fitness/community/discussions/:slugfitness.viewA discussion.
POST/api/fitness/community/groups/:slug/joinfitness.editJoins or leaves a group.
POST/api/fitness/community/challenges/:slug/joinfitness.editJoins or leaves a challenge.
POST/api/fitness/community/events/:slug/attendfitness.editAttends an event or not.
POST/api/fitness/community/groups/:slug/postsfitness.editWrites a post.
POST/api/fitness/community/groups/:slug/posts/:postKey/likefitness.editLikes or unlikes it.
POST/api/fitness/community/groups/:slug/posts/:postKey/commentsfitness.editComments.
POST/api/fitness/community/discussions/:slug/repliesfitness.editReplies.
GET/api/fitness/billingfitness.viewThe plan and its invoices. Read only.
GET/api/fitness/settings-hubsettings.viewThe settings page's figures, alerts, privacy and appearance.
PATCH/api/fitness/settings-hub/notificationsfitness.editThe four alert switches.
PATCH/api/fitness/settings-hub/privacyfitness.editSharing activity.
PATCH/api/fitness/settings-hub/appearancesettings.viewTheme, accent and motion.
GET/api/fitness/settings-hub/exportfitness.viewEverything the member recorded, as JSON.
GET/api/fitness/devicesfitness.viewThe devices and the account's connection to each.
POST/api/fitness/devices/:key/togglefitness.editConnects or disconnects one.
POST/api/fitness/devices/:key/syncfitness.editAlways 400 device_sync_unavailable.
GET/api/fitness/devices/keysfitness.viewThe account's ingest keys.
POST/api/fitness/devices/keysfitness.editCreates one; the full key is in this answer only.
DELETE/api/fitness/devices/keys/:idfitness.editRevokes one.

Workouts, exercises and the session

MethodPathWho can call itWhat it does
GET/api/fitness/workoutsfitness.viewThe plan page.
GET/api/fitness/workouts/programsfitness.viewThe programs and the active one.
POST/api/fitness/workouts/programs/:slug/enrollfitness.editStarts or stops a program.
GET/api/fitness/workouts/:slugfitness.viewOne workout.
POST/api/fitness/workouts/:slug/savefitness.editSaves or unsaves it.
GET/api/fitness/exercisesfitness.viewThe library (muscle, equipment, difficulty).
GET/api/fitness/exercises/:slugfitness.viewOne movement; records a view.
POST/api/fitness/exercises/:slug/savefitness.editSaves or unsaves it.
POST/api/fitness/exercises/:slug/log-setfitness.editLogs sets, reps and weight.
GET/api/fitness/workouts/sessionfitness.viewThe open session, opened from today's plan when none is.
POST/api/fitness/workouts/session/sets/:setfitness.editLogs a set (weightKg, reps, rpe).
POST/api/fitness/workouts/session/rest/skipfitness.editSkips the rest.
PATCH/api/fitness/workouts/session/notesfitness.editSaves the note.
POST/api/fitness/workouts/session/exercisesfitness.editAdds a movement (slug).
POST/api/fitness/workouts/session/exercises/:slug/selectfitness.editMakes it current.
DELETE/api/fitness/workouts/session/exercises/:slugfitness.editRemoves it.
POST/api/fitness/workouts/session/finishfitness.editFinishes the session.
POST/api/fitness/workouts/session/restartfitness.editDiscards it and starts again.

Coaching

The roster routes answer for the coach making the request: another coach's client is a 404.

MethodPathWho can call itWhat it does
GET/api/fitness/coaching/clientscoaching.clientsThe caller's roster.
GET/api/fitness/coaching/clients/:usernamecoaching.clientsOne client.
GET/api/fitness/coaching/available-memberscoaching.clientsMembers with no coach.
POST/api/fitness/coaching/clientscoaching.clientsTakes a member on (username).
GET/api/fitness/coaching/workoutscoaching.clientsThe workouts a coach assigns from.
GET/api/fitness/coaching/roster/assigned-workcoaching.clientsToday's work across the roster.
GET/api/fitness/coaching/roster/check-inscoaching.clientsThis week's check-ins, unanswered first.
GET/api/fitness/coaching/clients/:username/plancoaching.clientsA client's assigned work.
POST/api/fitness/coaching/clients/:username/plancoaching.clientsAssigns a workout (workoutSlug, startsOn, note).
DELETE/api/fitness/coaching/plan/:idcoaching.clientsRemoves an assignment.
GET/api/fitness/coaching/clients/:username/messagescoaching.clientsThe thread with a client.
POST/api/fitness/coaching/clients/:username/messagescoaching.clientsWrites to them (body, attachments).
GET/api/fitness/coaching/clients/:username/check-inscoaching.clientsA client's check-ins.
POST/api/fitness/coaching/check-ins/:id/replycoaching.clientsAnswers one (body).
GET/api/fitness/coaching/my-coachfitness.viewThe member's coach, or null.
GET/api/fitness/coaching/my-planfitness.viewWhat the coach assigned.
GET/api/fitness/coaching/messagesfitness.viewThe member's thread.
POST/api/fitness/coaching/messagesfitness.editWrites to the coach.
GET/api/fitness/coaching/check-insfitness.viewThe member's check-ins.
POST/api/fitness/coaching/check-insfitness.editFiles this week's (energy, weightKg, notes).
GET/api/fitness/coaching/coachescoaching.assignThe accounts holding the Coach role, with their client counts.
GET/api/fitness/coaching/assignments/:usernamecoaching.assignWho coaches a member.
POST/api/fitness/coaching/assignmentscoaching.assignMoves a member to a coach (coachUsername, memberUsername).
DELETE/api/fitness/coaching/assignments/:usernamecoaching.assignTakes 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.

MethodPathWho can call itWhat it does
GET/api/fitness/manage/<catalogue>fitness.manageA page of records (page, page_count, search, order and the catalogue's own filters).
GET/api/fitness/manage/<catalogue>/:idfitness.manageOne record.
POST/api/fitness/manage/<catalogue>fitness.manageCreates one; a slug or key left out is made from the English name. Answers 201.
PATCH/api/fitness/manage/<catalogue>/:idfitness.manageChanges the fields sent; lists inside a record are replaced whole.
DELETE/api/fitness/manage/<catalogue>/:idfitness.manageDeletes it and answers 200, or 409 while something still uses it.
POST/api/fitness/manage/exercises/:id/featurefitness.manageMakes 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.

MethodPathWho can call itWhat it does
GET/api/ingest/whoamiAn ingest keyThe key's account and name.
POST/api/ingest/daily-metricsAn ingest keyUpserts days of movement figures (days).
POST/api/ingest/sessionsAn ingest keyRecords finished workouts (sessions).

Search, uploads, notifications and countries

MethodPathWho can call itWhat it does
GET/api/searchAny signed-in accountHits for a term across the kinds the caller may open (q, locale, limit).
POST/api/helpers/uploadAny signed-in accountUploads one file (file, up to 150 MB) to the bucket; 400 with no bucket configured.
POST/api/helpers/upload-chunkAny signed-in accountOne part of a larger file (up to 16 MB a part).
GET/api/notificationsAny signed-in accountThe caller's notifications.
PATCH/api/notifications/read-allAny signed-in accountMarks them all read.
PATCH/api/notifications/:id/readAny signed-in accountMarks one read.
DELETE/api/notifications/:idAny signed-in accountDeletes one.
GET/api/helpers/countriesAnyoneCountries 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.

Stuck on a step?

Find a fix before you start over.

Troubleshooting

Cookie Preferences

We use cookies to enhance your browsing experience, analyze site traffic, and personalize content. By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising.