---
title: "Authentication"
description: "Bearer API keys, scopes, and key management."
canonical: https://docs.mentio.dev/authentication
markdown: https://docs.mentio.dev/authentication.mdx
---

# Authentication

Bearer API keys, scopes, and key management.

Every request to the API (and to the [MCP server](/mcp)) is authenticated with a Bearer API key scoped to your workspace:

```bash
curl -H "Authorization: Bearer mk_live_..." "$MENTIONS_API_URL/v1/keywords"
```

Only two endpoints are public: `GET /v1/health` and the OpenAPI spec at `GET /v1/openapi.json`.

`GET /v1/whoami` introspects whatever credential you send: the workspace it acts on, how it authenticated (`api_key`, an `oauth` token from an [MCP sign-in](/mcp), or the dashboard `session`), its `scope`, and for a key its id and expiry. Run it first in a script: a `read` key fails every write with `403 read_only_key`, and the wrong workspace is the classic mistake.

```json
{
  "workspace": { "id": "org_...", "name": "Acme" },
  "auth": { "kind": "api_key", "scope": "write", "apiKeyId": "key_...", "expiresAt": null },
  "user": null
}
```

## Keys

* Keys look like `mk_live_...` and are shown once at creation. Store them immediately; they cannot be retrieved later.
* The API stores only a SHA-256 hash of the key. Lookups are cached for performance.
* A key belongs to one workspace; all keywords, mentions, and settings it touches are scoped to that workspace.
* A key can carry an expiry (`expiresAt` at creation): it stops working at that instant and stays listed until revoked. The right shape for a contractor or a one-off script.

## Scopes

A key is `read` or `write` (`scope` at creation, `write` by default). A `read` key can only make `GET` requests; anything else is refused with `403 read_only_key`. On the MCP server a `read` key exposes the read tools only.

## Managing keys

| Method   | Path                | Description                                                |
| -------- | ------------------- | ---------------------------------------------------------- |
| `POST`   | `/v1/api-keys`      | Create a key (the plaintext key is only in this response)  |
| `GET`    | `/v1/api-keys`      | List keys (prefix and metadata only, never the key itself) |
| `DELETE` | `/v1/api-keys/{id}` | Revoke a key                                               |

```bash
curl -X POST "$MENTIONS_API_URL/v1/api-keys" \
  -H "Authorization: Bearer $MENTIONS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "ci", "scope": "read", "expiresAt": "2026-12-31T00:00:00Z" }'
```

```json
{
  "id": "key_...",
  "name": "ci",
  "prefix": "mk_live_a1b2",
  "scope": "read",
  "createdAt": "2026-09-03T10:04:44.881Z",
  "lastUsedAt": null,
  "expiresAt": "2026-12-31T00:00:00.000Z",
  "key": "mk_live_a1b2..."
}
```

`lastUsedAt` updates whenever the key authenticates a request. See the [API Keys reference](/api/api-keys/create-api-key) for schemas.

## Failed authentication

Missing or invalid keys return `401` with the standard [error envelope](/errors):

```json
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key"
  }
}
```
