---
title: "Authentication"
description: "How to authenticate with an API key or OAuth, what read and write scopes allow, and how to manage keys."
canonical: https://docs.rumoro.dev/authentication
markdown: https://docs.rumoro.dev/authentication.mdx
---

# Authentication

How to authenticate with an API key or OAuth, what read and write scopes allow, and how to manage keys.

Each request carries a Bearer credential for one workspace, an **API key** or an **OAuth token** from an MCP sign-in. Both work on REST and the [MCP server](/mcp).

```ts tab="TypeScript"
import { createRumoro } from '@rumoro-dev/sdk';

const rumoro = createRumoro({ apiKey: process.env.RUMORO_API_KEY! });
await rumoro.listKeywords();
```

```python tab="Python"
import os
from rumoro import Rumoro

rumoro = Rumoro(api_key=os.environ["RUMORO_API_KEY"])
rumoro.keywords.list()
```

```bash tab="curl"
curl https://api.rumoro.dev/v1/keywords -H "Authorization: Bearer $RUMORO_API_KEY"
```

`GET /v1/health`, `GET /v1/openapi.json` and MCP's tool list need no credential. MCP tool calls do.

`GET /v1/whoami` shows the workspace, the credential type (`auth.kind`), its `scope`, and a key's id and expiry. Call it first to catch a wrong workspace or read-only key.

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

For OAuth, `user` is the person.

## API keys

- Keys start with `ref_`, belong to one workspace and are shown once. Only a SHA-256 hash is stored.
- `expiresAt` (ISO 8601 or epoch milliseconds) turns a key off at that time. It stays listed until revoked.
- A workspace can have 100 keys that aren't revoked (`409 key_limit`).

## Scopes

`scope` is `read` or `write` (the default). A `read` key can only `GET`, otherwise it gets `403 read_only_key`. Over MCP it sees only read tools.

## OAuth sign-in

MCP clients with OAuth only need `https://mcp.rumoro.dev/mcp`. The person signs in, picks a workspace and allows read or write access.

- Access tokens last an hour, refresh tokens 30 days.
- A token acts as the person, within their role. Owners and admins manage the team, and only owners manage keys.
- Tokens and keys share the workspace [rate limit](/rate-limits).

## Managing keys

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/v1/api-keys` | Creates a key, shown only here |
| `GET` | `/v1/api-keys` | Lists keys without secrets |
| `DELETE` | `/v1/api-keys/{id}` | Revokes a key |

```ts tab="TypeScript"
const { data: key } = await rumoro.createApiKey({
  body: { name: 'nightly-report', scope: 'read', expiresAt: '2027-01-31T00:00:00Z' },
});
```

```python tab="Python"
key = rumoro.api_keys.create(name="nightly-report", scope="read", expiresAt="2027-01-31T00:00:00Z")
```

```bash tab="curl"
curl -X POST https://api.rumoro.dev/v1/api-keys \
  -H "Authorization: Bearer $RUMORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "nightly-report", "scope": "read", "expiresAt": "2027-01-31T00:00:00Z" }'
```

```json
{ "id": "key_...", "name": "nightly-report", "prefix": "ref_5d1e09a2", "scope": "read", "createdAt": "2026-10-04T08:30:12.104Z", "lastUsedAt": null, "expiresAt": "2027-01-31T00:00:00.000Z", "key": "ref_5d1e09a2-..." }
```

`lastUsedAt` updates at most once a minute. See the [reference](/api/api-keys/create-api-key).

## When authentication fails

A missing or invalid credential gets `401` with the [error envelope](/errors). Its `WWW-Authenticate` header sends OAuth clients to sign-in.

```json
{ "error": { "code": "unauthorized", "message": "Invalid API key or access token", "requestId": "..." } }
```

Without a credential the message is "Missing or invalid credentials". MCP answers `{ "error": "<message>" }`.
