Developers & Agents Updated 2026-08-01 View as Markdown

Authentication

Machine callers authenticate with a Bearer token:

Authorization: Bearer rt_ro_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

There is no OAuth flow on this endpoint. Tokens are minted deliberately, one per client, and revoked individually.

Token format

rt_<scopeTag>_<43 characters base64url>
  • rt identifies it as an nLight.fit API token. A credential without this prefix is never looked up in the database, so presenting an unrelated key costs nothing to reject.
  • <scopeTag> is ro or rw. It is cosmetic — it exists so you can recognise a token you find in a config file. Authorization always reads scopes from the database, never from the token string, which the holder controls.
  • The secret is 32 bytes of CSPRNG entropy, base64url-encoded to 43 characters with no padding.

Scopes

Scope Grants
read Every non-mutating tool. Required by every MCP method today, including initialize and ping.
write Anything that changes stored data. Defined and enforced, but nothing over MCP requires it yet.

Scopes are explicit and non-cumulative. A write token does not automatically get read. That is deliberate: granting one capability should never silently grant another. If you want a token that can do both, ask for both.

A token that is valid but lacks the scope a request needs gets 403, not 401 — you are who you say you are, you simply may not do this. Telling the client to re-authenticate would send it round a loop it cannot win.

What is stored

Only sha256(token). The plaintext is shown exactly once, at creation, and never again. A leaked database dump therefore yields no usable credentials, and a lost token cannot be recovered — revoke it and mint another.

Each token row also holds a name you chose, a 12-character display prefix so you can tell two tokens apart in a list, the granted scopes, creation time, last-used time, and revocation state.

Why SHA-256 and not bcrypt

bcrypt exists to slow down guessing of low-entropy human passwords. These tokens carry 256 bits of entropy, so brute force is not in the threat model, and a deliberately slow hash would add roughly 250ms to every single MCP call. Account passwords still use bcrypt, which is the right choice for what those protect.

Expiry and revocation

Tokens do not expire by default. That is only safe because revocation is immediate: every call looks the token up in the database, so a revoked token stops working on the next request rather than whenever a signature would have lapsed.

Revocation is idempotent. Revoking an already-revoked token reports success without changing the original revocation timestamp, so a panicked second attempt is not an error.

Why not session tokens

Session tokens are stateless and signed, which means they cannot be revoked — a stateless token stays valid until it expires, with no server-side blocklist.

That trade-off is acceptable for a 24-hour browser session. It is not acceptable for a credential that lives indefinitely inside a third party's cloud and unlocks a complete medical history. If one leaks it has to die immediately, which means the server must look it up. So API tokens are opaque random strings backed by a database row, not signed self-describing tokens.

Session tokens are accepted, read-only

A browser session token presented to /api/mcp will authenticate, but it is treated as carrying read and nothing else. This exists as a convenience for local testing and for any in-app "verify my connection" affordance.

Anything that mutates data over MCP must present a purpose-built token, so a stolen browser session can never be replayed into a write.

What is not accepted

The compatibility mode that lets some older in-app requests identify a user by a plain userId in the request body is never consulted here. That fallback exists only to keep already-open browser tabs working through a rollout. This surface is new, has no legacy clients, and is reachable from the public internet — honouring a bare userId would hand every account to anyone who can type a 24-character id.