Antes de tudo: abra o DevTools → Network e olhe a requisição que falhou. O status, o corpo da resposta (quase sempre {"error":{"code":"…"}}) e os headers resolvem a maior parte dos casos. Guarde um print disso se for abrir chamado.
Conexão e rede
"Failed to fetch" / "Network error"
Causa: a requisição nem terminou. URL errada, extensão de bloqueio, rede fora do ar, ou uma resposta de erro sem os headers de CORS.
- A URL do SDK é a do auth:
https://auth.superdb.com.br, sem barra no fim. O endereço dos dados (api.superdb.com.br) o SDK deduz sozinho. - Teste de fora do app:
curl -i https://auth.superdb.com.br/.well-known/jwks.jsonresponde200quando o serviço está no ar. - Desligue extensões de bloqueio e tente numa aba anônima.
A requisição demora ou dá timeout
Causa: quase sempre uma consulta sem índice, que percorre a tabela inteira.
Solução: rode a consulta com EXPLAIN ANALYZE no SQL Editor e crie o índice que falta. A tela Saúde do projeto aponta chave estrangeira sem índice e tabela sem RLS.
429 em rajadas
Causa: muitas requisições do mesmo IP em pouco tempo. Acontece com app renderizado no servidor, em que todos os usuários saem pelo IP do servidor.
Solução: use o @superdb/client 0.2.2 ou mais novo: ele reenvia sozinho quando o Retry-After é curto. Um 429 com espera longa é proteção contra força bruta (login, código), e o SDK devolve para você decidir.
Login e sessão
"project not found" / 404 na primeira chamada
Causa: falta o header X-SuperDB-Project. A chave anon não identifica o projeto para o auth: quem faz isso é esse header (ou o Host, se você configurou domínio próprio).
Solução: mande X-SuperDB-Project: <slug> em toda chamada a auth.superdb.com.br:
curl -X POST https://auth.superdb.com.br/auth/v1/signin \
-H "Content-Type: application/json" \
-H "X-SuperDB-Project: meu-projeto" \
-d '{"email":"...","password":"..."}'
Com o SDK, o header sai sozinho do project passado no createClient. A API de dados não precisa dele: lá, o projeto sai da própria credencial.
404 num endpoint de auth que "existe"
Causa: caminho do Supabase. O SuperDB não tem /auth/v1/token?grant_type=password nem /auth/v1/logout.
Solução: login é POST /auth/v1/signin, sair é POST /auth/v1/signout, renovar é POST /auth/v1/token/refresh e o código por e-mail ou WhatsApp é POST /auth/v1/signin/otp. A lista completa está em API REST.
401 depois de uma hora de uso ("JWT expired", PGRST301)
Causa: a sessão vale 1 hora e venceu sem ser renovada. O SDK renova sozinho desde a versão 0.3, um minuto antes de vencer; as versões anteriores não renovam, e quem chama a API por HTTP direto (ou cria o client com autoRefreshToken: false) renova por conta própria.
Solução: atualize o @superdb/client para a 0.3 ou mais nova. Sem o SDK, chame POST /auth/v1/token/refresh antes de vencer; com a sessão em cookies (Next.js), quem renova é o proxy.ts dos templates oficiais. Se o refresh também falhar, mande a pessoa entrar de novo.
401 na API de dados logo depois do login
Causa: o access_token foi mandado para api.superdb.com.br. Ele é um token PASETO, da API de auth; a de dados só aceita o data_plane_token, que chega no mesmo login.
Solução: com o SDK, isso não acontece. Em HTTP direto, mande Authorization: Bearer <data_plane_token>. Veja qual token vai em cada lugar.
refresh_token_reuse_detected e a pessoa cai do app
Causa: o mesmo refresh token foi usado duas vezes, quase sempre por duas abas renovando ao mesmo tempo. O SuperDB trata isso como roubo e derruba a família inteira de sessões.
Solução: renove uma vez por vez. O sessao.ts dos templates usa Web Locks para isso.
invalid_credentials (401) no login
Causa: e-mail ou senha errados. A resposta é a mesma para e-mail que não existe, de propósito. Conta criada por código ou pelo Google não tem senha e também cai aqui.
Solução: ofereça entrar com código no e-mail: funciona para qualquer conta com aquele e-mail. Depois de entrar, a pessoa pode criar uma senha.
A senha parou de funcionar depois de entrar por código ou pelo Google
Causa: a conta foi criada com senha e nunca confirmou nem e-mail nem telefone (o cadastro por senha não confirma). Quem prova ser dono do e-mail, pelo código, pelo link, pelo Google ou redefinindo a senha, passa a ser o dono da conta. Tudo o que existia antes dessa prova cai: a senha, as sessões abertas, os fatores de 2 etapas e os outros logins sociais. É o que impede alguém de cadastrar o e-mail de outra pessoa e esperar ela chegar. Acontece uma vez por conta, e a auditoria do projeto registra o evento account.claimed.
Solução: a pessoa continua entrando por código ou pelo Google. Para voltar a ter senha, ela cria uma nova depois de entrar, ou usa o "esqueci a senha".
account_locked (423) ou 429 no login
Causa: muitas senhas erradas seguidas. O bloqueio é temporário e protege contra força bruta.
Solução: mostre um aviso para esperar alguns minutos e ofereça o login por código.
user_already_exists (409) no cadastro
Causa: já há conta com aquele e-mail ou telefone.
Solução: leve para o login, com a opção de entrar por código.
"Esqueci a senha"
Peça o e-mail com a página do seu app que recebe a nova senha, e cadastre essa página, exatamente como ela é, em Autenticação → URLs de redirecionamento. Página não cadastrada não recebe o link: a chamada é recusada com redirect_to_nao_cadastrado e nenhum e-mail é enviado.
Hospeda na Vercel, na Netlify ou no Cloudflare Pages? O endereço de preview muda a cada publicação: cadastre o padrão do seu time, como https://*-seutime.vercel.app. O * só vale no começo do endereço, com o resto fixo; *.vercel.app é recusado no cadastro.
Projeto sem nenhuma URL cadastrada recusa o pedido com projeto_sem_url_de_retorno. Aí o dono do projeto também fica sabendo: recebe um e-mail com o passo a passo (no máximo um por dia), e o alerta aparece em Advisors, com o link para a tela que resolve.
curl -X POST https://auth.superdb.com.br/auth/v1/password/forgot \
-H "X-SuperDB-Project: meu-projeto" \
-H "Content-Type: application/json" \
-d '{"email":"ana@exemplo.com.br","redirect_to":"https://meuapp.com.br/nova-senha"}'
O link do e-mail abre https://meuapp.com.br/nova-senha#token=…. A sua página lê o token do fragmento (ele não chega ao seu servidor) e manda a senha nova:
curl -X POST https://auth.superdb.com.br/auth/v1/password/reset \
-H "X-SuperDB-Project: meu-projeto" \
-H "Content-Type: application/json" \
-d '{"token":"<token do fragmento>","new_password":"uma-senha-nova"}'
O token vale 1 hora e só uma vez. O pedido responde {"ok":true} mesmo para e-mail sem conta, para não revelar quem é cliente. O link mágico segue a mesma regra de URLs cadastradas.
Se o projeto tem a conferência de senha vazada ligada, a senha nova que aparece em vazamento conhecido volta password_breached (400), como no cadastro. O token não é gasto: a pessoa escolhe outra senha com o mesmo link.
Quem entrou por código ou pelo Google quer ter senha
Causa: conta criada por código, link mágico ou login social nasce sem senha.
Solução: com a pessoa logada, mande a senha nova com o access_token da sessão. Conta sem senha cria a primeira sem pedir a atual:
curl -X PUT https://auth.superdb.com.br/auth/v1/user/password \
-H "X-SuperDB-Project: meu-projeto" \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"new_password":"uma-senha-nova"}'
A resposta traz "criada": true. Conta que já tem senha troca pela mesma rota, mandando também a atual em current_password. Sem ela, a resposta é current_password_required (400). Na troca, as outras sessões da pessoa caem. Criar e trocar mandam o e-mail de "senha alterada". Durante o "entrar como" do painel, criar senha é recusado: a senha é da pessoa. Com a conferência de senha vazada ligada no projeto, criar ou trocar para uma senha que aparece em vazamento conhecido também volta password_breached (400).
O código não chegou
Causa: spam, e-mail digitado errado, ou o limite de pedidos (10 por hora, por IP, no projeto).
Solução: não existe "reenviar": chame signInWithOtp de novo, e o código novo vale 10 minutos. Mostre o endereço na tela do código, para a pessoa conferir. Para melhorar a entrega, verifique o seu domínio em Emails → Remetente dos emails.
whatsapp_not_in_plan (403) ao pedir código por WhatsApp
Causa: o código por WhatsApp faz parte dos planos Pro e Escala, e o projeto está em outro plano. Nenhum código foi gerado nem enviado.
Solução: mude o plano no painel, no menu do projeto em Billing (compare os planos em Preços), ou peça o código por e-mail (channel: 'email'), que vale em todos os planos.
whatsapp_send_failed (503) ao pedir código por WhatsApp
Causa: o código não saiu. Três motivos, e a mensagem do erro diz qual: o número de destino não tem WhatsApp; o número que envia foi desconectado; ou o número compartilhado do SuperDB chegou ao teto (60 mensagens por hora e 300 por dia, somando todos os projetos que não pareiam o próprio).
Solução: caia para o e-mail no app (channel: 'email'), como na receita. Se o seu projeto manda muito código por WhatsApp, pareie o seu número em Autenticação → WhatsApp do projeto: o teto do número compartilhado não vale para ele.
Row Level Security
"new row violates row-level security policy"
Causa: o WITH CHECK da policy recusou o INSERT ou UPDATE. Quase sempre o user_id da linha não é o auth.uid().
Solução: deixe o banco preencher o dono e não mande user_id do app:
alter table tasks alter column user_id set default auth.uid();
"permission denied for table X"
Causa: falta o GRANT para a role (authenticated ou anon). Não é o RLS: é a permissão do Postgres, que vem antes.
grant select, insert, update, delete on tasks to authenticated;
-- leitura pública, só se a tabela for mesmo pública:
grant select on posts to anon;
A consulta volta vazia, mas a tabela tem dados
Causa: o RLS. Toda tabela nova nasce fechada (RLS ligado, nenhuma policy), e consulta bloqueada volta lista vazia, não erro.
Solução: crie a policy (veja Row Level Security). Atenção: o SQL Editor roda como dono do projeto, que passa por cima do RLS. Lá a tabela sempre mostra tudo; para ver o que o usuário vê, consulte pelo app logado.
A tabela devolve tudo para todo mundo
Causa: uma policy com using (true), ou uma tabela antiga, criada antes de o RLS vir ligado por padrão (17/08/2026).
Solução: a tela Saúde lista tabelas sem RLS. Ligue com alter table x enable row level security; e escreva as policies.
Aviso: "a RLS de X está desligada, e a tabela ficou exposta pela API"
Causa: um alter table x disable row level security numa tabela que a API alcança. No SuperDB toda tabela nova já dá leitura ao anon e escrita ao authenticated: a RLS é a única porta. Desligada, qualquer pessoa com a chave pública do seu app lê a tabela inteira, e as policies que você criou deixam de valer, porque o Postgres não as consulta. O aviso vem na resposta do próprio comando: no SQL Editor, no campo avisos do /db/exec, no MCP e, numa conexão direta ao Postgres, como WARNING.
Solução: religue a RLS e libere só o necessário. Desligar a RLS quase nunca conserta "o insert deu erro": o que falta é uma policy de INSERT.
alter table fotos enable row level security;
-- leitura pública, só se a tabela for mesmo pública:
create policy "leitura" on fotos for select to anon, authenticated using (true);
-- gravação só do dono:
create policy "dono grava" on fotos for insert to authenticated
with check (user_id = auth.uid());
Meu grant ... to public virou anon e authenticated
Causa: é de propósito. PUBLIC não é "o público do seu app": no PostgreSQL ele vale para todo papel do servidor de banco, não só para os do seu projeto. O banco converte o grant na hora para anon e authenticated, os papéis que a API de dados usa, e avisa na resposta.
Solução: nada muda para o seu app: o acesso pela API é o mesmo de antes. Nas suas migrations, escreva direto grant ... to anon, authenticated e controle as linhas com RLS.
API REST
416 "Range not satisfiable"
Causa: a paginação pediu uma faixa além do fim (.range(1000, 1999) numa tabela com 50 linhas).
Solução: pare de paginar quando a página vier menor que o tamanho pedido.
406 com .single()
Causa: .single() exige exatamente uma linha, e vieram zero ou duas.
Solução: use .maybeSingle() quando zero linhas for um resultado normal.
"PGRST204" / "PGRST205": coluna ou tabela não encontrada
Causa: nome errado, ou a tabela acabou de ser criada. O cache de schema recarrega sozinho depois de uma mudança de estrutura no projeto, em alguns segundos.
Solução: confira o nome (maiúsculas contam) e tente de novo depois de alguns segundos.
Contar linhas
Com o SDK (0.3 em diante), select('*', { count: 'exact', head: true }) devolve o total em count, sem trazer as linhas. Pela API REST, mande Prefer: count=exact e leia o total no Content-Range. Em tabela grande, estimated no lugar de exact é instantâneo e aproximado. Exemplo em PostgREST filtros.
Embed ?select=*,autor:usuarios(*) não funciona
Causa: não há chave estrangeira entre as duas tabelas, e o PostgREST precisa dela para montar o join.
alter table posts add constraint posts_autor_fk foreign key (autor_id) references usuarios (id);
SQL e extensões
net.http_post ou um Database Webhook do Supabase não funciona
Causa: o pg_net não está disponível nos bancos dos projetos, assim como o pg_cron. Sem ele, net.http_post não chama URL nenhuma, e os Database Webhooks do Supabase, que são um gatilho em cima dele, não migram.
Solução: tire a chamada HTTP de dentro do banco. A sua aplicação chama a URL logo depois de gravar; ou um gatilho grava o evento numa tabela sua, e um worker (o seu backend ou um Cloudflare Worker) lê essa tabela e chama a URL. Tarefa agendada segue a mesma ideia: o agendador fica fora do banco e chama uma função via RPC.
Storage
O Storage isola projetos, não usuários. Qualquer usuário logado no seu projeto lê, envia e apaga arquivos em todos os buckets do projeto. A chave anon só lê bucket público. Não guarde no Storage um arquivo que um usuário não possa ver de outro.
Upload volta 403
Causa: ninguém logado. Sem sessão, o SDK troca a chave anon por um token de storage que só lê bucket público.
Solução: faça o upload com o usuário logado, ou do seu servidor com a service key.
402 em /st/v1/token
Causa: a cota de Storage do plano acabou, e o SuperDB para de emitir token de storage.
Solução: apague arquivos que sobraram ou mude de plano, em Billing.
"Bucket not found"
Causa: o bucket não existe ou o nome está diferente (maiúsculas contam: Avatares e avatares são dois buckets).
Solução: confira o nome na tela Storage do projeto.
Realtime
Conectou, mas os eventos não chegam
- A tabela está ligada na tela Realtime do projeto? Só entra tabela com RLS ligada: sem ela, o painel recusa com "Ligue a RLS de … antes de ativar o Realtime". Ligue a RLS, escreva as policies e ative de novo.
- O
schemado canal éproj_mais o slug do projeto? - O token foi pedido com a sessão do usuário? Com a anon key, o realtime entrega só o que um visitante sem login leria. Numa tabela cujo RLS libera só quem está logado, isso é nada. Peça o token com
db.auth.getDataPlaneToken(). - A regra de leitura da tabela consulta outra tabela? Ela precisa usar uma função
security definer; senão não vale no realtime. O painel avisa ao ativar a tabela. - O token de realtime vale 1 hora. Troque por um novo com
rt.setAuth(token)antes de vencer. - O
db.channel()do SDK ainda não funciona. A conexão é pelo@supabase/realtime-jscom um token de/rt/v1/token; veja SDK JS → Realtime.
O realtime segue o RLS. Cada inscrito recebe só as linhas que a credencial dele leria pela API. Se a tela precisa de um dado que o usuário não lê pela API, ela também não o recebe pelo realtime.
O /rt/v1/token responde 401 ou 403
401 API key revogada ou inválida: as chaves do projeto foram regeneradas no painel. Use a anon key nova, da aba API Keys.401 invalid tokencom a sessão: ela venceu (vale 1 hora; o SDK renova sozinho desde a 0.3, as versões anteriores não). Chameawait db.auth.refreshSession()e peça de novo.403 forbidden: not a user of …: o usuário da sessão foi banido ou apagado do projeto.403 project is not active: o projeto está suspenso. O motivo aparece no painel.
E-mails
O e-mail chega no spam
Causa: o projeto envia pelo remetente padrão do SuperDB.
Solução: em Emails → Remetente dos emails, verifique o seu domínio. Não há SMTP para configurar.
Uma variável do template não é substituída
Causa: nome de variável que o template não recebe. A sintaxe é {{nome}}, sem ponto.
Solução: no Código MFA (que também é o código de login), use {{code}} e {{expires_in_minutes}}. Nos templates de link, o endereço é {{url}}; o editor ainda lista {{link}} e {{name}}, que saem em branco.
CORS
"blocked by CORS policy"
As duas APIs respondem Access-Control-Allow-Origin: *: não existe lista de origens para configurar, e localhost funciona igual a produção. Quando o navegador fala em CORS, a causa é outra:
- Header fora da lista, no auth.
auth.superdb.com.braceita sóAuthorization,apikey,Content-Type,X-SuperDB-Project,Accept-Profile,Content-ProfileeX-Client-Info. Um header seu a mais derruba a requisição antes de ela sair. - Uma resposta de erro sem CORS. Um 502 ou 413 da borda chega sem os headers, e o navegador mostra como erro de CORS. Veja o status real na aba Network.
- URL errada, com
/rest/v1no caminho (isso é do Supabase) ou comhttp://.
Login social
redirect_uri_mismatch na tela do Google
Causa: com credenciais próprias, o endereço de callback do SuperDB não está no console do provedor.
Solução: cadastre exatamente https://auth.superdb.com.br/auth/v1/callback/<provider> (google, github ou apple).
oauth_not_enabled
Causa: o provedor não está ligado no projeto.
Solução: Autenticação → Login social (OAuth). O modo gerenciado não pede chave nenhuma.
oauth_state_unbound depois de escolher a conta
Causa: o redirectTo não é igual, caractere por caractere, a uma URL cadastrada no projeto.
Solução: cadastre em Autenticação → URLs de redirecionamento. localhost e 127.0.0.1 contam como endereços diferentes. O passo a passo está em Signup com Google.
no_pkce_verifier na página de volta
Causa: a troca do code rodou num navegador diferente do que começou o login, ou foi chamada duas vezes.
Solução: refaça o login pelo botão, e troque o code uma vez só.
MFA
mfa_invalid_code com o código certo
Causa: o relógio do celular está adiantado ou atrasado. O código TOTP depende da hora.
Solução: ligue a hora automática no aparelho e use o próximo código.
A pessoa perdeu o celular do autenticador
O login ainda não pede o segundo fator, então ela entra com a senha, remove o fator antigo e cadastra o novo celular. O passo a passo, com os códigos de reserva, está em MFA TOTP.
Não achou o seu erro? Abra um chamado no painel com: (1) o erro exato, (2) o que você estava tentando fazer e (3) um print da aba Network.