Authentication
Every REST call and every MCP request is authenticated with a bearer token. There are two kinds of token:
- API keys (
mdt_live_…in production,mdt_test_…elsewhere). A workspace owner or admin creates a key, and the key acts for the user who created it, inside one workspace. - OAuth 2.1 access tokens, issued when a person approves a connected app such as Claude.ai, ChatGPT, Cursor or Claude Code.
Both kinds of token pass through the same authorisation check, and both carry scopes. A call succeeds only when the token has the scope that the operation needs. Everything else is refused, so a token with no scopes can do nothing.
Authorization: Bearer mdt_live_your_key_hereAPI keys #
Creating a key #
Owners and admins create keys under API keys in the workspace settings (/w/<workspace>/settings/integrations). Other members see the list read-only. The create form asks for:
- a name, so you can tell keys apart later;
- an expiry: 30 days, 90 days (the default), 1 year, or never.
The secret is shown once, when the key is created. Mandatum stores only a SHA-256 hash of it, so a lost key cannot be recovered. Rotate it to get a new secret.
Using a key #
Send the key as the bearer token on every request. Keep keys in environment variables or a secrets manager. Use one key per integration and per environment, so a leak can be revoked without breaking everything else.
Revoking and rotating #
Owners and admins revoke a key from the same list. Revocation takes effect immediately: the next request with that key gets 401. Rotate revokes the key and issues a new one with the same name, scopes and expiry. The old secret stops working at once, and the new secret is shown once.
A key also stops working when its owner is deactivated or removed from the workspace.
Scopes #
A scope is a permission attached to a key or an OAuth grant. Each scope unlocks one or more tools, or one REST surface. The list below is the full set of scopes a key can carry today. Once a key carries a scope, the quickstart shows the first calls to make with it.
| Scope | What it unlocks |
|---|---|
statutes:read |
searchStatutes and getSection over MCP and REST. The family scope for the statute tools. |
searchStatutes |
searchStatutes only. Accepted for keys created before the family scope existed, and what the key form offers as Search Statutes. |
getSection |
getSection only. The same backward-compatible form, offered as Get Statute Section. |
citation:verify |
verifyCitation, checkSourceExists and scoreConfidence. |
benchmark:read |
compareLaws. |
gap:read |
identifyGaps and scoreAlignment. |
gap:issue |
issueGapReport, which issues a frozen gap report. Issuing is the billable act; see Usage and units. |
skills:read |
listSkills. |
skills:trigger |
triggerWorkflowRun. |
documents:write |
parseDocument. |
matters:read |
listMatters, getMatter, getMatterDocuments and getMatterConversations. Only Matters the key's owner belongs to. |
runs:read |
GET /api/v1/runs/{id}. REST only. |
webhooks:read |
List webhook endpoints and their deliveries. REST only. |
webhooks:write |
Register, rotate, delete and replay webhook deliveries. REST only. |
usage:read |
GET /api/v1/usage. REST only, for workspace owners and admins. |
Two rules make scopes safe to hand out:
- Default deny. A scope unlocks only the tools it names. A key with
citation:verifyand nothing else sees three tools, andtools/listshows only those. - Refusal is explicit. A call that needs a scope the token lacks returns
403with"code": "tool_not_enabled_for_key". The check runs on the server, so a client cannot widen its own access by asking.
The legacy scopes gazette:read and draft:generate are deprecated. Keys that carry them still validate, but they unlock nothing. See the changelog.
Matters need an extra step: a key sees a Matter only when its owner is a member of that Matter. Organisation-wide reads are not available through keys. Read Data egress before you enable matters:read, because Matters content leaves Mandatum when a client reads it.
OAuth 2.1 for connected apps #
Connected apps such as Claude.ai, ChatGPT, Cursor and Claude Code use OAuth 2.1 with PKCE. A person approves the app once in the browser. The app then holds an access token and a refresh token for that person and workspace.
The flow:
- The app sends the browser to
GET /api/oauth/authorizewithresponse_type=code, itsclient_id, aredirect_uri, acode_challenge(the S256 hash of a secret verifier it keeps), and thescopeit wants. - The person signs in to Mandatum if needed and approves the scopes.
- The browser returns to
redirect_uriwith a one-timecode. The code expires after 5 minutes. - The app calls
POST /api/oauth/tokenwithgrant_type=authorization_code, thecode, thecode_verifier, theredirect_uriand theclient_id. Mandatum checks the verifier against the challenge, then consumes the code. A code works once. - The response carries
access_token(lifetime 1 hour),refresh_tokenandscope.
When the access token expires, the app sends grant_type=refresh_token with its refresh token. The refresh token is valid for 30 days from the first approval, and rotation does not extend that window. After 30 days the person approves the app again. Both tokens are stored hashed, so Mandatum cannot show them again.
Apps that cannot register ahead of time can use dynamic client registration at POST /api/oauth/register. The discovery documents are at /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server.
Redirect addresses #
- A loopback address (
http://127.0.0.1orhttp://localhost, on any port) is always accepted. Desktop apps such as Cursor and Claude Code use this. - An
httpsaddress is accepted only when the operator has listed it. Hosted clients on the built-in list are accepted by default. - Anything else is refused with an error that names the rejected address.
Revoking a connected app #
Revoke a connected app in the workspace under Connected apps on the same integrations page, or call POST /api/oauth/revoke with the token. Either way, the access and refresh tokens stop working immediately. A connected app's last-used time is shown beside it.
What ends access #
| Event | Effect |
|---|---|
| A key is revoked or expires | Its next request gets 401. |
| A key's owner is deactivated or removed from the workspace | Their keys and OAuth tokens for that workspace stop working. |
| A connected app is revoked | Its access and refresh tokens stop working at once. |
| A scope is removed from a key | Calls that need it get 403 from the next request. |
For how tokens are stored and when each lifetime starts, read Token lifecycle. For how scopes map to tools, read Scopes and sharing.