Uma API pública é para sempre — projete-a como tal

Apostila completa de API Design: REST, GraphQL e gRPC

Uma API é um contrato: outras pessoas escrevem código contra ela e você não pode quebrá-lo à toa. Esta apostila cobre a semântica do HTTP, o design RESTful de verdade, o contrato OpenAPI, um modelo de erros consistente, GraphQL e gRPC com seus trade-offs, a segurança segundo o OWASP API Top 10, e a evolução sem quebrar clientes — sempre com o olho no que empresas pedem em vagas de back-end e plataforma.

10 módulosHTTP · REST · OpenAPIGraphQL · gRPC · ProtobufOAuth2 · OWASP API Top 10Boxes de entrevistaExercícios com gabarito
MÓDULO 01 · BÁSICO

A API como contrato e como produto

Objetivo: entender por que design de API é uma decisão de longo prazo, o que significa "API-first", e o panorama dos estilos disponíveis.

1.1 Uma API é um contrato

Quando você publica uma API, outras equipes (ou empresas, ou o seu app mobile de 2 anos atrás) escrevem código que depende do formato exato das suas requisições e respostas. Mudar um nome de campo, um código de status, o tipo de um valor — quebra o código de quem confiou no contrato. Diferente de uma função interna, que você refatora à vontade, uma API pública tem inércia enorme: clientes que você não controla, que atualizam devagar ou nunca.

💡 A regra que organiza a apostila

Projete a API como se ela fosse durar 10 anos e nunca poder quebrar. Isso força as decisões certas: contrato explícito, nomes claros e estáveis, erros consistentes, extensibilidade sem breaking change, e versionamento pensado desde o dia 1. "A gente arruma depois" custa caríssimo em API.

1.2 API como produto

Para quem consome, a sua API é o seu produto. As qualidades que importam:

1.3 API-first (design-first)

Em vez de codar o serviço e "gerar a API do que saiu", você projeta o contrato primeiro (OpenAPI/schema), revisa com os consumidores, e só então implementa — cliente e servidor trabalham em paralelo contra o mesmo contrato, com mocks. Reduz retrabalho, alinha as equipes e produz uma API pensada para quem usa, não para o esquema do banco.

1.4 O panorama de estilos

EstiloIdeiaBrilha em
REST / HTTPRecursos identificados por URL, manipulados por métodos HTTPAPIs públicas, CRUD, integração entre partes heterogêneas, cacheável
GraphQLUm schema tipado; o cliente pede exatamente os campos que quer em uma queryFront-ends variados (web/mobile) que precisam de dados de várias fontes sem N chamadas
gRPC / RPCChamar funções remotas com contrato binário (Protobuf) sobre HTTP/2Comunicação interna entre serviços, alta performance, streaming, polyglot
Eventos / Webhooks / AsyncO servidor notifica o cliente quando algo aconteceIntegrações reativas, evitar polling, fluxos assíncronos

Não é "um vence" — sistemas maduros usam vários: REST para a API pública, gRPC entre serviços internos, GraphQL para o BFF do app, webhooks para integrações. Os módulos 3, 6 e 7 aprofundam cada um e o módulo 7 fecha a comparação.

💼 Mercado de trabalho

Design de API é competência central de back-end e o gargalo em muitas vagas de plataforma. Perguntas de abertura: "Por que uma API é diferente de uma função interna?" (contrato com clientes que você não controla), "O que é API-first?", "Quando você escolheria GraphQL em vez de REST?". Demonstrar que você pensa em quem consome e em evolução vale mais que decorar convenções.

✏️ Exercício 1 — Contrato ou implementação?

Para cada mudança, diga se ela quebra o contrato (breaking) ou não: (a) adicionar um campo opcional na resposta; (b) renomear user_name para username; (c) tornar obrigatório um campo de entrada que era opcional; (d) mudar o código de sucesso de 200 para 201; (e) trocar o tipo de id de número para string; (f) adicionar um novo endpoint.

Gabarito: Não-breaking: (a) e (f) — adição de campo opcional e de endpoint são compatíveis (se os clientes ignoram o que não conhecem). Breaking: (b) renomear campo; (c) novo obrigatório rejeita requisições antes válidas; (d) clientes que checam == 200 quebram; (e) mudança de tipo quebra parsing. Regra: adicionar é seguro, mudar/remover/tornar-obrigatório não é.

MÓDULO 02 · BÁSICO

HTTP a fundo

Objetivo: dominar a semântica que sustenta toda API HTTP — métodos, códigos de status, headers, negociação de conteúdo e cache — porque usá-los errado é o erro mais comum.

2.1 Métodos e suas propriedades

MétodoUsoSafe?Idempotente?
GETLer um recurso; sem efeito colateralSimSim
HEADComo GET, só os headersSimSim
POSTCriar; ação não idempotente; operações que não cabem em CRUDNãoNão
PUTSubstituir o recurso inteiro (ou criar com id conhecido)NãoSim
PATCHAtualização parcialNãoNão necessariamente
DELETERemoverNãoSim

2.2 Códigos de status: as famílias e os que importam

FamíliaSignificadoOs que você usa
2xxSucesso200 OK, 201 Created (+ Location), 202 Accepted (async), 204 No Content
3xxRedirecionamento301/308 permanente, 304 Not Modified (cache)
4xxErro do cliente400 (inválido), 401 (não autenticado), 403 (sem permissão), 404, 405, 409 (conflito), 410 (foi-se), 422 (semântica inválida), 429 (rate limit)
5xxErro do servidor500 (bug), 502/503/504 (indisponível/timeout — o cliente pode retry)
⚠️ Erros clássicos de status

Retornar 200 com {"error": "..."} no corpo (quebra todo cliente e todo proxy que confia no status); usar 401 quando é 403 (autenticado mas sem permissão) e vice-versa; 404 para "sem permissão de ver" pode ser proposital (não revelar existência) — decida conscientemente; 500 para erro de validação do cliente (é 400/422). Use o status certo; o corpo dá o detalhe.

2.3 Headers que importam

2.4 CORS (o que confunde todo mundo)

CORS é uma proteção do navegador: por padrão, JavaScript numa origem não pode ler a resposta de outra origem, a menos que o servidor mande headers Access-Control-Allow-Origin etc. autorizando. Não é segurança da API (um cliente não-navegador ignora CORS) — é controle de quais sites no browser podem chamá-la. Requisições "não simples" disparam um preflight OPTIONS. Configure a allowlist de origens explicitamente; nunca * junto com credenciais.

💼 Mercado de trabalho

Perguntas quase garantidas: "Diferença entre PUT e PATCH; e entre PUT e POST", "O que significa um método ser idempotente e por que importa para retries?", "Quando 401 vs 403 vs 404?", "O que é CORS e por que 'funciona no Postman mas não no browser'?" (CORS é do navegador), "Como implementar concorrência otimista com ETag?".

✏️ Exercício 2 — Escolha método e status

Para cada operação, dê método + status de sucesso + um status de erro relevante: (a) criar um artigo; (b) marcar um artigo como publicado; (c) buscar um artigo que não existe; (d) editar o título de um artigo que outra pessoa alterou no meio-tempo; (e) apagar um artigo já apagado.

Gabarito: (a) POST /articles → 201 + Location; erro: 422 se o corpo é inválido, 409 se o slug já existe. (b) POST /articles/{id}/publish (ação) ou PATCH com {status:"published"} → 200; erro: 409 se já publicado / regra de negócio. (c) GET → 404. (d) PATCH com If-Match: "etag" → 200; erro: 412 Precondition Failed (o ETag não bate — recurso mudou). (e) DELETE → 204 (idempotente: deletar o já deletado ainda é "não existe mais" = sucesso), ou 404 se você prefere sinalizar; escolha e seja consistente.

MÓDULO 03 · INTERMEDIÁRIO

Design de APIs REST

Objetivo: modelar recursos, URIs, coleções, filtros e paginação segundo as convenções que tornam uma API REST previsível — e saber onde o "REST puro" cede à praticidade.

3.1 Recursos e URIs

3.2 Ações que não são CRUD

"Publicar um artigo", "reembolsar um pagamento", "arquivar" — nem tudo é criar/ler/atualizar/apagar. Opções, em ordem de preferência:

  1. Modelar como um sub-recurso: PUT /articles/{id}/publication, POST /payments/{id}/refunds (o reembolso é um recurso).
  2. Endpoint de ação explícito: POST /articles/{id}/publish — pragmático e claro; muito comum, ninguém vai te processar por isso.
  3. Campo de estado num PATCH: PATCH {id} {"status": "published"} — bom se o estado é o modelo real.

3.3 Coleções: filtro, ordenação, paginação, campos

GET /orders?status=paid&created_after=2026-01-01&sort=-created_at&page[size]=50&fields=id,total,customer

filtro       →  ?status=paid&min_total=100   (query params; nomes previsíveis)
ordenação    →  ?sort=-created_at,total       (prefixo - para desc)
projeção     →  ?fields=id,total              (sparse fieldsets — reduz payload)
expansão     →  ?expand=customer,items        (inclui recursos relacionados)

Paginação: as duas estratégias

Offset / page-number (?page=3&size=50)Cursor / keyset (?after=eyJ...&limit=50)
SimplicidadeFácil de entender e de "pular para a página 7"Cursor opaco; sem "pular para página N"
PerformanceDegrada em offsets grandes (o banco varre e descarta)Estável em qualquer profundidade (usa índice)
ConsistênciaItens inseridos/removidos deslocam páginas (duplica ou pula)Estável mesmo com escrita concorrente
Use quandoDatasets pequenos, UI com números de páginaFeeds, listas grandes, scroll infinito, exportação — o padrão para escala

Sempre retorne metadados de paginação (total quando barato, next/prev links ou next_cursor) e imponha um limit máximo.

3.4 O Richardson Maturity Model e o HATEOAS

💼 Mercado de trabalho

Perguntas: "Como você modelaria a API de um blog?" (recursos, URIs, métodos, ação de publicar), "Offset ou cursor pagination, e por quê?" (cursor para escala — offset degrada e é inconsistente sob escrita), "O que é HATEOAS e você usa?" (nível 3 do RMM; na prática, links de paginação/relacionados sim, HATEOAS completo raramente), "Como fazer uma ação que não é CRUD de forma RESTful?".

✏️ Exercício 3 — Projete a API

Desenhe os endpoints para: uma biblioteca com livros e empréstimos. Um usuário pode pegar um livro emprestado, devolver, e renovar. Liste método + path para cada operação e como você pagina a lista de livros.

Gabarito (uma boa resposta): GET /books?author=&available=true&sort=title&limit=50&after=cursor (cursor pagination); GET /books/{id}; POST /loans {book_id, user_id} → 201 (empréstimo é um recurso; 409 se o livro não está disponível); GET /loans/{id}; GET /users/{id}/loans?status=active; devolver: POST /loans/{id}/return → 200 (ação) ou PATCH /loans/{id} {"status":"returned"}; renovar: POST /loans/{id}/renewals → 201 (renovação como sub-recurso, com regra de "máx. 2 renovações" → 409/422). IDs opacos.

MÓDULO 04 · INTERMEDIÁRIO

Contrato e especificação: OpenAPI

Objetivo: descrever a API num contrato legível por máquina, escolher entre design-first e code-first, e aproveitar o ecossistema (docs, SDKs, mocks, lint).

4.1 OpenAPI (ex-Swagger)

Um documento YAML/JSON que descreve cada endpoint: path, método, parâmetros, corpo de requisição e resposta (com JSON Schema), códigos de status, autenticação e exemplos. É o padrão de fato para APIs HTTP.

# trecho de openapi.yaml
paths:
  /orders/{orderId}:
    get:
      operationId: getOrder
      parameters:
        - { name: orderId, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        '200':
          description: O pedido
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Order' }
        '404':
          $ref: '#/components/responses/NotFound'
components:
  schemas:
    Order:
      type: object
      required: [id, status, total]
      properties:
        id:     { type: string, format: uuid }
        status: { type: string, enum: [pending, paid, shipped, cancelled] }
        total:  { type: integer, description: 'em centavos' }

4.2 Design-first × code-first

Design-firstCode-first
Escreve o OpenAPI à mão (ou com editor), revisa, depois implementaAnota o código (decorators/atributos) e gera o OpenAPI a partir dele
+ contrato pensado para o consumidor; cliente e servidor em paralelo; mocks desde já+ menos duplicação; a spec nunca "mente" sobre o código
− risco de a implementação divergir da spec (mitigar com contract testing)− a API acaba refletindo o modelo interno; refino de design fica em segundo plano

Muitos times fazem híbrido: design-first para o formato e as decisões, geração a partir do código para os detalhes, e contract testing (Dredd, Schemathesis, Pact) garantindo que implementação e spec batem.

4.3 O que o contrato te dá "de graça"

💡 Style guide é o que dá consistência

Uma API grande feita por muitos times só fica coerente com um guia de estilo escrito (nomenclatura, paginação, formato de erro, datas, versionamento) e um linter que o aplica no CI. As guidelines públicas de Google, Microsoft, Stripe e Zalando são ótimas bases para adaptar.

💼 Mercado de trabalho

Perguntas: "O que é OpenAPI e para que serve?", "Design-first ou code-first?" (trade-offs; híbrido + contract testing), "Como você mantém consistência numa API com 200 endpoints e 10 times?" (style guide + Spectral no CI), "O que é contract testing?". Ter um repo com um OpenAPI bem feito no portfólio já sinaliza senioridade.

✏️ Exercício 4 — Regras de lint

Escreva (em português, como regras) 6 itens de um style guide de API que um linter poderia checar automaticamente.

Gabarito (exemplos): (1) Todo endpoint deve documentar as respostas 400, 401 e 500. (2) Coleções (paths no plural sem {id} no fim) devem aceitar limit e um parâmetro de cursor. (3) Nomes de propriedade em camelCase (ou snake_case) — um só, sem exceção. (4) Datas em string com format: date-time (ISO 8601, UTC). (5) Todo POST que cria recurso responde 201 com header Location. (6) Toda resposta de erro usa o schema ProblemDetails. (7) Nenhum path com mais de 3 níveis de aninhamento. (8) operationId presente e único.

MÓDULO 05 · INTERMEDIÁRIO

Erros, validação e formatos

Objetivo: um modelo de erro consistente e útil, validação de entrada robusta, e convenções de formato (datas, dinheiro, ids, null) que evitam ambiguidade.

5.1 Um modelo de erro consistente

Nada frustra mais um integrador do que cada endpoint errar de um jeito. Adote um formato para toda a API. O padrão recomendado é o Problem Details for HTTP APIs (RFC 9457, ex-7807):

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

{
  "type": "https://api.exemplo.com/problems/validation-error",
  "title": "Corpo da requisição inválido",
  "status": 422,
  "detail": "O campo 'email' não é um e-mail válido.",
  "instance": "/orders",
  "errors": [
    { "field": "email", "code": "invalid_format", "message": "formato inválido" },
    { "field": "items", "code": "min_length", "message": "pelo menos 1 item" }
  ],
  "traceId": "abc-123"
}

5.2 Validação de entrada

5.3 Convenções de formato

TipoConvenção
Data/horaString ISO 8601 em UTC com offset (2026-08-30T14:00:00Z). Nunca timestamps ambíguos, nunca só "data" quando é datetime.
DinheiroInteiro em unidade mínima (centavos) + código de moeda ({"amount": 1990, "currency": "BRL"}). Float para dinheiro é bug esperando acontecer.
IDsString opaca (UUID/ULID). Não prometa que é numérico nem que é ordenável.
EnumsStrings minúsculas estáveis ("in_transit"); documentadas; adicionar valor novo é potencialmente breaking para clientes que fazem switch exaustivo — comunique.
Booleanostrue/false reais, não "true"/1/"Y".
AusênciaDecida: omitir a chave vs null explícito. Em PATCH, essa distinção importa (omitido = não mexer; null = limpar) — considere JSON Merge Patch (RFC 7386) ou JSON Patch (RFC 6902).
Coleções vazias[], nunca null.

5.4 Envelope ou não?

Envelopar toda resposta em {"data": ..., "meta": ...} vs retornar o recurso direto. Prós do envelope: espaço padronizado para paginação, warnings, metadados; consistência. Contras: verbosidade, um nível a mais. Escolha um e aplique a toda a API. Comum: recurso direto para item único, envelope leve para coleções (por causa da paginação).

💼 Mercado de trabalho

Perguntas: "Como você padroniza erros numa API?" (Problem Details / RFC 9457, código legível por máquina + mensagem humana, traceId, todos os erros de validação de uma vez, sem stack trace), "Como representar dinheiro em JSON?" (inteiro em centavos + moeda), "Como um PATCH distingue 'não mexer' de 'limpar o campo'?" (omitido vs null; JSON Merge/JSON Patch).

✏️ Exercício 5 — Conserte a resposta de erro

Uma API retorna, para um e-mail inválido: HTTP 200, corpo {"success": false, "msg": "erro"}. Aponte os problemas e escreva a versão correta.

Gabarito: Problemas: status 200 para erro (quebra clientes e proxies), "success: false" no corpo obriga o cliente a inspecionar sempre, "erro" não diz nada, não há código legível por máquina, não indica qual campo, não há traceId. Correto: HTTP 422, Content-Type: application/problem+json, corpo com type (URI da classe de erro), title, status: 422, detail humano, errors: [{field:"email", code:"invalid_format", message:"..."}] e traceId.

MÓDULO 06 · AVANÇADO

GraphQL

Objetivo: entender o modelo do GraphQL, os problemas que ele resolve (over/under-fetching) e os que ele cria (N+1, cache, segurança), e quando ele é a escolha certa.

6.1 O modelo

Você publica um endpoint (POST /graphql) e um schema tipado. O cliente manda uma query declarando exatamente a árvore de campos que quer; o servidor resolve cada campo com uma função resolver e devolve exatamente aquela forma.

# schema
type Query { order(id: ID!): Order }
type Order {
  id: ID!
  total: Int!
  customer: Customer!
  items: [OrderItem!]!
}

# query do cliente — pede só o que precisa, de várias entidades, numa ida só
query {
  order(id: "o_123") {
    total
    customer { name email }
    items { productName quantity }
  }
}

6.2 Os problemas que o GraphQL traz

ProblemaExplicaçãoMitigação
N+1Resolver items de 100 orders dispara 100 queries ao banco, uma por orderDataLoader: agrupa (batch) e faz cache das buscas por chave dentro de uma request
Cache HTTPTudo é POST /graphql — o cache de GET do HTTP/CDN não funciona de graçaPersisted queries (o cliente envia um hash; vira GET cacheável), cache no nível de campo/entidade (Apollo), APQ
Consultas abusivasUm cliente pode pedir uma query profundamente aninhada e cara (ataque ou acidente)Limite de profundidade, análise de complexidade/custo, timeout, paginação obrigatória, allowlist de queries
ErrosGraphQL retorna 200 com {data, errors} — sucesso parcial é possível e confundeConvenção de erros clara (errors como dados tipados nas mutations — "errors as data"), documentar o contrato
Observabilidade/rate limitUm path (/graphql) — métricas por endpoint e rate limit por rota não distinguem operaçõesInstrumentar por operationName/campo; rate limit por custo

6.3 Paginação no GraphQL

A convenção madura é cursor connections (padrão Relay): edges { node cursor } + pageInfo { hasNextPage endCursor }. É cursor-based (Módulo 3), padronizado e componível.

6.4 Quando usar (e não usar) GraphQL

GraphQL brilhaPrefira REST/gRPC
Muitos clientes (web, iOS, Android) com necessidades de dados diferentesAPI pública simples, CRUD, para integradores heterogêneos
Telas que agregam dados de vários serviços (BFF, gateway de dados)Você quer cache HTTP/CDN sem esforço
Evolução rápida do front sem redeploy do back a cada campo novoUploads de arquivo, streaming binário, latência mínima entre serviços (gRPC)
Grafo de dados naturalmente interconectadoTime pequeno sem apetite para operar o runtime, DataLoader, custo, segurança
💼 Mercado de trabalho

Perguntas: "Que problema o GraphQL resolve em relação ao REST?" (over/under-fetching; um roundtrip para dados de várias fontes), "O que é o problema N+1 e como o DataLoader ajuda?", "Por que cache é mais difícil em GraphQL?" (tudo é POST num endpoint; persisted queries), "Como você protege um endpoint GraphQL de queries abusivas?" (depth/complexity limiting, timeout, allowlist).

✏️ Exercício 6 — Diagnóstico

Um resolver de User.posts num feed que lista 50 usuários está fazendo 51 queries ao banco e a latência é 1,2 s. Explique e corrija. Depois: o time reclama que o CDN não cacheia nada — por quê e o que fazer?

Gabarito: (1) N+1: 1 query para os 50 usuários + 1 por usuário para os posts. Corrigir com um DataLoader de posts por userId: dentro da request, ele acumula os 50 ids e faz SELECT ... WHERE user_id IN (...) numa query, com cache por chave. Latência cai para ~2 queries. (2) CDN não cacheia porque toda operação é POST /graphql com corpo variável — o cache de GET não se aplica. Solução: automatic/trusted persisted queries (o cliente manda um hash da query; o servidor serve via GET cacheável), e/ou cache de entidade no cliente (Apollo/urql) e cache de resposta no gateway por operação.

MÓDULO 07 · AVANÇADO

gRPC e RPC

Objetivo: entender o modelo RPC com contrato Protobuf, os modos de streaming, a evolução de schema, e onde gRPC ganha de REST e de GraphQL.

7.1 O modelo

gRPC é "chamar uma função remota": você define o serviço e as mensagens em Protocol Buffers (.proto), o compilador gera stubs de cliente e servidor em várias linguagens, e a chamada trafega serializada em binário sobre HTTP/2 (multiplexação, header compression, streaming).

// pedido.proto — o contrato, versionável e polyglot
syntax = "proto3";

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc WatchOrders(WatchRequest) returns (stream OrderEvent);   // server streaming
}

message GetOrderRequest { string order_id = 1; }

message Order {
  string id = 1;
  int64  total_cents = 2;
  Status status = 3;
  enum Status { PENDING = 0; PAID = 1; SHIPPED = 2; }
}

7.2 Os quatro modos de chamada

7.3 Evolução de schema em Protobuf

Compatibilidade se ganha respeitando as regras dos números de campo (a "tag" que vai no wire, não o nome):

7.4 Quando gRPC

gRPC ganhaCuidados
Comunicação interna entre microserviços — baixa latência, payload compacto, contrato forteNavegadores não falam gRPC direto — precisa de gRPC-Web + proxy (Envoy), com limitações
Ambientes polyglot (Go chama Java chama Python) com um contrato sóMenos "explorável" que REST — sem curl trivial; ferramentas: grpcurl, Buf
Streaming nativo nos dois sentidosDebug e observabilidade exigem ferramental próprio
APIs de alta vazão (o Protobuf serializa/parseia muito mais rápido que JSON)APIs públicas para terceiros: REST ainda é o esperado
Geração de código elimina o "cliente HTTP escrito à mão"Governança do .proto (Buf Schema Registry, lint, breaking-change detection)

7.5 A comparação final, agora informada

CritérioRESTGraphQLgRPC
Público / terceiros★★★ o padrão★★ ok★ raro
Front-end com dados de várias fontes★★★
Entre microserviços internos★★★★★
Cache HTTP / CDN★★★★ (persisted queries)
Streaming★ (SSE/WebSocket à parte)★★ (subscriptions)★★★
Performance de serialização★★ (JSON)★★ (JSON)★★★ (binário)
Curva / ferramental universal★★★★★★★

Arquitetura típica: REST na fronteira pública, gRPC entre serviços, GraphQL no BFF que serve os apps.

💼 Mercado de trabalho

Perguntas: "Quando gRPC em vez de REST?" (interno, alta vazão, streaming, polyglot; não para browser/público sem proxy), "Como evoluir um schema Protobuf sem quebrar clientes?" (não mudar/reusar números de campo; reserved ao remover; só adicionar), "Os 4 modos de streaming do gRPC", "Por que Protobuf é mais rápido que JSON?" (binário, schema conhecido, sem parsing de texto).

✏️ Exercício 7 — Evolua o contrato

Você tem message User { string id = 1; string name = 2; string email = 3; } em produção. Precisa: (a) adicionar phone; (b) remover email (foi para outro serviço); (c) dividir name em first_name/last_name. O que fazer com compatibilidade?

Gabarito: (a) Adicionar string phone = 4; — número novo, clientes antigos ignoram. (b) Remover o campo do código, mas manter reserved 3; reserved "email"; para o número 3 nunca ser reutilizado; clientes antigos que ainda pedem email recebem string vazia (default) — comunicar deprecação antes. (c) Não mexer no name (número 2) de imediato: adicionar first_name = 5 e last_name = 6, preencher os três por um tempo (transição), atualizar clientes, e só então marcar name como deprecated e, muito depois, reserved. Nunca trocar o que o número 2 significa.

MÓDULO 08 · AVANÇADO

Segurança de API

Objetivo: autenticação, autorização, limites e validação — e o OWASP API Security Top 10, cujo campeão (BOLA) é um erro de design, não de código.

8.1 Autenticação (quem é você)

MecanismoUsoNotas
API keyServer-to-server simples, identificar a aplicaçãoFácil; não expira sozinha; rotacionável; não identifica o usuário final. Nunca no front-end público.
OAuth 2.0 / OIDCAcesso delegado; usuários; apps de terceirosO padrão. Authorization Code + PKCE para apps; Client Credentials para máquina-a-máquina. OIDC adiciona identidade (ID token).
JWT (Bearer)Token de acesso auto-contidoRápido (sem consulta), mas não revogável facilmente — use expiração curta + refresh token. Valide assinatura, iss, aud, exp. Nunca aceite alg: none. Não guarde dado sensível (é só base64).
mTLSServiços internos, parceiros de alta confiançaAutenticação mútua por certificado; forte; operacionalmente mais pesado.

8.2 Autorização (o que você pode fazer)

8.3 OWASP API Security Top 10 (essencial)

RiscoO que é
API1: BOLA (Broken Object Level Authorization)O nº 1. GET /orders/123 retorna o pedido de qualquer um — o servidor não checa se o pedido 123 pertence ao usuário autenticado. Trocar o id (IDOR) vaza tudo. Defesa: checar posse/permissão do objeto em toda requisição; ids opacos ajudam mas não bastam.
API2: Autenticação quebradaFluxos de login/refresh/reset fracos, JWT mal validado, sem rate limit no login.
API3: Object Property Level AuthMass assignment (o cliente manda "role":"admin" e o servidor aceita) ou excessive data exposure (a resposta traz campos sensíveis que o front "não mostra"). Defesa: allowlist de campos na entrada e na saída (DTOs), nunca serializar o modelo do banco cru.
API4: Consumo irrestrito de recursosSem rate limit, sem paginação obrigatória, sem limite de tamanho — DoS e custo. (Ver apostilas de Arquitetura e de Otimização de Custos.)
API5: BFLA (Function Level Auth)Endpoints administrativos acessíveis a usuário comum porque "não estão no menu".
API6–10Acesso irrestrito a fluxos de negócio sensíveis; SSRF; má configuração de segurança; gestão de inventário deficiente (APIs "fantasma", versões antigas no ar); consumo inseguro de APIs de terceiros.
⚠️ BOLA é um erro de design

A maioria dos vazamentos grandes de API é BOLA: a autorização em nível de objeto foi esquecida em algum endpoint. Não dá para resolver com um middleware genérico — cada handler que recebe um id precisa responder "este usuário pode agir sobre este recurso?". Teste isso explicitamente (um usuário tentando acessar o recurso de outro deve receber 403/404).

8.4 Outros controles

💼 Mercado de trabalho

Perguntas: "O que é BOLA/IDOR e como preveni-lo?" (autorização em nível de objeto em toda requisição), "OAuth2 vs API key vs JWT — quando cada um?", "Perigos do JWT" (revogação, alg:none, dado sensível, expiração longa), "O que é mass assignment e como evitar?" (DTO/allowlist de campos), "Onde colocar a checagem de autorização?" (servidor, por requisição, sobre o recurso concreto).

✏️ Exercício 8 — Ache as falhas

Uma API tem: GET /invoices/{id} que só valida o token (não checa dono); o endpoint aceita o corpo inteiro do usuário e faz Object.assign(user, body); o login não tem rate limit; o JWT expira em 30 dias; a resposta de /users/{id} inclui passwordHash. Classifique cada uma no OWASP API Top 10 e corrija.

Gabarito: (1) GET /invoices/{id} sem checar dono = API1 BOLA → validar que a invoice pertence ao usuário/tenant; retornar 404/403 caso contrário; testar com usuário alheio. (2) Object.assign(user, body) = API3 mass assignment → aceitar só um DTO com campos permitidos. (3) Login sem rate limit = API2 → rate limit + lockout progressivo + captcha. (4) JWT de 30 dias = API2 → access token curto (minutos) + refresh token rotacionável e revogável. (5) passwordHash na resposta = API3 excessive data exposure → serializar via DTO de saída, nunca o modelo cru.

MÓDULO 09 · MUITO AVANÇADO

Versionamento, evolução e API em escala

Objetivo: evoluir uma API sem quebrar clientes — mudanças compatíveis, estratégias de versão, deprecação — e operar muitas APIs com gateway, observabilidade e governança.

9.1 Breaking × non-breaking

Non-breaking (seguro)Breaking (exige versão / negociação)
Adicionar endpoint, campo opcional na resposta, valor novo raramente em enum de saídaRemover/renomear campo ou endpoint; mudar tipo
Adicionar parâmetro opcional com defaultTornar um parâmetro obrigatório; mudar o default
Afrouxar validação; adicionar novo código de erro específicoEndurecer validação; mudar código de status de um caso existente
Novo scope opcionalMudar semântica de um campo/endpoint sem mudar a forma

Regra do consumidor tolerante (robustness principle): clientes devem ignorar campos que não conhecem — isso é o que torna a adição de campos segura. Documente essa expectativa.

9.2 Estratégias de versionamento

EstratégiaExemploPrós / contras
Na URI/v2/ordersExplícito, fácil de rotear e cachear; "polui" a URL e sugere versionar o recurso todo. O mais comum.
Header / media typeAccept: application/vnd.api+json; version=2URL limpa, versiona por representação; menos visível, cache exige Vary.
Query param?version=2Simples; fácil de esquecer; mistura versão com filtro.
Baseada em data (estilo Stripe)Stripe-Version: 2026-08-01 fixada por contaMudanças pequenas e frequentes sem "v3, v4, v5"; o servidor mantém camadas de transformação. Poderoso, mais trabalho.
💡 A melhor versão é a que você não precisa lançar

Versionar é caro (manter N versões, migrar clientes). Projete para extensão sem quebra — campos opcionais, novos endpoints, feature flags por campo — e reserve a mudança de versão para rupturas reais e raras. Quando lançar, tenha um plano de deprecação: anúncio, header Deprecation + Sunset (RFC 8594), janela longa (6–12+ meses), métricas de quem ainda usa, e comunicação ativa.

9.3 API Gateway

Um ponto de entrada único na frente das APIs, responsável por o que não deve estar espalhado: autenticação e verificação de token, rate limiting e quotas, roteamento e versionamento, terminação TLS, transformação de request/response, agregação, cache, CORS, logging e métricas. Ferramentas: Kong, Apigee, AWS API Gateway, Azure API Management, Tyk, Envoy/Gloo. O gateway não deve conter lógica de negócio.

9.4 Observabilidade de API

9.5 Governança e o mundo assíncrono

💼 Mercado de trabalho

Perguntas sénior: "O que é uma mudança breaking e como você evita versionar?", "Estratégias de versionamento e trade-offs", "Como você deprecia um endpoint?" (Deprecation/Sunset headers, janela longa, métricas de uso, comunicação), "O que um API gateway faz e o que ele NÃO deve fazer?" (não lógica de negócio), "Boas práticas de webhook" (assinatura HMAC, retry, idempotência, reconciliação).

✏️ Exercício 9 — Planeje a mudança

Você precisa mudar o campo full_name (string) para first_name + last_name numa API pública com centenas de integradores. Descreva o plano sem quebrar ninguém.

Gabarito: (1) Fase aditiva: adicionar first_name e last_name na resposta (mantendo full_name); na entrada, aceitar os dois novos e o antigo, com regra de precedência documentada. Non-breaking. (2) Anunciar deprecação de full_name: changelog, header Deprecation: true + Sunset: <data +12 meses> nas respostas que ainda o incluem, e-mail aos integradores, doc atualizada. (3) Monitorar quantos clientes ainda enviam/leem full_name; contatar os retardatários. (4) Só depois do sunset e do uso perto de zero, considerar remover — provavelmente numa /v2 ou versão datada, mantendo /v1 por mais um tempo. Em nenhum momento full_name muda de significado ou some sem aviso.

MÓDULO 10 · CARREIRA

Mercado de trabalho: roadmap, entrevistas e portfólio

Objetivo: converter o conteúdo dos módulos anteriores em aprovação — perfis, roadmap, banco de perguntas com respostas e projetos que geram entrevista.

10.1 Onde design de API pesa

10.2 Roadmap de estudo (6–8 semanas)

SemanasFocoPrática
1HTTP a fundo: métodos, status, headers, cache, CORS (Módulo 2)Explorar uma API pública com curl; anotar semântica de cada resposta
2Design REST: recursos, coleções, paginação, ações (Módulos 1, 3)Desenhar no papel a API de um domínio (biblioteca, e-commerce) inteira
3OpenAPI + erros + formatos (Módulos 4–5)Escrever o openapi.yaml dessa API, com Problem Details e exemplos; rodar Spectral
4Implementar: um serviço real contra o contrato, com validação, paginação cursor, ETagMock com Prism; contract test com Schemathesis
5GraphQL: schema, resolvers, DataLoader, cursor connections, segurança (Módulo 6)Expor o mesmo domínio em GraphQL; provocar e corrigir um N+1
6gRPC: .proto, streaming, evolução de schema (Módulo 7)Um serviço gRPC com server streaming; simular uma mudança de schema compatível
7Segurança: OAuth2/OIDC, JWT, OWASP API Top 10 (Módulo 8)Adicionar auth + checagem de BOLA + rate limit; escrever testes de autorização negativa
8Versionamento, gateway, observabilidade, webhooks (Módulo 9); portfólioPlano de deprecação de um campo; métricas RED; um endpoint de webhook assinado

10.3 Banco de perguntas de entrevista (com a resposta que aprova)

Júnior/pleno — "Diferença entre PUT, PATCH e POST"

POST: cria ou executa ação; não idempotente (dois POSTs de "criar" criam dois). PUT: substitui o recurso inteiro (ou cria com id conhecido); idempotente. PATCH: atualização parcial; não necessariamente idempotente. Consequência prática: PUT/DELETE podem ser reexecutados com segurança em retry; POST que cria precisa de chave de idempotência.

Pleno — "Offset ou cursor pagination?"

Cursor (keyset) para qualquer coisa que escale: performance estável em qualquer profundidade (usa índice, não varre-e-descarta) e consistente sob escrita concorrente. Offset só para datasets pequenos ou UI que precisa "ir para a página 7". Sempre com limit máximo e metadados (next_cursor/links).

Pleno — "Como você padroniza erros?"

Um formato único para toda a API — Problem Details (RFC 9457): type (URI da classe de erro, estável), title, status, detail, lista de erros por campo com código legível por máquina + mensagem humana, e um traceId. Status HTTP correto (não 200 com erro no corpo). Retornar todos os erros de validação de uma vez. Nunca stack trace em produção.

Pleno/sénior — "REST, GraphQL ou gRPC para [cenário]?"

REST: API pública/terceiros, CRUD, cacheável. GraphQL: muitos front-ends com necessidades diferentes, telas que agregam várias fontes, evolução rápida do cliente — ao custo de N+1, cache e segurança de query. gRPC: entre serviços internos, alta vazão, streaming, polyglot — não para browser sem proxy. Comum: os três juntos (REST na borda, gRPC interno, GraphQL no BFF).

Sénior — "O que é BOLA e como preveni-lo?"

Broken Object Level Authorization: o endpoint valida que você está autenticado, mas não que este objeto é seu — trocar o id no path vaza dados de outros. É o risco nº 1 do OWASP API Top 10. Prevenção: checar posse/permissão sobre o recurso concreto em toda requisição que recebe um id; testar explicitamente com um usuário tentando acessar o recurso de outro (deve dar 403/404). Ids opacos ajudam mas não substituem a checagem.

Sénior — "Como evoluir uma API sem quebrar clientes?"

Preferir mudanças aditivas (novos campos opcionais, novos endpoints) e o princípio do consumidor tolerante (ignora o que não conhece). Reservar versionamento para rupturas reais. Ao mudar: fase aditiva mantendo o antigo, anúncio de deprecação com headers Deprecation/Sunset, janela longa (6–12+ meses), métricas de quem ainda usa, comunicação ativa, e só então remover — geralmente numa nova versão, mantendo a antiga por um tempo.

Armadilha — "Nossa API REST retorna 200 sempre, o erro vai no corpo"

Anti-padrão: quebra clientes HTTP, proxies, caches e ferramentas que confiam no status; obriga todo consumidor a inspecionar o corpo em toda resposta. O status HTTP existe para isso — 4xx para erro do cliente, 5xx para erro do servidor — e o corpo (Problem Details) dá o detalhe. Idem para GraphQL: entender que 200 com errors é do protocolo, mas o contrato de erros precisa ser explícito.

10.4 Projetos de portfólio que geram entrevista

  1. API completa design-first (âncora): um domínio real, openapi.yaml caprichado (Problem Details, cursor pagination, ETag, auth), implementação que passa em contract test, docs geradas, e um README explicando cada decisão de design e o plano de versionamento.
  2. O mesmo domínio nos três estilos: REST, GraphQL e gRPC, com um texto comparando trade-offs medidos (payload, latência, DX).
  3. Estudo de segurança: uma API com 5 vulnerabilidades do OWASP API Top 10 plantadas e depois corrigidas, com testes de autorização negativa demonstrando o antes/depois.
  4. Kit de evolução: demonstrar uma mudança breaking conduzida sem quebrar clientes — fase aditiva, headers de deprecação, detecção de breaking change no CI (oasdiff/Buf).
  5. Style guide + linter: um guia de estilo de API escrito e um conjunto de regras Spectral que o aplica, rodando em CI sobre a spec.

10.5 Fontes para continuar

🏁 Síntese final da apostila

Quatro ideias sustentam design de API: (1) a API é um contrato de longa duração com pessoas que você não controla — projete para não quebrar; (2) use a semântica do HTTP (métodos, status, cache, idempotência) — a maioria dos erros é ignorá-la; (3) consistência > perfeição — um style guide aplicado por linter vale mais que endpoints individualmente elegantes; (4) a autorização em nível de objeto é a falha nº 1 e mora no design, não num middleware. REST, GraphQL e gRPC são ferramentas — escolha pela força de cada uma e combine-as.