@lexnode/legal-data-sdk 1.29.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,13 +491,46 @@ 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
 
501
+ Para uma integração que precisa continuar mesmo quando a fonte demora, use
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:
505
+
506
+ ```ts
507
+ import { readProcessWithRecovery } from "@lexnode/legal-data-sdk";
508
+
509
+ const leitura = await readProcessWithRecovery(lexnode, {
510
+ tribunalAlias: "TJSP",
511
+ processNumber: numeroDoProcesso,
512
+ view: "PROCESS",
513
+ waitSeconds: 5,
514
+ staleOnFailure: true, // aceita cópia anterior à falha: confira sourceFetchedAt
515
+ }, { completionBudgetMs: 30_000 });
516
+
517
+ if (leitura.kind === "ready") {
518
+ usarProcesso(leitura.data, leitura.meta.sourceFetchedAt);
519
+ } else if (leitura.kind === "pending") {
520
+ guardarConsulta(leitura.lookup.lookupId, leitura.nextReadAt);
521
+ // leitura.stale, quando presente, conserva a data da cópia.
522
+ // Continue pelo pollUrl ou pelo webhook PROCESS_LOOKUP_COMPLETED.
523
+ } else {
524
+ registrarDesfecho(leitura.kind, leitura.lookup.error);
525
+ // failed, canceled ou expired; nenhum deles afirma que o processo não existe.
526
+ }
527
+ ```
528
+
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`.
533
+
498
534
  `lookupProcess` diz, no tipo, qual dos dois desfechos aconteceu, e
499
535
  `waitForProcessLookup` espera a consulta terminar:
500
536
 
@@ -502,7 +538,7 @@ em segundo plano em vez de virar erro:
502
538
  import { lookupProcess, waitForProcessLookup } from "@lexnode/legal-data-sdk";
503
539
 
504
540
  const desfecho = await lookupProcess(lexnode, {
505
- tribunalAlias: "api_publica_tjsp",
541
+ tribunalAlias: "TJSP",
506
542
  processNumber: numero,
507
543
  view: "SNAPSHOT",
508
544
  });
@@ -510,9 +546,9 @@ const desfecho = await lookupProcess(lexnode, {
510
546
  if (desfecho.kind === "ready") {
511
547
  desfecho.data.movements; // o retrato, como processes.snapshot o devolveria
512
548
  } else {
513
- // O orçamento é explícito. A leitura seguinte só acontece na hora da
514
- // próxima tentativa (nextAttemptAt): antes dela, o estado não muda. Com
515
- // 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.
516
552
  const consulta = await waitForProcessLookup(
517
553
  lexnode,
518
554
  desfecho.lookup.lookupId!,
@@ -532,22 +568,23 @@ chamada repetida tenta a fonte na hora, dentro da espera dela. O tempo
532
568
  esgotado da fonte continua sem repetição automática.
533
569
 
534
570
  **A consulta se lê de graça, por 24 h.** `processLookups.get(lookupId)` custa 0
535
- e, depois da entrega, traz o processo por 24 h (`resultExpiresAt`), relido do
536
- acervo e passado de novo pelo controle de confidencialidade: o processo que
537
- passou a segredo de justiça depois responde 403. O retrato lido da consulta
538
- traz a instância principal no topo e cada instância com os movimentos da
539
- última captura dela, e `sourcePayload` é nulo. `processLookups.list` e `processLookups.cancel`
540
- completam a superfície; o cancelamento devolve a retenção e não gera evento.
541
-
542
- **Por webhook, sem esperar nada.** O fim sai em `PROCESS_LOOKUP_COMPLETED`, um
543
- ponteiro para a consulta que nunca traz o processo; o movimento novo que a
544
- 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.
545
582
 
546
583
  **A cópia guardada e a atualização, uma compra só.** Com `staleOnFailure: true`,
547
- o 202 traz em `stale` a cópia que o acervo guarda, com a data e a causa, por 1
548
- crédito, e a consulta segue buscando a versão atual. A entrega cobra só o resto
549
- do preço (`alreadyCharged` diz quanto já foi): as duas juntas custam o mesmo que
550
- 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.
551
588
 
552
589
  **A leitura direta fecha a consulta.** A ficha ou o retrato do mesmo processo
553
590
  lidos com sucesso por `processes.get` ou `processes.snapshot` fecham a consulta
@@ -560,8 +597,8 @@ consulta.
560
597
  ## Frescor: o `total: 0` qualificado
561
598
 
562
599
  Um resultado vazio não distingue sozinho "não existe" de "a fonte ainda não
563
- indexou". O motor publica a data-limite do índice; o SDK a lê nos dois eixos
564
- (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):
565
602
 
566
603
  ```ts
567
604
  import {
@@ -577,15 +614,15 @@ if (isNotYetIndexed(busca, { since: "2026-09-15" })) {
577
614
  mostrar(describeFreshness(busca)); // "fonte lida até 10/09/2026"
578
615
  }
579
616
 
580
- 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
581
618
  ```
582
619
 
583
620
  ## Vigília com vários interessados
584
621
 
585
- O motor separa duas primitivas: o **sensor** (`Monitor`), único por alvo na
586
- conta e com teto, e a **assinatura** (`ProcessSubscription`), única por
587
- `externalRef`, sem teto e ecoada no aviso. N interessados no mesmo processo são
588
- 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.
589
626
 
590
627
  ```ts
591
628
  import { unwatchProcess, watchProcess } from "@lexnode/legal-data-sdk";
@@ -594,23 +631,26 @@ const { monitorId, monitorAlreadyExisted, subscription } = await watchProcess(
594
631
  lexnode,
595
632
  {
596
633
  processNumber: "00012345620268260100",
597
- tribunalAlias: "api_publica_tjsp",
634
+ tribunalAlias: "TJSP",
598
635
  officeId: "usuario-4711",
599
636
  externalRef: "caso-42",
600
637
  },
601
638
  );
602
639
 
603
- // Um interessado desiste; o sensor continua varrendo para os demais.
640
+ // Um interessado sai; o monitor continua ativo para os demais.
604
641
  await unwatchProcess(lexnode, {
605
642
  externalRef: "caso-42",
606
643
  officeId: "usuario-4711",
607
644
  });
608
645
  ```
609
646
 
610
- **Limitação conhecida:** uma assinatura criada **antes** de o processo entrar no
611
- acervo não recebe `PROCESS_MOVEMENT_CREATED` até a primeira aparição do alvo na
612
- fonte. O contorno é refazer a assinatura quando chegar o
613
- `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`:
614
654
 
615
655
  ```ts
616
656
  import { resubscribeOnFirstAppearance } from "@lexnode/legal-data-sdk";
@@ -620,10 +660,9 @@ await resubscribeOnFirstAppearance(lexnode, evento, [
620
660
  ]);
621
661
  ```
622
662
 
623
- ## Só os tipos
663
+ ## Os tipos do contrato
624
664
 
625
- O uso antigo continua valendo — o pacote exporta `paths` e `operations` como
626
- sempre:
665
+ O pacote exporta `paths` e `operations`, gerados do contrato OpenAPI:
627
666
 
628
667
  ```ts
629
668
  import type { operations, paths } from "@lexnode/legal-data-sdk";
@@ -636,7 +675,7 @@ type Tribunals =
636
675
  operations["listTribunals"]["responses"][200]["content"]["application/json"]["data"];
637
676
  ```
638
677
 
639
- E os atalhos derivados deles, que o cliente usa internamente:
678
+ E atalhos derivados deles:
640
679
 
641
680
  ```ts
642
681
  import type { Body, Data, Query } from "@lexnode/legal-data-sdk";
@@ -648,11 +687,11 @@ type Tribunais = Data<"/v1/tribunals", "get">;
648
687
 
649
688
  ## A chave de teste
650
689
 
651
- A chave `lapi_test_` lê o acervo já formado, mas **não consulta a fonte viva nem
652
- cria trabalho de fundo**: monitor, vigília, colheita e busca assíncrona
653
- respondem 403 `SANDBOX_UPSTREAM_DISABLED`. Na prática, `watchProcess` e o ramo
654
- `async` de `searchProcesses` só fecham com chave viva — vale saber antes de
655
- 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.
656
695
 
657
696
  ## Referências
658
697