# Erros de API (problem+json) e paginação (cursor, header Link)

> A RFC 9457 problem+json normaliza as respostas de erro como um objeto JSON com type, title, status, detail e instance, servido como application/problem+json — para que os clientes façam parse dos erros de forma uniforme. A paginação divide coleções grandes em páginas usando um cursor (estável) ou offset (simples), com links next/prev transportados no header Link da RFC 8288.

- URL canônico: https://www.broadnet.me/pt/reference/api-errors-and-pagination
- Idioma: pt
- Última atualização: 2026-07-19
- Editor: BroadNet (https://www.broadnet.me)

Duas normas que mantêm uma API HTTP previsível: RFC 9457 problem+json para erros legíveis por máquina (type, title, status, detail, instance) com uma taxonomia retryable-vs-terminal, e paginação — cursor vs offset, o header Link da RFC 8288 para next/prev e limites de tamanho de página.

## problem+json: um objeto de erro padrão

Formas de erro à medida obrigam cada cliente a tratar como caso especial cada API. A RFC 9457 (que torna obsoleta a RFC 7807) fixa um único formato de erro JSON, extensível, servido com o media type application/problem+json para que um cliente saiba interpretá-lo como um problema. O objeto tem cinco membros padrão: type, um URI que identifica a classe do problema e serve também de link para documentação humana; title, um resumo curto, estável e legível dessa classe; status, o status code HTTP ecoado no corpo; detail, uma explicação legível específica desta ocorrência; e instance, um URI que identifica esta ocorrência em particular. Quaisquer membros adicionais (um array de erros de validação, um request id, uma pista de retry) estendem o objeto sem quebrar os padrão, pelo que um cliente genérico lê os campos comuns e um cliente específico lê também as extensões.

*Corpo de erro problem+json da RFC 9457*

```json
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://example/problems/invalid-destination",
  "title": "Invalid destination number",
  "status": 422,
  "detail": "The field 'to' is not a valid E.164 number.",
  "instance": "/v1/messages/req-01HZY9",
  "errors": [ { "field": "to", "issue": "not_e164" } ]
}
```

## Type, title e instance

A disciplina que torna o problem+json útil é manter type e title estáveis e orientados a máquina, ao passo que detail e instance variam por ocorrência. Um cliente deve ramificar por type (um URI estável), nunca pela prosa em detail ou title, porque detail é livre de mudar e pode ser localizado. O URI de type resolve idealmente para documentação dessa classe de erro, para que um programador que o siga aterre numa explicação e num remédio. instance identifica o pedido específico que falhou — útil para citar num ticket de suporte — e espelha muitas vezes um request id que a API também devolve num header. Como type e title identificam a classe e status/detail/instance descrevem a ocorrência, um tipo de problema pode ser reutilizado em muitas falhas sem ambiguidade.

## Taxonomia de erros: retryable vs terminal

Para além da forma, o cliente precisa de saber o que fazer, e a classe do status code responde a isso. Um 4xx é um erro de cliente: o pedido tal como enviado nunca terá sucesso, pelo que repeti-lo sem alteração é inútil — a correção é corrigir o pedido (400/422 para input inválido, 401/403 para autenticação, 404 para um recurso em falta, 409 para um conflito). Um 5xx é um erro de servidor ou a montante: o pedido pode muito bem ter sucesso mais tarde, pelo que é retryable com backoff — idealmente sob o mesmo Idempotency-Key para que o retry não possa enviar em duplicado. O 429 é o caso transitório especial: retryable, mas só depois do atraso Retry-After. Codificar isto no status code (e reforçá-lo com um tipo de problema) permite a um cliente decidir automaticamente entre corrigir-e-reenviar, recuar-e-repetir e desistir.

*Classe de erro e a ação correta do cliente*

| Classe | Significado | Permite nova tentativa? | Ação do cliente |
|---|---|---|---|
| 400 / 422 | Entrada malformada/inválida | No | Corrija a solicitação e envie-a novamente |
| 401 / 403 | Não autenticado / proibido | No | Corrija as credenciais ou o âmbito |
| 404 | Recurso não encontrado | No | Corrija o identificador |
| 409 | Conflito de estado (ex: reutilização de chave) | No | Resolver o conflito |
| 429 | Limite de taxa excedido | Sim (com atraso) | Aguardar o Retry-After e tentar novamente |
| 500 / 502 / 503 | Falha no servidor/upstream | Sim | Backoff + nova tentativa com a mesma chave de idempotência |

## Paginação: cursor vs offset

Um endpoint de coleção que poderia devolver milhões de mensagens tem de paginar. Os dois modelos principais trocam simplicidade por correção. A paginação por offset (?limit=50&offset=100) é trivial e permite ao cliente saltar para uma página arbitrária, mas é instável numa coleção em mudança: se linhas forem inseridas ou apagadas entre páginas, os itens deslocam-se e o cliente salta ou repete registos — e offsets profundos ficam lentos porque o armazenamento tem de percorrer e descartar tudo o que os antecede. A paginação por cursor (keyset) entrega ao cliente um cursor opaco que codifica a posição do último item visto (por exemplo um id e um timestamp); o pedido seguinte pede as linhas após esse cursor. É estável sob escritas concorrentes e mantém-se rápida a qualquer profundidade porque a query salta diretamente para o cursor, ao custo de não suportar saltos arbitrários de página. Para recursos de alto volume e append-heavy, como logs de mensagens, a paginação por cursor é o default correto.

*Paginação por cursor vs offset*

| Propriedade | Offset | Cursor (conjunto de teclas) |
|---|---|---|
| Requisição | ?limit=50&offset=100 | ?limit=50&after=<cursor> |
| Salto de página arbitrário | Sim | Não (somente sequencial) |
| Estável sob inserções/exclusões | Não — os itens mudam de posição | Sim |
| Desempenho em páginas profundas | Degrada (escanear + descartar) | Constante (seek até o cursor) |
| Ideal para | Coleções pequenas e estáveis | Logs grandes, com muitas inclusões ao final (append) |

## O header Link (RFC 8288)

Em vez de enterrar os URLs next/previous no corpo, uma API pode transportá-los no header Link padrão definido pela RFC 8288 (Web Linking), usando tipos de relação registados — next, prev, first, last. O cliente segue o link rel="next" para avançar sem construir URLs por si nem conhecer o esquema de cursor, o que mantém o mecanismo de paginação inteiramente controlado pelo servidor e o deixa evoluir (offset hoje, cursor amanhã) sem quebrar clientes. Uma abordagem redundante comum também espelha os links no corpo JSON para clientes que não leem headers, mas o header Link é o lar normalizado, ao nível do transporte, para eles e é o mesmo mecanismo que um browser ou um cliente de API bem-comportado já compreendem.

*Paginação através do header Link da RFC 8288*

```http
HTTP/1.1 200 OK
Content-Type: application/json
Link: </v1/messages?after=01HZY8Q3M6&limit=50>; rel="next",
      </v1/messages?limit=50>; rel="first"

{ "data": [ /* up to 50 messages */ ],
  "page": { "count": 50, "has_more": true } }
```

## Limites e defaults de tamanho de página

O tamanho da página tem de ser limitado no servidor, não deixado ao cliente, ou um único pedido de uma página enorme torna-se um vetor de negação de serviço e uma fonte de latência imprevisível. O padrão é um default sensato (usado quando o cliente omite limit) e um máximo rígido a que o servidor limita silenciosamente — um cliente que peça mais recebe simplesmente o máximo. Anunciar tanto o tamanho de página efetivo como se restam mais páginas (uma flag has_more ou a presença de um link rel="next") permite a um cliente iterar com segurança: continuar a seguir next até deixar de haver next. Combinado com paginação por cursor e erros problem+json, isto dá uma API de coleção previsível de consumir a qualquer escala. A BroadNet documenta os seus códigos de erro e paginação concretos para programadores.

## Perguntas frequentes

### O que é o problem+json e porquê usá-lo?

problem+json (RFC 9457, que torna obsoleta a RFC 7807) é um formato de erro JSON padrão servido como application/problem+json, com os membros type, title, status, detail e instance. Permite que cada cliente faça parse dos erros da mesma forma em vez de tratar como caso especial uma forma à medida por API, e é extensível para que membros extra possam ser acrescentados sem quebrar os padrão.

### Por que campo do problem+json deve um cliente ramificar?

type — um URI estável que identifica a classe de erro. title e detail são legíveis por humanos e podem mudar ou ser localizados, pelo que o código nunca deve decidir pela sua prosa. O URI de type resolve idealmente para documentação desse erro, ao passo que status, detail e instance descrevem a ocorrência específica.

### Como sabe um cliente se deve repetir um erro?

Pela classe do status. Um 4xx é um erro de cliente que não terá sucesso sem alteração, pelo que se corrige o pedido em vez de repetir. Um 5xx é uma falha de servidor/a montante que pode ter sucesso mais tarde, pelo que se repete com backoff sob o mesmo Idempotency-Key. O 429 é retryable mas só depois do atraso Retry-After.

### Qual é a diferença entre paginação por cursor e por offset?

O offset (?limit&offset) é simples e suporta saltar para qualquer página, mas é instável quando linhas são inseridas ou apagadas entre pedidos e abranda em offsets profundos. A paginação por cursor (keyset) passa um marcador opaco do último item visto e pede as linhas após ele — estável sob escritas concorrentes e rápida a qualquer profundidade, mas só sequencial. O cursor é o default certo para logs de mensagens grandes.

### Como ajuda o header Link da RFC 8288 na paginação?

Transporta os URLs das páginas next, prev, first e last num header padrão usando tipos de relação registados, para que o cliente siga rel="next" para avançar sem construir URLs nem conhecer o esquema de cursor. Isso mantém o mecanismo de paginação controlado pelo servidor e livre de evoluir, e é um mecanismo que os clientes HTTP genéricos já compreendem.

### Porquê limitar o tamanho de página no servidor?

Porque um tamanho de página ilimitado deixa um pedido puxar um conjunto de resultados enorme, o que é um risco de negação de serviço e dá latência imprevisível. O servidor aplica um default quando limit é omitido e limita silenciosamente a um máximo rígido, e anuncia se restam mais páginas para que os clientes possam iterar seguindo next até este estar ausente.

## Fontes

- [IETF RFC 9457 — Problem Details for HTTP APIs](https://datatracker.ietf.org/doc/html/rfc9457)
- [IETF RFC 8288 — Web Linking (Link header)](https://datatracker.ietf.org/doc/html/rfc8288)

## Relacionados

- [Design de API REST de mensagens](https://www.broadnet.me/pt/reference/rest-messaging-api-design)
- [Idempotência e rate limiting](https://www.broadnet.me/pt/reference/idempotency-and-rate-limiting)
- [Autenticação de API e assinatura](https://www.broadnet.me/pt/reference/api-authentication-and-signing)
- [Códigos de erro da BroadNet](https://www.broadnet.me/pt/developers/error-codes)
- [Guia de paginação da BroadNet](https://www.broadnet.me/developers/pagination)
