Blog

Desenhando uma API pensada para rastreabilidade.

Uma API normal responde “como estão as coisas”. Uma API com rastreabilidade responde também “como elas chegaram aqui” e “prove”.

A diferença parece pequena e muda o desenho inteiro. Adaptar depois é caro, porque a informação que faltava não foi guardada.

O identificador é a promessa

Se o seu produto vende rastreabilidade, o identificador é o produto. Ele vai ser colado em e-mail, impresso em documento, digitado à mão e lido por telefone.

Isso impõe requisitos que ninguém pensa na primeira versão.

evt_2026W36_7f3a9c2e4b8d1a05

Prefixo que diz o tipo, porque quem recebe precisa saber do que se trata sem consultar nada. Um componente temporal, que ajuda a fazer partição e a diagnosticar. E o hash em si.

Sobre o formato: evite Base64 puro, que tem + e / e quebra em URL. Base58 ou Base32 sem os caracteres ambíguos evita a confusão entre 0 e O, 1 e l, que é real quando alguém digita.

E um dígito verificador, se o identificador for digitado por gente. Custa quatro linhas e transforma erro de digitação em mensagem clara em vez de “não encontrado”.

Todo recurso rastreável precisa de três endpoints

Não um. Três.

O estado. GET /shipments/{id} devolve como está agora. É o que todo mundo constrói.

O histórico. GET /shipments/{id}/events devolve a sequência do que aconteceu, em ordem, com quem, quando e o hash de cada evento.

{
  "data": [
    {
      "event_id": "evt_2026W36_7f3a9c2e4b8d1a05",
      "type": "shipment.dispatched",
      "occurred_at": "2026-09-01T14:32:11.482Z",
      "recorded_at": "2026-09-01T14:32:11.913Z",
      "actor": { "type": "user", "id": "usr_4b2c" },
      "hash": "9f2b...",
      "prev_hash": "3c8e..."
    }
  ]
}

A verificação. GET /verify/{event_id} recebe o identificador e devolve se a cadeia está íntegra, com a prova.

Esse terceiro é o que muda a natureza do produto. Sem ele, a rastreabilidade é uma afirmação sua. Com ele, é verificável por quem não confia em você, e essa é a diferença entre “confia em mim” e auditável.

Ele deve funcionar sem autenticação, ou com autenticação separada, porque quem verifica frequentemente é um terceiro que não tem conta no seu sistema. Cuidado com o que a resposta revela: confirmar integridade não deve expor o conteúdo.

Duas datas em todo evento

occurred_at é quando o fato aconteceu no mundo. recorded_at é quando o sistema soube.

Elas divergem sempre que existe fila, integração ou aplicativo offline. Ter uma coluna só te impede de responder “o dado chegou atrasado ou está errado?”, que é a primeira pergunta em qualquer investigação séria.

E occurred_at precisa vir de quem observou o fato, não do now() da gravação. Se vier da gravação, dois envios do mesmo fato produzem hashes diferentes e a idempotência não funciona.

Consulta por tempo, não só por ID

Rastreabilidade sem consulta temporal é um arquivo morto.

GET /events?entity_type=shipment&entity_id=shp_1f2e
GET /events?occurred_after=2026-09-01T00:00:00Z&occurred_before=2026-09-02T00:00:00Z
GET /events?correlation_id=ord_9f2b

Paginação por cursor, não por offset. Log cresce durante a paginação, e offset em coleção que cresce pula e repete registro. O cursor sendo o ID do último item resolve, e fica trivial se o ID for ordenável, como escrevi em UUIDv7, ULID ou Snowflake.

Versionar sem invalidar o passado

O formato canônico que entra no hash vai mudar. Campo novo, tipo diferente, regra de normalização ajustada.

Se a versão não estiver dentro do hash, qualquer mudança invalida o histórico inteiro. Com ela, as duas versões convivem: eventos antigos verificam com a regra v1, novos com a v2.

{ "hash_version": "v2", "hash": "9f2b..." }

Exponha a versão na resposta. Quem verifica precisa saber qual algoritmo aplicar.

Idempotência na escrita

Toda API de rastreabilidade recebe o mesmo evento duas vezes. É integração, e integração retenta.

O identificador natural do evento vira constraint única, e a resposta ao duplicado é 200 com o registro original, não erro. Erro faz o cliente retentar de novo, e você entra num laço.

Erros que ensinam

Quando a verificação falha, a resposta precisa dizer o que falhou:

{
  "error": "chain_broken",
  "message": "Hash do evento não corresponde ao conteúdo canônico.",
  "event_id": "evt_2026W36_7f3a9c2e4b8d1a05",
  "expected_hash": "9f2b...",
  "computed_hash": "1a7c...",
  "hash_version": "v2"
}

Um 400 genérico transforma cada problema de integração numa conversa por e-mail. A qualidade da mensagem de erro é a parte da API que mais afeta o custo de suporte, e é a que menos recebe atenção.

O que guardar mesmo sem uso hoje

Uma lista curta, do que é barato agora e impossível de recuperar depois: o payload cru como recebido, o identificador do sistema de origem, a versão da sua aplicação que processou, e o correlation ID.

Nenhum deles tem uso imediato. Todos eles são a diferença entre responder uma pergunta em cinco minutos e não conseguir responder.

O desenho completo do lado do armazenamento está em system design de rastreabilidade com hash único.

← Voltar pro blog