Autenticação
A API Groups usa dois tipos de autenticação dependendo do recurso:
- Platform Admin JWT — para gerenciar Groups via
/platform/v1/groups/*. Obtenha em admin.superdb.com.br. - 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/:id
Deleta um tenant e apaga o schema Postgres — operação irreversível. Todos os dados do tenant são removidos.
IRREVERSÍVEL. Esta operação executa DROP SCHEMA tenant_xyz CASCADE. Faça backup antes. Confirme o slug do tenant antes de chamar.
curl -X DELETE \
"https://auth.superdb.com.br/groups/v1/grp_a1b2c3d4e5f6/tenants/ten_f7e8d9c0b1a2" \
-H "Authorization: Bearer $SUPERDB_GROUP_API_KEY"
# Response: 204 No Content
export async function deleteTenant(tenantId: string): Promise<void> {
const url = `${process.env.SUPERDB_BASE_URL}/groups/v1/${process.env.SUPERDB_GROUP_ID}/tenants/${tenantId}`
const resp = await fetch(url, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.SUPERDB_GROUP_API_KEY}`,
},
})
if (!resp.ok && resp.status !== 204) {
throw new Error(`Deleção falhou: ${resp.status}`)
}
}
Códigos de erro
| Status | Código | Descrição |
|---|---|---|
400 |
invalid_slug |
Slug contém caracteres inválidos ou está vazio. |
401 |
unauthorized |
Token ausente, expirado ou inválido. |
403 |
forbidden |
API Key não tem permissão para este Group. |
409 |
slug_conflict |
Slug já existe neste Group. |
422 |
provisioning_failed |
Erro interno ao clonar o schema. Tente novamente. |
402 |
tenant_limit_reached |
O grupo chegou a 100 tenants ativos — é o teto por grupo, e não há limite por minuto: este é o único freio quantitativo. |
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.
foreign_keys em null significa que o fechamento do clone falhou e o template ficou sem integridade referencial: não trate como sucesso.