Authentication
There are two ways in. An MCP client that signs in with OAuth - Codex, Claude, and any client that supports it - mints its own credential when you connect it, with nothing to paste and no key on disk. Everything else, including a script or server calling /v1 directly, authenticates with an API key sent as a bearer token. A dashboard session token will not work here, and a key will not work on the dashboard's own endpoints; the two are deliberately separate.
On this page
| You are connecting | Use |
|---|---|
| A script, a server or a scheduled job calling /v1 | An API key, sent as a Bearer header |
| Codex, Claude Code or another coding assistant | OAuth - one command, then sign in |
| Claude.ai, ChatGPT or Claude Desktop | OAuth - add the address, then sign in with your Mastheads login |
| An MCP client that does not support OAuth | An API key in its config |
Signing in with OAuth
For OpenAI Codex, one command does the whole thing:
codex mcp add mastheads --url https://api.mastheads.app/v1/mcpCodex detects OAuth from the address, opens the consent page, and stores the token itself. The same address works with claude mcp add --transport http for Claude Code. Claude.ai, ChatGPT and Claude Desktop connect the same way, without a command line - point them at https://api.mastheads.app/v1/mcp and sign in with your Mastheads login. Disconnect any of them at any time in Settings → Account → Developer; see Full access & MCP for what each one can then do.
Do not put a key in Codex's bearer_token_env_var field: it takes the name of an environment variable, not the key itself, so a pasted key makes Codex look up a variable that does not exist, send no Authorization header, and drop the server from its tool list with no error shown.
Getting a key
The rest of this page is the API-key path, for scripts, servers, and any client without OAuth support.
Mint one in the dashboard under Settings → Account → Developer. Keys start with mh_live_ and you can hold up to 3 active at a time. Revoke one to make room for another.
You also choose what the key may do: Read only, the default, or Full access, which can also start work. That choice is fixed for the life of the key - there is no switch, so a spending key is a new key. See Full access & MCP.
The key is shown once, at the moment you create it. We store only a hash of it, so we cannot show it to you again and cannot recover it for you. Copy it straight into your secret store. Lost it? Revoke it and mint another.
Sending the key
Send it as an Authorization: Bearer header. An X-Api-Key header works too, if that fits your tooling better.
curl https://api.mastheads.app/v1/editions \
-H "Authorization: Bearer mh_live_your_key_here"Send only one. If the Authorization header starts with Bearer , that is the credential we check and X-Api-Key is never read - so a stale Bearer alongside a good X-Api-Key is a 401, not a fallback.
Plan access
The content API is on Growth and Pro. Minting a key on a plan without it is refused in the database rather than by the interface, so there is no version of the screen that lets one through.
The plan is checked again on the request, not only when the key was made. If the plan lapses or is downgraded, a key that is still valid answers 402 plan_required from then on, and a closed account answers 401 account_closed. Both are immediate; there is no grace window to plan around.
Key lifecycle
Keys are server-side secrets. Never ship one in browser code, in a mobile app, or in a public repository: it can read the articles on your account, and a full-access key can spend from your allowance. Reading covers the image endpoint too, so an image URL from the API cannot be used as a public <img src>.
Revoke a key in Settings at any time. Revocation is checked on every request, so it takes effect immediately with no propagation delay and nothing to purge. The dashboard also shows when each key was last used, stamped at most once a minute so a busy puller does not pay for the bookkeeping.
Keys do not expire on a timer and there is no rotation deadline. If you want rotation, mint the new key first, move your integration over, then revoke the old one - you have three slots so the two can overlap.
When a key is rejected
A missing, malformed, unknown or revoked key is a 401 with the code invalid_key. The message is deliberately the same for all four, so a probe cannot tell which one it hit.
{
"error": {
"code": "invalid_key",
"message": "Missing, invalid, or revoked API key"
}
}Repeated failures from one address are throttled at 30 per minute and answer 429. If you are seeing that while testing, you are being counted with everyone else behind your address - wait a minute rather than retrying in a loop. The full table is on Rate limits & errors.