> **Shapeless documentation index**
> Fetch https://shapelessai.com/llms.txt to discover every page before exploring further.
> Every page also answers Markdown: append `.md` to its path, or send `Accept: text/markdown`.

# Authentication

API keys and their three scopes, OAuth for the MCP server, rate limits, and what every refusal code means.

*Verified 2026-09-14. Source: https://shapelessai.com/docs/auth*

Two credentials reach the same account. A person in a browser rides a signed session cookie. An
agent rides either an **API key** (the API and the CLI) or an **OAuth token** (the hosted MCP
server). Both are judged by one scope policy.

## API keys

Mint one under [Settings, API keys](https://shapelessai.com/studio/api-keys). Name it, pick its
scopes, copy it once: only its sha256 is stored, so we cannot show it again. Revoking it in the
same screen kills it on the next request.

A key is `slk_` followed by 40 base64url characters. Send it on every request:

```bash
curl -H "Authorization: Bearer slk_..." https://shapelessai.com/api/me
```

```bash
shapeless login                    # paste it; we verify it and store it 0600
export SHAPELESS_API_KEY=slk_...   # beats the stored key - the shape for CI
```

## Scopes

A key holds any subset of three. A cookie session is the human at the keyboard, so it implicitly
holds all three.

| Scope | What it opens |
| --- | --- |
| `read` | Every GET: posts, agents, runs, analytics, Brand Memory, media, identity. |
| `write` | Drafting and editing: chat turns, files, assets, agents, queue slots, dismissals. |
| `publish` | Anything that puts content out or marks it out: creating a post, approving, publishing, waking an agent, sending an engagement action. |

The line that matters is `write` against `publish`. A `write` key can draft all day and change
nothing the world sees. Only `publish` puts something on a real account.

Give a key the least it needs. A read-only key handed to a reporting script that leaks is a leak,
not a foothold.

## OAuth, for MCP

The hosted MCP server at `https://shapelessai.com/mcp` takes no key. It is OAuth 2.1 with dynamic
client registration and PKCE, so a host registers itself:

- Protected-resource metadata: `https://shapelessai.com/.well-known/oauth-protected-resource/mcp`
- Scopes: `read`, `write`, `publish`, `offline_access`
- Transport: streamable HTTP

An unauthenticated call answers `401` with a `WWW-Authenticate: Bearer resource_metadata="..."`
header pointing at that document, which is the handshake every MCP client keys off. Tokens are
revocable by the human under Settings, API keys, Connected apps. Setup per host is on
[MCP server](/docs/mcp).

## Rate limit

**600 requests an hour per key.** Requests that fail the scope check still count. Cookie sessions
are not limited by this.

Over the limit is a `429`. Back off; do not spin. Polling a job every second for ten minutes is
600 requests, which is the whole hour - use `jobs_tail` (or `GET /api/studio/tail`), which holds
one connection open instead.

## Refusals

| Status | Meaning |
| --- | --- |
| `400` | The body is wrong, and the message says how. |
| `401` | Unknown or revoked key, or an expired OAuth token. |
| `402` | Out of allowance: the Free plan's ten posts for that UTC day (`code: "free_daily_cap"`, with the `day` and its `resetsAt`) or an empty credit wallet (`code: "trial_exhausted"`). |
| `403` | The credential is missing the scope this route needs. |
| `409` | The request contradicts the row's current state. |
| `422` | A platform rule refused the content. The message names the rule. |
| `429` | Over the rate limit. |
| `503` | The service has no database. |

## What a key deliberately cannot do

These refuse a bearer key. It is not an oversight:

- **Key management** (`/api/keys`) - a key must never mint itself a wider key.
- **Money** (`/api/checkout`, `/api/portal`) - buying and cancelling belong to the person paying.
- **Sign-in and platform connections** - browser redirect flows carrying platform grants. Only
  `GET /api/connections`, the identity list, takes a key.
- **Webhooks and internal cron** - they carry their own signatures.

The full list is on [API reference](/docs/api#cookie-only-on-purpose).
