Skip to content
anymd

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 as Authorization: Bearer amd_… or X-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 get 401 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
admin-read-only Admin read-only (monitoring agent) settings:read, optouts:read, users:read, credits:read, billing:read, audit:read, system:read, content:read, pages:read
support Support agent users:read, users:sessions:write, users:credentials:write, credits:read, credits:write, billing:read, audit:read
site-ops Site operations settings:read, settings:write, optouts:read, optouts:write, audit:read, system:read
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. The three admin presets are capped by role like every other key: an admin who picks support gets no credits:write, because only owners hold it.

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; lets a key or OAuth client change or reset your saved deep reading defaults (signed-in sessions of every role, including viewer, can always change their own)
content:read · content:write · content:publish Blog posts: read drafts, edit, publish
pages:read · pages:write · pages:publish Landing pages: read, edit, publish
settings:read · settings:write Read and change site settings (settings:write also reads)
optouts:read · optouts:write Read and change site opt-outs (domains anymd must not read)
users:read Every user account, its support context and credential inventory (never secrets)
users:roles:write Change user roles. Older keys holding users:write keep working: it means this scope
users:sessions:write Sign users out; suspend and reactivate accounts
users:credentials:write Revoke another user's API keys and OAuth grants
credits:read · credits:write Read credit grants; grant and revoke them
billing:read Subscriptions, plan consistency and billing diagnostics (read-only)
billing:write Reserved for future billing actions; no endpoint or tool uses it
audit:read Read and export the audit log
system:read System-wide overview, usage, errors and traces

Everything below keys:manage and outside content and pages is elevated: it touches other people's accounts or the whole deployment.

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, settings and opt-outs; support lower-ranked users editor scopes + every :read admin scope, settings:write, optouts:write, users:sessions:write, users:credentials:write
owner Full control every scope, including users:roles:write, credits:write and billing:write

Owner vs admin. Changing roles and granting or revoking credits are owner-only. Admins can read everything operational and act on accounts ranked below their own: sign them out, suspend them, revoke their keys and grants. Nobody can change their own account through admin tools. Reading is not ranked: an admin with users:read can view any account's support context, including an owner's API key and OAuth grant metadata (key name, prefix, scopes, created, last used, expiry and revocation; grant client and scopes). That view is read-only and never includes key secrets, token hashes or session tokens, so admins can audit who holds which access without being able to use or change it. GET /api/v1/admin/roles returns the live role templates and presets.

Plans follow billing. A user's plan comes from the billing provider and cannot be edited by hand. To give someone more allowance for support or a promotion, grant credits instead: an active grant raises their allowance. By default a grant expires at the end of the current month (UTC), or at the end of next month when fewer than 7 days of this month remain; a grant without recurring is a one-time pool: its credits are spent once over its whole lifetime, so whatever one month uses is gone the next (5,000 granted on the 26th with 3,000 used that month leaves at most 2,000 for next month). Pass recurring: true (with or without expiresAt) for credits that renew in full every month; an expiresAt later than the default without recurring: true is 422. On Pro and Scale, credits are used in this order: the plan's included credits, then grants, then paid overage. A grant covers usage from the moment it is granted onward and is not a refund: overage reported before it stays billed. Example: on a plan with 10,000 included credits, a 5,000-credit grant made when the account had used 12,000 this period keeps the next 5,000 credits (12,000 to 17,000) off the overage bill; the 2,000 used before the grant stay billed. Positions are counted within the current period of a monthly subscription, or the calendar month (UTC) otherwise.

How capping works

Effective scopes = the key's (or grant's) scopes ∩ the owner's current role.

  • A key created with full by an editor gets 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 created with no preset and no scopes gets the scopes of the credential that created it.
  • A suspended account's keys and OAuth grants stop working until it is reactivated.

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). Setup: MCP server.

  • Least privilege by default. A client that asks for no scopes gets convert, library:read and library:write: it can read the web and save to and search your own library, never everything your role allows. The MCP endpoint advertises only these three scopes in its 401 challenge and protected-resource metadata, so spec-following clients start there.
  • Elevated scopes are explicit. A client must request admin, content, pages or keys:manage scopes by name, and the consent screen lists them separately as elevated so you see exactly what you approve. Scopes your role lacks are dropped and shown as unavailable.
  • No keys from connected apps. An OAuth connection can list and revoke your keys (with keys:manage) but never create one (403 oauth_key_creation_forbidden): a key would outlive the connection. Create keys in the dashboard.
  • Capped on every request. The grant is intersected with your current role each time, so a demotion takes effect immediately. A token the client downscoped at refresh keeps only its own scopes.
  • Older grants. Grants approved before this consent model keep their non-admin scopes but lose admin scopes. One stored with no scopes (it meant "everything my role allows") now gets your role's non-elevated scopes (convert, library:read, library:write, usage:read where your role has them). Reconnect the client to grant more explicitly.

An admin agent with least privilege

  1. Decide what the agent does. Monitoring needs only reads; a support agent needs users:sessions:write; a site operator needs settings:write and optouts:write.
  2. Create a dedicated key with the matching preset (admin-read-only, support or site-ops) and an expiry, or connect over OAuth and request just those scopes.
  3. Check it with GET /api/v1/me, then connect it to the MCP server.
  4. Review what it did in the audit log (list_audit_events, or GET /api/v1/admin/audit): every change records the tool or route, the key or OAuth client, and the request id.

Good hygiene

  • One key per integration, named after it. Revoke it when the integration goes away.
  • Never give an agent full when a preset fits. Admin agents especially should not hold owner-only scopes unless they need them.
  • Set expires_in_days for 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-10-09 · Edit on GitHub

# Markdown
Open .md