{
  "info": {
    "name": "Noryk Gateway — kit do cliente · Ambiente real",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Coleção sem credenciais. Crie sua primeira senha pelo convite privado e emita sua chave no painel. Configure apiKey apenas no seu ambiente privado/Vault; remova valores antes de compartilhar/exportar. Use Send manualmente. Não há login, ativação de conta, envio de saques, scripts de pós-resposta, geração automática de idempotência ou reenvio de POST. O ambiente real movimenta dinheiro. Revise o valor antes de enviar. Documentação: https://gateway.noryk.finance/docs. Consulte as tarifas em GET /v1/me. As consultas de cobranças, saques e extrato aceitam limit e cursor; copie nextCursor sem modificá-lo. Cada lançamento do extrato tem id único; journalId serve somente para agrupamento."
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://gateway.noryk.finance",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "idempotencyKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "chargeId",
      "value": "",
      "type": "string"
    },
    {
      "key": "withdrawalId",
      "value": "",
      "type": "string"
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "const base = String(pm.variables.get('baseUrl') || '').replace(/\\/$/, '');",
          "const official = base === 'https://gateway.noryk.finance';",
          "const local = /^http:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])(:[0-9]{1,5})?$/.test(base);",
          "function stop(message) { pm.execution.skipRequest(); throw new Error(message); }",
          "if (!official && !local) stop('Use a origem oficial ou um ambiente local explícito.');",
          "const resolved = pm.variables.replaceIn(pm.request.url.toString());",
          "if (!resolved.startsWith(base + '/')) stop('O destino não corresponde à origem revisada.');",
          "const relative = resolved.slice(base.length);",
          "if (relative.includes('#') || /[\\x00-\\x20\\x7f]/.test(relative)) stop('Confira o caminho e os parâmetros antes de enviar.');",
          "const question = relative.indexOf('?');",
          "const path = question < 0 ? relative : relative.slice(0, question);",
          "const query = question < 0 ? '' : relative.slice(question + 1);",
          "const allowed = ['/health','/v1/me','/v1/wallet','/v1/ledger','/v1/charges','/v1/withdrawals'];",
          "const detail = /^\\/v1\\/(charges|withdrawals)\\/[a-zA-Z0-9-]{1,200}$/.test(path);",
          "if (!allowed.includes(path) && !detail) stop('Confira o identificador e o caminho antes de enviar.');",
          "if (question >= 0) {",
          "  if (pm.request.method !== 'GET' || !['/v1/ledger','/v1/charges','/v1/withdrawals'].includes(path) || !query || query.length > 2048) stop('Parâmetros são aceitos somente nas consultas paginadas.');",
          "  const seen = new Set();",
          "  for (const pair of query.split('&')) {",
          "    const equal = pair.indexOf('=');",
          "    if (equal < 1) stop('Informe parâmetros de paginação válidos.');",
          "    let name, value;",
          "    try { name = decodeURIComponent(pair.slice(0, equal).replace(/\\+/g, ' ')); value = decodeURIComponent(pair.slice(equal + 1).replace(/\\+/g, ' ')); }",
          "    catch { stop('Codificação de parâmetro inválida.'); }",
          "    if (!['limit','cursor'].includes(name) || seen.has(name)) stop('Use somente limit e cursor, uma vez por parâmetro.');",
          "    seen.add(name);",
          "    if (name === 'limit' && (!/^[1-9][0-9]{0,2}$/.test(value) || Number(value) > 100)) stop('limit deve ser um inteiro entre 1 e 100.');",
          "    if (name === 'cursor' && !/^[a-zA-Z0-9_-]{1,1024}$/.test(value)) stop('Use o cursor opaco retornado pela API, sem alterações.');",
          "  }",
          "}",
          "if (path !== '/health') { const key = String(pm.variables.get('apiKey') || ''); if (!key || /[\\x00-\\x20\\x7f]/.test(key)) stop('Configure apiKey no ambiente privado do Postman.'); }",
          "if (pm.request.method !== 'GET') {",
          "  if (pm.request.method !== 'POST' || path !== '/v1/charges') stop('Esta coleção não envia saques nem altera contas.');",
          "  const key = String(pm.variables.get('idempotencyKey') || '');",
          "  if (!/^[a-zA-Z0-9_.:-]{8,128}$/.test(key)) stop('Defina a mesma idempotencyKey já persistida para este pedido.');",
          "  const payload = JSON.parse(pm.variables.replaceIn(pm.request.body.raw));",
          "  if (!Number.isSafeInteger(payload.amountCents) || payload.amountCents < 1 || payload.amountCents > 100000000 || typeof payload.description !== 'string' || !payload.description.trim() || [...payload.description].length > 200) stop('Revise amountCents inteiro e a descrição.');",
          "}"
        ]
      }
    }
  ],
  "protocolProfileBehavior": {
    "followRedirects": false
  },
  "item": [
    {
      "name": "1 · Conexão e conta",
      "item": [
        {
          "name": "Saúde do ambiente",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/health",
            "description": "Consulta de saúde; não garante homologação financeira.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Minha conta e tarifas",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/me",
            "description": "Confira fees.processingFeeBps e fees.withdrawalFeeCents da sua conta."
          },
          "response": []
        },
        {
          "name": "Minha carteira",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/wallet",
            "description": "Diferencie a liberar, disponível e reservado."
          },
          "response": []
        },
        {
          "name": "Meu extrato",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/ledger",
            "description": "Listas usam nextCursor. Consulte a documentação para paginação."
          },
          "response": []
        }
      ]
    },
    {
      "name": "2 · Cobranças",
      "description": "Somente a criação abaixo altera dados, quando você escolhe Send. Persista a idempotencyKey antes da primeira chamada e conserve a mesma chave/corpo ao recuperar o pedido.",
      "item": [
        {
          "name": "Listar cobranças",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/charges",
            "description": "O retorno é paginado por cursor."
          },
          "response": []
        },
        {
          "name": "Criar cobrança Pix — revisar antes de enviar",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "type": "text"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}",
                "type": "text"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amountCents\": 10000,\n  \"description\": \"Pedido 000001\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/charges",
            "description": "R$100 em centavos; ambiente real; cobrança pagável. Defina a chave já persistida. Se a resposta se perder, não crie outra chave. Confira id/status/amountCents/feeCents/netCents e guarde o identificador. Sem processamento automático do resultado."
          },
          "response": []
        },
        {
          "name": "Consultar cobrança por id",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/charges/{{chargeId}}",
            "description": "Preencha chargeId com o UUID retornado. paid ainda é pendente; settled indica liquidação."
          },
          "response": []
        }
      ]
    },
    {
      "name": "3 · Saques — somente consulta",
      "description": "Não existe POST de saque nesta coleção. A saída exige habilitação, limites e destinos aprovados, revisão e autorização do titular. Leia /docs#saques para a integração específica. A coleção não envia saques; a cobrança manual emitida em produção é pagável e deve ter valor revisado.",
      "item": [
        {
          "name": "Listar saques",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/withdrawals",
            "description": "Apenas consulta. processing/verifying não autorizam repetir o envio."
          },
          "response": []
        },
        {
          "name": "Consultar saque por id",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/v1/withdrawals/{{withdrawalId}}",
            "description": "Preencha withdrawalId com um UUID existente da sua conta. Um resultado desconhecido mantém reserva para conferência."
          },
          "response": []
        }
      ]
    }
  ]
}
