Docs›REST API›JSON responses
REST API

JSON responses

Formato de cada ação, status codes, projeção de campos, transformers.

Toda ação de ApiResourceController devolve um Illuminate\Http\JsonResponse via response()->json(...) — sem echo, sem view, sem MadResponse (esse é o builder de ops parciais do MadWire, da camada reativa do admin; aqui a resposta é o corpo JSON inteiro, de uma vez).

Listagem — GET /recurso

Params aceitos (query ou body JSON): filters, sort, direction (asc/desc), page, per_page.

200 OK
{
  "data": [
    { "id": 1, "title": "Clientes", "groups_count": 2, "users_count": 1 }
  ],
  "meta": {
    "total": 1,
    "per_page": 15,
    "current_page": 1,
    "last_page": 1
  }
}

Um registro — GET /recurso/{id}

Corpo = o item, com a projeção de $showFields (ou toArray() completo se vazio), $appends e detalhes eager-loaded.

200 OK
{ "id": 1, "title": "Clientes", "groups": [...], "users": [...] }

404 Not Found
{ "error": "Resource not found" }

Criar / atualizar — POST · PUT/PATCH

Payload = master + uma chave por relação declarada em $details (array de linhas). Resposta = o master recarregado com o eager-load default (não é o array que você enviou — é o que ficou gravado, já com IDs gerados e relações atualizadas):

POST /api/import-templates → 201 Created
{
  "id": 42,
  "title": "Clientes",
  "groups": [ { "id": 1, "group_id": 3 } ],
  "users":  [ { "id": 1, "user_id": 7 } ]
}

PUT /api/import-templates/42 → 200 OK
{ "id": 42, "title": "Clientes (renomeado)", "groups": [...], "users": [...] }

Validação falha → Laravel lança ValidationException, renderizada pelo handler nativo como 422 com o shape padrão {message, errors} — o mesmo formato que qualquer formulário Laravel produz:

422 Unprocessable Content
{
  "message": "The title field is required.",
  "errors": {
    "title": ["The title field is required."],
    "mapping_json": ["The mapping json field is required."]
  }
}

Apagar — DELETE /recurso/{id}

Apaga master + detalhes em cascata (uma linha por relação de $details), em transação:

200 OK
{ "message": "Resource deleted successfully" }

404 Not Found
{ "error": "Resource not found" }

Projeção e alias — $indexFields / $showFields

Vazio → resposta = toArray() (tudo menos $hidden, mais $appends e detalhes carregados). Com uma lista, suporta dot-notation de relação (achatada para relacao_campo) e alias por chave:

protected array $indexFields = [
    'id',
    'total',
    'cliente' => 'customer.name',  // alias → chave 'cliente' na resposta
    'customer.email',              // achata → chave 'customer_email'
];

A PK do recurso sempre aparece na resposta (mesmo fora da lista), a menos que esteja explicitamente em $hidden.

Transformers

Pós-processam um campo já serializado — útil para formatar valores na borda sem mexer no model:

// dentro do controller (construtor, boot, ou onde fizer sentido)
$this->addTransformer('price', fn ($v, $model) => 'R$ ' . number_format($v, 2, ',', '.'));

Status codes usados

CodeQuando
200index, show, update, destroy bem-sucedidos.
201store bem-sucedido.
401mad.auth negou (sem sessão), ou mad.api sem Bearer / com token inválido, expirado ou revogado (resposta {"error": "…"} + header WWW-Authenticate: Bearer) — ver Autenticação.
403mad.api: escopo do token inválido na revalidação (user/unit/tenant inexistente ou inativo, unit fora do usuário, unit sem empresa em modo de isolamento) ou token sem a ability exigida pela rota. Também o que o authorize() do controller costuma lançar.
404show/update/destroy com PK inexistente, ou rota sem match dentro de /api/*.
422ValidationException — master ou alguma linha de detalhe falhou Model::rules().
429mad.api: brute-force por IP (20 falhas de auth em 600s) — corpo {"error": "Too many requests. Try again later."} + Retry-After: 600.
500Exceção não tratada — também renderizada como JSON (ver callout abaixo), nunca página de erro HTML.
Por que /api/* nunca devolve HTML

bootstrap/app.php tem $exceptions->shouldRenderJsonWhen(fn ($request) => $request->is('api/*')). Isso vale para QUALQUER exceção que escape do controller — não só as que o ApiResourceController trata explicitamente (404/422). Um erro de SQL, um TypeError, uma LogicException do seu código: tudo vira JSON ({"message": "..."}, com stack trace se APP_DEBUG=true), consistente com o resto do contrato desta API.

Próximos passos