Docs
API keys & roles
Create scoped anymd API keys, pick presets, and understand how roles cap what every key and OAuth grant can do.
Every call to anymd runs as a principal with a set of scopes. Scopes come from your role, and each API key or OAuth grant can only narrow them, never widen them.
API keys
- Format:
amd_followed by a random string. Send it asAuthorization: Bearer amd_…orX-API-Key: amd_…. - The full key is shown once, at creation. anymd stores only a hash; if you lose a key, revoke it and make a new one.
- Keys can expire (
expires_in_days) and can be revoked at any time. Unknown, revoked or expired keys get401 invalid_api_key. - Create and revoke them in Dashboard → API keys, or through the API:
curl https://anymd.cc/api/v1/keys \
-H "Authorization: Bearer $ANYMD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"research-agent","preset":"library","expires_in_days":30}'
Body: { name, preset?, scopes?, expires_in_days? }. Use a preset, or pass an explicit scopes array.
Presets
| Preset | Label | Scopes |
|---|---|---|
convert-only |
Convert only | convert |
library |
Convert + library (agents) | convert, library:read, library:write |
read-only |
Read-only | library:read, usage:read |
content-editor |
Content editor (AI CMS) | content:read, content:write, content:publish, pages:read, pages:write, pages:publish |
full |
Everything my role allows | all scopes, capped by your role |
Rule of thumb: give each integration its own key with the smallest preset that works. A CI job that only converts gets convert-only; an assistant that remembers what it reads gets library.
Scopes
| Scope | Allows |
|---|---|
convert |
Convert URLs and files |
library:read |
List, read and search your library |
library:write |
Save conversions, tag and delete documents |
usage:read |
Usage, credits and traces |
keys:manage |
Create, list and revoke your API keys |
content:read · content:write · content:publish |
Blog posts: read drafts, edit, publish |
pages:read · pages:write · pages:publish |
Landing pages: read, edit, publish |
settings:write |
Site settings |
users:read · users:write |
See users; change roles and plans |
A request missing a scope gets 403 forbidden with the scopes it needed in required. The endpoint-to-scope map is in the REST API reference; MCP tools are hidden entirely when you lack their scope.
Roles
Roles are templates. Your account has exactly one.
| Role | For | Scopes |
|---|---|---|
user |
Customers (default) | convert, library:read, library:write, usage:read, keys:manage |
viewer |
Read drafts and analytics | convert, library:read, usage:read, content:read, pages:read |
author |
Draft posts and pages; an editor publishes | user scopes + content:read, content:write, pages:read, pages:write |
editor |
Write and publish | author scopes + content:publish, pages:publish |
admin |
Run content, pages and settings | editor scopes + settings:write, users:read |
owner |
Full control | every scope, including users:write |
Only an owner can change another user's role (users:write). GET /api/v1/admin/roles returns the live role templates and presets.
How capping works
Effective scopes = the key's (or grant's) scopes ∩ the owner's current role.
- A key created with
fullby aneditorgets editor scopes, not owner scopes. - If a user is demoted, their existing keys lose the extra power on the very next request. No need to rotate.
- A key with no explicit scopes gets everything the role allows.
Check what a key can actually do right now:
curl https://anymd.cc/api/v1/me -H "Authorization: Bearer $ANYMD_API_KEY"
# { "id": "…", "email": "…", "name": "…", "role": "user", "plan": "free", "scopes": ["convert", …] }
OAuth
MCP clients can skip keys and sign in with OAuth 2.1 (Dynamic Client Registration + PKCE). The grant is capped by your role the same way. Setup: MCP server.
Good hygiene
- One key per integration, named after it. Revoke it when the integration goes away.
- Set
expires_in_daysfor anything temporary. - Keep keys in environment variables or a secret manager, never in source control or client-side code.
- Browser sessions can't be used cross-site: cookie-authenticated writes must come from anymd.cc itself.
Updated 2026-09-26 · Edit on GitHub