Authentication
Bearer API keys, the scope model, job tokens, and how to rotate a key without visiting the dashboard.
Every authenticated endpoint takes an API key in the Authorization header:
Authorization: Bearer wm_your_api_key_hereA key is the literal prefix wm_ followed by 40 hex characters. A Bearer token is the only
auth for direct API calls — there is no cookie session for any API operation.
GET /health and GET /pricing are public and need no auth.
No key? You may not need one.
An agent with a crypto wallet can pay per request instead of holding a key. See x402 payments.
Getting a key
Register at wellmarked.io. The raw key is returned once, in the registration response body.
Persist it immediately. If it is lost, POST /keys/rotate with the current key issues a new
one; signing in to the dashboard is the last resort, not the intended path.
Scopes
A key carries a list of scopes. A request is authorized only if the key holds the scope its
route requires, or holds the * wildcard. Default account keys hold *.
| Scope | Authorizes |
|---|---|
extract | POST /extract |
bulk | POST /bulk, GET /bulk/{job_id} |
crawl | POST /crawl, GET /crawl/{job_id} |
search | POST /search |
keys | /keys/* and /webhook/rotate |
Routes not listed require no particular scope, only a valid key — GET /usage, GET /logs,
and GET /jobs/{job_id}.
Two of those deserve an explanation:
/jobs/{job_id}is deliberately unscoped. It resolves a job id without knowing whether it came from bulk or crawl, so gating it on either would reintroduce the exact 403 it exists to avoid — a crawl-scoped key unable to poll its own job. Access is not widened: the route enforces the same ownership check, job ids are unguessable UUIDs, and the response is read-only./webhook/rotatesits behindkeys, not "any valid key". It mints the HMAC secret every delivery is signed with and invalidates the previous one instantly. That is credential management. Without the gate, an extract-only key or a connector token could silently break every signature check you have deployed.
Requesting a route your key isn't scoped for returns 403 insufficient_scope. Retrying will
not help — the fix is a broader key, which a developer mints. It is not a plan limit, so
the response carries no upgrade_url.
A key can only grant scopes the calling key already holds.
Job tokens
x402 submissions to /bulk and /crawl return a job_token alongside job_id. Send it as
X-Job-Token on GET /bulk/{job_id} or GET /crawl/{job_id} to poll a job you already paid
for, with no key and no second payment.
curl https://api.wellmarked.io/bulk/9aaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee \
-H "X-Job-Token: jt_..."Rotating a key
curl -X POST https://api.wellmarked.io/keys/rotate \
-H "Authorization: Bearer wm_your_current_key"Rotation is instant and irreversible
The old key is dead the moment the rotate response arrives. Switch to the new key before making
any further request. Both official SDKs swap the client's key automatically when you call
rotate_key() / rotateKey().
OAuth (remote MCP only)
One OAuth 2.1 authorization server exists — /.well-known/oauth-authorization-server,
/oauth/authorize, /oauth/token, plus RFC 7591 dynamic client registration. It mints scoped,
expiring wm_ access tokens for MCP hosts such as Claude.ai, so an agent host can onboard a
user through a browser instead of a pasted key.
Tokens issued this way never include the keys scope, by construction.
Direct API callers never touch this flow. See the MCP server.
API Documentation
Full reference for the WellMarked URL-to-Markdown API — extract, bulk, crawl, search, usage, keys, and signed webhooks, with Python, JavaScript, TypeScript, and curl examples.
x402 payments
Keyless, pay-per-request access over USDC on Base — for autonomous agents with a wallet and no human to hold an API key.