Kora/developer documentation/Internal API

Internal API

How the dashboard talks to Kora's API, who a request acts as, and how permissions are checked.

Kora runs two servers. The dashboard on port 3000 serves the web app and signs members in. The API on port 3001 holds every route, including your module's. The dashboard never touches the database. It calls the API for everything.

Requests From the Dashboard

When the dashboard calls the API it sends Kora's internal secret plus headers that say who the signed-in member is:

HeaderWhat it carries
Authorization: Bearer <secret>Kora's internal secret from config.json.
x-actor-kindstaff or customer.
x-actor-id, x-actor-nameThe member.
x-actor-permissionsTheir permissions as a JSON array.
x-actor-customerThe customer ID, for client dashboard members.
x-actor-discordTheir Discord ID, if linked.
x-actor-ipTheir real IP address.

The API trusts these headers only alongside the secret. Keep the secret private, since anything holding it can act as any member.

This happens in three places:

WherePath
Your page's load()client.send("GET", "/api/modules/<module>/<path>")
Resource pages and your own viewsrequest(module, path), which goes through /api/m/<module>/<path> on the dashboard
Kora's own pagesBuilt-in routes such as /api/settings and /api/audit

In each case the request reaches your route as that member, so you never handle sessions yourself.

Actors

Every request gets an actor on request.actor:

KindFrom
systemThe secret with no actor headers, such as Kora's own startup calls. Has every permission.
staffA dashboard team member.
customerA client dashboard member.
tokenAn API token from the API Tokens page. Its permissions are the token's scopes.
publicNobody, on a route with access: "public". Has no permissions.

A private route with no valid secret or token answers 401. A token that does not match is written to the audit log.

Checking Permissions

Routes check permissions for you through their permissions option. To check by hand:

if (!this.kora.api.allowed(request.actor, "licenses.edit")) this.fail(403, "You cannot edit licenses.");
Actor hasPasses
*Everything.
licenses.editlicenses.edit.
licenses.*Any licenses. permission.
licenses.manageAny licenses. permission, for staff.

customer is a special permission that only customers and the system pass. Public actors fail every check except an empty one.

Request Helpers

HelperWhat it does
this.kora.api.allowed(actor, permission)The check above.
this.kora.api.ip(request)The caller's IP. Reads x-actor-ip from the dashboard, then cf-connecting-ip, then x-forwarded-for.
this.kora.api.address()The public API address from API URL, or http://localhost:3001. Use it for links you give out, such as webhook URLs.

Built-in Routes

These belong to Kora rather than a module. They all need the secret except the first three.

RouteWhat it does
GET /api/statusWhether Kora is running and configured.
GET /api/health200 when Kora is running with the database up and no failed modules, otherwise 503. Point uptime monitors here.
GET /api/v1/public-keyThe Ed25519 public key used to sign responses.
GET /api/modules, POST /api/modules/toggleList modules and turn them on or off.
GET /api/pagesThe pages the member can see.
GET /api/modules/configs, POST /api/modules/configRead and save config groups.
GET /api/modules/langs, POST /api/modules/langRead and save lang text.
GET /api/settings, POST /api/settingsKora's settings, with secrets hidden.
GET /api/tokens, POST /api/tokens, DELETE /api/tokens/:idAPI tokens.
GET /api/auditThe audit log.
GET /api/logsRecent console lines.

Your module's routes live under /api/modules/<module>, or at their own path when they set absolute: true. See Routes.