authentication
Keys, kinds and scopes
Every request carries a key. The key decides which Site you are talking to, what you may do, and whether you are safe to embed in a browser.
Sending a key#
Authorization: Bearer <your key>Authorization: Bearer <key>.
Two kinds of key, distinguished by their prefix:
- `wv_pub_`, publishable. Safe in a browser or a mobile app. Read only, and it can only see content at
status: published. It cannot read drafts, cannot write, and cannot see keys, usage, media, the pipeline queue or webhooks. The operations it may reach are markedx-publishable: true; everything else is403 INSUFFICIENT_SCOPEfor a publishable key regardless of its scopes. - `wv_sk_`, secret. Server side only. Never ship one to a client, and never commit one. It can carry any scope.
A secret key's authority is its creator's live permissions intersected with the scopes it was given. Narrowing or removing that person's access narrows every key they created, on the next request. A key never outranks the person who made it.
Only a hash is stored. A lost key cannot be recovered, only rotated.
The two kinds#
publishable keys are prefixed wv_pub_ and are safe in client code: read only, limited to published content, and admitted only to the operations marked x-publishable: true. Anything else returns 403 INSUFFICIENT_SCOPE, whatever scopes the key carries.
secret keys are prefixed wv_sk_ and are server side only.
wv_pub_
Publishable
Safe in a browser bundle, a mobile app or a static site build. Read only, published content only, and admitted to 11 of the 50 operations. Everything else is a 403 whatever scopes it carries.
wv_sk_
Secret
Server side only. Can carry any scope, can read drafts, can write, can spend. Treat it like a database password: never in a repository, never in client code, never in a log line.
What a publishable key cannot do
returns 403curl https://api.writavo.com/v1/usage \
-H "Authorization: Bearer wv_pub_EXAMPLE000000000000000000000000000"GET /usage returns the organisation's credit balance and plan, so a browser-safe key is refused whatever scopes it carries.
What a publishable key may reach#
The list is deny by default. An operation is reachable by a publishable key only when it has been explicitly reasoned about, which is why three read scopes are not available to one at all.
- GET /ping
- GET /site
- GET /content-types
- GET /articles
- GET /articles/{id}
- GET /categories
- GET /categories/{id}
- GET /tags
- GET /tags/{id}
- GET /authors
- GET /authors/{id}
GET /usage is the instructive exception. It sits under meta:read, which a publishable key may hold, but it returns the organisation credit balance and plan. Scope alone was not a fine enough gate, so each operation states its own answer.Scopes#
What a key is permitted to do. A key carries a set of these, and every operation names the one it needs.
Scope is the outer bound, not the whole answer. A request must also pass the creator's live permissions, the plan entitlement, and, for a pipeline run, credits and the spend cap. A scope you hold can still be refused by the gate behind it.
A publishable key may only carry :read scopes, and only ever sees published content. Three of them are deliberately NOT available to a publishable key, because the endpoints behind them return things a browser bundle must not carry: media:read lists the whole library including assets attached only to drafts, pipeline:read returns the Site's unpublished editorial queue, and webhooks:read is account configuration.
meta:read IS publishable, but GET /usage within it is not: it returns the organisation's credit balance and plan, and a publishable key presenting it gets 403 INSUFFICIENT_SCOPE. Every operation states which side of that line it is on in x-publishable.
articles:readarticles:writetaxonomy:readtaxonomy:writeauthors:readauthors:writemedia:readmedia:writepipeline:readpipeline:runkeys:readkeys:writewebhooks:readwebhooks:writemeta:readExpiry, rotation and revocation#
- Shown once. Only a hash is stored. A lost key cannot be recovered, only rotated.
- Expiry is optional. Set
expires_atwhen you create a key and it stops working at that moment withAPI_KEY_EXPIRED, which is a cleaner failure than a key that lives for ever. - Rotation issues a new secret and returns it once. Update your integration, then let the old one go.
- Revocation is immediate on the next request, and answers
API_KEY_REVOKED. - `key_prefix` is the only displayable fragment. Use it to tell keys apart in your own logs and UI.
All four are operations on the keys resource, and all four are also available in the dashboard.
When authentication fails#
INVALID_API_KEY, API_KEY_REVOKED or API_KEY_EXPIRED. There is no key, or it is not usable. This response never distinguishes "no such key" from "wrong key", so a caller cannot probe for valid keys.
INSUFFICIENT_SCOPE. The key is valid but does not carry the scope this operation requires, or its creator's permissions no longer cover it.
This is the only 403 in the API. In particular it is never returned for an object belonging to another Site: that case is always 404, so this response never reveals that something exists.