# Internal API

URL: https://zerodev.ca/docs/kora/developer-documentation/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:

| Header                           | What it carries                                |
| -------------------------------- | ---------------------------------------------- |
| `Authorization: Bearer <secret>` | Kora's internal secret from `config.json`.     |
| `x-actor-kind`                   | `staff` or `customer`.                         |
| `x-actor-id`, `x-actor-name`     | The member.                                    |
| `x-actor-permissions`            | Their permissions as a JSON array.             |
| `x-actor-customer`               | The customer ID, for client dashboard members. |
| `x-actor-discord`                | Their Discord ID, if linked.                   |
| `x-actor-ip`                     | Their 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:

| Where                             | Path                                                                                  |
| --------------------------------- | ------------------------------------------------------------------------------------- |
| Your page's `load()`              | `client.send("GET", "/api/modules/<module>/<path>")`                                  |
| Resource pages and your own views | `request(module, path)`, which goes through `/api/m/<module>/<path>` on the dashboard |
| Kora's own pages                  | Built-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`:

| Kind       | From                                                                                      |
| ---------- | ----------------------------------------------------------------------------------------- |
| `system`   | The secret with no actor headers, such as Kora's own startup calls. Has every permission. |
| `staff`    | A dashboard team member.                                                                  |
| `customer` | A client dashboard member.                                                                |
| `token`    | An API token from the **API Tokens** page. Its permissions are the token's scopes.        |
| `public`   | Nobody, 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:

```js
if (!this.kora.api.allowed(request.actor, "licenses.edit")) this.fail(403, "You cannot edit licenses.");
```

| Actor has         | Passes                                 |
| ----------------- | -------------------------------------- |
| `*`               | Everything.                            |
| `licenses.edit`   | `licenses.edit`.                       |
| `licenses.*`      | Any `licenses.` permission.            |
| `licenses.manage` | Any `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

| Helper                                     | What 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.

| Route                                                           | What it does                                                                                                        |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `GET /api/status`                                               | Whether Kora is running and configured.                                                                             |
| `GET /api/health`                                               | `200` when Kora is running with the database up and no failed modules, otherwise `503`. Point uptime monitors here. |
| `GET /api/v1/public-key`                                        | The Ed25519 public key used to sign responses.                                                                      |
| `GET /api/modules`, `POST /api/modules/toggle`                  | List modules and turn them on or off.                                                                               |
| `GET /api/pages`                                                | The pages the member can see.                                                                                       |
| `GET /api/modules/configs`, `POST /api/modules/config`          | Read and save [config](/docs/kora/developer-documentation/configuration) groups.                                    |
| `GET /api/modules/langs`, `POST /api/modules/lang`              | Read and save [lang](/docs/kora/developer-documentation/languages) text.                                            |
| `GET /api/settings`, `POST /api/settings`                       | Kora's settings, with secrets hidden.                                                                               |
| `GET /api/tokens`, `POST /api/tokens`, `DELETE /api/tokens/:id` | API tokens.                                                                                                         |
| `GET /api/audit`                                                | The audit log.                                                                                                      |
| `GET /api/logs`                                                 | Recent console lines.                                                                                               |

Your module's routes live under `/api/modules/<module>`, or at their own path when they set `absolute: true`. See [Routes](/docs/kora/developer-documentation/routes).
