{"info":{"name":"RTDPJ · Análise de documentos por IA","description":"O caminho inteiro da API, na ordem, com os valores do ambiente de demonstração.\n\nRode de cima para baixo: cada chamada guarda o que a próxima precisa. O guia passo a passo está em {{host}}/api/integracao/ia/v1/guia.\n\nPARA ALTERNAR DE AMBIENTE: importe os *environments* (local, homologação, produção) e use o seletor do Postman. Os links estão na página do guia. A coleção fala só `{{host}}`.\n\nPARA USAR COM A SUA EMPRESA: troque `integradorId`, `kid`, `chavePrivada` e `serventiaId` nas variáveis da coleção. O resto continua igual.\n\nEsta API analisa documentos. Ela não registra, não sela e não lavra.","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},"variable":[{"key":"host","value":"http://localhost:3100","description":"O ENDEREÇO do ambiente — e a única coisa que muda entre eles.\n\nNão edite aqui: importe os *environments* e use o seletor do canto superior direito do Postman. É o que evita disparar contra o ambiente errado por distração.\n\n· Local: http://localhost:3100\n· Homologação: https://iah.rtdbrasil.org.br\n· Produção: https://ia.rtdbrasil.org.br"},{"key":"audiencia","value":"https://rtdpj.local/integracao/ia/v1/token","description":"O `aud` que a asserção precisa declarar. NÃO é a URL que você chama: é o identificador desta porta, e conferi-lo é o que impede uma asserção assinada para outro serviço de valer aqui."},{"key":"integradorId","value":"a7c41e90-3b52-4d18-9e6f-2c8b5a1d7e40","description":"O id da sua empresa integradora, dado pelo Console Nacional no cadastro."},{"key":"kid","value":"prolink-2026","description":"Qual das suas chaves assina. Aparece no cabeçalho da asserção."},{"key":"chavePrivada","value":"-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgDVguLrevWtKCxhqX\nPh7cuKji1Le1QYVbLYFbjaHZbsmhRANCAATXlrLgDYh9pe51vLSHhNpPegjQoJuJ\n6+qhbhBzGsn6c+xSP8LwALN3O5IUUitjlL4RjG6SDGyfUGWqd3cxHTLp\n-----END PRIVATE KEY-----","description":"A chave privada de TESTE deste ambiente. A sua nunca sai da sua máquina — nós guardamos apenas a pública."},{"key":"serventiaId","value":"0f1a2b3c-4d5e-4f60-9a71-8b2c3d4e5f60","description":"A serventia para a qual você pede o token. É ela que o token carrega."},{"key":"assercao","value":"","description":"Preenchida pelo script antes do pedido de token."},{"key":"token","value":"","description":"Preenchida pela resposta do token. Vale 10 minutos."},{"key":"arquivoId","value":"","description":"Preenchida pelo preparo do arquivo."},{"key":"urlDeEnvio","value":"","description":"Preenchida pelo preparo: é nela que você faz o PUT."},{"key":"analiseId","value":"","description":"Preenchida pelo pedido de análise."}],"item":[{"name":"1 · Token (assina a asserção e troca)","event":[{"listen":"prerequest","script":{"type":"text/javascript","exec":["const jsrsasign = require(\"jsrsasign\");","","// A asserção vale para UMA troca: dois minutos é folga de relógio, não sessão.","const agora = Math.floor(Date.now() / 1000);","const cabecalho = { alg: \"ES256\", typ: \"JWT\", kid: pm.collectionVariables.get(\"kid\") };","const corpo = {","  iss: pm.collectionVariables.get(\"integradorId\"),","  sub: pm.collectionVariables.get(\"integradorId\"),","  aud: pm.collectionVariables.get(\"audiencia\"),","  // o `jti` não pode repetir: a mesma asserção reapresentada é recusada","  jti: \"postman-\" + agora + \"-\" + Math.floor(Math.random() * 1e9),","  iat: agora,","  exp: agora + 120,","};","const assinada = jsrsasign.KJUR.jws.JWS.sign(","  \"ES256\", JSON.stringify(cabecalho), JSON.stringify(corpo),","  pm.collectionVariables.get(\"chavePrivada\"),",");","pm.collectionVariables.set(\"assercao\", assinada);"]}},{"listen":"test","script":{"type":"text/javascript","exec":["const r = pm.response.json();","pm.test(\"veio o token\", () => pm.expect(r.access_token).to.be.a(\"string\"));","// as chamadas seguintes leem daqui: não há o que copiar à mão","pm.collectionVariables.set(\"token\", r.access_token);"]}}],"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/token","host":["{{host}}"],"path":["api","integracao","ia","v1","token"]},"body":{"mode":"raw","raw":"{\n  \"client_assertion\": \"{{assercao}}\",\n  \"cns\": \"{{serventiaId}}\"\n}","options":{"raw":{"language":"json"}}},"description":"A asserção é assinada aqui mesmo, no script de pré-requisição — você não precisa produzir um JWT à mão para ver a API responder.\n\nO token vale 10 minutos e carrega a serventia: nenhuma chamada abaixo pergunta de qual cartório é o pedido."}},{"name":"2 · Naturezas (o de/para do seu lado)","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{token}}"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/naturezas","host":["{{host}}"],"path":["api","integracao","ia","v1","naturezas"]},"description":"A árvore de naturezas ATIVAS desta serventia. É por estes códigos que a análise é pedida, e a correspondência com os códigos do SEU sistema é mantida por você.\n\nA UF não é parâmetro: sai da serventia do token. Releia periodicamente — o catálogo muda quando o cartório personaliza ou o estado publica natureza nova."}},{"name":"3 · Preparar o envio do arquivo","event":[{"listen":"test","script":{"type":"text/javascript","exec":["const r = pm.response.json();","pm.test(\"veio o arquivoId\", () => pm.expect(r.arquivoId).to.be.a(\"string\"));","pm.collectionVariables.set(\"arquivoId\", r.arquivoId);","// `url` é nula no ambiente de disco local: ali o passo 4 não se aplica","pm.collectionVariables.set(\"urlDeEnvio\", r.url || \"\");"]}}],"request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{token}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/arquivos/preparar","host":["{{host}}"],"path":["api","integracao","ia","v1","arquivos","preparar"]},"body":{"mode":"raw","raw":"{\n  \"nome\": \"11-RTD-locacao-comercial.pdf\",\n  \"tamanho\": 186432\n}","options":{"raw":{"language":"json"}}},"description":"Devolve uma autorização curta para UMA chave. O arquivo NÃO passa pela nossa aplicação: você faz `PUT` direto na URL devolvida.\n\n`tamanho` é opcional e serve para recusar cedo o que já se sabe grande demais. Ele não é acreditado — a confirmação pergunta ao armazenamento qual foi o tamanho real.\n\nPreparar não gasta crédito."}},{"name":"4 · PUT do arquivo (direto no armazenamento)","request":{"method":"PUT","header":[{"key":"Content-Type","value":"application/pdf"}],"url":{"raw":"{{urlDeEnvio}}","host":["{{urlDeEnvio}}"]},"body":{"mode":"file","file":{"src":""}},"description":"ESTA CHAMADA NÃO PASSA POR NÓS — é entre o seu sistema e o armazenamento.\n\nNo Postman: aba Body › binary › escolha o PDF. Um contrato de locação serve; os documentos da demonstração estão em `cypress/fixtures/` do repositório.\n\nSe `urlDeEnvio` voltou vazia, este ambiente guarda em disco local e você pode pular para o passo 5 — a confirmação dirá o que falta."}},{"name":"5 · Confirmar que o arquivo chegou","request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{token}}"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/arquivos/{{arquivoId}}/confirmar","host":["{{host}}"],"path":["api","integracao","ia","v1","arquivos","{{arquivoId}}","confirmar"]},"description":"Só agora o arquivo existe. Conferimos que o objeto chegou, o tamanho REAL e se os primeiros bytes são de um formato aceito — arquivo recusado é apagado do armazenamento.\n\nConfirmar duas vezes devolve o mesmo arquivo: repetição de chamada de rede é normal."}},{"name":"5b · Em que pé está a leitura (opcional)","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{token}}"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/arquivos/{{arquivoId}}","host":["{{host}}"],"path":["api","integracao","ia","v1","arquivos","{{arquivoId}}"]},"description":"A leitura acontece em segundo plano depois da confirmação e demora mais que as chamadas do envio. `leitura` vem `pendente`, `concluida` ou `falhou`.\n\nVOCÊ NÃO PRECISA CHAMAR ISTO. Pedir a análise cedo é seguro — ela espera sozinha. Esta rota serve a quem prefere acompanhar o passo, e a entender o que houve quando algo dá errado.\n\nSe você cadastrou webhook, o aviso `arquivo.pronto` chega sozinho quando ela termina."}},{"name":"6 · Pedir a análise","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"202 — aceito, não concluído\", () => pm.response.to.have.status(202));","const r = pm.response.json();","pm.collectionVariables.set(\"analiseId\", r.id);"]}}],"request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{token}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/analises","host":["{{host}}"],"path":["api","integracao","ia","v1","analises"]},"body":{"mode":"raw","raw":"{\n  \"arquivoId\": \"{{arquivoId}}\",\n  \"especieAto\": \"registro_integral\",\n  \"naturezaCodigo\": \"LOCACAO_PREDIO\"\n}","options":{"raw":{"language":"json"}}},"description":"202 é \"aceito\", não \"concluído\": a leitura roda em segundo plano. Guarde o `id`.\n\nREPETIR O MESMO PEDIDO NÃO CRIA OUTRA ANÁLISE. Enquanto o mesmo arquivo estiver `aguardando_ocr`, `na_fila` ou `analisando`, você recebe de volta a MESMA análise, com o mesmo id e sem custo novo — a repetição de uma chamada de rede é normal e não pode cobrar duas vezes.\n\nE NÃO ESPERE ANTES DE PEDIR: se a leitura do arquivo ainda não terminou, a análise nasce `aguardando_ocr` e entra na fila sozinha quando o texto fica pronto.\n\nNÃO mande `uf`: ela vem da serventia do token, e mandá-la é recusado. Um engano de duas letras devolveria um parecer fundado na norma de outro estado, sem erro em lugar nenhum.\n\n403 aqui significa TETO DIÁRIO de gasto, não excesso de velocidade — não implemente recuo exponencial para isso: reabre à meia-noite."}},{"name":"6a² · Pedir a análise com VÁRIOS documentos (arquivoId como lista)","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"202 — aceito\", () => pm.response.to.have.status(202));","const r = pm.response.json();","pm.collectionVariables.set(\"analiseId\", r.id);"]}}],"request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{token}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/analises","host":["{{host}}"],"path":["api","integracao","ia","v1","analises"]},"body":{"mode":"raw","raw":"{\n  \"arquivoId\": [\n    \"{{arquivoId}}\",\n    \"<id do anexo, confirmado>\"\n  ],\n  \"especieAto\": \"registro_integral\",\n  \"naturezaCodigo\": \"LOCACAO_PREDIO\"\n}","options":{"raw":{"language":"json"}}},"description":"Mesma rota do 6a, com `arquivoId` em LISTA. Todos os documentos são interpretados e usados JUNTOS no mesmo parecer — o primeiro é o principal, os demais complementares, como o exame do balcão trata o título e seus anexos. O custo é o da análise (uma só), não um por arquivo."}},{"name":"6b · Pedir a análise com o TEOR já extraído","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"202 — aceito, não concluído\", () => pm.response.to.have.status(202));","const r = pm.response.json();","pm.test(\"a resposta diz de onde veio o texto\", () => pm.expect(r.origemDoTeor).to.eql(\"informado\"));","pm.collectionVariables.set(\"analiseId\", r.id);"]}}],"request":{"method":"POST","header":[{"key":"Authorization","value":"Bearer {{token}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/analises","host":["{{host}}"],"path":["api","integracao","ia","v1","analises"]},"body":{"mode":"raw","raw":"{\n  \"teor\": \"ATA DA ASSEMBLEIA GERAL EXTRAORDINÁRIA DO CONDOMÍNIO EDIFÍCIO EXEMPLO. Aos vinte e nove dias do mês de setembro de dois mil e vinte e seis, reuniram-se os condôminos…\",\n  \"nomeDoDocumento\": \"ata-do-meu-sistema.pdf\",\n  \"especieAto\": \"registro_integral\",\n  \"naturezaCodigo\": \"LOCACAO_PREDIO\"\n}","options":{"raw":{"language":"json"}}},"description":"Para quem JÁ TEM o texto: passou o próprio OCR, ou o documento nasceu digital do seu lado. Não sobe arquivo, não espera leitura, e o passo 3-4-5 inteiro fica de fora.\n\nÉ ALTERNATIVA a `arquivoId`, não complemento: mandar os dois é recusado com 400 — com os dois não haveria como saber sobre qual dos textos o parecer foi feito.\n\nA resposta marca `origemDoTeor: \"informado\"`. Isso não é enfeite: com arquivo, o que se examina é o DOCUMENTO; com teor, é o que VOCÊ afirmou que o documento diz. É a mesma análise e não é a mesma cadeia de responsabilidade.\n\nA cobrança é por página, e sem arquivo não há página para contar: ela é estimada pelo tamanho do texto."}},{"name":"7a · Consultar a análise (por id)","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{token}}"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/analises/{{analiseId}}","host":["{{host}}"],"path":["api","integracao","ia","v1","analises","{{analiseId}}"]},"description":"Enquanto a situação for `aguardando_ocr`, `na_fila` ou `analisando`, ainda não há resultado — e isso NÃO é erro. Quando for `pronta`, vem junto a ficha extraída, os achados e o checklist respondido.\n\n`aguardando_ocr` significa que o arquivo ainda está sendo lido. A análise entra na fila sozinha quando o texto ficar pronto; não é preciso pedir de novo."}},{"name":"7b · Reconciliar (o que mudou desde…)","request":{"method":"GET","header":[{"key":"Authorization","value":"Bearer {{token}}"}],"url":{"raw":"{{host}}/api/integracao/ia/v1/analises","host":["{{host}}"],"path":["api","integracao","ia","v1","analises"],"query":[{"key":"desde","value":"2026-01-01T00:00:00.000Z","description":"guarde o `atualizadoAte` da resposta e mande-o aqui na próxima leitura"}]},"description":"A rota de quem ficou fora do ar e precisa recuperar o que perdeu, sem consultar uma por uma.\n\nA paginação é por TEMPO, e não por página: item que termina entre uma página e outra escapa de paginação por posição, e some sem ninguém notar.\n\nMantenha isto rodando mesmo com webhook: entrega perdida acontece, e a análise já foi paga."}}]}