{
  "info": {
    "name": "AdigitalMAX API v1",
    "description": "Colecao oficial da API REST do AdigitalMAX (assinatura eletronica de documentos).\n\n>>> PENDENTE DE BACKEND <<<\nNenhuma requisicao desta colecao foi executada contra um servidor de producao. Ela espelha o contrato canonico docs/CONTRATO_API.openapi.yaml e sera conferida chamada por chamada quando o servico subir.\n\nCOMO USAR\n1. Importe este arquivo (funciona no Postman, no Insomnia e no Bruno).\n2. Abra as variaveis da colecao e preencha `chave` com uma chave de sandbox, criada no painel em Configuracoes > API > Chaves. Ela tem o formato adx_test_<prefixo>.<segredo>.\n3. Comece por 'Identidade > GET /organizacao'. Se responder 200, a autenticacao esta correta.\n\nA variavel `base` ja aponta para o sandbox. Para producao, remova o sufixo /sandbox e use uma chave adx_live_.\n\nDUAS COISAS QUE SURPREENDEM QUEM VEM DE OUTRAS APIS\n- Nao existe rota /me. A chave pertence a uma organizacao, e nao a uma pessoa.\n- Criar documento NUNCA dispara. O documento nasce em RASCUNHO e enviar e uma segunda chamada, explicita.\n\nDocumentacao completa: https://docs.adigitalmax.com.br",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "base",
      "value": "https://api.adigitalmax.com.br/v1/sandbox",
      "type": "string",
      "description": "URL base. Sandbox por padrao; para producao remova o sufixo /sandbox."
    },
    {
      "key": "chave",
      "value": "",
      "type": "string",
      "description": "Sua chave de API, no formato adx_test_<prefixo>.<segredo> em sandbox ou adx_live_<prefixo>.<segredo> em producao. O valor completo aparece uma unica vez, na tela da criacao: a plataforma guarda apenas o hash do segredo."
    },
    {
      "key": "validacao",
      "value": "https://adigitalmax.com.br/api/v1/validacao/consultar",
      "type": "string",
      "description": "A validacao publica mora no host institucional, e nao em api., para evitar consulta entre origens no endpoint mais exposto do sistema."
    },
    {
      "key": "documentoId",
      "value": "",
      "type": "string",
      "description": "Preenchido automaticamente pelo teste de 'POST /documentos'."
    },
    {
      "key": "signatarioId",
      "value": "",
      "type": "string",
      "description": "Preenchido automaticamente pelo teste de 'POST /documentos'."
    },
    { "key": "pastaId", "value": "", "type": "string" },
    { "key": "modeloId", "value": "", "type": "string" },
    { "key": "webhookId", "value": "", "type": "string" },
    {
      "key": "codigoValidacao",
      "value": "",
      "type": "string",
      "description": "Codigo impresso no rodape do PDF assinado, no formato ADM-XXXX-XXXX-XXXX."
    }
  ],
  "auth": {
    "type": "bearer",
    "bearer": [{ "key": "token", "value": "{{chave}}", "type": "string" }]
  },
  "event": [
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Roda depois de TODA requisicao da colecao.",
          "",
          "// O orcamento de requisicoes do minuto corrente.",
          "const restante = pm.response.headers.get('X-RateLimit-Remaining');",
          "if (restante !== null && Number(restante) < 10) {",
          "    console.warn('Restam apenas ' + restante + ' requisicoes neste minuto.');",
          "}",
          "",
          "// Em erro, o corpo e um problem details da RFC 9457. Ramifique",
          "// sempre por `codigo`, nunca pelo status nem pelo texto.",
          "if (pm.response.code >= 400) {",
          "    try {",
          "        const corpo = pm.response.json();",
          "        console.error(pm.response.code, corpo.codigo, corpo.title);",
          "        // O requestId e o codigo de correlacao: e ele que o suporte pede.",
          "        if (corpo.requestId) console.error('requestId:', corpo.requestId);",
          "        if (corpo.erros) console.error('erros:', JSON.stringify(corpo.erros));",
          "    } catch (e) {",
          "        console.error(pm.response.code, pm.response.text());",
          "    }",
          "}"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "1. Identidade e organizacao",
      "item": [
        {
          "name": "GET /organizacao",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('responde 200', () => pm.response.to.have.status(200));",
                  "pm.test('traz a organizacao', () => {",
                  "    const c = pm.response.json();",
                  "    pm.expect(c).to.have.property('id');",
                  "    console.log('Organizacao:', c.nome);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": { "raw": "{{base}}/organizacao", "host": ["{{base}}"], "path": ["organizacao"] },
            "description": "Confere a credencial e devolve a organizacao dona dela, com plano e marca. E a chamada mais barata da API e o teste de fumaca da integracao.\n\nNAO EXISTE ROTA /me: a chave pertence a uma organizacao, e nao a uma pessoa.\n\nEscopo: organizacao:ler | Papel minimo: VISUALIZADOR"
          }
        },
        {
          "name": "PATCH /organizacao",
          "request": {
            "method": "PATCH",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"nome\": \"Empresa Exemplo LTDA\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": { "raw": "{{base}}/organizacao", "host": ["{{base}}"], "path": ["organizacao"] },
            "description": "Altera dados cadastrais e a marca aplicada aos e-mails de convite e a pagina de assinatura.\n\nEscopo: organizacao:escrever | Papel minimo: ADMINISTRADOR"
          }
        },
        {
          "name": "GET /organizacao/membros",
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": {
              "raw": "{{base}}/organizacao/membros?limite=25",
              "host": ["{{base}}"],
              "path": ["organizacao", "membros"],
              "query": [{ "key": "limite", "value": "25" }]
            },
            "description": "Membros da organizacao com papel, estado e ultimo acesso.\n\nOs quatro papeis sao PROPRIETARIO, ADMINISTRADOR, OPERADOR e VISUALIZADOR. Cuidado: OPERADOR aqui e o usuario operacional da organizacao CLIENTE, e nao o funcionario da plataforma.\n\nEscopo: organizacao:ler | Papel minimo: OPERADOR"
          }
        }
      ]
    },
    {
      "name": "2. Documentos",
      "item": [
        {
          "name": "POST /documentos (criar rascunho)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('responde 201', () => pm.response.to.have.status(201));",
                  "",
                  "if (pm.response.code === 201) {",
                  "    const doc = pm.response.json();",
                  "    // Guarda os ids para as requisicoes seguintes da colecao.",
                  "    pm.collectionVariables.set('documentoId', doc.id);",
                  "    if (doc.signatarios && doc.signatarios.length) {",
                  "        pm.collectionVariables.set('signatarioId', doc.signatarios[0].id);",
                  "    }",
                  "    console.log('documentoId =', doc.id, '| status =', doc.status);",
                  "    pm.test('nasce em RASCUNHO', () => {",
                  "        pm.expect(doc.status).to.eql('RASCUNHO');",
                  "    });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "postman-{{$guid}}",
                "description": "Repetir a requisicao com a mesma chave dentro de 24 horas devolve a resposta original em vez de criar de novo. Em producao, use um identificador do SEU dominio (numero do pedido), e nao um aleatorio: aleatorio gerado na hora muda a cada retentativa, que e exatamente o caso que a idempotencia deveria cobrir."
              }
            ],
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "dados",
                  "type": "text",
                  "contentType": "application/json",
                  "value": "{\n  \"titulo\": \"Contrato de teste - Postman\",\n  \"descricao\": \"Criado pela colecao de exemplo.\",\n  \"nivelAssinatura\": \"SEGURA\",\n  \"fatoresExigidos\": [\"EMAIL\", \"SMS_OTP\"],\n  \"ordenado\": false,\n  \"recusavel\": true,\n  \"pararSeRecusado\": true,\n  \"lembrete\": \"SEMANAL\",\n  \"signatarios\": [\n    {\n      \"nome\": \"Robo Que Assina\",\n      \"email\": \"assina@teste.adigitalmax.com.br\",\n      \"telefone\": \"+5511900000000\",\n      \"funcao\": \"ASSINAR\",\n      \"metodoEntrega\": \"EMAIL\",\n      \"ordem\": 1\n    }\n  ],\n  \"campos\": [\n    { \"tipo\": \"ASSINATURA\", \"pagina\": 1, \"x\": 0.10, \"y\": 0.75, \"largura\": 0.34, \"altura\": 0.07 }\n  ]\n}",
                  "description": "O JSON com o documento, os signatarios e os campos.\n\nfatoresExigidos com dois valores JA E dois fatores simultaneos: nao existe booleano de 'exigir todos', a lista E o conjunto exigido.\n\nCoordenadas sao fracao de 0 a 1, origem no canto superior esquerdo, e o campo tem que caber: x + largura <= 1."
                },
                {
                  "key": "arquivo",
                  "type": "file",
                  "src": [],
                  "description": "Selecione um PDF de ate 20 MB. O tipo real e conferido pelo conteudo, nao pela extensao. Em sandbox, o e-mail assina@teste.adigitalmax.com.br assina sozinho em ~5 segundos depois do envio."
                }
              ]
            },
            "url": { "raw": "{{base}}/documentos", "host": ["{{base}}"], "path": ["documentos"] },
            "description": "Cria o documento a partir de um PDF, em multipart/form-data com as partes `dados` e `arquivo`.\n\nO DOCUMENTO NASCE EM RASCUNHO E NAO DISPARA NADA. Enviar e uma operacao separada e explicita, porque disparar por efeito colateral de uma criacao e como se envia contrato errado para cliente.\n\nEscopo: documentos:escrever | Papel minimo: OPERADOR"
          }
        },
        {
          "name": "POST /documentos/{id}/enviar",
          "request": {
            "method": "POST",
            "header": [{ "key": "Idempotency-Key", "value": "postman-enviar-{{documentoId}}" }],
            "url": {
              "raw": "{{base}}/documentos/{{documentoId}}/enviar",
              "host": ["{{base}}"],
              "path": ["documentos", "{{documentoId}}", "enviar"]
            },
            "description": "Tira o documento do rascunho e dispara os convites conforme a ordem configurada. E ESTA chamada que consome um envelope da franquia.\n\nSo funciona a partir de RASCUNHO. Franquia esgotada devolve o codigo FRANQUIA_ESGOTADA, e retentar nao ajuda.\n\nEmite o evento document.sent.\n\nEscopo: documentos:escrever | Papel minimo: OPERADOR"
          }
        },
        {
          "name": "GET /documentos (listar)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "    const p = pm.response.json();",
                  "    console.log(p.itens.length + ' de ' + p.total + ' itens');",
                  "    // Pare quando proximoCursor vier nulo ou ausente, e NUNCA",
                  "    // porque a pagina veio com menos itens que o limite.",
                  "    if (p.proximoCursor) console.log('proximoCursor:', p.proximoCursor);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": {
              "raw": "{{base}}/documentos?limite=25&status=AGUARDANDO_ASSINATURAS",
              "host": ["{{base}}"],
              "path": ["documentos"],
              "query": [
                { "key": "limite", "value": "25" },
                { "key": "status", "value": "AGUARDANDO_ASSINATURAS", "description": "Um StatusDocumento: RASCUNHO, AGUARDANDO_ASSINATURAS, PARCIALMENTE_ASSINADO, ASSINADO, RECUSADO, CANCELADO ou EXPIRADO." },
                { "key": "cursor", "value": "", "disabled": true, "description": "O proximoCursor da pagina anterior." },
                { "key": "busca", "value": "", "disabled": true },
                { "key": "pastaId", "value": "", "disabled": true }
              ]
            },
            "description": "Listagem paginada por cursor. A resposta e { itens, total, proximoCursor }.\n\nO total obedece ao mesmo escopo dos itens: um total calculado fora do escopo entregaria o volume de negocio de outra organizacao mesmo sem entregar uma linha.\n\nEscopo: documentos:ler | Papel minimo: VISUALIZADOR"
          }
        },
        {
          "name": "GET /documentos/{id}",
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": {
              "raw": "{{base}}/documentos/{{documentoId}}",
              "host": ["{{base}}"],
              "path": ["documentos", "{{documentoId}}"]
            },
            "description": "O documento completo, com signatarios, campos e o estado de cada assinatura.\n\nDocumento de outra organizacao responde 404, e nao 403, com corpo e latencia identicos aos de um id que nunca existiu: 403 significaria 'existe, e voce nao pode', e isso e um oraculo de existencia.\n\nEscopo: documentos:ler | Papel minimo: VISUALIZADOR"
          }
        },
        {
          "name": "PUT /documentos/{id}/campos",
          "request": {
            "method": "PUT",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"campos\": [\n    {\n      \"signatarioId\": \"{{signatarioId}}\",\n      \"tipo\": \"ASSINATURA\",\n      \"pagina\": 1,\n      \"x\": 0.10,\n      \"y\": 0.72,\n      \"largura\": 0.34,\n      \"altura\": 0.07\n    },\n    {\n      \"signatarioId\": \"{{signatarioId}}\",\n      \"tipo\": \"DATA\",\n      \"pagina\": 1,\n      \"x\": 0.10,\n      \"y\": 0.82,\n      \"largura\": 0.20,\n      \"altura\": 0.03\n    }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base}}/documentos/{{documentoId}}/campos",
              "host": ["{{base}}"],
              "path": ["documentos", "{{documentoId}}", "campos"]
            },
            "description": "Substitui em lote o mapa inteiro de campos. E PUT, e nao PATCH, de proposito: posicionamento de campo e um desenho, e desenho se troca inteiro. O que nao vier no corpo deixa de existir.\n\nEscopo: documentos:escrever | Papel minimo: OPERADOR"
          }
        },
        {
          "name": "POST /documentos/{id}/reenviar",
          "request": {
            "method": "POST",
            "url": {
              "raw": "{{base}}/documentos/{{documentoId}}/reenviar",
              "host": ["{{base}}"],
              "path": ["documentos", "{{documentoId}}", "reenviar"]
            },
            "description": "Reenvia o convite a quem ainda nao assinou. Nao consome envelope novo e nao altera o prazo.\n\nEscopo: documentos:escrever | Papel minimo: OPERADOR"
          }
        },
        {
          "name": "POST /documentos/{id}/cancelar",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"motivo\": \"Cancelado durante teste da integracao.\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base}}/documentos/{{documentoId}}/cancelar",
              "host": ["{{base}}"],
              "path": ["documentos", "{{documentoId}}", "cancelar"]
            },
            "description": "Interrompe o processo. Os links dos signatarios param de funcionar na hora, quem ja assinou continua registrado na auditoria, e o motivo entra na trilha. Nao tem volta.\n\nEmite o evento document.cancelled.\n\nEscopo: documentos:escrever | Papel minimo: OPERADOR"
          }
        },
        {
          "name": "GET /documentos/{id}/auditoria",
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": {
              "raw": "{{base}}/documentos/{{documentoId}}/auditoria?limite=100",
              "host": ["{{base}}"],
              "path": ["documentos", "{{documentoId}}", "auditoria"],
              "query": [{ "key": "limite", "value": "100" }]
            },
            "description": "A trilha de auditoria completa, em ordem cronologica, com ator, horario, IP, agente e geolocalizacao aproximada. Imutavel: nao ha rota que altere ou apague evento.\n\nNote que o escopo e auditoria:ler, e NAO documentos:ler: uma chave pode ler documentos sem ler a trilha deles.\n\nEscopo: auditoria:ler | Papel minimo: VISUALIZADOR"
          }
        }
      ]
    },
    {
      "name": "3. Arquivo e certificado",
      "item": [
        {
          "name": "GET /documentos/{id}/certificado",
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": {
              "raw": "{{base}}/documentos/{{documentoId}}/certificado",
              "host": ["{{base}}"],
              "path": ["documentos", "{{documentoId}}", "certificado"]
            },
            "description": "O certificado de conclusao e as evidencias: hashes, codigo de validacao publica, e para cada signatario os fatores exigidos e usados, datas, IP e dados tecnicos.\n\nE versionado e encadeado: emitir de novo cria uma versao nova ligada por hashAnterior a anterior, e nao sobrescreve.\n\nSo existe depois de o documento chegar a ASSINADO; antes disso responde com o codigo CERTIFICADO_INDISPONIVEL. Certificado parcial seria prova de algo que nao aconteceu.\n\nEscopo: documentos:ler | Papel minimo: VISUALIZADOR"
          }
        },
        {
          "name": "GET /documentos/{id}/arquivo",
          "request": {
            "method": "GET",
            "url": {
              "raw": "{{base}}/documentos/{{documentoId}}/arquivo?tipo=ASSINADO",
              "host": ["{{base}}"],
              "path": ["documentos", "{{documentoId}}", "arquivo"],
              "query": [
                {
                  "key": "tipo",
                  "value": "ASSINADO",
                  "description": "ORIGINAL, ASSINADO, PADES ou CERTIFICADO. Padrao ASSINADO. PADES so existe no nivel ICP_BRASIL, que ainda nao esta disponivel."
                }
              ]
            },
            "description": "Baixa o arquivo do documento.\n\nA API resolve o objeto dentro do escopo da organizacao, verifica o direito e so entao delega a entrega ao servidor web por X-Accel-Redirect apontando para um caminho interno. Nao existe caminho publico previsivel para o arquivo, e o diretorio nunca e servido como estatico.\n\nConfira o hash do que baixou contra o hashDocumento do certificado: arquivo corrompido no caminho nao serve como prova, e o erro so aparece anos depois.\n\nEscopo: arquivos:ler | Papel minimo: VISUALIZADOR"
          }
        }
      ]
    },
    {
      "name": "4. Pastas",
      "item": [
        {
          "name": "GET /pastas",
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": {
              "raw": "{{base}}/pastas?limite=100",
              "host": ["{{base}}"],
              "path": ["pastas"],
              "query": [{ "key": "limite", "value": "100" }]
            },
            "description": "Escopo: pastas:ler | Papel minimo: VISUALIZADOR"
          }
        },
        {
          "name": "POST /pastas",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) {",
                  "    pm.collectionVariables.set('pastaId', pm.response.json().id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"nome\": \"Contratos 2026\",\n  \"paiId\": null\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": { "raw": "{{base}}/pastas", "host": ["{{base}}"], "path": ["pastas"] },
            "description": "Cria pasta. `paiId` aninha.\n\nEscopo: pastas:escrever | Papel minimo: OPERADOR"
          }
        },
        {
          "name": "POST /documentos/{id}/mover",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"pastaId\": \"{{pastaId}}\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base}}/documentos/{{documentoId}}/mover",
              "host": ["{{base}}"],
              "path": ["documentos", "{{documentoId}}", "mover"]
            },
            "description": "Move o documento para outra pasta. `pastaId: null` devolve a raiz.\n\nPasta e organizacao, nao permissao: colocar um documento em pasta nao restringe quem o ve.\n\nEscopo: documentos:escrever | Papel minimo: OPERADOR"
          }
        }
      ]
    },
    {
      "name": "5. Modelos",
      "item": [
        {
          "name": "GET /modelos",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "    const lista = pm.response.json().itens;",
                  "    if (lista && lista.length) {",
                  "        pm.collectionVariables.set('modeloId', lista[0].id);",
                  "        console.log('modeloId =', lista[0].id, '| variaveis:', (lista[0].variaveis || []).join(', '));",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": { "raw": "{{base}}/modelos", "host": ["{{base}}"], "path": ["modelos"] },
            "description": "Modelos de documento visiveis a credencial. O sandbox ja vem com um modelo de exemplo pronto.\n\nEscopo: modelos:ler | Papel minimo: VISUALIZADOR"
          }
        },
        {
          "name": "POST /modelos/{id}/documentos (instanciar)",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "postman-modelo-{{$guid}}" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"titulo\": \"Contrato Maria Oliveira - agosto/2026\",\n  \"variaveis\": {\n    \"cliente_nome\": \"Maria Oliveira\",\n    \"cliente_documento\": \"12.345.678/0001-90\",\n    \"valor_mensal\": \"R$ 2.480,00\",\n    \"vigencia_meses\": \"12\"\n  },\n  \"papeis\": [\n    {\n      \"papelId\": \"SUBSTITUA_PELO_ID_DO_PAPEL\",\n      \"nome\": \"Robo Que Assina\",\n      \"email\": \"assina@teste.adigitalmax.com.br\",\n      \"telefone\": \"+5511900000000\"\n    }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base}}/modelos/{{modeloId}}/documentos",
              "host": ["{{base}}"],
              "path": ["modelos", "{{modeloId}}", "documentos"]
            },
            "description": "Instancia um documento a partir do modelo: sem upload, sem posicionar campo de novo. E a rota mais usada em integracao de volume, e evita o contador DOCUMENTO_ENVIADO do arquivo.\n\nOs papelId saem de GET /modelos/{id}. Variavel declarada no modelo e ausente aqui devolve ENTRADA_INVALIDA com a lista do que faltou, no campo `erros`.\n\nComo toda criacao, o documento nasce em RASCUNHO: o disparo continua sendo uma chamada separada.\n\nEscopo: documentos:escrever | Papel minimo: OPERADOR"
          }
        }
      ]
    },
    {
      "name": "6. Webhooks",
      "item": [
        {
          "name": "GET /webhooks",
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": { "raw": "{{base}}/webhooks", "host": ["{{base}}"], "path": ["webhooks"] },
            "description": "Escopo: webhooks:gerenciar | Papel minimo: ADMINISTRADOR"
          }
        },
        {
          "name": "POST /webhooks",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) {",
                  "    const w = pm.response.json();",
                  "    pm.collectionVariables.set('webhookId', w.id);",
                  "    // O segredo nao se repete em nenhuma outra resposta.",
                  "    console.warn('GUARDE O SEGREDO AGORA, ele nao sera exibido de novo:', w.segredo);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://seu-produto.com.br/webhooks/adigitalmax\",\n  \"eventos\": [\n    \"document.sent\",\n    \"document.viewed\",\n    \"signer.authenticated\",\n    \"document.signed\",\n    \"document.completed\",\n    \"document.refused\",\n    \"document.expired\",\n    \"document.cancelled\"\n  ],\n  \"ativo\": true,\n  \"descricao\": \"Receptor de desenvolvimento\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": { "raw": "{{base}}/webhooks", "host": ["{{base}}"], "path": ["webhooks"] },
            "description": "Cadastra um endpoint. O `segredo` de assinatura HMAC volta uma unica vez, nesta resposta.\n\nCada entrega leva o cabecalho X-Adigitalmax-Signature: t=<epoch>,v1=<hex>, em que v1 e o HMAC-SHA256 de '<t>.<corpo bruto>' com o segredo. Confira sobre o CORPO BRUTO, antes de qualquer desserializacao, e recuse carimbos com mais de cinco minutos.\n\nA URL e validada A CADA ENTREGA, e nao so no cadastro: IP privado, loopback, link-local, endereco de metadados de nuvem e redirecionamento para faixa interna sao recusados, porque o DNS pode mudar depois.\n\nEscopo: webhooks:gerenciar | Papel minimo: ADMINISTRADOR"
          }
        },
        {
          "name": "GET /webhooks/{id}/entregas",
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": {
              "raw": "{{base}}/webhooks/{{webhookId}}/entregas?limite=50",
              "host": ["{{base}}"],
              "path": ["webhooks", "{{webhookId}}", "entregas"],
              "query": [{ "key": "limite", "value": "50" }]
            },
            "description": "Historico com o corpo enviado, o codigo que o seu servidor devolveu, a duracao e o numero de tentativas. E por onde se responde a pergunta 'voces mandaram?' sem depender do log do seu lado.\n\nEntrega e pelo menos uma vez: trate o consumo como idempotente pelo `eventoId` do corpo, que se repete nas retentativas.\n\nEscopo: webhooks:gerenciar | Papel minimo: ADMINISTRADOR"
          }
        }
      ]
    },
    {
      "name": "7. Validacao publica (sem autenticacao)",
      "item": [
        {
          "name": "POST /validacao/consultar (por codigo)",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"codigo\": \"{{codigoValidacao}}\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": { "raw": "{{validacao}}", "host": ["{{validacao}}"] },
            "description": "Valida pelo codigo impresso no rodape do PDF, no formato ADM-XXXX-XXXX-XXXX.\n\nATENCAO AO HOST: esta rota mora em adigitalmax.com.br, e nao em api. A pagina de validacao fica no mesmo dominio, entao a chamada e de mesma origem e nao exige CORS aberto justamente no endpoint mais exposto do sistema.\n\nSEM AUTENTICACAO, de proposito: quem valida costuma ser exatamente quem nao e cliente de ninguem, a contraparte, o cartorio, o banco, o juiz.\n\nSempre responde 200, inclusive em NAO_ENCONTRADO: o desfecho vai no corpo, e nao no status, para que o status nao vire oraculo de existencia consultavel sem ler a resposta.\n\nLimite: 30 por minuto por IP, com rajada de 10. O limite e inseparavel da entropia do codigo, que tem 60 bits."
          }
        },
        {
          "name": "POST /validacao/consultar (por hash)",
          "request": {
            "auth": { "type": "noauth" },
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"hashArquivo\": \"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": { "raw": "{{validacao}}", "host": ["{{validacao}}"] },
            "description": "Valida pelo conteudo. O ARQUIVO NUNCA E ENVIADO: o navegador calcula o SHA-256 localmente com crypto.subtle e transmite apenas os 64 caracteres. Isso importa porque quem valida costuma nao confiar na outra parte, e nao teria por que confiar na plataforma dela tambem.\n\nOS TRES DESFECHOS, que sao diferentes e nao podem ser confundidos:\n\nAUTENTICO: o registro existe e o hash bate. E o mesmo arquivo que foi assinado.\n\nADULTERADO: o registro existe, o codigo confere, e o hash NAO bate. E o unico desfecho que afirma algo negativo sobre o arquivo, e exige o hash: nunca e retornado a partir do codigo sozinho.\n\nNAO_ENCONTRADO: a plataforma nao tem registro. NAO SIGNIFICA QUE O DOCUMENTO E FALSO. Ele pode ter sido assinado em outra plataforma, expurgado por politica de retencao, ou o codigo pode ter sido digitado errado. A sua interface precisa dizer isso com essas palavras.\n\nHa tambem a entrada por `qrCode`, com o conteudo lido do QR impresso no documento."
          }
        }
      ]
    },
    {
      "name": "8. Consumo",
      "item": [
        {
          "name": "GET /consumo",
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": { "raw": "{{base}}/consumo", "host": ["{{base}}"], "path": ["consumo"] },
            "description": "A medicao do periodo por tipo: ENVELOPE_ENVIADO, DOCUMENTO_ENVIADO, ASSINATURA_COLETADA, REQUISICAO_API, EMAIL_ENVIADO, SMS_ENVIADO e os demais, com franquia e excedente. E como voce preve custo sem esperar a fatura.\n\nO contador que surpreende e o de SMS: cada reenvio de codigo conta de novo.\n\nEscopo: consumo:ler | Papel minimo: ADMINISTRADOR"
          }
        },
        {
          "name": "GET /consumo/extrato",
          "request": {
            "method": "GET",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": {
              "raw": "{{base}}/consumo/extrato?limite=50",
              "host": ["{{base}}"],
              "path": ["consumo", "extrato"],
              "query": [{ "key": "limite", "value": "50" }]
            },
            "description": "O consumo linha a linha, em vez de agregado por tipo. E o que permite atribuir custo a um cliente seu, e nao so ao periodo.\n\nEscopo: consumo:ler | Papel minimo: ADMINISTRADOR"
          }
        }
      ]
    }
  ]
}
