Chaves, escopos e restrições
Existem duas classes de chave de API, uma lista fixa de escopos e quatro restrições opcionais que o dono de uma chave pode anexar a ela. As três coisas são definidas quando a chave é criada, no admin, em Configurações → Integrações API.
Duas classes de chave
Seção intitulada “Duas classes de chave”| Chave secreta | Chave de leitura pública | |
|---|---|---|
| Prefixo | ck_ |
pk_ |
| Onde fica | Só no servidor — variável de ambiente, gerenciador de segredos | No HTML da página que incorpora um widget |
| Pode escrever | Sim | Não, nunca |
| Enviada como | Authorization: Bearer ck_… |
Authorization: Bearer pk_… ou ?key=pk_… |
| Utilizável a partir do navegador | Não — as rotas de leitura propositalmente não devolvem cabeçalhos CORS para uma chave ck_ |
Sim, só a partir de origens registradas |
| Allowlist de origens | Não se aplica (rejeitada na criação) | Obrigatória, de 1 a 20 origens |
As duas são o prefixo seguido de 32 caracteres base62 (≈190 bits de entropia) e as duas são exibidas uma única vez, na criação. A Canverly armazena apenas um hash SHA-256. As chaves são opacas: não as analise nem as divida.
O que uma chave pk_ alcança de fato
Seção intitulada “O que uma chave pk_ alcança de fato”Uma chave de leitura pública só pode ser criada com escopos de leitura: pedir
posts:write nela é um 400 na criação. Na prática, a superfície que ela alcança
são as duas rotas públicas de leitura:
Todas as outras rotas autenticam apenas tokens ck_ e respondem 401 a uma chave pk_,
mesmo quando a chave carrega o escopo correspondente. Considere post_types:read e
sites:read numa chave pública como inertes hoje.
Escopos
Seção intitulada “Escopos”Os escopos são opt-in por chave: uma chave recebe exatamente o que foi pedido na criação, e nada por padrão. Estes são os catorze que a API reconhece; qualquer outro é rejeitado quando a chave é emitida.
| Escopo | Concede |
|---|---|
posts:write |
POST /v1/posts, PATCH /v1/posts/{id} |
posts:read |
GET /v1/posts, GET /v1/posts/{reference} |
post_types:read |
GET /v1/post-types |
sites:read |
GET /v1/sites/me |
site:read |
GET /v1/sites/me/settings |
site:write |
PATCH /v1/sites/me/settings |
seo:read |
GET /v1/posts/{reference}/seo, GET /v1/sites/me/seo |
seo:write |
PATCH /v1/posts/{reference}/seo, PATCH /v1/sites/me/seo |
media:read |
GET /v1/media, GET /v1/media/{id} |
media:write |
POST /v1/media, DELETE /v1/media/{id} |
analytics:read |
GET /v1/analytics/summary, GET /v1/analytics/posts |
leads:read |
GET /v1/leads, GET /v1/leads/{id} |
export |
GET /v1/export |
import |
POST /v1/import |
Pontos que costumam pegar as pessoas de surpresa:
sites:read≠site:read. O plural é a chamada de identidade; o singular é o bloco de configurações. A diferença não é erro de digitação.- Leitura e escrita são sempre separadas. Uma chave com
seo:readnunca consegue escrever SEO. Isso vale por par, de propósito. - Algumas rotas exigem dois escopos.
POST /v1/importexigeimporteposts:write.GET /v1/export?resource=leadsexigeexporteleads:read;?resource=analyticsexigeexporteanalytics:read. leads:readsão dados pessoais. Nunca é concedido por padrão, toda leitura é gravada na trilha de auditoria com a chave como autora, e ele tem um bucket próprio de 10 req/min além do limite geral.
Uma requisição a uma rota cujo escopo a chave não tem é um 403 que nomeia o escopo:
{ "error": { "code": "forbidden", "message": "scope `posts:write` not granted" } }Restrições
Seção intitulada “Restrições”Além dos escopos, uma chave pode carregar até quatro restrições. Elas são avaliadas
por requisição, em chaves ck_ e pk_, em todo o /v1, e antes de
o bucket do limite de requisição ser cobrado, então uma requisição bloqueada não consome cota.
Uma chave sem restrições pula a verificação inteira.
Uma dimensão vazia não restringe. Uma dimensão que está definida e não pode ser avaliada nega: uma restrição que falha aberta é uma restrição que não existe justamente no momento em que mais importa.
| Restrição | Aceita | Máximo de entradas |
|---|---|---|
| Allowlist de IP | Endereços exatos ou faixas CIDR — 203.0.113.4, 2001:db8::/32 |
64 |
| Allowlist de região | Códigos de país ISO 3166-1 alpha-2, em maiúsculas — BR, US |
250 |
| Fuso horário | Um fuso IANA — America/Sao_Paulo |
1 |
| Janelas de horário | Conjunto de dias da semana + intervalo HH:MM–HH:MM, interpretado nesse fuso |
32 |
- IP: se o endereço do cliente não puder ser determinado, uma allowlist de IP ativa nega.
- Região: resolvida por GeoIP a partir do endereço do cliente. Um país indeterminado (endereço privado, faixa desconhecida, falha na consulta) nega quando há uma allowlist de região definida. Isso nunca é lido como “sem restrição”.
- Janelas de horário: a requisição passa se cair dentro de qualquer janela. Os dias
usam as formas de três letras minúsculas
mon…sun;endprecisa ser estritamente posterior astart. As janelas precisam de um fuso horário para significar algo: sem ele, elas não restringem.
Uma requisição bloqueada é um 403 que nomeia a dimensão e nunca a lista:
{ "error": { "code": "forbidden", "message": "request blocked by this key's IP restriction" } }As outras duas mensagens são request blocked by this key's region restriction
e request blocked by this key's time-window restriction.
Allowlist de origens (só chaves públicas)
Seção intitulada “Allowlist de origens (só chaves públicas)”Uma chave pk_ é verificada contra o cabeçalho Origin do navegador, e a comparação é
byte a byte sobre uma forma normalizada. As regras, cada uma existente por causa de uma
forma conhecida de errar:
- Só
https, excetohttp://localhost,http://127.0.0.1ehttp://[::1]: você precisa conseguir testar o widget na sua máquina. - Sem curingas.
https://*.example.comdelegaria para qualquer subdomínio, inclusive um que um atacante registre num serviço de páginas de terceiros sob o domínio do cliente. - Sem path, query, fragmento ou userinfo. Uma “origem” com path nunca corresponde ao que o navegador envia, e você ficaria depurando uma lista que parece certa.
- Portas padrão são removidas da forma armazenada, porque é assim que o
navegador serializa o
Origin(RFC 6454): uma página emhttps://x.example:443enviaOrigin: https://x.example. Origin: null(frames em sandbox,file://) é rejeitado como entrada e nunca corresponde.- Até 20 origens por chave, deduplicadas após a normalização.
Uma origem não registrada recebe 403: a chave é válida, a página não é.
Limites de requisição por classe
Seção intitulada “Limites de requisição por classe”| Bucket | Limite | Aplica-se a |
|---|---|---|
| Escrita / geral | 60 req/min por chave | Todas as rotas, exceto as duas rotas públicas de leitura |
| Leitura | 600 req/min por chave | GET /v1/posts, GET /v1/posts/{reference} |
| Leitura por (chave, IP) | 60 req/min | As mesmas duas rotas, só para chaves pk_ |
| Leads | 10 req/min por chave | GET /v1/leads, GET /v1/leads/{id}, exportação de leads — além do bucket geral |
Ler é dez vezes mais barato que escrever, de propósito: compartilhar um bucket deixaria uma grade de widgets movimentada consumir a cota de publicação do dono. Veja Erros e limites de requisição.
Higiene
Seção intitulada “Higiene”- Uma chave por integração. Revogue um único consumidor sem quebrar os
outros; o
last_used_atmostra quais chaves ainda estão vivas. - Nunca faça commit de uma chave
ck_nem a coloque em código client-side. Se uma vazar, revogue-a imediatamente: a revogação vale a partir da próxima requisição. - Rotacione sem indisponibilidade: crie uma nova chave com os mesmos escopos, mude a integração, confirme e depois revogue a antiga.
- Peça o menor conjunto de escopos que funcione.
leads:readesite:write, em especial, devem ficar em chave própria, não juntos na chave de publicação.
Gerenciar chaves por HTTP
Seção intitulada “Gerenciar chaves por HTTP”A interface do admin se apoia em quatro rotas (POST e GET /admin/api-keys,
PATCH e DELETE /admin/api-keys/{id}), que definem exatamente o que esta página
descreve: nome, classe, escopos, origens e as quatro restrições.
O que um PATCH pode mudar: a política — escopos, origens e as restrições de IP, região
e janela de horário. O que ele não pode mudar: o segredo. A plataforma
armazena apenas um SHA-256 da sua chave, então ninguém, nem mesmo a interface do admin, consegue
exibi-la de novo ou reemiti-la. Essa assimetria é o ponto: corrigir um escopo faltante
não deveria obrigar você a trocar o segredo em toda integração que o usa.
Mudar o que uma chave pode fazer não muda qual chave ela é.
Uma regra sobrevive à edição: uma chave pk_ nunca pode receber um escopo de escrita, a
mesma recusa que você recebe na criação. Do contrário, a garantia de somente leitura seria uma
formalidade: criar uma leitora e promovê-la depois.
Elas ficam fora do contrato /v1 do /openapi.json
pelo mesmo motivo.