@lexnode/legal-data-sdk 1.30.0 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,20 +5,20 @@ processos e movimentações, publicações dos diários oficiais, jurisprudênci
5
5
  monitores e webhooks.
6
6
 
7
7
  O pacote traz **os tipos gerados do contrato OpenAPI** e um **cliente de
8
- runtime** construído sobre eles: transporte com os cabeçalhos certos, erros
8
+ runtime** construído sobre eles: chamadas com os cabeçalhos certos, erros
9
9
  classificados, verificação de assinatura de webhook, espera de busca
10
- assíncrona, leitura do alcance da fonte e a composição de vigília com vários
10
+ assíncrona, leitura do frescor da fonte e a vigília de um processo para vários
11
11
  interessados.
12
12
 
13
13
  - **Sem dependências de runtime.** `fetch` global e `node:crypto`.
14
14
  - **Node >= 20**, ESM.
15
15
 
16
- > ⚠️ **Servidor apenas, e só por TLS.** A credencial é uma chave de **conta**:
17
- > no browser ela está publicada, e em `http://` ela é lida por quem estiver no
18
- > caminho — uma `baseUrl` sem https é recusada no construtor (host local é a
19
- > exceção, para desenvolvimento). Um app com usuários finais fala com o próprio
20
- > backend, e é o backend que fala com o motor — atribuindo cada chamada ao seu
21
- > sub-tenant pelo cabeçalho `X-Office-Id`.
16
+ > ⚠️ **Só no servidor, e só por TLS.** A credencial é uma chave de **conta**:
17
+ > no browser ela fica exposta, e em `http://` qualquer um no caminho a lê —
18
+ > uma `baseUrl` sem https é recusada no construtor (exceto no host local, para
19
+ > desenvolvimento). Um app com usuários finais chama o próprio backend, e o
20
+ > backend chama a API, atribuindo cada chamada ao sub-tenant dela (um
21
+ > escritório, um usuário final) pelo cabeçalho `X-Office-Id`.
22
22
 
23
23
  ## Instalação
24
24
 
@@ -34,33 +34,33 @@ import { createLexNodeClient } from "@lexnode/legal-data-sdk";
34
34
  const lexnode = createLexNodeClient({
35
35
  baseUrl: "https://api.lexnode.com.br",
36
36
  apiKey: process.env.LEXNODE_API_KEY!,
37
- // Sub-tenant padrão; cada chamada pode trocá-lo.
37
+ // Escritório padrão; cada chamada pode trocá-lo.
38
38
  officeId: "escritorio-01",
39
39
  timeoutMs: 30_000,
40
40
  userAgent: "meu-app/1.0",
41
41
  });
42
42
 
43
43
  const { data, meta } = await lexnode.processes.get("00012345620268260100", {
44
- tribunalAlias: "api_publica_tjsp",
44
+ tribunalAlias: "TJSP",
45
45
  });
46
46
 
47
47
  console.log(data.formattedNumber, meta.credits, meta.rateLimit?.remaining);
48
48
  ```
49
49
 
50
- Toda chamada manda `Authorization: Bearer`, `X-Request-Id`, `X-Trace-Id` e,
51
- quando há um, `X-Office-Id`; respeita o prazo do cliente **combinado** com o
52
- `signal` de quem chamou; e devolve `{ data, meta }` tipados pela rota. O `meta`
53
- é o do contrato mais `status` (o código HTTP) e `rateLimit` (os cabeçalhos
54
- `RateLimit-*`, lidos **inclusive no sucesso**, e o `Retry-After` da recusa).
50
+ Toda chamada envia `Authorization: Bearer`, `X-Request-Id`, `X-Trace-Id` e,
51
+ quando houver, `X-Office-Id`; respeita o prazo **e** o `signal` que você
52
+ passar, o que vier primeiro; e devolve `{ data, meta }` tipados pela rota. O
53
+ `meta` é o do contrato mais `status` (o código HTTP) e `rateLimit` (os
54
+ cabeçalhos `RateLimit-*`, lidos **também no sucesso**, e o `Retry-After` da
55
+ recusa).
55
56
 
56
- Em `rateLimit`, use `remaining` para desacelerar: `resetSeconds` repete o que o
57
- cabeçalho `RateLimit-Reset` trouxe, e o motor lhe dá dois sentidos — segundos
58
- até reabrir na recusa, comprimento da janela (60 s) no sucesso.
57
+ Em `rateLimit`, use `remaining` para desacelerar. `resetSeconds` repete o
58
+ cabeçalho `RateLimit-Reset`, que tem dois sentidos: na recusa, os segundos até
59
+ a janela reabrir; no sucesso, a duração da janela (60 s).
59
60
 
60
61
  O pacote **não valida a resposta em tempo de execução**: `data` e `meta` chegam
61
- tipados pelo contrato, e o que garante a correspondência é a versão fixada, não
62
- uma conferência a cada chamada. Quem precisa de barreira na borda — porque o
63
- dado alimenta um banco, por exemplo — valida o que consome.
62
+ tipados pelo contrato da versão instalada. Se o dado alimenta um banco, por
63
+ exemplo, valide o que consumir.
64
64
 
65
65
  ### Métodos nomeados e a chamada genérica
66
66
 
@@ -80,8 +80,8 @@ await lexnode.webhooks.unsubscribedEventTypes();
80
80
  await lexnode.usage.get();
81
81
  ```
82
82
 
83
- Qualquer rota do contrato — inclusive uma que o pacote ainda não batizou — sai
84
- pela chamada genérica, tipada por `paths`:
83
+ Qualquer rota do contrato, inclusive as que não têm método nomeado, sai pela
84
+ chamada genérica, tipada por `paths`:
85
85
 
86
86
  ```ts
87
87
  const { data } = await lexnode.request("/v1/precedents", "get", {
@@ -91,9 +91,9 @@ const { data } = await lexnode.request("/v1/precedents", "get", {
91
91
 
92
92
  ### Páginas
93
93
 
94
- O motor pagina de três jeitos (cursor no `meta`, cursor no `data`, e
95
- página/tamanho ou deslocamento/teto). `paginate` percorre qualquer um deles, com
96
- teto explícito de páginas — cada página é uma chamada medida:
94
+ A API pagina de três jeitos: cursor no `meta`, cursor no `data`, e página e
95
+ tamanho ou deslocamento e limite. `paginate` percorre qualquer um deles, até
96
+ `maxPages` páginas (padrão 100) — cada página é uma chamada:
97
97
 
98
98
  ```ts
99
99
  import { paginate } from "@lexnode/legal-data-sdk";
@@ -116,7 +116,7 @@ try {
116
116
  error.class; // "RETRYABLE" | "TERMINAL"
117
117
  error.retryable;
118
118
  error.status;
119
- error.upstreamStatus; // o status real da fonte, quando preservado
119
+ error.upstreamStatus; // o status que a fonte respondeu, quando informado
120
120
  error.requestId; // o que o suporte pede
121
121
  error.fieldErrors; // recusa de validação, por campo
122
122
  error.retryAfterSeconds;
@@ -126,17 +126,17 @@ try {
126
126
  ```
127
127
 
128
128
  Prazo esgotado vira `CLIENT_TIMEOUT` (504, retentável); falha de rede vira
129
- `CLIENT_NETWORK_ERROR` (502, retentável); resposta fora do envelope vira
130
- `INVALID_RESPONSE` (502) com o corpo cru truncado na mensagem; o que o
131
- transporte recusa mandar — cabeçalho com quebra de linha, corpo que não
132
- serializa — vira `CLIENT_INVALID_REQUEST` (400, **não** retentável), porque
133
- repetir o que nunca saiu não cura.
129
+ `CLIENT_NETWORK_ERROR` (502, retentável); resposta fora do formato da API vira
130
+ `INVALID_RESPONSE` (502), com o início do corpo na mensagem; e o pedido que não
131
+ se monta — cabeçalho com quebra de linha, corpo que não vira JSON — vira
132
+ `CLIENT_INVALID_REQUEST` (400, **não** retentável), porque não chegou a ser
133
+ enviado.
134
134
 
135
135
  `LEXNODE_ERROR_CODES` traz o catálogo para o `switch`, e `LexNodeErrorCode`
136
- mantém o autocompletar sem fechar a porta a um código novo. Importa na leitura
137
- de processo: o motor responde 404 tanto para `PROCESS_NOT_FOUND` (o processo não
138
- existe na fonte) quanto para `MOVEMENTS_NOT_AVAILABLE` (existe, mas as
139
- movimentações não vêm) — quem trata os dois igual conta a história errada.
136
+ mantém o autocompletar e aceita um código novo da API. Na leitura de processo
137
+ isso importa: a API responde 404 tanto para `PROCESS_NOT_FOUND` (o processo não
138
+ existe na fonte) quanto para `MOVEMENTS_NOT_AVAILABLE` (ele existe, mas as
139
+ movimentações não vieram) — trate os dois pelo `code`.
140
140
 
141
141
  ### Retentativa (opcional, desligada por padrão)
142
142
 
@@ -148,19 +148,22 @@ const lexnode = createLexNodeClient({
148
148
  });
149
149
  ```
150
150
 
151
- Repete só o que é `RETRYABLE`, respeitando `Retry-After`. `GET` e `DELETE` são
152
- repetidos por definição do HTTP. Uma **escrita** só é repetida quando o contrato
153
- declara a repetição segura — hoje `POST /v1/publications/text-searches`, que usa
154
- o `requestId` que você fixar como chave de replay — ou quando você mesmo o
155
- declara na chamada:
151
+ Repete só o que é `RETRYABLE`, respeitando `Retry-After`: quando a recusa pede
152
+ uma espera maior que `maxDelayMs` (8 s por padrão), como a da manutenção
153
+ declarada pela fonte, ela é lançada sem repetição, e `retryAfterSeconds` diz
154
+ quando vale tentar de novo. `GET` e `DELETE` são repetidos por definição do
155
+ HTTP. Uma **escrita** só é repetida quando o contrato declara a repetição
156
+ segura — hoje `POST /v1/publications/text-searches`, que usa o `requestId` que
157
+ você fixar como chave de reenvio — ou quando você mesmo o declara na chamada:
156
158
 
157
159
  ```ts
158
160
  await lexnode.webhooks.endpoints.create(corpo, { idempotent: true });
159
161
  ```
160
162
 
161
- A afirmação é sua: sem ela, um `POST` repetido cria um **segundo** recurso — dois
162
- endpoints para a mesma URL entregam cada evento em dobro, e o segredo do
163
- primeiro fica órfão. Em toda outra rota o `requestId` é só rastreio.
163
+ A declaração é sua: sem ela, um `POST` repetido cria um **segundo** recurso —
164
+ dois endpoints para a mesma URL entregam cada evento em dobro, e o segredo do
165
+ primeiro se perde. Nas outras rotas, o `requestId` só serve para rastrear a
166
+ chamada.
164
167
 
165
168
  A exceção é o `UPSTREAM_TIMEOUT` da leitura de um processo, que a política não
166
169
  repete — ver [a seção da fonte lenta](#ler-um-processo-quando-a-fonte-está-lenta).
@@ -173,15 +176,16 @@ import {
173
176
  webhookRejectionStatus,
174
177
  } from "@lexnode/legal-data-sdk";
175
178
 
176
- // O corpo CRU, nunca um JSON reserializado: o digest é sobre os bytes.
179
+ // O corpo como chegou, nunca um JSON serializado de novo: a assinatura vale
180
+ // para os bytes.
177
181
  const resultado = parseWebhookEvent(
178
182
  rawBody,
179
183
  request.headers,
180
184
  process.env.LEXNODE_WEBHOOK_SIGNING_SECRET!,
181
185
  );
182
186
 
183
- // O status de cada recusa vem junto: um corpo que não se autentica é 401 e não
184
- // volta; segredo ausente é 503, porque a falha é SUA e o motor precisa insistir.
187
+ // Cada recusa tem o seu status: a assinatura que não confere é 401; o segredo
188
+ // ausente no seu receptor é 503.
185
189
  if (!resultado.ok) {
186
190
  return reply.status(webhookRejectionStatus(resultado.reason)).send();
187
191
  }
@@ -210,22 +214,22 @@ switch (resultado.event.type) {
210
214
  }
211
215
  break;
212
216
  case "PROCESS_LOOKUP_COMPLETED":
213
- // Um ponteiro: o processo se lê em payload.pollUrl, sem nova cobrança.
217
+ // Aponta a consulta: o processo se lê em payload.pollUrl, sem nova cobrança.
214
218
  resultado.event.payload.lookupId;
215
219
  break;
216
220
  case "WEBHOOK_TEST":
217
- // A entrega de teste do console. Chega a qualquer endpoint, não se assina
218
- // e não se recusa — é ela que valida o receptor antes do primeiro evento.
221
+ // A entrega de teste (`webhooks.endpoints.test` ou o portal). Chega a
222
+ // qualquer endpoint sem constar de `eventTypes`; use-a para validar o
223
+ // receptor antes do primeiro evento.
219
224
  break;
220
225
  default:
221
226
  break;
222
227
  }
223
228
  ```
224
229
 
225
- O envelope precisa ser endereçável: entrega sem `id` ou sem `occurredAt` é
226
- recusada como `INVALID_ENVELOPE`. O `id` é a chave de idempotência de todo
227
- receptor — aceitá-lo vazio faria a primeira entrega ocupar a única vaga de
228
- deduplicação e todas as seguintes parecerem repetição dela.
230
+ Entrega sem `id` ou sem `occurredAt` é recusada como `INVALID_ENVELOPE`. A
231
+ mesma entrega pode chegar mais de uma vez: use o `id` para descartar a
232
+ repetida.
229
233
 
230
234
  `WEBHOOK_EVENT_TYPES` é o catálogo **assinável** (o que entra em `eventTypes`
231
235
  ao cadastrar um endpoint) e não inclui `WEBHOOK_TEST`, que chega sem ser
@@ -234,22 +238,20 @@ pedido — use `isWebhookTest(event)` para separá-lo.
234
238
  `verifyWebhookSignature(rawBody, headers, secret)` faz só a conferência e
235
239
  devolve o motivo da recusa (`MISSING_SIGNATURE`, `MALFORMED_SIGNATURE`,
236
240
  `TIMESTAMP_MISMATCH`, `TIMESTAMP_OUT_OF_TOLERANCE`, `SIGNATURE_MISMATCH`,
237
- `RAW_BODY_REQUIRED`), em vez de um `false` que faria vários incidentes
238
- diferentes virarem a mesma resposta.
241
+ `RAW_BODY_REQUIRED`), para você tratar cada caso.
239
242
 
240
- O corpo entra como `string` ou `Buffer` — os bytes como chegaram. Passar o JSON
241
- já parseado é recusado como `RAW_BODY_REQUIRED`, e não confundido com
242
- adulteração: reserializar reordena as chaves e o digest deixa de bater.
243
+ O corpo entra como `string` ou `Buffer`, como chegou. Um objeto já convertido
244
+ de JSON é recusado como `RAW_BODY_REQUIRED`: serializar de novo muda a ordem
245
+ das chaves, e a assinatura deixa de conferir.
243
246
 
244
- A janela de replay e o digest saem do `t` que viaja **dentro** da assinatura; o
245
- cabeçalho `x-lexnode-timestamp` é redundante e só é conferido quando vem. Um
246
- receptor escrito à mão que o exigia continua correto — o SDK apenas não faz da
247
- ausência dele um motivo de recusa, porque ela não prova nada.
247
+ A tolerância do instante e o digest usam o `t` que vem **dentro** da
248
+ assinatura. O cabeçalho `x-lexnode-timestamp` repete esse valor: quando vem,
249
+ precisa ser igual; quando falta, não é motivo de recusa.
248
250
 
249
251
  ## Busca síncrona ou assíncrona
250
252
 
251
253
  `POST /v1/processes/search` responde **200** com o resultado pronto ou **202**
252
- com um trabalho enfileirado. O SDK diz qual foi, no tipo:
254
+ com uma busca enfileirada. O SDK diz qual foi, no tipo:
253
255
 
254
256
  ```ts
255
257
  import { searchProcesses, waitForSearchJob } from "@lexnode/legal-data-sdk";
@@ -259,9 +261,8 @@ const desfecho = await searchProcesses(lexnode, { tribunalAlias, number });
259
261
  if (desfecho.kind === "sync") {
260
262
  desfecho.result.items;
261
263
  } else {
262
- // O orçamento é explícito: num ambiente serverless, esperar sem teto gasta
263
- // a requisição inteira. Quem integrou por webhook nem espera — recebe o
264
- // SEARCH_JOB_COMPLETED.
264
+ // Espere com orçamento — num ambiente serverless, dentro do prazo da
265
+ // função — ou receba o SEARCH_JOB_COMPLETED por webhook.
265
266
  const job = await waitForSearchJob(lexnode, desfecho.job.jobId, {
266
267
  budgetMs: 20_000,
267
268
  });
@@ -272,30 +273,30 @@ if (desfecho.kind === "sync") {
272
273
  ## Um número, várias instâncias
273
274
 
274
275
  A fonte processual guarda um documento por instância do processo — 1º grau, 2º
275
- grau, turma recursal… —, às vezes dois do mesmo grau, e o motor guarda cada um
276
- na sua instância.
276
+ grau, turma recursal… —, às vezes dois do mesmo grau, e o LexNode guarda cada
277
+ um na sua instância.
277
278
 
278
279
  **O topo não troca.** O topo da ficha (`data`) e do retrato (`data.process` e
279
280
  `data.movements`) é sempre a instância principal do número, com o grau em
280
281
  `degree`: a mesma de uma leitura para a outra, qualquer que seja a ordem em que
281
282
  a fonte devolve os documentos. Ela só muda quando uma resposta completa da
282
- fonte pelo número deixa de trazê-la. Quem compara movimentos pela chave não vê
283
- o histórico de outro grau aparecer como novo no topo — e o topo pode não ser o
283
+ fonte pelo número deixa de trazê-la. Se você compara movimentos pela chave, o
284
+ histórico de outro grau não aparece como novo no topo — e o topo pode não ser o
284
285
  grau que anda: o 2º grau guardado primeiro continua no topo enquanto o 1º se
285
286
  move.
286
287
 
287
288
  **Todos os graus em `instances`.** A ficha e o retrato listam em `instances`
288
- todos os documentos que o motor guarda do número, a principal primeiro
289
+ todos os documentos guardados do número, a principal primeiro
289
290
  (`primary: true`). Cada um diz de onde veio nesta resposta (`origin`: `SOURCE`
290
- quando a fonte o trouxe, `LAKE` quando saiu do acervo), quando foi buscado
291
- (`fetchedAt`) e desde quando a resposta completa da fonte vem sem ele
292
- (`absentSince`). No retrato, cada instância traz os movimentos da última captura
293
- dela, do mais recente ao mais antigo e sem o payload bruto da fonte; a que o
294
- motor nunca capturou vem com `movementsCaptured: false` e `movements` nulo, nunca
295
- com lista vazia, que diria que o grau não andou. Todos os documentos levam a
296
- classificação do número inteiro — basta um em segredo de justiça para todos
297
- saírem marcados —, e a ficha retida pelo controle de confidencialidade sai sem a
298
- lista.
291
+ quando a fonte o trouxe, `LAKE` quando veio da cópia guardada), quando foi
292
+ buscado (`fetchedAt`) e desde quando a resposta completa da fonte vem sem ele
293
+ (`absentSince`). No retrato, cada instância traz os movimentos da última
294
+ captura dela, do mais recente ao mais antigo e sem o payload bruto da fonte; a
295
+ instância cujos movimentos nunca foram capturados vem com
296
+ `movementsCaptured: false` e `movements` nulo, nunca com lista vazia, que diria
297
+ que o grau não andou. Todos os documentos levam a classificação do número
298
+ inteiro — basta um em segredo de justiça para todos saírem marcados —, e a
299
+ ficha retida pelo controle de confidencialidade sai sem a lista.
299
300
 
300
301
  ```ts
301
302
  const { data } = await lexnode.processes.snapshot(numero, { tribunalAlias });
@@ -309,9 +310,9 @@ for (const instancia of data.instances) {
309
310
 
310
311
  No lote (`processes.snapshotBatch`), `instances` traz só o cabeçalho de cada
311
312
  documento; os movimentos de cada instância vêm no retrato de um processo. A
312
- busca por número completo servida do acervo devolve um item por documento que a
313
- fonte ainda traz do número, a principal primeiro; quando eles não cabem em
314
- `limit`, a busca vai à fonte, que pagina.
313
+ busca por número completo respondida com os dados guardados devolve um item por
314
+ documento que a fonte ainda traz do número, a principal primeiro; quando eles
315
+ não cabem em `limit`, a busca vai à fonte, que pagina.
315
316
 
316
317
  **Recorte por grau.** A lista de movimentações aceita `degree` e devolve só os
317
318
  movimentos que o documento daquele grau trouxe na última captura; cada item diz
@@ -327,21 +328,21 @@ data.items; // só o que o 1º grau trouxe
327
328
  ```
328
329
 
329
330
  **O grau novo chega num evento só.** Quando outro grau de um processo assinado
330
- aparece na fonte, o histórico dele entra no acervo sem virar
331
- `PROCESS_MOVEMENT_CREATED` movimento a movimento: chega um
332
- `PROCESS_INSTANCE_DISCOVERED`, com o grau, o órgão julgador e as contagens —
333
- `historyMovementCount` entrou sem aviso próprio, `announcedMovementCount` saiu
334
- também como movimento novo. O `PROCESS_MOVEMENT_CREATED` diz o grau do
335
- movimento mais recente (`degree`, `instanceSourceId`) e quantos movimentos novos
336
- vieram de cada grau (`instances`). Assine os dois: quem assina só o movimento
337
- não fica sabendo do grau que apareceu.
331
+ aparece na fonte, o histórico dele não sai movimento a movimento em
332
+ `PROCESS_MOVEMENT_CREATED`: chega um `PROCESS_INSTANCE_DISCOVERED`, com o grau,
333
+ o órgão julgador e as contagens — `historyMovementCount` não saiu em aviso
334
+ próprio, `announcedMovementCount` saiu também como movimento novo. O
335
+ `PROCESS_MOVEMENT_CREATED` diz o grau do movimento mais recente (`degree`,
336
+ `instanceSourceId`) e quantos movimentos novos vieram de cada grau
337
+ (`instances`). Assine os dois: só com o movimento, você não fica sabendo do
338
+ grau que apareceu.
338
339
 
339
340
  **`meta.otherInstances`: o resumo do que veio.** Quando a resposta da fonte traz
340
341
  outros documentos do número além da principal, `meta.otherInstances` resume cada
341
342
  um com o grau (`grau`), a data em que a fonte o atualizou (`updatedAt`) e o
342
343
  órgão julgador (`courtBody`, quando vem). A lista não sai para processo restrito
343
- e não inclui documento que o motor classificaria como segredo de justiça; os
344
- documentos inteiros estão em `instances`.
344
+ e não inclui documento em segredo de justiça; os documentos inteiros estão em
345
+ `instances`.
345
346
 
346
347
  ```ts
347
348
  for (const outra of meta.otherInstances ?? []) {
@@ -350,24 +351,24 @@ for (const outra of meta.otherInstances ?? []) {
350
351
  ```
351
352
 
352
353
  **Continuação.** Na API nada é cortado: o retrato traz os movimentos guardados
353
- de cada instância, e a lista de movimentações lê do acervo, grau a grau, sem ir
354
- à fonte. No canal para agentes (MCP), `consultar_processo` cabe no teto de uma
355
- resposta: de cada grau vêm os movimentos mais recentes, o grau que se moveu por
356
- último primeiro, e a mesma tool com `grau` e `pagina` traz os seguintes do que o
357
- motor guardou, por 1 crédito e sem ir à fonte — só para quem já leu o processo
358
- ou o acompanha.
354
+ de cada instância, e a lista de movimentações lê os movimentos guardados, grau a
355
+ grau, sem ir à fonte. No canal para agentes (MCP), que não passa por este
356
+ pacote, `consultar_processo` cabe numa resposta: de cada grau vêm os movimentos
357
+ mais recentes, o grau que se moveu por último primeiro, e a mesma tool com
358
+ `grau` e `pagina` traz os seguintes, por 1 crédito e sem ir à fonte — só para
359
+ quem já leu o processo ou o acompanha.
359
360
 
360
361
  ## Ler um processo quando a fonte está lenta
361
362
 
362
- A ficha (`processes.get`) vai à fonte processual quando o acervo não tem o
363
- processo fresco; o retrato (`processes.snapshot`) vai sempre. Os dois só
364
- recorrem ao acervo quando a fonte falha (seção seguinte). A fonte às vezes leva
365
- perto de um minuto por consulta. `waitSeconds` diz quanto o motor espera por
366
- ela: de 10 a 180 segundos, padrão 20 na API.
363
+ A ficha (`processes.get`) vai à fonte processual quando não há uma cópia recente
364
+ guardada; o retrato (`processes.snapshot`) vai sempre. Os dois só respondem com
365
+ a cópia guardada quando a fonte falha (seção seguinte). A fonte às vezes leva
366
+ perto de um minuto por consulta. `waitSeconds` diz quanto a API espera por ela:
367
+ de 10 a 180 segundos, padrão 20.
367
368
 
368
369
  ```ts
369
370
  const { data, meta } = await lexnode.processes.get(numero, {
370
- tribunalAlias: "api_publica_tjsp",
371
+ tribunalAlias: "TJSP",
371
372
  waitSeconds: 120,
372
373
  });
373
374
  ```
@@ -384,27 +385,27 @@ chamada genérica (`request`) nessas duas rotas.
384
385
  duas leituras é lentidão da fonte, não ausência do processo, e a política de
385
386
  retentativa não o repete: a espera já correu inteira, e repetir na hora a
386
387
  dobraria diante da mesma fonte lenta. As outras recusas retentáveis seguem a
387
- política. Para insistir, a decisão é sua — uma nova chamada com espera maior,
388
- ou a vigília do processo (`watchProcess`), que consulta em segundo plano.
388
+ política. Para insistir, a decisão é sua — uma nova chamada com espera maior, a
389
+ leitura que segue em segundo plano (`readProcessWithRecovery`, abaixo) ou a
390
+ vigília do processo (`watchProcess`).
389
391
 
390
392
  **`meta.partialIndex`: o processo veio de parte do índice.** Quando a fonte
391
393
  responde com só parte do índice e o processo estava na parte que respondeu, ele
392
394
  é servido, e `meta.partialIndex` diz quantas partes falharam (`shardsFailed`),
393
395
  de quantas (`shardsTotal`, nulo quando a fonte não as contou) e se o prazo
394
- interno dela estourou (`timedOut`). O documento que o acervo já guardava e não
395
- veio sai em `instances` do acervo (`origin: "LAKE"`), com a data dele, e outro
396
- grau que o acervo não conhece, se existir, pode estar na parte que não
397
- respondeu. A resposta parcial sem o processo é 502 `UPSTREAM_UNAVAILABLE`
398
- retentável, nunca 404.
396
+ interno dela estourou (`timedOut`). O documento já guardado que não veio sai em
397
+ `instances` com `origin: "LAKE"` e a data dele, e outro grau ainda não guardado,
398
+ se existir, pode estar na parte que não respondeu. A resposta parcial sem o
399
+ processo é 502 `UPSTREAM_UNAVAILABLE` retentável, nunca 404.
399
400
 
400
401
  ```ts
401
402
  if (meta.partialIndex) {
402
- // Outro grau deste número que o acervo não conhece pode não ter vindo.
403
+ // Um grau deste número ainda não guardado pode não ter vindo.
403
404
  }
404
405
  ```
405
406
 
406
407
  Antes de recusar a resposta parcial que veio sem o processo porque partes do
407
- índice ficaram fora, o motor consulta a fonte de novo — até duas vezes, com uma
408
+ índice ficaram fora, a API consulta a fonte de novo — até duas vezes, com uma
408
409
  pausa curta, enquanto a espera couber.
409
410
 
410
411
  **Agentes têm a espera deles.** No canal para agentes (MCP), as tools
@@ -415,7 +416,7 @@ mesma faixa e com padrão de 120 s; esse canal não passa pelo SDK.
415
416
 
416
417
  Quando a fonte processual falha — não responde dentro de `waitSeconds`, fica
417
418
  indisponível, responde com parte do índice sem poder servir o processo ou pede
418
- para esperar por volume — e o acervo guarda uma cópia do processo, a ficha
419
+ para esperar por volume — e há uma cópia guardada do processo, a ficha
419
420
  (`processes.get`) e o retrato (`processes.snapshot`) respondem com essa cópia em
420
421
  vez do erro. A resposta diz que é cópia, e de quando:
421
422
 
@@ -423,38 +424,39 @@ vez do erro. A resposta diz que é cópia, e de quando:
423
424
  `UPSTREAM_UNAVAILABLE` ou `UPSTREAM_RATE_LIMITED`;
424
425
  - `meta.lakeRescue.retryAfterSeconds`, repetido no header `Retry-After`, diz na
425
426
  recusa por volume quanto esperar antes de consultar a fonte de novo. É a
426
- espera da fonte, não do teto do motor: `meta.rateLimit.retryAfterSeconds`
427
- fica `null` no sucesso;
427
+ espera pedida pela fonte, não pelo limite de vazão da API:
428
+ `meta.rateLimit.retryAfterSeconds` fica `null` no sucesso;
428
429
  - `meta.sourceFetchedAt` diz quando a cópia foi buscada na fonte. Esse campo
429
- acompanha toda resposta das duas leituras, socorrida ou não, menos a ficha
430
- que o controle de confidencialidade entrega retida, sem o teor.
430
+ acompanha toda resposta das duas leituras, venha ela da fonte ou da cópia,
431
+ menos a ficha que o controle de confidencialidade entrega retida, sem o teor.
431
432
 
432
433
  A cópia custa 1 crédito, na ficha e no retrato. O retrato confere o saldo pelo
433
434
  preço cheio, 8, antes de ir à fonte; a ficha confere pelo preço do que
434
435
  entregou, depois da leitura. No retrato guardado, cada instância traz os
435
- movimentos da última vez que o motor a buscou, com a data em
436
+ movimentos da última vez que ela foi buscada, com a data em
436
437
  `instances[].fetchedAt`, e `sourcePayload` é nulo: o que andou depois dessas
437
438
  datas não está na resposta. O número que a resposta completa mais recente da
438
- fonte já não trazia em documento nenhum não é socorrido. Na ficha guardada, a
439
- data vale para os dados do processo, e o resumo das publicações (`djen`) é
440
- relido na hora.
439
+ fonte já não trazia em documento nenhum não tem cópia servida. Na ficha
440
+ guardada, a data vale para os dados do processo, e o resumo das publicações
441
+ (`djen`) é relido na hora.
441
442
 
442
- **Até que idade.** Sem pedir nada, só a cópia de até 24 h socorre a leitura —
443
- nessas duas rotas, uma resposta de sucesso já podia ser cópia do acervo dentro
444
- desse prazo. A cópia de qualquer idade vem com `staleOnFailure: true`:
443
+ **Até que idade.** Sem pedir nada, só a cópia de até 24 h responde na falha da
444
+ fonte — nessas duas rotas, uma resposta de sucesso já pode ser uma cópia
445
+ guardada dentro desse prazo. A cópia de qualquer idade vem com
446
+ `staleOnFailure: true`:
445
447
 
446
448
  ```ts
447
449
  import { describeFreshness, lakeRescueOf } from "@lexnode/legal-data-sdk";
448
450
 
449
451
  const retrato = await lexnode.processes.snapshot(numero, {
450
- tribunalAlias: "api_publica_tjsp",
452
+ tribunalAlias: "TJSP",
451
453
  staleOnFailure: true,
452
454
  });
453
455
 
454
- const socorro = lakeRescueOf(retrato);
455
- if (socorro) {
456
+ const copia = lakeRescueOf(retrato);
457
+ if (copia) {
456
458
  // A fonte falhou e isto é a cópia guardada: o que andou depois de
457
- // socorro.sourceFetchedAt não está aqui.
459
+ // copia.sourceFetchedAt não está aqui.
458
460
  mostrar(describeFreshness(retrato));
459
461
  // "cópia guardada em 26/09/2026, servida porque a fonte não respondeu a tempo"
460
462
  }
@@ -465,16 +467,17 @@ if (socorro) {
465
467
  devolve a data de qualquer resposta dessas leituras. As duas aceitam o envelope
466
468
  inteiro e o item do lote.
467
469
 
468
- **O que não é socorrido.** A resposta da fonte que não traz o processo segue
469
- 404 `PROCESS_NOT_FOUND`, com cópia guardada ou sem ela: é a fonte dizendo que
470
- não tem o número, e não uma falha dela. A cópia passa pelo mesmo controle de
471
- confidencialidade da leitura; além de 24 h, só a recebe quem já tem interesse
472
- registrado no processo — de uma leitura anterior dele ou de uma assinatura
473
- ativa —, e os demais recebem a falha da fonte, como se não houvesse cópia.
470
+ **Quando a cópia não é servida.** A resposta da fonte que não traz o processo segue 404
471
+ `PROCESS_NOT_FOUND`, com cópia guardada ou sem ela: é a fonte dizendo que não
472
+ tem o número, e não uma falha dela. A cópia passa pelo mesmo controle de
473
+ confidencialidade da leitura; além de 24 h, só a recebe a conta que já tem
474
+ interesse registrado no processo — de uma leitura anterior dele ou de uma
475
+ assinatura ativa —, e as demais recebem a falha da fonte, como se não houvesse
476
+ cópia.
474
477
 
475
- **No lote.** `processes.snapshotBatch` socorre só com a cópia de até 24 h. O
476
- item socorrido traz `lakeRescue` com a causa e a data, e custa 1 crédito em vez
477
- de 8.
478
+ **No lote.** `processes.snapshotBatch` só usa a cópia de até 24 h. O item
479
+ respondido com ela traz `lakeRescue`, com a causa e a data, e custa 1 crédito
480
+ em vez de 8.
478
481
 
479
482
  ## Leitura que não coube na espera
480
483
 
@@ -488,23 +491,23 @@ em segundo plano em vez de virar erro:
488
491
  `PROCESS`) ou `snapshot` (visão `SNAPSHOT`), com as instâncias do número em
489
492
  `instances`, o mesmo `meta` e o mesmo preço da leitura direta;
490
493
  - **202**: a fonte falhou — não respondeu a tempo, ficou indisponível ou pediu
491
- para esperar —, e o motor abriu uma consulta que tenta sozinha, até 60 s por
492
- tentativa, por até 6 h (`giveUpAt`). Nada é cobrado na hora: o preço da
493
- leitura fica retido em `creditsReserved`, o máximo que a entrega cobra, e
494
+ para esperar —, e a API abriu uma consulta que segue tentando sozinha, até
495
+ 60 s por tentativa, por até 6 h (`giveUpAt`). Nada é cobrado na hora: o preço
496
+ da leitura fica retido em `creditsReserved`, o máximo que a entrega cobra, e
494
497
  só é cobrado se o processo vier. Se não vier, a retenção volta;
495
498
  - qualquer outra recusa é a da leitura direta: 404 quando a fonte diz não ter o
496
499
  processo, 403 no segredo de justiça, 402 sem saldo.
497
500
 
498
501
  Para uma integração que precisa continuar mesmo quando a fonte demora, use
499
- `readProcessWithRecovery`. A aceitação da cópia antiga é explícita; a espera
500
- total é limitada. O fim da espera devolve `pending`, com a mesma consulta e
501
- a próxima leitura útil, sem cancelar nem abrir outra compra:
502
+ `readProcessWithRecovery`. A aceitação da cópia antiga é explícita, e a espera
503
+ total é limitada. O fim da espera devolve `pending`, com a mesma consulta e a
504
+ próxima leitura útil, sem cancelar a consulta nem abrir outra:
502
505
 
503
506
  ```ts
504
507
  import { readProcessWithRecovery } from "@lexnode/legal-data-sdk";
505
508
 
506
509
  const leitura = await readProcessWithRecovery(lexnode, {
507
- tribunalAlias: "api_publica_tjsp",
510
+ tribunalAlias: "TJSP",
508
511
  processNumber: numeroDoProcesso,
509
512
  view: "PROCESS",
510
513
  waitSeconds: 5,
@@ -523,10 +526,10 @@ if (leitura.kind === "ready") {
523
526
  }
524
527
  ```
525
528
 
526
- Sem `completionBudgetMs`, o helper entrega o 200 ou o 202 imediatamente após
527
- a abertura. O orçamento adicional cobre somente o acompanhamento: cada chamada
528
- mantém seu próprio timeout. Abortar a espera local não cancela a consulta no motor.
529
- O custo, o gate de confidencialidade e a cobrança única são os do endpoint existente.
529
+ Sem `completionBudgetMs`, a função devolve na hora o desfecho da abertura (200
530
+ ou 202). O orçamento cobre só o acompanhamento, e cada chamada mantém o próprio
531
+ prazo. Abortar a espera não cancela a consulta. O custo, o controle de
532
+ confidencialidade e a cobrança única são os de `processLookups.create`.
530
533
 
531
534
  `lookupProcess` diz, no tipo, qual dos dois desfechos aconteceu, e
532
535
  `waitForProcessLookup` espera a consulta terminar:
@@ -535,7 +538,7 @@ O custo, o gate de confidencialidade e a cobrança única são os do endpoint ex
535
538
  import { lookupProcess, waitForProcessLookup } from "@lexnode/legal-data-sdk";
536
539
 
537
540
  const desfecho = await lookupProcess(lexnode, {
538
- tribunalAlias: "api_publica_tjsp",
541
+ tribunalAlias: "TJSP",
539
542
  processNumber: numero,
540
543
  view: "SNAPSHOT",
541
544
  });
@@ -543,9 +546,9 @@ const desfecho = await lookupProcess(lexnode, {
543
546
  if (desfecho.kind === "ready") {
544
547
  desfecho.data.movements; // o retrato, como processes.snapshot o devolveria
545
548
  } else {
546
- // O orçamento é explícito. A leitura seguinte só acontece na hora da
547
- // próxima tentativa (nextAttemptAt): antes dela, o estado não muda. Com
548
- // uma tentativa em curso, a consulta é lida de novo a cada 10 s.
549
+ // Espere com orçamento. A consulta é lida de novo na hora da próxima
550
+ // tentativa (nextAttemptAt), quando o estado pode mudar, e a cada 10 s
551
+ // enquanto uma tentativa está em curso.
549
552
  const consulta = await waitForProcessLookup(
550
553
  lexnode,
551
554
  desfecho.lookup.lookupId!,
@@ -565,22 +568,23 @@ chamada repetida tenta a fonte na hora, dentro da espera dela. O tempo
565
568
  esgotado da fonte continua sem repetição automática.
566
569
 
567
570
  **A consulta se lê de graça, por 24 h.** `processLookups.get(lookupId)` custa 0
568
- e, depois da entrega, traz o processo por 24 h (`resultExpiresAt`), relido do
569
- acervo e passado de novo pelo controle de confidencialidade: o processo que
570
- passou a segredo de justiça depois responde 403. O retrato lido da consulta
571
- traz a instância principal no topo e cada instância com os movimentos da
572
- última captura dela, e `sourcePayload` é nulo. `processLookups.list` e `processLookups.cancel`
573
- completam a superfície; o cancelamento devolve a retenção e não gera evento.
574
-
575
- **Por webhook, sem esperar nada.** O fim sai em `PROCESS_LOOKUP_COMPLETED`, um
576
- ponteiro para a consulta que nunca traz o processo; o movimento novo que a
577
- entrega trouxer chega no `PROCESS_MOVEMENT_CREATED` de sempre.
571
+ e, depois da entrega, traz o processo por 24 h (`resultExpiresAt`), relido e
572
+ passado de novo pelo controle de confidencialidade: o processo que passou a
573
+ segredo de justiça depois responde 403. O retrato lido da consulta traz a
574
+ instância principal no topo e cada instância com os movimentos da última
575
+ captura dela, e `sourcePayload` é nulo. `processLookups.list` e
576
+ `processLookups.cancel` completam a superfície; o cancelamento devolve a
577
+ retenção e não gera evento.
578
+
579
+ **Por webhook, sem esperar nada.** O fim sai em `PROCESS_LOOKUP_COMPLETED`, que
580
+ aponta a consulta e nunca traz o processo; o movimento novo que a entrega
581
+ trouxer chega no `PROCESS_MOVEMENT_CREATED` de sempre.
578
582
 
579
583
  **A cópia guardada e a atualização, uma compra só.** Com `staleOnFailure: true`,
580
- o 202 traz em `stale` a cópia que o acervo guarda, com a data e a causa, por 1
581
- crédito, e a consulta segue buscando a versão atual. A entrega cobra só o resto
582
- do preço (`alreadyCharged` diz quanto já foi): as duas juntas custam o mesmo que
583
- a leitura direta, e a atualização que não vier não cobra mais nada.
584
+ o 202 traz em `stale` a cópia guardada, com a data e a causa, por 1 crédito, e
585
+ a consulta segue buscando a versão atual. A entrega cobra só o resto do preço
586
+ (`alreadyCharged` diz quanto já foi): as duas juntas custam o mesmo que a
587
+ leitura direta, e a atualização que não vier não cobra mais nada.
584
588
 
585
589
  **A leitura direta fecha a consulta.** A ficha ou o retrato do mesmo processo
586
590
  lidos com sucesso por `processes.get` ou `processes.snapshot` fecham a consulta
@@ -593,8 +597,8 @@ consulta.
593
597
  ## Frescor: o `total: 0` qualificado
594
598
 
595
599
  Um resultado vazio não distingue sozinho "não existe" de "a fonte ainda não
596
- indexou". O motor publica a data-limite do índice; o SDK a lê nos dois eixos
597
- (processo e diário):
600
+ indexou". A API publica a data-limite do índice da fonte; o SDK a lê nos dois
601
+ eixos (processo e diário):
598
602
 
599
603
  ```ts
600
604
  import {
@@ -610,15 +614,15 @@ if (isNotYetIndexed(busca, { since: "2026-09-15" })) {
610
614
  mostrar(describeFreshness(busca)); // "fonte lida até 10/09/2026"
611
615
  }
612
616
 
613
- readThrough(busca); // Date | null — null é ESTADO indeterminado, não erro
617
+ readThrough(busca); // Date | null — null quando a resposta não declara a data-limite
614
618
  ```
615
619
 
616
620
  ## Vigília com vários interessados
617
621
 
618
- O motor separa duas primitivas: o **sensor** (`Monitor`), único por alvo na
619
- conta e com teto, e a **assinatura** (`ProcessSubscription`), única por
620
- `externalRef`, sem teto e ecoada no aviso. N interessados no mesmo processo são
621
- um sensor e N assinaturas.
622
+ A vigília de um processo tem duas partes: o **monitor**, que consulta a fonte —
623
+ um por processo na conta, e o plano limita quantos —, e a **assinatura**, que
624
+ recebe o aviso — uma por `externalRef`, sem limite, e o `externalRef` volta no
625
+ evento. N interessados no mesmo processo são um monitor e N assinaturas.
622
626
 
623
627
  ```ts
624
628
  import { unwatchProcess, watchProcess } from "@lexnode/legal-data-sdk";
@@ -627,23 +631,26 @@ const { monitorId, monitorAlreadyExisted, subscription } = await watchProcess(
627
631
  lexnode,
628
632
  {
629
633
  processNumber: "00012345620268260100",
630
- tribunalAlias: "api_publica_tjsp",
634
+ tribunalAlias: "TJSP",
631
635
  officeId: "usuario-4711",
632
636
  externalRef: "caso-42",
633
637
  },
634
638
  );
635
639
 
636
- // Um interessado desiste; o sensor continua varrendo para os demais.
640
+ // Um interessado sai; o monitor continua ativo para os demais.
637
641
  await unwatchProcess(lexnode, {
638
642
  externalRef: "caso-42",
639
643
  officeId: "usuario-4711",
640
644
  });
641
645
  ```
642
646
 
643
- **Limitação conhecida:** uma assinatura criada **antes** de o processo entrar no
644
- acervo não recebe `PROCESS_MOVEMENT_CREATED` até a primeira aparição do alvo na
645
- fonte. O contorno é refazer a assinatura quando chegar o
646
- `MONITOR_SYNC_SUCCEEDED` com `firstAppearance: true`:
647
+ Quando o último interessado sair, remova o monitor com
648
+ `lexnode.monitors.delete(monitorId)`.
649
+
650
+ **Limitação conhecida:** uma assinatura criada **antes** de o processo aparecer
651
+ na fonte não recebe `PROCESS_MOVEMENT_CREATED` até a primeira aparição dele. O
652
+ contorno é refazer a assinatura quando chegar o `MONITOR_SYNC_SUCCEEDED` com
653
+ `firstAppearance: true`:
647
654
 
648
655
  ```ts
649
656
  import { resubscribeOnFirstAppearance } from "@lexnode/legal-data-sdk";
@@ -653,10 +660,9 @@ await resubscribeOnFirstAppearance(lexnode, evento, [
653
660
  ]);
654
661
  ```
655
662
 
656
- ## Só os tipos
663
+ ## Os tipos do contrato
657
664
 
658
- O uso antigo continua valendo — o pacote exporta `paths` e `operations` como
659
- sempre:
665
+ O pacote exporta `paths` e `operations`, gerados do contrato OpenAPI:
660
666
 
661
667
  ```ts
662
668
  import type { operations, paths } from "@lexnode/legal-data-sdk";
@@ -669,7 +675,7 @@ type Tribunals =
669
675
  operations["listTribunals"]["responses"][200]["content"]["application/json"]["data"];
670
676
  ```
671
677
 
672
- E os atalhos derivados deles, que o cliente usa internamente:
678
+ E atalhos derivados deles:
673
679
 
674
680
  ```ts
675
681
  import type { Body, Data, Query } from "@lexnode/legal-data-sdk";
@@ -681,11 +687,11 @@ type Tribunais = Data<"/v1/tribunals", "get">;
681
687
 
682
688
  ## A chave de teste
683
689
 
684
- A chave `lapi_test_` lê o acervo já formado, mas **não consulta a fonte viva nem
685
- cria trabalho de fundo**: monitor, vigília, colheita e busca assíncrona
686
- respondem 403 `SANDBOX_UPSTREAM_DISABLED`. Na prática, `watchProcess` e o ramo
687
- `async` de `searchProcesses` só fecham com chave viva — vale saber antes de
688
- achar que a integração está errada.
690
+ A chave `lapi_test_` relê o que a conta já buscou com a chave de produção, mas
691
+ **não consulta a fonte nem cria trabalho em segundo plano**: monitor, vigília,
692
+ colheita e busca assíncrona respondem 403 `SANDBOX_UPSTREAM_DISABLED`. Por isso
693
+ `watchProcess` e o ramo `async` de `searchProcesses` só funcionam com a chave
694
+ de produção.
689
695
 
690
696
  ## Referências
691
697