Super Admin

Endpoints sob /api/admin/*. Todos protegidos por auth:api + middleware super_admin (checa user.is_super_admin = true). Super admins ignoram os scopes BelongsToTenant/BelongsToTenantAndUnit, podendo consultar e modificar dados de qualquer clínica. Respostas de erro padrão:
StatusQuando
401JWT inválido ou ausente
403Usuário autenticado mas não é super admin ({"error": "Forbidden"})
404Recurso não encontrado (via findOrFail)
422Validação

Tenants (Clínicas)

GET /api/admin/tenants

Lista paginada de todas as clínicas.

Query params

Busca ILIKE em name e slug. Máximo 255 caracteres.

Response 200

Paginação de 50 itens por página:
{
  "data": [
    {
      "id": "uuid",
      "name": "string",
      "slug": "string",
      "is_active": true,
      "logo": "data:image/... | null",
      "created_at": "iso8601",
      "updated_at": "iso8601"
    }
  ],
  "current_page": 1,
  "per_page": 50,
  "total": 12
}

POST /api/admin/tenants

Cria uma nova clínica.

Request body

name
string
required
Nome da clínica. Máximo 255 caracteres.
slug
string
required
Slug único (normalizado via Str::slug() — lowercase, hífens). Máximo 255 caracteres.
is_active
boolean
default:"true"
Se a clínica nasce ativa.

Response 201

{
  "id": "uuid",
  "name": "string",
  "slug": "string",
  "is_active": true,
  "logo": null,
  "created_at": "iso8601",
  "updated_at": "iso8601"
}

Erros

StatusSituação
422Nome duplicado (comparação case-insensitive: "Já existe uma clínica com esse nome") ou slug não único

PATCH /api/admin/tenants//status

Ativa ou desativa uma clínica. Efeito cascata crítico.

Request body

is_active
boolean
required
Novo estado da clínica.

Response 200

Objeto Tenant atualizado.

Efeitos colaterais

Ao desativar uma clínica (is_active = false), todos os usuários dela são desativados automaticamente na mesma transação (UPDATE users SET is_active = false WHERE tenant_id = :id). Usuários não são reativados automaticamente ao reativar a clínica.

Erros

StatusSituação
404Tenant não encontrado

Units (Unidades)

GET /api/admin/units

Lista paginada de unidades de todas as clínicas.

Query params

tenant_id
uuid
Filtra por clínica.
search
string
Busca ILIKE em name e slug.

Response 200

Paginação de 50 itens por página:
{
  "data": [
    {
      "id": "uuid",
      "tenant_id": "uuid",
      "name": "string",
      "slug": "string | null",
      "is_active": true,
      "created_at": "iso8601",
      "updated_at": "iso8601"
    }
  ],
  "current_page": 1,
  "per_page": 50,
  "total": 32
}

POST /api/admin/units

Cria uma unidade vinculada a uma clínica.

Request body

tenant_id
uuid
required
FK para a clínica.
name
string
required
Nome da unidade. Máximo 255 caracteres. Unicidade case-insensitive dentro da mesma clínica.
slug
string
Slug único global. Máximo 255 caracteres.

Response 201

Objeto Unit criado.

Erros

StatusSituação
404tenant_id não encontrado
422Nome duplicado na mesma clínica ("Já existe uma unidade com esse nome para esta clínica") ou slug não único

Modalities (Aparelhos DICOM)

GET /api/admin/modalities

Lista paginada de aparelhos DICOM cadastrados.

Query params

tenant_id
uuid
Filtra por clínica.
unit_id
uuid
Filtra por unidade.
search
string
Busca ILIKE em name e aet.

Response 200

Paginação de 50 itens por página:
{
  "data": [
    {
      "id": 42,
      "tenant_id": "uuid",
      "unit_id": "uuid",
      "name": "string",
      "aet": "STRING_UPPERCASE",
      "vpn_ip": "10.0.0.5 | null",
      "created_at": "iso8601",
      "updated_at": "iso8601"
    }
  ],
  "current_page": 1,
  "per_page": 50,
  "total": 18
}

POST /api/admin/modalities

Cadastra um aparelho DICOM. O aet (AETitle) é usado pelo webhook do Orthanc para identificar a origem das imagens e resolver o tenant/unit.

Request body

tenant_id
uuid
required
FK para a clínica.
unit_id
uuid
required
FK para a unidade. Deve pertencer ao tenant_id informado.
name
string
required
Nome descritivo. Máximo 255 caracteres.
aet
string
required
Application Entity Title. Normalizado para maiúsculas (strtoupper(trim())). Único globalmente — o webhook usa isso pra resolver o tenant.
vpn_ip
string
IP fixo do aparelho no túnel WireGuard da clínica. Validado como IP (v4 ou v6).

Response 201

Objeto Modality criado.

Erros

StatusSituação
404tenant_id ou unit_id não encontrado, ou unit_id não pertence ao tenant
422Nome duplicado na mesma unidade, ou aet já existe em outra clínica

Users

GET /api/admin/users

Lista paginada de todos os usuários da plataforma, com tenant e unit via eager load.

Query params

tenant_id
uuid
Filtra por clínica.
unit_id
uuid
Filtra por unidade.
search
string
Busca ILIKE em name e email.

Response 200

Paginação de 50 itens por página:
{
  "data": [
    {
      "id": "uuid",
      "tenant_id": "uuid | null",
      "unit_id": "uuid | null",
      "name": "string",
      "email": "string",
      "crm": "123456/SP | null",
      "is_active": true,
      "is_super_admin": false,
      "is_tenant_admin": false,
      "has_signature": true,
      "tenant": { "id": "uuid", "name": "string", "slug": "string" },
      "unit": { "id": "uuid", "name": "string", "slug": "string" },
      "created_at": "iso8601",
      "updated_at": "iso8601"
    }
  ],
  "current_page": 1,
  "per_page": 50,
  "total": 124
}
O campo signature (base64, pesado) é hidden — só é exposto o booleano has_signature. O password nunca é exposto.

POST /api/admin/users

Cria um usuário.

Request body

tenant_id
uuid
required
FK para a clínica.
unit_id
uuid
required
FK para a unidade. Deve pertencer ao tenant_id informado.
name
string
required
Nome. Máximo 255 caracteres.
email
string
required
Normalizado para lowercase. Único globalmente.
password
string
required
Mínimo 8 caracteres. Hasheada automaticamente pelo cast do model. Sem confirmation no body.
crm
string
Formato NNNN/SS a NNNNNN/SS (4-6 dígitos + / + 2 letras maiúsculas). Ex.: 123456/SP.
signature
string
Data URL base64 (data:image/jpeg;base64,... ou data:image/png;base64,...). Mesma validação regex do logo do tenant.
is_tenant_admin
boolean
default:"false"
Se o usuário é administrador do tenant.
is_active
boolean
default:"true"
Se o usuário nasce ativo.

Response 201

Objeto User criado (com has_signature, sem signature/password).

Erros

StatusSituação
404tenant_id ou unit_id não encontrado
422Email duplicado, unit_id não pertence ao tenant, ou validação de crm/signature/password falhou

Notas importantes

  • O campo is_super_admin é sempre forçado para false no create — super admins só podem ser criados via seed ou console
  • Email sempre normalizado para lowercase antes de salvar

PATCH /api/admin/users/

Atualiza um usuário. Todos os campos opcionais.

Request body

Mesmos campos do POST, exceto:
  • passwordnão é aceito (não há endpoint admin para resetar senha)
  • is_super_adminimutável via API
A unicidade do email ignora o próprio registro (unique('users', 'email')->ignore($user->id)).

Response 200

Usuário atualizado.

Erros

StatusSituação
404Usuário, tenant_id ou unit_id não encontrado
422Email duplicado, unit_id não pertence ao tenant, ou validação falhou

Cobertura

Este documento cobre todos os 10 endpoints atualmente expostos sob /api/admin/* (conferidos em routes/api.php):
  • Tenants: GET, POST, PATCH /{id}/status
  • Units: GET, POST
  • Modalities: GET, POST
  • Users: GET, POST, PATCH /{id}
Operações de manutenção que ainda não existem (e precisariam ser adicionadas no backend primeiro): PATCH /admin/units/{id}/status, DELETE /admin/modalities/{id}, DELETE /admin/users/{id}, reset de senha via admin.