> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lmrp.uk/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> How callers prove who they are: the browser session, service credentials, and temporary Developer Access.

Argus has two kinds of caller.

| Caller | Credential | Used for |
| - | - | - |
| A person in the web application | Session cookie from Discord sign-in | Everything |
| An integration the community runs, such as a bot | Scoped service credential (bearer token) | Endpoints that do not need a signed-in user |

There are no public API keys, no OAuth for third parties, and no user-issued tokens.

## Browser session

### Signing in with Discord

Sign-in is a browser redirect flow, not a JSON exchange.

<Steps>
  <Step title="Start">
    The browser navigates to `GET /api/auth/discord`. Argus creates a single-use state, valid for ten minutes, binds it to this browser with a short-lived cookie, and redirects to Discord asking for the `identify` scope.
  </Step>

  <Step title="Callback">
    Discord redirects to `GET /api/auth/discord/callback`. Argus checks the state against the cookie, exchanges the code, and creates or updates the Argus account from the Discord profile.
  </Step>

  <Step title="Session">
    Argus sets the session cookie and redirects to the application root. Any previous session in that browser is replaced.
  </Step>
</Steps>

Signing in creates an account. It never grants a staff role.

### The session cookie

| Property | Value |
| - | - |
| Name | `__Host-argus-session` in production, `argus-session` otherwise |
| Contents | An opaque random token. Only its SHA-256 hash is stored. |
| Lifetime | Seven days |
| Attributes | `HttpOnly`, `SameSite=Lax`, `Path=/`, and `Secure` in production |

The cookie is `HttpOnly`, so scripts cannot read it. Same-origin `fetch` sends it automatically.

On every request Argus re-checks the session's expiry, the account's status and the current staff membership. A suspended or disabled account is refused with `ACCOUNT_DISABLED`, and suspending an account revokes its sessions.

### Origin check on changes

Every cookie-authenticated request other than `GET` must carry an `Origin` header equal to the application's own origin (`APP_URL`). Anything else is refused with `403 INVALID_ORIGIN`. Browsers add this header themselves on same-origin requests.

Argus sends no CORS headers. A page on another origin cannot call this API with a user's session.

### Signing out

`POST /api/auth/logout` revokes the current session and clears the cookie.

### Linking a Roblox account

A signed-in user can prove ownership of a Roblox account through `GET /api/auth/roblox`, an authorisation-code flow with PKCE. The callback must arrive in a session belonging to the user who started it. A Roblox account can be linked to one Argus account, and there is no unlinking endpoint.

## Service credentials

An administrator can issue a credential for an integration with `POST /api/admin/services`. The response contains the token once; Argus keeps only its hash.

```http theme={null}
Authorization: Bearer argus_<43 URL-safe characters>
```

* **Scopes are permission keys.** A credential can do exactly what its scopes allow. `admin.manage` and `staff.manage` cannot be granted.
* **It expires.** The lifetime is 1 to 90 days, 30 by default. Revocation takes effect on the next request.
* **It is not a user.** Endpoints that need a signed-in person refuse it with `403 USER_REQUIRED`. Those are: the current user, sign-out, Developer Access, Roblox linking, all administration including staff membership, everything in the civilian portal, every official PNC change, and all MDT self-service.
* **It is never mixed with a cookie.** If an `Authorization` header is present and invalid, the request fails with `401`; Argus does not fall back to the session.
* **No origin check.** Bearer requests do not need an `Origin` header.

Actions taken with a credential are attributed to it in the audit log by `serviceId`. A credential cannot claim to be a person.

Each operation in the reference states its access: **Browser session only**, or **Browser session, or a service credential**.

## Developer Access

<Warning>
  Developer Access is temporary, internal tooling for the people building Argus. It is not part of the product's permission model and is expected to be removed.
</Warning>

`POST /api/auth/developer` with the developer password gives the **current session** every permission in the catalogue. `DELETE /api/auth/developer` turns it off.

* It is off unless the server has `ARGUS_DEVELOPER_PASSWORD` set. Clearing that variable disables it for sessions that already had it.
* It changes no account, role or staff record, and does not affect the user's other sessions.
* It does not create a staff profile. Rules that need a real one still apply: for example booking on to a unit, starting a shift, or hosting a session.
* Attempts are limited to five per account per 15 minutes. A wrong password, a malformed body and a disabled feature all return the same `401 AUTHENTICATION_FAILED`.
* `GET /api/me` reports it as `developerAccess: true`, with the expanded `permissions`.

The password is a deployment secret. It is never returned by the API and does not belong in documentation, source control or client code.

## Rate limits

Limits are counted per caller, per minute, in the database, so they hold across server instances.

| Budget | Limit |
| - | - |
| Reads (`GET`) per user or credential | 180 per minute |
| Everything else per user or credential | 60 per minute |
| Starting Discord or Roblox sign-in, shared by all callers | 120 per minute per provider |
| Developer Access attempts per account | 5 per 15 minutes |

Exceeding one returns `429 RATE_LIMITED` with a `Retry-After` header in seconds.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.