URL base
https://api.superdb.com.br/{tabela}
Path raiz, sem /rest/v1/ e sem {projeto} na URL. O tenant sai da credencial que você manda — não da URL nem de um header de schema.
Autenticação
O login devolve dois tokens, e só um deles funciona aqui. Se você vem do Supabase — onde existe um token só — este é o tropeço mais caro da migração.
Antes de tudo: o login precisa saber qual é o seu projeto. Mande X-SuperDB-Project: <slug> em toda chamada a auth.superdb.com.br — a anon key sozinha não identifica o tenant. Sem esse header a resposta é 404 tenant_not_found, que é fácil confundir com URL errada. (Se o seu projeto tem domínio próprio configurado, o host já resolve e o header é dispensável.)
{
"access_token": "v4.public.eyJzdWIiOi...", // sessão do usuário — NÃO serve para dados
"data_plane_token": "eyJhbGciOiJFUzI1NiI...", // ← este vai para api.superdb.com.br
"refresh_token": "...",
"expires_in": 3600
}
O access_token é a sessão (formato PASETO) e é aceito em auth.superdb.com.br. A API de dados fala JWT e só aceita o data_plane_token. Mandar o token errado devolve 401.
Authorization: Bearer $DATA_PLANE_TOKEN
Não precisa mandar Accept-Profile. O tenant é deduzido da própria credencial — e um Accept-Profile divergente é ignorado em favor do projeto da chave.
Para leitura pública, a anon key vale como credencial sozinha:
curl 'https://api.superdb.com.br/tasks?select=*&limit=10' \
-H "apikey: $ANON_KEY"
O data_plane_token expira
Vale 1 hora. Renove em POST auth.superdb.com.br/auth/v1/token/refresh com o refresh_token — a resposta traz um data_plane_token novo. Se as chamadas começarem a voltar 401 depois de um tempo funcionando, é isso.
Voltou [] e você esperava dados
Consulta bloqueada por RLS devolve lista vazia, não erro — o mesmo que uma tabela sem linhas. Toda tabela nasce com RLS ligado, então uma tabela recém-criada sem política responde [] para todo mundo. É o primeiro lugar a olhar quando o corpo vem vazio sem motivo aparente.
Pedindo um registro só
O .single() do SDK (e o header Accept: application/vnd.pgrst.object+json no HTTP puro) exige exatamente uma linha. Com zero ou com duas, a resposta é 406:
{
"code": "PGRST116",
"details": "The result contains 0 rows",
"message": "JSON object requested, multiple (or no) rows returned"
}
Para "buscar um registro que pode ainda não existir" — o perfil do usuário logo após o cadastro, por exemplo — use .maybeSingle(). Ele devolve null em vez de erro quando não há linha.
SELECT
# Todos os registros
curl https://api.superdb.com.br/tasks \
-H "Authorization: Bearer $DATA_PLANE_TOKEN"
# Filtro: tarefas não concluídas
curl "https://api.superdb.com.br/tasks?done=eq.false" \
-H "Authorization: Bearer $DATA_PLANE_TOKEN"
# Selecionar colunas
curl "https://api.superdb.com.br/tasks?select=id,title" \
-H "Authorization: Bearer $DATA_PLANE_TOKEN"
# Ordem + paginação
curl "https://api.superdb.com.br/tasks?order=created_at.desc&limit=10" \
-H "Authorization: Bearer $DATA_PLANE_TOKEN"
Operadores de filtro
Use no formato ?coluna=op.valor. Operadores principais:
eq— igual:?done=eq.trueneq— diferente:?status=neq.deletedgt,gte,lt,lte— comparações:?age=gt.18like,ilike— pattern matching:?name=ilike.*silva*is— NULL/TRUE:?deleted_at=is.nullin— lista:?status=in.(active,pending)
INSERT
curl -X POST https://api.superdb.com.br/tasks \
-H "Authorization: Bearer $DATA_PLANE_TOKEN" \
-H "Content-Type: application/json" \
-H "Prefer: return=representation" \
-d '{"title": "Nova tarefa", "done": false}'
UPDATE
curl -X PATCH "https://api.superdb.com.br/tasks?id=eq.UUID" \
-H "Authorization: Bearer $DATA_PLANE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"done": true}'
DELETE
curl -X DELETE "https://api.superdb.com.br/tasks?id=eq.UUID" \
-H "Authorization: Bearer $DATA_PLANE_TOKEN"
Joins automáticos
PostgREST detecta foreign keys e permite joins por sintaxe tabela(colunas):
curl "https://api.superdb.com.br/tasks?select=*,user:users(name,email)" \
-H "Authorization: Bearer $DATA_PLANE_TOKEN"
Preview no Studio
Toda chamada REST roda como SQL embaixo. Você pode espiar no Studio:
Limites
- Máximo de 1.000 registros por request por padrão
- Use
limiteoffsetpra paginar - Use
Rangeheader pra range requests
Requisições por segundo
O limite de requisições por segundo é por projeto, não por endereço de IP — um app renderizado no servidor faz todas as chamadas de um endereço só, e contar por IP transformaria o limite do seu app inteiro no limite de um usuário. O número de cada plano está na página de preços.
Ao ultrapassar, a API responde 429 com o cabeçalho
Retry-After em segundos, e um corpo que diz qual plano e qual
limite foram aplicados:
HTTP/1.1 429 Too Many Requests
Retry-After: 1
X-SuperDB-Plano: pro
X-SuperDB-Limite-Rps: 500
{
"error": "rate_limited",
"message": "Limite de 500 requisições por segundo do plano pro atingido.",
"hint": "Aguarde um segundo e tente de novo, ou mude de plano em https://superdb.com.br/precos"
}
O SDK já trata isso. O
@superdb/client lê o Retry-After e reenvia sozinho
quando a espera é curta. Espera longa ele devolve para você decidir — é
assim que ele distingue uma rajada momentânea de um bloqueio deliberado,
como o de tentativas de login.
Dica: Para queries tipadas, use o SDK @superdb/client (com os nomes do supabase-js) com createClient(url, anonKey, { project: 'meuapp' }) — o .from() monta as URLs PostgREST e manda o Accept-Profile automaticamente, sem header manual.