Authentication
Every request authenticates with a per-site API key sent as an HTTP Bearer token.
Authorization: Bearer ck_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTwo classes of key
Section titled “Two classes of key”| Class | Prefix | Can write | Where it belongs |
|---|---|---|---|
| Secret | ck_ |
yes | Server-side only. Never in a browser. |
| Public read | pk_ |
no | In the HTML of a page that embeds the widget. |
A pk_ key is read-only, bound to one site, works only from the origins its
owner registered, and may travel as ?key=pk_… so a browser can skip the CORS
preflight. It reaches only the two public read routes. A ck_ key is a secret:
the read routes answer it but never return CORS headers for it, so a browser
cannot use one even if you try, and passing it as ?key= is a deliberate 400.
Full comparison, the origin allowlist rules and the per-key restrictions: Keys, scopes & restrictions.
Key format
Section titled “Key format”A key is a class prefix (ck_ or pk_) followed by 32 base62 characters
(≈ 190 bits of entropy — the same entropy in both classes; “public” means
visible, not guessable). Keys are opaque — do not parse or split them.
Canverly stores only a SHA-256 hash of the secret and reserves the right to
change the encoding.
A single key is bound to exactly one site. The site (site_id) is derived from the key on every request, so a key can only ever read or write its own site — there is no way to address another site by putting an id in the URL or body.
Where keys come from
Section titled “Where keys come from”Generate a key in the admin under Configurações → Integrações API:
- Open the site, go to Configurações → Integrações API.
- Click Gerar chave, give it a recognizable name (e.g.
zapier-blog,ci-publisher) and pick the scopes it needs. - The full
ck_…secret is shown once in a modal with a Copy button. Store it in your secret manager immediately — it is never retrievable again.
After closing the modal only the key’s name, scopes and last_used_at are visible. If you lose a secret, revoke it and create a new one.
Scopes
Section titled “Scopes”A key carries one or more scopes, and scopes are opt-in: a key gets exactly what was asked for at creation and nothing by default. Request only what the integration needs.
| Scope | Grants |
|---|---|
posts:write |
Create and update posts |
posts:read |
Read the published archive (listing + detail) |
post_types:read |
Discover valid post_type slugs |
sites:read |
Resolve the site’s id, slug, domain and default language |
site:read / site:write |
Read / update the site’s settings |
seo:read / seo:write |
Read / update post-level and site-level SEO |
media:read / media:write |
List and read / upload and delete media assets |
analytics:read |
Site views and visitor numbers |
leads:read |
Form submissions — personal data, audited, own rate limit |
export |
Bulk export (also needs leads:read / analytics:read for those resources) |
import |
Bulk import (also needs posts:write) |
sites:read and site:read are different scopes and the singular/plural is not
a typo — see the full table with the routes each one unlocks.
A request to an endpoint whose scope the key lacks returns 403:
{ "error": { "code": "forbidden", "message": "scope `posts:write` not granted" } }Per-key restrictions
Section titled “Per-key restrictions”Independently of scopes, the owner can pin a key to an IP allowlist, a
country allowlist (GeoIP, fail-closed), a time window in a chosen
zone, and — for pk_ keys — a list of allowed origins. They are checked on
every request, before the rate-limit bucket is charged, and a violation is a
403 naming the dimension without ever revealing the list:
{ "error": { "code": "forbidden", "message": "request blocked by this key's region restriction" } }See Keys, scopes & restrictions.
Error envelope
Section titled “Error envelope”The API returns a compact JSON error envelope (not RFC-7807):
{ "error": { "code": "unauthorized", "message": "missing Authorization header" } }| Situation | Status | error.code |
|---|---|---|
No / non-Bearer / malformed Authorization |
401 |
unauthorized |
| Valid scheme, unknown or revoked key | 401 |
unauthorized |
A pk_ key on a route that accepts only ck_ |
401 |
unauthorized |
| Key lacks the required scope | 403 |
forbidden |
| Request blocked by the key’s IP / region / time restriction | 403 |
forbidden |
A pk_ key used from an unregistered origin |
403 |
forbidden |
| Over the key’s rate limit | 429 |
rate_limited |
See Errors & rate limits for the full table.
Revoking & rotating
Section titled “Revoking & rotating”Revoke a key from Configurações → Integrações API (trash icon). Revocation is immediate — the next request with that key returns 401. There is no soft-delete and no “show again”.
To rotate without downtime:
- Generate a new key with the same scopes.
- Switch the integration to the new key.
- Once you confirm it works, revoke the old key.
Hygiene
Section titled “Hygiene”- Never commit a key to a repo or ship it in client-side code (a key is a server-side secret). If one leaks, revoke it immediately.
- No API key of any class can mint, list or revoke another key — key management is a site-owner session in the admin. An integration cannot escalate its own permissions.
- Treat
ck_…like a password: store it in a secret manager / environment variable. - Prefer one key per consumer so blast radius on a leak is a single integration.