Autenticação
A API Groups usa dois tipos de autenticação dependendo do recurso:
- Token de sessão da sua conta: para gerenciar Groups via
/platform/v1/groups/*. É o mesmo login do painel; o passo a passo está em Criar group. - Group API Key — para provisionar e gerenciar tenants via
/groups/v1/*. Gerada em/groupsno painel, na página do seu group — o texto claro aparece apenas uma vez.
Group API Key é secreta. Nunca exponha no frontend. Guarde no .env do seu backend e jamais comite no git.
Todos os endpoints retornam JSON e aceitam Content-Type: application/json.
Endpoints de Platform Admin
Requerem o token de sessão do painel (Authorization: Bearer <token>). Você só enxerga e altera os groups que são seus — desde 04/08/2026 estas rotas deixaram de exigir cargo de admin e passaram a checar posse.
POST /platform/v1/groups
Cria um novo Group associado a um projeto template.
POST https://auth.superdb.com.br/platform/v1/groups
Authorization: Bearer <platform_admin_jwt>
Content-Type: application/json
{
"name": "ERP Clínicas",
"description": "Template para sistemas de gestão de clínicas médicas",
"template_project_id": "proj_erp_clinica_template"
}
{
"id": "grp_a1b2c3d4e5f6",
"name": "ERP Clínicas",
"description": "Template para sistemas de gestão de clínicas médicas",
"template_project_id": "proj_erp_clinica_template",
"tenant_count": 0,
"created_at": "2026-05-14T10:30:00Z"
}
const resp = await fetch('https://auth.superdb.com.br/platform/v1/groups', {
method: 'POST',
headers: {
'Authorization': `Bearer ${platformAdminJwt}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'ERP Clínicas',
template_project_id: 'proj_erp_clinica_template',
}),
})
const group = await resp.json()
GET /platform/v1/groups
Lista todos os Groups da plataforma.
curl https://auth.superdb.com.br/platform/v1/groups \
-H "Authorization: Bearer $PLATFORM_ADMIN_JWT"
[
{
"id": "grp_a1b2c3d4e5f6",
"name": "ERP Clínicas",
"tenant_count": 12,
"created_at": "2026-05-14T10:30:00Z"
},
{
"id": "grp_x9y8z7w6v5u4",
"name": "Gestão Escolar",
"tenant_count": 4,
"created_at": "2026-05-10T08:15:00Z"
}
]
GET /platform/v1/groups/:id
Retorna detalhes de um Group específico.
curl https://auth.superdb.com.br/platform/v1/groups/grp_a1b2c3d4e5f6 \
-H "Authorization: Bearer $PLATFORM_ADMIN_JWT"
DELETE /platform/v1/groups/:id
Deleta um Group. Retorna 409 Conflict se o Group ainda tiver tenants ativos.
curl -X DELETE https://auth.superdb.com.br/platform/v1/groups/grp_a1b2c3d4e5f6 \
-H "Authorization: Bearer $PLATFORM_ADMIN_JWT"
# Response: 204 No Content (sucesso)
# Response: 409 Conflict (group tem tenants ativos)
Endpoints de Group API (tenants)
Requerem Authorization: Bearer <group_api_key>. Use no backend do seu SaaS para provisionar clientes.
POST /groups/v1/:group_id/tenants
Provisiona um novo tenant para o Group. Esta é a operação mais importante — chame quando um novo cliente se cadastrar no seu SaaS.
O SuperDB clona o schema template, cria as tabelas de auth, gera o role Postgres dedicado e retorna as keys em ~1-2 segundos.
POST https://auth.superdb.com.br/groups/v1/grp_a1b2c3d4e5f6/tenants
Authorization: Bearer <group_api_key>
Content-Type: application/json
{
"slug": "clinica_sao_lucas",
"external_id": "company_789",
"metadata": {
"cnpj": "12.345.678/0001-99",
"plan": "professional",
"admin_email": "dr.pedro@saolucas.com.br"
}
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
slug |
string | Sim | Identificador único. Só letras minúsculas, números e underscore. |
external_id |
string | Não | ID do cliente no seu banco de dados. |
metadata |
object | Não | JSON livre. Armazenado mas não indexado. |
{
"tenant": {
"id": "ten_f7e8d9c0b1a2",
"slug": "clinica_sao_lucas",
"schema_name": "proj_clinica_sao_lucas",
"external_id": "company_789",
"group_id": "grp_a1b2c3d4e5f6",
"metadata": { "cnpj": "12.345.678/0001-99", "plan": "professional" },
"created_at": "2026-05-14T11:45:00Z"
},
"anon_key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsInRlbmFudCI6ImNsaW5pY2Ffc2FvX2x1Y2FzIn0.XXXX",
"service_role_key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIn0.YYYY"
}
Salve as keys imediatamente. O service_role_key não é armazenado no SuperDB após a criação — por segurança. Se perder, precisará revogar e gerar novas keys.
interface ProvisionResult {
tenant: {
id: string
slug: string
schema_name: string
external_id?: string
created_at: string
}
anon_key: string
service_role_key: string
}
export async function provisionTenant(params: {
slug: string
externalId?: string
metadata?: Record<string, unknown>
}): Promise<ProvisionResult> {
const url = `${process.env.SUPERDB_BASE_URL}/groups/v1/${process.env.SUPERDB_GROUP_ID}/tenants`
const resp = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SUPERDB_GROUP_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
slug: params.slug,
external_id: params.externalId,
metadata: params.metadata,
}),
})
if (!resp.ok) {
const error = await resp.text()
throw new Error(`Provisioning falhou (${resp.status}): ${error}`)
}
return resp.json() as Promise<ProvisionResult>
}
GET /groups/v1/:group_id/tenants
Lista todos os tenants de um Group.
curl "https://auth.superdb.com.br/groups/v1/grp_a1b2c3d4e5f6/tenants?limit=20&offset=0" \
-H "Authorization: Bearer $SUPERDB_GROUP_API_KEY"
{
"tenants": [
{
"id": "ten_f7e8d9c0b1a2",
"slug": "clinica_sao_lucas",
"external_id": "company_789",
"created_at": "2026-05-14T11:45:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}
GET /groups/v1/:group_id/tenants/:id
Retorna detalhes de um tenant específico. Não retorna as keys (por segurança).
curl "https://auth.superdb.com.br/groups/v1/grp_a1b2c3d4e5f6/tenants/ten_f7e8d9c0b1a2" \
-H "Authorization: Bearer $SUPERDB_GROUP_API_KEY"
DELETE /groups/v1/:group_id/tenants/:slug
Exclui o tenant: ele sai do ar e a assinatura dele é cancelada, mas as tabelas, os arquivos e as chaves ficam guardados. Responde 200 com { "ok": true }, ou 404 se o tenant não existe ou já foi excluído. Para apagar os dados de vez, use a exclusão definitiva, logo abaixo.
curl -X DELETE \
"https://auth.superdb.com.br/groups/v1/grp_a1b2c3d4e5f6/tenants/clinica_sao_lucas" \
-H "Authorization: Bearer $SUPERDB_GROUP_API_KEY"
# Response: 200 { "ok": true }
export async function deleteTenant(slug: string): Promise<void> {
const url = `${process.env.SUPERDB_BASE_URL}/groups/v1/${process.env.SUPERDB_GROUP_ID}/tenants/${slug}`
const resp = await fetch(url, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.SUPERDB_GROUP_API_KEY}`,
},
})
if (!resp.ok) {
throw new Error(`Exclusão falhou: ${resp.status}`)
}
}
DELETE /groups/v1/:group_id/tenants/:slug/purge
Exclusão definitiva, para atender o direito de exclusão da LGPD: apaga as tabelas, os dados, os arquivos e as chaves do tenant. Não tem volta.
- Mande o slug de novo no corpo, em
confirm_slug. Sem ele, ou com outro valor, a resposta é400 confirmation_requirede nada é apagado. - O projeto que é template de um group não é apagado: a resposta é
409 template_protected. 200compurgedquando a remoção termina na hora;202com"pendente": truequando ela termina sozinha, em alguns minutos. Nos dois casos, o acesso ao tenant já está encerrado.- O slug de um tenant excluído em definitivo não volta a ser usado: provisionar de novo com ele responde
409 slug_taken. Para o mesmo cliente voltar, use outro slug; oexternal_idpode ser o mesmo.
IRREVERSÍVEL. Confirme o slug antes de chamar. Se precisa guardar os dados, peça antes o dump do tenant ao suporte (veja Backup e exportação).
curl -X DELETE \
"https://auth.superdb.com.br/groups/v1/grp_a1b2c3d4e5f6/tenants/clinica_sao_lucas/purge" \
-H "Authorization: Bearer $SUPERDB_GROUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "confirm_slug": "clinica_sao_lucas" }'
# Response: 200 { "ok": true, "purged": { ... } }
# ou: 202 { "ok": true, "pendente": true, "message": "..." }
Códigos de erro
| Status | Código | Descrição |
|---|---|---|
400 |
invalid_slug |
Slug contém caracteres inválidos ou está vazio. |
400 |
slug_reservado |
O slug é um nome reservado da plataforma. Escolha outro. |
401 |
unauthorized |
Token ausente, expirado ou inválido. |
402 |
tenant_quota_exceeded |
O template está no plano Grátis e o group já usa os tenants inclusos. Subir o plano do template destrava. |
402 |
tenant_limit_reached |
O grupo chegou a 100 tenants ativos, o teto por grupo. Não há limite por minuto: este é o único freio quantitativo, e subir de plano não o muda. |
403 |
forbidden |
API Key não tem permissão para este Group. |
409 |
slug_taken |
O slug já está em uso, ou pertenceu a um tenant excluído em definitivo. Por segurança, o slug de um tenant excluído em definitivo não volta a ser usado: escolha outro. |
409 |
template_indisponivel |
O template do group não pode ser clonado agora, e nada é criado. Dois casos: o projeto template foi excluído (publique uma versão nova a partir da fonte com POST /platform/v1/groups/:id/template/publish, ou aponte o group para outro template), ou o template roda num banco dedicado e a estrutura usada para clonar não está alinhada com ele (fale com o suporte, que alinha o template). Repetir a chamada não resolve. |
500 |
sem código | Falha interna ao montar o tenant. O provisionamento é desfeito por inteiro; repita a chamada com o mesmo slug. |
Publicar uma versão do template
POST /groups/v1/{groupId}/template/publish — autenticado com a Group API Key.
Congela o schema atual do projeto de origem num template novo (<fonte>_v2, _v3…) e aponta o group para ele. A partir daí, todo cliente provisionado nasce com essa estrutura. Use como passo do seu deploy, depois de rodar as migrations.
curl -X POST https://auth.superdb.com.br/groups/v1/{groupId}/template/publish \
-H "Authorization: Bearer sdb_gk_..."
Resposta 201:
{
"template": { "id": "...", "slug": "idepot_erp_v2", "name": "..." },
"source_project_slug": "idepot_erp",
"tables_copied": 46,
"foreign_keys": 87,
"tenants_na_versao_anterior": 12,
"aviso": "12 tenant(s) continuam com a estrutura anterior — publicar não retroage."
}
Não retroage. Tenant que já existe continua com a estrutura anterior — por isso a resposta devolve tenants_na_versao_anterior. Para migrar os que já estão no ar, rode a migration em cada um via /db/exec, com a management key do tenant. Trocar o template e deixar você achar que os antigos acompanharam seria pior do que não ter a rota.
Se o fechamento do clone falhar, a resposta é 500 clone_incompleto, com o project_id e o slug do projeto criado: não use esse template.
Se o projeto de origem roda num banco dedicado, a publicação compara a estrutura dele com a cópia que a plataforma usa para clonar. Se a origem mudou desde a última sincronização, a resposta é 409 fonte_indisponivel e nada é criado: peça ao suporte para sincronizar e repita a mesma chamada.