Skip to main content
Toda credencial que a API emite segue um formato só: bytes aleatórios de alta entropia atrás de um prefixo que diz o que o token é, armazenada como hash SHA-256, mostrada em texto puro exatamente uma vez. O prefixo faz dois trabalhos. Ele permite reconhecer um token vazado — num log, num git push, numa varredura de repositório — e impede que uma credencial de uma superfície abra as rotas de outra.

As quatro famílias

uk_st_… — sessão de staff

Uma pessoa logada no painel. Administra a organização. Vive 30 dias, revogável.

uk_sk_… — chave de API da organização

Seu backend. Lê e escreve no plano de clientes, delimitado ao ambiente da chave.

uk_pk_… — chave publicável

Suas páginas. Pública por definição; a lista de origens é a fronteira dela.

uk_ct_… — sessão de contato

Um dos seus usuários. Lê o próprio contato e mais nada.
As quatro viajam no header Authorization: Bearer, exceto a chave publicável, que vai no corpo da requisição dos endpoints que a aceitam.
Nada mais é aceito como credencial. Nenhum parâmetro de query — cairia em logs de acesso e no histórico do navegador. Nenhum cookie — esta API não é autenticada por cookie, e é também por isso que o CORS nunca precisa de credenciais.

Apresentando a errada

Uma credencial bem formada de outra família recebe isso, em vez de um “inválido” seco:
401
Essa diferença importa: um caso é erro de configuração, o outro é evento de segurança, e os dois não deveriam ler igual nos seus logs.

Sessões de staff

uk_st_…, emitidas por /v1/auth/signup, /v1/auth/login e /v1/auth/two-factor. Válidas por 30 dias. Longas porque são revogáveis. Sair, trocar a senha, redefinir a senha ou perder o vínculo matam a sessão no servidor — ao contrário de um JWT autocontido, que continua válido até expirar, não importa o que aconteça com a conta.
Um usuário pode ver onde está logado e sair de todos os outros lugares:
“Todos os outros” mantém a sessão que pediu — ninguém quer ser deslogado da aba em que clicou.

Chaves de API da organização

uk_sk_…, somente no servidor. Criadas no painel, mostradas uma vez, armazenadas como hash.
O ambiente faz parte do prefixo e, mais importante, da linha armazenada: é de lá que ele é resolvido a cada requisição. Veja Ambientes. Revogar carimba revoked_at em vez de deletar a linha, então o rastro de auditoria de quais chaves existiram não desaparece junto.
Uma chave no bundle do navegador é uma chave na mão de cada visitante. uk_sk_ pertence ao seu servidor. Para páginas, use uma chave publicável.

Chaves publicáveis

uk_pk_…, uma por ambiente, criada junto com ele, armazenada em texto puro — porque uma chave publicável identifica e quase nada autoriza, e o painel precisa conseguir mostrá-la de novo. Ela fica no HTML da sua página por definição. O que impede um estranho que a leu de inundar seu tenant com contatos-lixo é a lista de origens permitidas na linha dela, mais um rate limit por chave no boot.
A comparação é exata sobre a origem normalizada — sem curingas, sem prefixos, porque a lista é curta e a ambiguidade de qualquer coisa mais esperta é onde moram os bypasses.
Uma lista vazia permite qualquer origem. É o estado de começo de vida, e é a única coisa a arrumar antes de ir para produção.
Com a lista preenchida, um header Origin ausente também é recusado: navegadores sempre mandam um em requisição cross-origin, então “sem Origin” significa “não é um navegador” — exatamente quem a lista existe para barrar.

Sessões de contato

uk_ct_…, a credencial do seu usuário, emitida pelo boot, por um magic link ou código por e-mail resgatado, por login hosted ou por verificação de e-mail. Válida por 30 dias. Uma sessão de contato lê o próprio contato e mais nada. Esse escopo é todo o modelo de autorização do uk_ct_.
Ela também carrega uma flag verified — se a identidade por trás dela foi comprovada: Uma sessão não verificada é usável em desenvolvimento e fica marcada no painel, mas é barrada de qualquer coisa por onde os dados de outra pessoa pudessem vazar. Um contato consegue ver e encerrar as próprias sessões — GET /v1/contact/sessions, DELETE /v1/contact/sessions/{id}, e DELETE /v1/contact/sessions para todas as outras. Isso faz parte da defesa do token, não é um extra: ele vive no seu domínio sem httpOnly.

A única credencial não opaca

Tudo acima é opaco — uma string aleatória cujo significado vive no nosso banco. A exceção é o JWT de sessão: cinco minutos, ES256, assinado por ambiente, emitido a partir de uma sessão uk_ct_… em POST /v1/contact/token e verificado offline pelo seu backend contra GET /v1/jwks/{publishable_key}. Ele existe para a sua API não precisar chamar a nossa a cada requisição. E é curto justamente porque não pode ser revogado. Veja Tokens de sessão.

Tokens de uso único

Não são credenciais que você guarda, mas seguem o mesmo formato — um prefixo cada, para que nenhum possa ser confundido com uma sessão:

O que é armazenado

Um dump do banco não entrega nenhuma credencial funcionando.