{
  "openapi": "3.1.0",
  "info": {
    "title": "API RMF",
    "version": "1.0.0",
    "summary": "API pública da plataforma fiscal RMF",
    "description": "API REST da plataforma fiscal RMF, para quem quer usar os serviços da RMF dentro do próprio sistema, sem usar a plataforma.\n\n## Comece pelo sandbox\n\nO sandbox é um serviço separado, com banco próprio, em `https://sandbox.api.rmfestrategia.com/v1`. Nada do que você enviar lá chega à produção, e nada de lá é dado de cliente. Integre e teste primeiro no sandbox, com uma chave `rmf_test_`, e só depois troque o endereço e a chave para produção (`https://api.rmfestrategia.com/v1`, chave `rmf_live_`). Os exemplos de código usam a variável `$RMF_API_URL`: comece com ela apontando para o sandbox.\n\nO \"testar agora\" do portal chama o sandbox. Só o `sandbox.api.` aceita chamadas do navegador, e só vindas de `https://docs.rmfestrategia.com` (CORS). A produção (`api.`) não tem CORS: chame-a do seu servidor, nunca do navegador.\n\n## Sandbox\n\nO sandbox tem limites próprios: upload de até 20 MB, cotas do dia e do mês 10 vezes menores que as do plano (o limite por minuto é o mesmo da produção) e até 10 entregas de webhook por dia. O sandbox devolve resultados fixos para os arquivos de exemplo do portal; outros arquivos são aceitos, mas o resultado é ilustrativo. Use só arquivos sintéticos, nunca SPED real.\n\n**Simular situações no sandbox.** O cabeçalho `X-Sandbox-Simular` (só no sandbox; na produção é ignorado) provoca situações difíceis de reproduzir à mão: `limite` faz qualquer chamada autenticada responder 429 `limite_excedido` com `Retry-After: 1` (mande só na primeira tentativa, para ver o seu tratamento de espera), `falha` faz o relatório criado terminar `falhou` com `arquivo_faltando` e dispara `relatorio.falhou`, `sem_dados` faz o relatório terminar `concluido_sem_dados` e `rejeitar` (em `enviarArquivo`) faz o SPED aceito com 202 terminar `rejeitado` com o motivo `sem_movimento` e dispara `arquivo.rejeitado`.\n\n## Autenticação\n\nToda chamada leva o cabeçalho `Authorization: Bearer <chave>`. A chave nunca vai na URL. A chave é emitida pela equipe da RMF; peça pelo seu contato comercial. Cada chave tem escopos; sem o escopo da operação a resposta é 403 `escopo_insuficiente`. Veja o esquema `bearerAuth` para o formato da chave e a lista de escopos.\n\n## Conta e plano\n\nA conta de API é de um de dois tipos: `rede`, para o cliente direto, ou `parceiro`, que enxerga a carteira inteira (a união das redes que atende). Cada conta tem um plano, que define os produtos liberados, os escopos que as chaves podem receber, se a planilha de resultado dos relatórios está incluída e quais capturas estão liberadas. Toda conta nasce com o espelho no sandbox, com o mesmo plano. Operação fora do plano responde 403 `fora_do_plano`: nesse caso a solução é mudar o plano com a equipe da RMF, não trocar de chave. `GET /conta` mostra o tipo, o plano e os escopos permitidos.\n\n## Resultado, nunca a regra\n\nAs respostas trazem o valor final e o estado. Não trazem memória de cálculo, fórmula nem colunas intermediárias. Erros e avisos dizem o que você deve fazer. Para entender um valor específico, fale com a equipe da RMF.\n\n## Convenções\n\n- Nomes de campo e textos em português, em `snake_case`, sem acento nos nomes.\n- NCM com 8 dígitos sem ponto; CNPJ com 14 caracteres (letras e números são aceitos), sem máscara; competência `AAAA-MM`; datas `AAAA-MM-DD`; data e hora em ISO 8601 com fuso.\n- Valor monetário como texto decimal com ponto (`\"1234.56\"`). Alíquota como número em percentual (`18`, `13.3`); alíquota que não se aplica vem `null`, e `0` só quando a alíquota é zero de fato.\n- Identificadores opacos com prefixo (`emp_`, `rede_`, `arq_`, `rel_`, `rarq_`, `job_`, `whk_`, `evt_`, `chv_`; na captura, `crf_`, `carq_`, `lnf_` e `dfe_`).\n- Erros em `application/problem+json` (RFC 9457), com `codigo` estável e `request_id`. Guarde o `request_id` ao pedir suporte.\n- ZIP de XML de NF-e e CF-e entram no v1: são lidos e contados, e os XML lidos ficam disponíveis para os relatórios que os usam, previstos para depois do lançamento.\n- Listas paginadas por cursor: `?limite=` (padrão 50, máximo 200) e `?cursor=`; a resposta traz `proximo_cursor`, um valor opaco que só vale se veio da API (outro valor: 422 `parametro_invalido`).\n- Operações que criam trabalho respondem 202 com `Location` apontando para `/v1/jobs/{id}`. Todo `POST` que cria recurso exige `Idempotency-Key`, guardada por 24 horas, por conta e por operação.\n- Limites de uso devolvem 429 com `Retry-After`, `RateLimit-Policy` e `RateLimit`. A cota do dia está em `GET /conta/uso`.\n- Descontinuações vêm com os cabeçalhos `Deprecation` e `Sunset`, com pelo menos 6 meses entre um e outro.",
    "contact": {
      "name": "Suporte da API RMF",
      "url": "https://docs.rmfestrategia.com"
    },
    "license": {
      "name": "Uso sujeito aos termos de serviço da RMF",
      "url": "https://docs.rmfestrategia.com/termos"
    }
  },
  "servers": [
    {
      "url": "https://sandbox.api.rmfestrategia.com/v1",
      "description": "Sandbox (comece por aqui; serviço e banco separados da produção, CORS só para o portal)"
    },
    {
      "url": "https://api.rmfestrategia.com/v1",
      "description": "Produção (sem CORS; chame do seu servidor)"
    }
  ],
  "tags": [
    {
      "name": "Consultas de ICMS",
      "description": "Alíquota interna de ICMS por NCM e UF, alíquota para a substituição tributária, situação na ST e confiança. Escopo `consultas:ler`."
    },
    {
      "name": "Reforma: tabela NCM e NBS",
      "description": "Tabela de NCM e NBS da LC 214/2025 e série de alíquotas de referência. Consulta pura: não recebe dado da sua empresa. Escopo `consultas:ler`."
    },
    {
      "name": "Reforma: simulações",
      "description": "Simulações da Reforma: transição de 2026 a 2033 e comparação entre regimes. Síncronas, sem guardar o pedido. Devolvem o resultado de cada ano ou regime, sem a memória de cálculo. Escopo `consultas:ler`."
    },
    {
      "name": "Reforma: linha do tempo",
      "description": "Marcos da transição e ações por perfil de empresa (regime e setor). Escopo `consultas:ler`."
    },
    {
      "name": "Empresas",
      "description": "Empresas da sua conta (a rede ou, em conta de parceiro, a carteira), cadastro e cobertura de arquivos por competência. Escopos `empresas:escrever` e `empresas:ler`."
    },
    {
      "name": "Arquivos",
      "description": "Envio de SPED (EFD ICMS/IPI e EFD Contribuições) e de ZIP de XML (NF-e e CF-e), e acompanhamento da leitura. Escopos `arquivos:enviar` e `arquivos:ler`."
    },
    {
      "name": "Relatórios",
      "description": "Relatórios fiscais sobre os arquivos já lidos da empresa: catálogo, criação assíncrona (pela empresa e período ou pelo id do arquivo), resumo em JSON e, se o plano incluir, a planilha de resultado. Escopos `relatorios:criar` e `relatorios:ler`."
    },
    {
      "name": "Jobs",
      "description": "Acompanhamento dos trabalhos assíncronos (leitura de arquivo, relatório e, depois do lançamento, exclusão e captura). Estados únicos: `na_fila`, `processando`, `concluido`, `concluido_sem_dados`, `falhou`, `cancelado`."
    },
    {
      "name": "Webhooks",
      "description": "Cadastro de webhooks e reenvio de eventos. As entregas seguem o padrão Standard Webhooks (cabeçalhos `webhook-id`, `webhook-timestamp` e `webhook-signature`, HMAC-SHA256) e nunca levam dado fiscal. Escopo `webhooks:gerenciar`."
    },
    {
      "name": "Conta",
      "description": "Tipo e plano da conta, consumo de cotas e chaves. Escopo `conta:ler`."
    }
  ],
  "paths": {
    "/icms/aliquotas": {
      "get": {
        "operationId": "consultarAliquotaIcms",
        "summary": "Alíquota interna de ICMS por NCM e UF",
        "description": "Use para saber, período a período, a alíquota interna de ICMS de um NCM em uma UF (`aliquota_interna`), se o NCM está na substituição tributária (`na_st`), a alíquota para a ST (`aliquota_st`, `null` fora dela) e a confiança da resposta.\n\nA resposta traz o resultado, não a regra: não traz o artigo da lei nem o caminho que levou ao valor. O valor vem da lei do estado ou, quando não há mapa da lei, da alíquota padrão do estado. Quando a lei não decide sozinha, ele pode vir da prática de mercado agregada e anonimizada (`origem: pratica_mercado`), nunca de uma empresa identificável. Para entender um valor específico, fale com a equipe da RMF.\n\nA resposta é determinística: a mesma célula (NCM, UF, mês) dá a mesma resposta para qualquer conta, aqui ou no lote, enquanto a `versao_base` for a mesma.\n\nA resposta é agrupada em períodos: um novo período começa quando alguma informação muda. Sempre confira `confianca` e `versao_base` antes de usar o valor em apuração.\n\n**Quando não usar:** para muitos pares de uma vez (use `consultarAliquotasIcmsEmLote`) ou para saber a cobertura dos estados (use `consultarCoberturaIcms`). Não é cálculo de imposto: a API não recebe valores de nota.",
        "tags": [
          "Consultas de ICMS"
        ],
        "security": [
          {
            "bearerAuth": [
              "consultas:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "ncm",
            "in": "query",
            "description": "NCM com 8 dígitos, sem ponto.",
            "schema": {
              "$ref": "#/components/schemas/Ncm"
            },
            "required": true,
            "example": "85414121"
          },
          {
            "name": "uf",
            "in": "query",
            "description": "UF da operação, em maiúsculas.",
            "schema": {
              "$ref": "#/components/schemas/Uf"
            },
            "required": true,
            "example": "SP"
          },
          {
            "name": "inicio",
            "in": "query",
            "description": "Primeira competência da consulta (`AAAA-MM`). Padrão `2021-01`. A base vai de `2021-01` até o mês corrente mais 12 meses; fora disso, 422 `parametro_invalido`.",
            "schema": {
              "$ref": "#/components/schemas/Competencia"
            },
            "example": "2022-10"
          },
          {
            "name": "fim",
            "in": "query",
            "description": "Última competência (`AAAA-MM`). Padrão: o mês corrente. Não pode ser anterior a `inicio` (422 `periodo_invalido`). Aceita até o mês corrente mais 12 meses; fora disso, 422 `parametro_invalido`.",
            "schema": {
              "$ref": "#/components/schemas/Competencia"
            },
            "example": "2026-10"
          }
        ],
        "responses": {
          "200": {
            "description": "Períodos da alíquota do NCM na UF.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConsultaAliquotaIcms"
                },
                "examples": {
                  "led_sp": {
                    "summary": "LED em SP: 13,3% até dez/2022 e 12% desde jan/2023",
                    "description": "NCM 85414121 (iluminação LED), SP, de 2022-10 a 2026-10. Gerado pela base, sem dado de cliente.",
                    "value": {
                      "ncm": "85414121",
                      "uf": "SP",
                      "versao_base": "e964db7cb0e8",
                      "periodos": [
                        {
                          "inicio": "2022-10",
                          "fim": "2022-12",
                          "aliquota_interna": 13.3,
                          "aliquota_st": 13.3,
                          "na_st": true,
                          "situacao_st": "Na lista de ST",
                          "origem": "lei",
                          "confianca": "confirmada",
                          "texto_curto": "Lei do estado; na lista de ST; confirmada"
                        },
                        {
                          "inicio": "2023-01",
                          "fim": "2026-10",
                          "aliquota_interna": 12,
                          "aliquota_st": 12,
                          "na_st": true,
                          "situacao_st": "Na lista de ST",
                          "origem": "lei",
                          "confianca": "confirmada",
                          "texto_curto": "Lei do estado; na lista de ST; confirmada"
                        }
                      ]
                    }
                  },
                  "cosmetico_sp": {
                    "summary": "Cosmético em SP: sai da lista de ST em abril de 2026",
                    "description": "NCM 33049990, SP, de 2025-10 a 2026-10. Os dois períodos saem `a_confirmar` pela mesma razão, a alíquota interna: a base não decide sozinha a alíquota interna do produto (a lei depende de característica que o NCM não mostra). No primeiro período a ST é provável, mas a interna é o elo mais fraco (linha `lei`, `true`, `a_confirmar` da tabela de `confianca`). No segundo, o NCM está fora da lista (`na_st: false`): a incerteza da ST não entra, `aliquota_st` vem `null` e só a interna conta (linha `lei`, `false`, `a_confirmar`). O período de 2026-04 a 2026-10 é ilustrativo: mostra a resposta do adaptador da decisão 13 (alíquota interna fora da ST), que ainda não existe.",
                    "value": {
                      "ncm": "33049990",
                      "uf": "SP",
                      "versao_base": "e964db7cb0e8",
                      "periodos": [
                        {
                          "inicio": "2025-10",
                          "fim": "2026-03",
                          "aliquota_interna": 18,
                          "aliquota_st": 18,
                          "na_st": true,
                          "situacao_st": "ST provável, descrição do produto não conferida",
                          "origem": "lei",
                          "confianca": "a_confirmar",
                          "texto_curto": "Lei do estado; ST provável, descrição do produto não conferida; alíquota interna a confirmar"
                        },
                        {
                          "inicio": "2026-04",
                          "fim": "2026-10",
                          "aliquota_interna": 18,
                          "aliquota_st": null,
                          "na_st": false,
                          "situacao_st": "Fora da lista de ST no mês",
                          "origem": "lei",
                          "confianca": "a_confirmar",
                          "texto_curto": "Lei do estado; fora da lista de ST; alíquota interna a confirmar"
                        }
                      ]
                    }
                  },
                  "bebida_sp": {
                    "summary": "NCM provável na ST em SP, a 18%",
                    "description": "NCM 22021000, SP. `provavel`: o NCM está na lista de ST, mas a descrição do produto não foi conferida.",
                    "value": {
                      "ncm": "22021000",
                      "uf": "SP",
                      "versao_base": "e964db7cb0e8",
                      "periodos": [
                        {
                          "inicio": "2025-01",
                          "fim": "2026-10",
                          "aliquota_interna": 18,
                          "aliquota_st": 18,
                          "na_st": true,
                          "situacao_st": "ST provável, descrição do produto não conferida",
                          "origem": "lei",
                          "confianca": "provavel",
                          "texto_curto": "Lei do estado; ST provável, descrição do produto não conferida; provável"
                        }
                      ]
                    }
                  },
                  "padrao_mg": {
                    "summary": "Minas Gerais: só a alíquota padrão do estado",
                    "description": "NCM 19059090, MG. Sem mapa da lei para MG: `origem` `padrao_uf`, `na_st` nulo, `aliquota_st` nula e confiança `a_confirmar`.",
                    "value": {
                      "ncm": "19059090",
                      "uf": "MG",
                      "versao_base": "e964db7cb0e8",
                      "periodos": [
                        {
                          "inicio": "2025-01",
                          "fim": "2026-10",
                          "aliquota_interna": 18,
                          "aliquota_st": null,
                          "na_st": null,
                          "situacao_st": "Lista de ST não avaliada neste estado",
                          "origem": "padrao_uf",
                          "confianca": "a_confirmar",
                          "texto_curto": "Alíquota padrão do estado; lista de ST não avaliada neste estado; a confirmar"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "description": "Parâmetro ausente, fora do formato ou fora do período da base (de 2021-01 até o mês corrente mais 12 meses).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "parametro_invalido": {
                    "summary": "Parâmetro inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "Um ou mais parâmetros não passaram na validação.",
                      "instance": "/v1/icms/aliquotas",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "ncm",
                          "mensagem": "Use 8 dígitos, sem ponto."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/icms/aliquotas?ncm=85414121&uf=SP&inicio=2022-10&fim=2026-10\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript (exemplo completo)",
            "source": "// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).\n// Node 22.18 ou mais novo (fetch nativo; roda o .ts direto). Precisa de RMF_API_URL e RMF_API_KEY.\nconst BASE = process.env.RMF_API_URL;\nconst CHAVE = process.env.RMF_API_KEY;\nif (!BASE || !CHAVE) {\n  console.error(\"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\");\n  process.exit(2);\n}\n\ntype Periodo = {\n  inicio: string;\n  fim: string;\n  aliquota_interna: number;\n  aliquota_st: number | null;\n  na_st: boolean | null;\n  confianca: \"confirmada\" | \"provavel\" | \"a_confirmar\";\n};\n\nasync function consultarAliquota(ncm: string, uf: string, competencia: string): Promise<Periodo> {\n  const url = new URL(`${BASE}/icms/aliquotas`);\n  url.search = new URLSearchParams({ ncm, uf, inicio: competencia, fim: competencia }).toString();\n\n  for (let tentativa = 1; tentativa <= 5; tentativa++) {\n    // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)\n    const simular = tentativa === 1 && process.env.RMF_SIMULAR_429 === \"1\" ? { \"X-Sandbox-Simular\": \"limite\" } : {};\n    const resp = await fetch(url, {\n      headers: { Authorization: `Bearer ${CHAVE}`, Accept: \"application/json\", ...simular },\n    });\n    if (resp.status === 429) {\n      const espera = Number(resp.headers.get(\"Retry-After\") ?? \"1\");\n      console.error(`429: aguardando ${espera}s (tentativa ${tentativa})`);\n      await new Promise((r) => setTimeout(r, espera * 1000));\n      continue;\n    }\n    if (!resp.ok) {\n      const erro = await resp.json(); // application/problem+json\n      throw new Error(`${erro.codigo} (${erro.request_id}): ${erro.detail}`);\n    }\n    const corpo = await resp.json();\n    return corpo.periodos[0];\n  }\n  throw new Error(\"limite_excedido: tente mais tarde\");\n}\n\nconst p = await consultarAliquota(\"85414121\", \"SP\", \"2026-10\");\nconsole.log(`aliquota_interna=${p.aliquota_interna} aliquota_st=${p.aliquota_st} confianca=${p.confianca}`);\n"
          },
          {
            "lang": "python",
            "label": "Python (exemplo completo)",
            "source": "\"\"\"Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).\nPython 3.9 ou mais novo, com requests. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\"\"\"\nimport os\nimport sys\nimport time\n\nimport requests\n\nBASE = os.environ.get(\"RMF_API_URL\")\nCHAVE = os.environ.get(\"RMF_API_KEY\")\nif not BASE or not CHAVE:\n    sys.exit(\"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\")\n\n\ndef consultar_aliquota(ncm: str, uf: str, competencia: str) -> dict:\n    params = {\"ncm\": ncm, \"uf\": uf, \"inicio\": competencia, \"fim\": competencia}\n    headers = {\"Authorization\": f\"Bearer {CHAVE}\", \"Accept\": \"application/json\"}\n    for tentativa in range(1, 6):\n        # RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)\n        extra = {\"X-Sandbox-Simular\": \"limite\"} if tentativa == 1 and os.environ.get(\"RMF_SIMULAR_429\") == \"1\" else {}\n        resp = requests.get(f\"{BASE}/icms/aliquotas\", params=params, headers={**headers, **extra}, timeout=15)\n        if resp.status_code == 429:\n            espera = int(resp.headers.get(\"Retry-After\", \"1\"))\n            print(f\"429: aguardando {espera}s (tentativa {tentativa})\", file=sys.stderr)\n            time.sleep(espera)\n            continue\n        if not resp.ok:\n            erro = resp.json()  # application/problem+json\n            raise RuntimeError(f\"{erro['codigo']} ({erro['request_id']}): {erro.get('detail')}\")\n        return resp.json()[\"periodos\"][0]\n    raise RuntimeError(\"limite_excedido: tente mais tarde\")\n\n\np = consultar_aliquota(\"85414121\", \"SP\", \"2026-10\")\nprint(f\"aliquota_interna={p['aliquota_interna']} aliquota_st={p['aliquota_st']} confianca={p['confianca']}\")\n"
          },
          {
            "lang": "php",
            "label": "PHP (exemplo completo)",
            "source": "<?php\n// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).\n// PHP 8.1 ou mais novo, extensão curl. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\n$base = getenv('RMF_API_URL');\n$chave = getenv('RMF_API_KEY');\nif (!$base || !$chave) {\n    fwrite(STDERR, \"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\\n\");\n    exit(2);\n}\n\nfunction consultarAliquota(string $base, string $chave, string $ncm, string $uf, string $competencia): array\n{\n    $url = $base . '/icms/aliquotas?' . http_build_query([\n        'ncm' => $ncm, 'uf' => $uf, 'inicio' => $competencia, 'fim' => $competencia,\n    ]);\n    for ($tentativa = 1; $tentativa <= 5; $tentativa++) {\n        $retryAfter = 1;\n        $ch = curl_init($url);\n        $cabecalhos = [\"Authorization: Bearer $chave\", 'Accept: application/json'];\n        // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)\n        if ($tentativa === 1 && getenv('RMF_SIMULAR_429') === '1') {\n            $cabecalhos[] = 'X-Sandbox-Simular: limite';\n        }\n        curl_setopt_array($ch, [\n            CURLOPT_RETURNTRANSFER => true,\n            CURLOPT_HTTPHEADER => $cabecalhos,\n            CURLOPT_HEADERFUNCTION => function ($ch, $linha) use (&$retryAfter) {\n                if (stripos($linha, 'Retry-After:') === 0) {\n                    $retryAfter = (int) trim(substr($linha, 12));\n                }\n                return strlen($linha);\n            },\n        ]);\n        $corpo = curl_exec($ch);\n        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\n        curl_close($ch);\n\n        if ($status === 429) {\n            fwrite(STDERR, \"429: aguardando {$retryAfter}s (tentativa $tentativa)\\n\");\n            sleep($retryAfter);\n            continue;\n        }\n        $json = json_decode($corpo, true);\n        if ($status >= 400) { // application/problem+json\n            throw new RuntimeException(\"{$json['codigo']} ({$json['request_id']}): {$json['detail']}\");\n        }\n        return $json['periodos'][0];\n    }\n    throw new RuntimeException('limite_excedido: tente mais tarde');\n}\n\n$p = consultarAliquota($base, $chave, '85414121', 'SP', '2026-10');\necho 'aliquota_interna=', $p['aliquota_interna'], ' aliquota_st=', $p['aliquota_st'] ?? 'null',\n    ' confianca=', $p['confianca'], PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C# (exemplo completo)",
            "source": "// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).\n// .NET 8 ou mais novo, top-level statements. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\nusing System.Net;\nusing System.Net.Http.Headers;\nusing System.Text.Json;\n\nvar baseUrl = Environment.GetEnvironmentVariable(\"RMF_API_URL\");\nvar chave = Environment.GetEnvironmentVariable(\"RMF_API_KEY\");\nif (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(chave))\n{\n    Console.Error.WriteLine(\"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\");\n    return 2;\n}\n\nvar http = new HttpClient { BaseAddress = new Uri(baseUrl.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", chave);\n\nvar p = await ConsultarAliquota(\"85414121\", \"SP\", \"2026-10\");\nConsole.WriteLine($\"aliquota_interna={p.GetProperty(\"aliquota_interna\")} aliquota_st={p.GetProperty(\"aliquota_st\")} confianca={p.GetProperty(\"confianca\").GetString()}\");\nreturn 0;\n\nasync Task<JsonElement> ConsultarAliquota(string ncm, string uf, string competencia)\n{\n    var caminho = $\"icms/aliquotas?ncm={ncm}&uf={uf}&inicio={competencia}&fim={competencia}\";\n    for (var tentativa = 1; tentativa <= 5; tentativa++)\n    {\n        // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)\n        using var req = new HttpRequestMessage(HttpMethod.Get, caminho);\n        if (tentativa == 1 && Environment.GetEnvironmentVariable(\"RMF_SIMULAR_429\") == \"1\")\n            req.Headers.Add(\"X-Sandbox-Simular\", \"limite\");\n        using var resp = await http.SendAsync(req);\n        if (resp.StatusCode == HttpStatusCode.TooManyRequests)\n        {\n            var espera = resp.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1);\n            Console.Error.WriteLine($\"429: aguardando {espera.TotalSeconds}s (tentativa {tentativa})\");\n            await Task.Delay(espera);\n            continue;\n        }\n        using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());\n        if (!resp.IsSuccessStatusCode) // application/problem+json\n        {\n            var e = doc.RootElement;\n            throw new Exception($\"{e.GetProperty(\"codigo\")} ({e.GetProperty(\"request_id\")}): {e.GetProperty(\"detail\")}\");\n        }\n        return doc.RootElement.GetProperty(\"periodos\")[0].Clone();\n    }\n    throw new Exception(\"limite_excedido: tente mais tarde\");\n}\n"
          },
          {
            "lang": "java",
            "label": "Java (exemplo completo)",
            "source": "// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).\n// Java 17 ou mais novo, com Jackson (com.fasterxml.jackson.core:jackson-databind).\n// Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Consulta {\n    static final String BASE = System.getenv(\"RMF_API_URL\");\n    static final String CHAVE = System.getenv(\"RMF_API_KEY\");\n    static final HttpClient HTTP = HttpClient.newHttpClient();\n    static final ObjectMapper JSON = new ObjectMapper();\n\n    static JsonNode consultarAliquota(String ncm, String uf, String competencia) throws Exception {\n        var uri = URI.create(BASE + \"/icms/aliquotas?ncm=\" + ncm + \"&uf=\" + uf\n                + \"&inicio=\" + competencia + \"&fim=\" + competencia);\n        for (int tentativa = 1; tentativa <= 5; tentativa++) {\n            var pedido = HttpRequest.newBuilder(uri)\n                    .header(\"Authorization\", \"Bearer \" + CHAVE)\n                    .header(\"Accept\", \"application/json\");\n            // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)\n            if (tentativa == 1 && \"1\".equals(System.getenv(\"RMF_SIMULAR_429\"))) {\n                pedido.header(\"X-Sandbox-Simular\", \"limite\");\n            }\n            var resp = HTTP.send(pedido.GET().build(), HttpResponse.BodyHandlers.ofString());\n            if (resp.statusCode() == 429) {\n                long espera = Long.parseLong(resp.headers().firstValue(\"Retry-After\").orElse(\"1\"));\n                System.err.println(\"429: aguardando \" + espera + \"s (tentativa \" + tentativa + \")\");\n                Thread.sleep(espera * 1000);\n                continue;\n            }\n            var corpo = JSON.readTree(resp.body());\n            if (resp.statusCode() >= 400) { // application/problem+json\n                throw new RuntimeException(corpo.path(\"codigo\").asText() + \" (\"\n                        + corpo.path(\"request_id\").asText() + \"): \" + corpo.path(\"detail\").asText());\n            }\n            return corpo.path(\"periodos\").get(0);\n        }\n        throw new RuntimeException(\"limite_excedido: tente mais tarde\");\n    }\n\n    public static void main(String[] args) throws Exception {\n        if (BASE == null || CHAVE == null) {\n            System.err.println(\"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\");\n            System.exit(2);\n        }\n        var p = consultarAliquota(\"85414121\", \"SP\", \"2026-10\");\n        System.out.println(\"aliquota_interna=\" + p.get(\"aliquota_interna\") + \" aliquota_st=\" + p.get(\"aliquota_st\")\n                + \" confianca=\" + p.get(\"confianca\").asText());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi (exemplo completo)",
            "source": "program Consulta;\n// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).\n// Delphi 10.3 ou mais novo. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\n// Exemplo escrito e revisado à mão: não roda no CI (não existe compilador Delphi em container).\n\n{$APPTYPE CONSOLE}\n\nuses\n  System.SysUtils, System.JSON, System.Net.HttpClient, System.Net.URLClient;\n\nfunction ConsultarAliquota(const ABase, AChave, ANcm, AUf, ACompetencia: string): TJSONObject;\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Corpo: TJSONObject;\n  Tentativa, Espera: Integer;\nbegin\n  Http := THTTPClient.Create;\n  try\n    Http.CustomHeaders['Authorization'] := 'Bearer ' + AChave;\n    Http.CustomHeaders['Accept'] := 'application/json';\n    for Tentativa := 1 to 5 do\n    begin\n      // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)\n      if (Tentativa = 1) and (GetEnvironmentVariable('RMF_SIMULAR_429') = '1') then\n        Http.CustomHeaders['X-Sandbox-Simular'] := 'limite'\n      else\n        Http.CustomHeaders['X-Sandbox-Simular'] := '';\n      Resp := Http.Get(Format('%s/icms/aliquotas?ncm=%s&uf=%s&inicio=%s&fim=%s',\n        [ABase, ANcm, AUf, ACompetencia, ACompetencia]));\n      if Resp.StatusCode = 429 then\n      begin\n        Espera := StrToIntDef(Resp.HeaderValue['Retry-After'], 1);\n        Writeln(ErrOutput, Format('429: aguardando %ds (tentativa %d)', [Espera, Tentativa]));\n        Sleep(Espera * 1000);\n        Continue;\n      end;\n      Corpo := TJSONObject.ParseJSONValue(Resp.ContentAsString(TEncoding.UTF8)) as TJSONObject;\n      if Resp.StatusCode >= 400 then // application/problem+json\n      try\n        raise Exception.CreateFmt('%s (%s): %s', [Corpo.GetValue<string>('codigo'),\n          Corpo.GetValue<string>('request_id'), Corpo.GetValue<string>('detail')]);\n      finally\n        Corpo.Free;\n      end;\n      Exit(Corpo);\n    end;\n    raise Exception.Create('limite_excedido: tente mais tarde');\n  finally\n    Http.Free;\n  end;\nend;\n\nvar\n  Base, Chave: string;\n  Resposta, Periodo: TJSONObject;\nbegin\n  Base := GetEnvironmentVariable('RMF_API_URL');\n  Chave := GetEnvironmentVariable('RMF_API_KEY');\n  if (Base = '') or (Chave = '') then\n  begin\n    Writeln(ErrOutput, 'defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY');\n    Halt(2);\n  end;\n  Resposta := ConsultarAliquota(Base, Chave, '85414121', 'SP', '2026-10');\n  try\n    Periodo := Resposta.GetValue<TJSONArray>('periodos').Items[0] as TJSONObject;\n    Writeln('aliquota_interna=', Periodo.GetValue('aliquota_interna').ToString,\n      ' aliquota_st=', Periodo.GetValue('aliquota_st').ToString,\n      ' confianca=', Periodo.GetValue<string>('confianca'));\n  finally\n    Resposta.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/icms/aliquotas/consultas": {
      "post": {
        "operationId": "consultarAliquotasIcmsEmLote",
        "summary": "Alíquotas de ICMS em lote",
        "description": "Use para consultar até 500 pares (NCM, UF, competência) em uma única chamada síncrona, por exemplo para conferir o cadastro de produtos de uma empresa. Cada item devolve uma competência só, com os mesmos campos de `consultarAliquotaIcms`: `aliquota_interna`, `aliquota_st` (`null` fora da ST), `na_st`, `origem` e `confianca`. Um item com NCM ou UF inválidos não derruba o lote: ele volta com `erro`.\n\nÉ uma consulta (não cria recurso), por isso não exige `Idempotency-Key`.\n\n**Quando não usar:** para ver a evolução de um NCM ao longo dos meses (use `consultarAliquotaIcms`, que já agrupa em períodos) ou com mais de 500 pares (divida em várias chamadas).",
        "tags": [
          "Consultas de ICMS"
        ],
        "security": [
          {
            "bearerAuth": [
              "consultas:ler"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoteConsultaIcms"
              },
              "examples": {
                "lote": {
                  "summary": "Cinco pares, um deles com NCM inválido",
                  "value": {
                    "itens": [
                      {
                        "ncm": "33049990",
                        "uf": "SP",
                        "competencia": "2026-03"
                      },
                      {
                        "ncm": "33049990",
                        "uf": "SP",
                        "competencia": "2026-10"
                      },
                      {
                        "ncm": "85414121",
                        "uf": "SP",
                        "competencia": "2026-10"
                      },
                      {
                        "ncm": "19059090",
                        "uf": "MG",
                        "competencia": "2026-10"
                      },
                      {
                        "ncm": "1234",
                        "uf": "SP",
                        "competencia": "2026-10"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Um resultado por item, na ordem do pedido.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RespostaLoteIcms"
                },
                "examples": {
                  "lote": {
                    "summary": "Quatro itens resolvidos e um recusado",
                    "description": "Alíquotas geradas pela base, sem dado de cliente. O item 1 (cosmético em 2026-10, fora da ST) é ilustrativo do adaptador da decisão 13, como no exemplo `cosmetico_sp` da consulta por período.",
                    "value": {
                      "versao_base": "e964db7cb0e8",
                      "resultados": [
                        {
                          "indice": 0,
                          "ncm": "33049990",
                          "uf": "SP",
                          "competencia": "2026-03",
                          "aliquota_interna": 18,
                          "aliquota_st": 18,
                          "na_st": true,
                          "situacao_st": "ST provável, descrição do produto não conferida",
                          "origem": "lei",
                          "confianca": "a_confirmar",
                          "texto_curto": "Lei do estado; ST provável, descrição do produto não conferida; alíquota interna a confirmar"
                        },
                        {
                          "indice": 1,
                          "ncm": "33049990",
                          "uf": "SP",
                          "competencia": "2026-10",
                          "aliquota_interna": 18,
                          "aliquota_st": null,
                          "na_st": false,
                          "situacao_st": "Fora da lista de ST no mês",
                          "origem": "lei",
                          "confianca": "a_confirmar",
                          "texto_curto": "Lei do estado; fora da lista de ST; alíquota interna a confirmar"
                        },
                        {
                          "indice": 2,
                          "ncm": "85414121",
                          "uf": "SP",
                          "competencia": "2026-10",
                          "aliquota_interna": 12,
                          "aliquota_st": 12,
                          "na_st": true,
                          "situacao_st": "Na lista de ST",
                          "origem": "lei",
                          "confianca": "confirmada",
                          "texto_curto": "Lei do estado; na lista de ST; confirmada"
                        },
                        {
                          "indice": 3,
                          "ncm": "19059090",
                          "uf": "MG",
                          "competencia": "2026-10",
                          "aliquota_interna": 18,
                          "aliquota_st": null,
                          "na_st": null,
                          "situacao_st": "Lista de ST não avaliada neste estado",
                          "origem": "padrao_uf",
                          "confianca": "a_confirmar",
                          "texto_curto": "Alíquota padrão do estado; lista de ST não avaliada neste estado; a confirmar"
                        },
                        {
                          "indice": 4,
                          "ncm": "1234",
                          "uf": "SP",
                          "competencia": "2026-10",
                          "erro": {
                            "codigo": "ncm_invalido",
                            "mensagem": "Use 8 dígitos, sem ponto."
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "413": {
            "description": "Mais de 500 itens no lote.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "lote_grande_demais": {
                    "summary": "Lote grande demais",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/lote_grande_demais",
                      "title": "Lote grande demais",
                      "status": 413,
                      "detail": "O lote tem mais de 500 itens.",
                      "instance": "/v1/icms/aliquotas/consultas",
                      "codigo": "lote_grande_demais",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Parâmetro inválido",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "parametro_invalido": {
                    "summary": "Parâmetro inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "O corpo não passou na validação.",
                      "instance": "/v1/icms/aliquotas/consultas",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "itens",
                          "mensagem": "Envie de 1 a 500 itens."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/icms/aliquotas/consultas\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"itens\": [{\"ncm\": \"33049990\", \"uf\": \"SP\", \"competencia\": \"2026-03\"}, {\"ncm\": \"33049990\", \"uf\": \"SP\", \"competencia\": \"2026-10\"}, {\"ncm\": \"85414121\", \"uf\": \"SP\", \"competencia\": \"2026-10\"}, {\"ncm\": \"19059090\", \"uf\": \"MG\", \"competencia\": \"2026-10\"}, {\"ncm\": \"1234\", \"uf\": \"SP\", \"competencia\": \"2026-10\"}]}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/icms/aliquotas/consultas`, {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"itens\": [\n      {\n        \"ncm\": \"33049990\",\n        \"uf\": \"SP\",\n        \"competencia\": \"2026-03\"\n      },\n      {\n        \"ncm\": \"33049990\",\n        \"uf\": \"SP\",\n        \"competencia\": \"2026-10\"\n      },\n      {\n        \"ncm\": \"85414121\",\n        \"uf\": \"SP\",\n        \"competencia\": \"2026-10\"\n      },\n      {\n        \"ncm\": \"19059090\",\n        \"uf\": \"MG\",\n        \"competencia\": \"2026-10\"\n      },\n      {\n        \"ncm\": \"1234\",\n        \"uf\": \"SP\",\n        \"competencia\": \"2026-10\"\n      }\n    ]\n  }),\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.post(\n    os.environ[\"RMF_API_URL\"] + \"/icms/aliquotas/consultas\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    json={\n      \"itens\": [\n        {\n          \"ncm\": \"33049990\",\n          \"uf\": \"SP\",\n          \"competencia\": \"2026-03\"\n        },\n        {\n          \"ncm\": \"33049990\",\n          \"uf\": \"SP\",\n          \"competencia\": \"2026-10\"\n        },\n        {\n          \"ncm\": \"85414121\",\n          \"uf\": \"SP\",\n          \"competencia\": \"2026-10\"\n        },\n        {\n          \"ncm\": \"19059090\",\n          \"uf\": \"MG\",\n          \"competencia\": \"2026-10\"\n        },\n        {\n          \"ncm\": \"1234\",\n          \"uf\": \"SP\",\n          \"competencia\": \"2026-10\"\n        }\n      ]\n    },\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/icms/aliquotas/consultas');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'POST',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => '{\"itens\":[{\"ncm\":\"33049990\",\"uf\":\"SP\",\"competencia\":\"2026-03\"},{\"ncm\":\"33049990\",\"uf\":\"SP\",\"competencia\":\"2026-10\"},{\"ncm\":\"85414121\",\"uf\":\"SP\",\"competencia\":\"2026-10\"},{\"ncm\":\"19059090\",\"uf\":\"MG\",\"competencia\":\"2026-10\"},{\"ncm\":\"1234\",\"uf\":\"SP\",\"competencia\":\"2026-10\"}]}',\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Post, \"icms/aliquotas/consultas\");\nreq.Content = new StringContent(\"\"\"\n{\n  \"itens\": [\n    {\n      \"ncm\": \"33049990\",\n      \"uf\": \"SP\",\n      \"competencia\": \"2026-03\"\n    },\n    {\n      \"ncm\": \"33049990\",\n      \"uf\": \"SP\",\n      \"competencia\": \"2026-10\"\n    },\n    {\n      \"ncm\": \"85414121\",\n      \"uf\": \"SP\",\n      \"competencia\": \"2026-10\"\n    },\n    {\n      \"ncm\": \"19059090\",\n      \"uf\": \"MG\",\n      \"competencia\": \"2026-10\"\n    },\n    {\n      \"ncm\": \"1234\",\n      \"uf\": \"SP\",\n      \"competencia\": \"2026-10\"\n    }\n  ]\n}\n\"\"\", System.Text.Encoding.UTF8, \"application/json\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/icms/aliquotas/consultas\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .header(\"Content-Type\", \"application/json\")\n                .method(\"POST\", HttpRequest.BodyPublishers.ofString(\"\"\"\n{\n  \"itens\": [\n    {\n      \"ncm\": \"33049990\",\n      \"uf\": \"SP\",\n      \"competencia\": \"2026-03\"\n    },\n    {\n      \"ncm\": \"33049990\",\n      \"uf\": \"SP\",\n      \"competencia\": \"2026-10\"\n    },\n    {\n      \"ncm\": \"85414121\",\n      \"uf\": \"SP\",\n      \"competencia\": \"2026-10\"\n    },\n    {\n      \"ncm\": \"19059090\",\n      \"uf\": \"MG\",\n      \"competencia\": \"2026-10\"\n    },\n    {\n      \"ncm\": \"1234\",\n      \"uf\": \"SP\",\n      \"competencia\": \"2026-10\"\n    }\n  ]\n}\n\"\"\"))\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Corpo: TStringStream;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/icms/aliquotas/consultas';\n  Http := THTTPClient.Create;\n  try\n    Corpo := TStringStream.Create('{\"itens\":[{\"ncm\":\"33049990\",\"uf\":\"SP\",\"competencia\":\"2026-03\"},{\"ncm\":\"33049990\",\"uf\":\"SP\",\"competencia\":\"2026-10\"},{\"ncm\":\"85414121\",\"uf\":\"SP\",\"competencia\":\"2026-10\"},{\"ncm\":\"19059090\",\"uf\":\"MG\",\"competencia\":\"2026-10\"},{\"ncm\":\"1234\",\"uf\":\"SP\",\"competencia\":\"2026-10\"}]}', TEncoding.UTF8);\n    try\n      Resp := Http.Post(Url, Corpo, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY')), TNetHeader.Create('Content-Type', 'application/json')]);\n    finally\n      Corpo.Free;\n    end;\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/icms/cobertura": {
      "get": {
        "operationId": "consultarCoberturaIcms",
        "summary": "Cobertura da base de ICMS por estado",
        "description": "Use para saber, por UF, se a base tem o mapa da lei por NCM ou só a alíquota padrão do estado, e qual a data da versão. Serve para decidir, antes de consultar, o quanto confiar na resposta de cada estado: onde a cobertura é `padrao_uf`, toda alíquota sai `a_confirmar`.\n\n**Quando não usar:** para consultar a alíquota de um NCM (use `consultarAliquotaIcms`).",
        "tags": [
          "Consultas de ICMS"
        ],
        "security": [
          {
            "bearerAuth": [
              "consultas:ler"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Cobertura das 27 UFs.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CoberturaIcms"
                },
                "examples": {
                  "cobertura": {
                    "summary": "Cobertura em outubro de 2026",
                    "description": "Gerada a partir da base. A data de atualização é a da última alteração dos dados.",
                    "value": {
                      "versao_base": "e964db7cb0e8",
                      "atualizada_em": "2026-10-27",
                      "referencia": "2026-10",
                      "dados": [
                        {
                          "uf": "AC",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 19,
                          "vigente_desde": "2023-04-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "AL",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20.5,
                          "vigente_desde": "2026-04-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "AM",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20,
                          "vigente_desde": "2023-04-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "AP",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 18,
                          "vigente_desde": "2021-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "BA",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20.5,
                          "vigente_desde": "2024-02-07",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "CE",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20,
                          "vigente_desde": "2024-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "DF",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20,
                          "vigente_desde": "2024-01-22",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "ES",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 17,
                          "vigente_desde": "2021-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "GO",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 19,
                          "vigente_desde": "2024-04-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "MA",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 23,
                          "vigente_desde": "2025-02-23",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "MG",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 18,
                          "vigente_desde": "2021-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "MS",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 17,
                          "vigente_desde": "2021-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "MT",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 17,
                          "vigente_desde": "2021-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "PA",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 19,
                          "vigente_desde": "2023-03-16",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "PB",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20,
                          "vigente_desde": "2024-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "PE",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20.5,
                          "vigente_desde": "2024-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "PI",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 22.5,
                          "vigente_desde": "2025-04-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "PR",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 19.5,
                          "vigente_desde": "2024-03-18",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "RJ",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20,
                          "vigente_desde": "2024-03-20",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "RN",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20,
                          "vigente_desde": "2025-03-20",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "RO",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 19.5,
                          "vigente_desde": "2024-01-12",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "RR",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20,
                          "vigente_desde": "2023-03-30",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "RS",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 17,
                          "vigente_desde": "2022-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "SC",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 17,
                          "vigente_desde": "2021-01-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "SE",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 19,
                          "vigente_desde": "2023-04-01",
                          "ncms_no_mapa": 0
                        },
                        {
                          "uf": "SP",
                          "cobertura": "mapa_lei",
                          "aliquota_padrao": 18,
                          "vigente_desde": "2021-01-01",
                          "ncms_no_mapa": 901
                        },
                        {
                          "uf": "TO",
                          "cobertura": "padrao_uf",
                          "aliquota_padrao": 20,
                          "vigente_desde": "2024-01-01",
                          "ncms_no_mapa": 0
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/icms/cobertura\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/icms/cobertura`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/icms/cobertura\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/icms/cobertura');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"icms/cobertura\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/icms/cobertura\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/icms/cobertura';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/reforma/ncm/{codigo}": {
      "get": {
        "operationId": "consultarNcmReforma",
        "summary": "Tratamento de um NCM ou NBS na LC 214/2025",
        "description": "Use para saber como a LC 214/2025 trata um código de mercadoria (NCM) ou de serviço (NBS): tipo de benefício, redução, anexo, ressalvas e os códigos CST e cClassTrib sugeridos. Aceita o código completo ou parcial; a resposta traz todas as linhas da tabela que alcançam o código, com o `caminho` do casamento.\n\nA tabela mostra o que a lei diz e não decide por você: leia `excecao` e `observacoes` antes de adotar uma linha.\n\n**Quando não usar:** para buscar por descrição ou por trecho de código (use `buscarNcmReforma`) ou para calcular imposto de uma operação (use as simulações).",
        "tags": [
          "Reforma: tabela NCM e NBS"
        ],
        "security": [
          {
            "bearerAuth": [
              "consultas:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "codigo",
            "in": "path",
            "description": "NCM (2 a 8 dígitos: capítulo, posição, subposição ou item) ou NBS (até 9 dígitos), sem pontuação.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{2,9}$"
            },
            "required": true,
            "example": "10062010"
          }
        ],
        "responses": {
          "200": {
            "description": "Linhas da tabela que alcançam o código.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConsultaNcmReforma"
                },
                "examples": {
                  "arroz": {
                    "summary": "NCM de arroz: linha herdada da subposição",
                    "description": "A linha e os códigos vêm da tabela da lei. `referencia` é ilustrativa (série de alíquotas de referência de exemplo); como a redução é de 100%, as alíquotas da linha são zero.",
                    "value": {
                      "codigo": "10062010",
                      "tipo_codigo": "NCM",
                      "referencia": {
                        "ano": 2033,
                        "aliquota_ibs": 17.7,
                        "aliquota_cbs": 8.8,
                        "fonte": "tabela de alíquotas de referência"
                      },
                      "enquadramentos": [
                        {
                          "codigo": "10062",
                          "codigo_exibicao": "1006.2",
                          "tipo_codigo": "NCM",
                          "descricao": "Arroz das subposições",
                          "lei": "LC 214/2025",
                          "anexo": "ANEXO I",
                          "item": 1,
                          "grupo": "PRODUTOS DESTINADOS À ALIMENTAÇÃO HUMANA SUBMETIDOS À REDUÇÃO A ZERO DAS ALÍQUOTAS",
                          "classificacao": "PRODUTO DESTINADOS À ALIMENTAÇÃO HUMANA",
                          "beneficio": "aliquota_zero",
                          "reducao_percentual": 100,
                          "aliquota_ibs": 0,
                          "aliquota_cbs": 0,
                          "aliquota_is": null,
                          "excecao": null,
                          "observacoes": null,
                          "cst": "200",
                          "cclasstrib": "200003",
                          "fundamento_legal": "Art. 125",
                          "caminho": "herdado"
                        }
                      ]
                    }
                  },
                  "sem_linha": {
                    "summary": "Código sem benefício na tabela",
                    "description": "Lista vazia: a tabela não traz benefício para o código. Não confirma tributação integral.",
                    "value": {
                      "codigo": "85414121",
                      "tipo_codigo": "NCM",
                      "referencia": {
                        "ano": 2033,
                        "aliquota_ibs": 17.7,
                        "aliquota_cbs": 8.8,
                        "fonte": "tabela de alíquotas de referência"
                      },
                      "enquadramentos": []
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "description": "Parâmetro inválido",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "parametro_invalido": {
                    "summary": "Parâmetro inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "O código deve ter de 2 a 9 dígitos, sem pontuação.",
                      "instance": "/v1/reforma/ncm/10.06",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "codigo",
                          "mensagem": "Use apenas dígitos."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/reforma/ncm/10062010\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/reforma/ncm/10062010`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/reforma/ncm/10062010\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/reforma/ncm/10062010');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"reforma/ncm/10062010\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/reforma/ncm/10062010\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/reforma/ncm/10062010';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/reforma/ncm": {
      "get": {
        "operationId": "buscarNcmReforma",
        "summary": "Busca na tabela de NCM e NBS da LC 214/2025",
        "description": "Use para procurar linhas da tabela por texto da descrição ou por trecho de código, com filtro por anexo e por existência de redução. A resposta é paginada por cursor.\n\n**Quando não usar:** para o tratamento de um código específico, com o caminho de casamento (use `consultarNcmReforma`).",
        "tags": [
          "Reforma: tabela NCM e NBS"
        ],
        "security": [
          {
            "bearerAuth": [
              "consultas:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Texto da descrição ou trecho de código (NCM ou NBS). Sem `q`, lista a tabela inteira, paginada.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 120
            },
            "example": "arroz"
          },
          {
            "name": "anexo",
            "in": "query",
            "description": "Filtra por anexo da lei, como aparece em `anexo` (por exemplo `ANEXO I`).",
            "schema": {
              "type": "string",
              "maxLength": 40
            },
            "example": "ANEXO I"
          },
          {
            "name": "com_reducao",
            "in": "query",
            "description": "`true`: só linhas com redução de alíquota (`reducao_percentual` maior que zero). `false` ou ausente: todas.",
            "schema": {
              "type": "boolean"
            },
            "example": true
          },
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de linhas da tabela.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BuscaNcmReforma"
                },
                "examples": {
                  "busca": {
                    "summary": "Busca por \"arroz\" no Anexo I",
                    "description": "`referencia` é ilustrativa; a redução de 100% zera as alíquotas das linhas.",
                    "value": {
                      "dados": [
                        {
                          "codigo": "10062",
                          "codigo_exibicao": "1006.2",
                          "tipo_codigo": "NCM",
                          "descricao": "Arroz das subposições",
                          "lei": "LC 214/2025",
                          "anexo": "ANEXO I",
                          "item": 1,
                          "grupo": "PRODUTOS DESTINADOS À ALIMENTAÇÃO HUMANA SUBMETIDOS À REDUÇÃO A ZERO DAS ALÍQUOTAS",
                          "classificacao": "PRODUTO DESTINADOS À ALIMENTAÇÃO HUMANA",
                          "beneficio": "aliquota_zero",
                          "reducao_percentual": 100,
                          "aliquota_ibs": 0,
                          "aliquota_cbs": 0,
                          "aliquota_is": null,
                          "excecao": null,
                          "observacoes": null,
                          "cst": "200",
                          "cclasstrib": "200003",
                          "fundamento_legal": "Art. 125",
                          "caminho": "exato"
                        },
                        {
                          "codigo": "10063",
                          "codigo_exibicao": "1006.3",
                          "tipo_codigo": "NCM",
                          "descricao": "Arroz das subposições",
                          "lei": "LC 214/2025",
                          "anexo": "ANEXO I",
                          "item": 1,
                          "grupo": "PRODUTOS DESTINADOS À ALIMENTAÇÃO HUMANA SUBMETIDOS À REDUÇÃO A ZERO DAS ALÍQUOTAS",
                          "classificacao": "PRODUTO DESTINADOS À ALIMENTAÇÃO HUMANA",
                          "beneficio": "aliquota_zero",
                          "reducao_percentual": 100,
                          "aliquota_ibs": 0,
                          "aliquota_cbs": 0,
                          "aliquota_is": null,
                          "excecao": null,
                          "observacoes": null,
                          "cst": "200",
                          "cclasstrib": "200003",
                          "fundamento_legal": "Art. 125",
                          "caminho": "exato"
                        }
                      ],
                      "proximo_cursor": null,
                      "total": 2,
                      "referencia": {
                        "ano": 2033,
                        "aliquota_ibs": 17.7,
                        "aliquota_cbs": 8.8,
                        "fonte": "tabela de alíquotas de referência"
                      }
                    }
                  },
                  "vazia": {
                    "summary": "Nenhum resultado",
                    "value": {
                      "dados": [],
                      "proximo_cursor": null,
                      "total": 0,
                      "referencia": {
                        "ano": 2033,
                        "aliquota_ibs": 17.7,
                        "aliquota_cbs": 8.8,
                        "fonte": "tabela de alíquotas de referência"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "description": "Parâmetro inválido",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "parametro_invalido": {
                    "summary": "Parâmetro inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "O limite máximo é 200.",
                      "instance": "/v1/reforma/ncm",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "limite",
                          "mensagem": "Use um valor de 1 a 200."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/reforma/ncm?q=arroz&anexo=ANEXO%20I&com_reducao=true\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/reforma/ncm`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/reforma/ncm\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/reforma/ncm');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"reforma/ncm\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/reforma/ncm\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/reforma/ncm';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/reforma/aliquotas": {
      "get": {
        "operationId": "listarAliquotasReforma",
        "summary": "Série de alíquotas de referência da Reforma",
        "description": "Use para obter a série de referência de CBS, IBS e Imposto Seletivo de 2026 a 2033, com a fonte de cada número. É a mesma série que alimenta a tabela de NCM e as simulações.\n\nAs alíquotas de referência dos anos plenos dependem de resolução do Senado: trate-as como premissa e guarde a `fonte`.\n\n**Quando não usar:** para calcular imposto de uma operação (use `simularTransicao`).",
        "tags": [
          "Reforma: tabela NCM e NBS"
        ],
        "security": [
          {
            "bearerAuth": [
              "consultas:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "ano_de",
            "in": "query",
            "description": "Primeiro ano da série.",
            "schema": {
              "type": "integer",
              "minimum": 2026,
              "maximum": 2033,
              "default": 2026
            },
            "example": 2026
          },
          {
            "name": "ano_ate",
            "in": "query",
            "description": "Último ano da série.",
            "schema": {
              "type": "integer",
              "minimum": 2026,
              "maximum": 2033,
              "default": 2033
            },
            "example": 2026
          }
        ],
        "responses": {
          "200": {
            "description": "Uma linha por ano pedido.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaAliquotasReforma"
                },
                "examples": {
                  "ano_teste": {
                    "summary": "Ano-teste de 2026",
                    "description": "CBS 0,9% e IBS 0,1% são os percentuais do ano-teste previstos na LC 214/2025, arts. 343 e 346.",
                    "value": {
                      "dados": [
                        {
                          "ano": 2026,
                          "aliquota_cbs": 0.9,
                          "aliquota_ibs": 0.1,
                          "aliquota_is": null,
                          "fonte": "tabela de alíquotas de referência",
                          "base_legal": "LC 214/2025, arts. 343 e 346 (ADCT art. 125)",
                          "vigencia_inicio": "2026-01-01"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "description": "Parâmetro inválido",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "parametro_invalido": {
                    "summary": "Parâmetro inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "A série vai de 2026 a 2033.",
                      "instance": "/v1/reforma/aliquotas",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "ano_de",
                          "mensagem": "Use um ano de 2026 a 2033."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/reforma/aliquotas?ano_de=2026&ano_ate=2026\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/reforma/aliquotas`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/reforma/aliquotas\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/reforma/aliquotas');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"reforma/aliquotas\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/reforma/aliquotas\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/reforma/aliquotas';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/reforma/simulacoes/transicao": {
      "post": {
        "operationId": "simularTransicao",
        "summary": "Simulação da transição 2026 a 2033",
        "description": "Use para ver, ano a ano, como CBS, IBS e Imposto Seletivo entram e como PIS, COFINS, ICMS, ISS e IPI saem, a partir da receita e das alíquotas que a empresa paga hoje. A chamada é síncrona e não guarda o pedido nem o resultado.\n\nA resposta traz o resultado de cada ano: o valor de cada tributo, o total da nota e a variação contra 2026. Não traz a memória de cálculo nem valores intermediários da conta. É uma simulação de referência, com as alíquotas de referência da série (veja `listarAliquotasReforma`). O exemplo de resposta mostra só dois dos oito anos, para ficar curto.\n\n**Quando não usar:** para comparar Simples, Presumido e Real (use `simularRegimes`) ou para apurar imposto de um período real da empresa.",
        "tags": [
          "Reforma: simulações"
        ],
        "security": [
          {
            "bearerAuth": [
              "consultas:ler"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SimulacaoTransicaoRequisicao"
              },
              "examples": {
                "industria": {
                  "summary": "Indústria",
                  "value": {
                    "perfil": "industria",
                    "receita_liquida": "1000000.00",
                    "aliquotas_atuais": {
                      "pis": 1.65,
                      "cofins": 7.6,
                      "icms": 18,
                      "ipi": 5,
                      "is": 0
                    },
                    "fator_beneficio_ibs": 1,
                    "fator_beneficio_cbs": 1
                  }
                },
                "servicos": {
                  "summary": "Serviços",
                  "value": {
                    "perfil": "servicos",
                    "receita_liquida": "500000.00",
                    "aliquotas_atuais": {
                      "pis": 0.65,
                      "cofins": 3,
                      "iss": 5,
                      "is": 0
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado por ano, de 2026 a 2033.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimulacaoTransicaoResposta"
                },
                "examples": {
                  "industria": {
                    "summary": "Indústria com receita de R$ 1 milhão (recorte de 2 anos)",
                    "description": "Valores ilustrativos: não vêm de um cálculo real e não devem ser lidos como resultado fiscal. O exemplo traz só 2026 e 2027; a resposta real traz os oito anos.",
                    "value": {
                      "perfil": "industria",
                      "resultados": [
                        {
                          "ano": 2026,
                          "valor_is": "0.00",
                          "valor_cbs": "9000.00",
                          "valor_ibs": "1000.00",
                          "valor_pis_cofins": "92500.00",
                          "valor_icms": "180000.00",
                          "valor_ipi": "50000.00",
                          "total_nfe": "1332500.00",
                          "variacao_2026_percentual": 0
                        },
                        {
                          "ano": 2027,
                          "valor_is": "0.00",
                          "valor_cbs": "88000.00",
                          "valor_ibs": "1000.00",
                          "valor_pis_cofins": "0.00",
                          "valor_icms": "180000.00",
                          "valor_ipi": "0.00",
                          "total_nfe": "1269000.00",
                          "variacao_2026_percentual": -4.8
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "description": "Parâmetro inválido",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "parametro_invalido": {
                    "summary": "Parâmetro inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "Para o perfil industria, informe icms e ipi.",
                      "instance": "/v1/reforma/simulacoes/transicao",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "aliquotas_atuais.icms",
                          "mensagem": "Obrigatório para o perfil industria."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/reforma/simulacoes/transicao\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"perfil\": \"industria\", \"receita_liquida\": \"1000000.00\", \"aliquotas_atuais\": {\"pis\": 1.65, \"cofins\": 7.6, \"icms\": 18, \"ipi\": 5, \"is\": 0}, \"fator_beneficio_ibs\": 1, \"fator_beneficio_cbs\": 1}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/reforma/simulacoes/transicao`, {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"perfil\": \"industria\",\n    \"receita_liquida\": \"1000000.00\",\n    \"aliquotas_atuais\": {\n      \"pis\": 1.65,\n      \"cofins\": 7.6,\n      \"icms\": 18,\n      \"ipi\": 5,\n      \"is\": 0\n    },\n    \"fator_beneficio_ibs\": 1,\n    \"fator_beneficio_cbs\": 1\n  }),\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.post(\n    os.environ[\"RMF_API_URL\"] + \"/reforma/simulacoes/transicao\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    json={\n      \"perfil\": \"industria\",\n      \"receita_liquida\": \"1000000.00\",\n      \"aliquotas_atuais\": {\n        \"pis\": 1.65,\n        \"cofins\": 7.6,\n        \"icms\": 18,\n        \"ipi\": 5,\n        \"is\": 0\n      },\n      \"fator_beneficio_ibs\": 1,\n      \"fator_beneficio_cbs\": 1\n    },\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/reforma/simulacoes/transicao');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'POST',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => '{\"perfil\":\"industria\",\"receita_liquida\":\"1000000.00\",\"aliquotas_atuais\":{\"pis\":1.65,\"cofins\":7.6,\"icms\":18,\"ipi\":5,\"is\":0},\"fator_beneficio_ibs\":1,\"fator_beneficio_cbs\":1}',\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Post, \"reforma/simulacoes/transicao\");\nreq.Content = new StringContent(\"\"\"\n{\n  \"perfil\": \"industria\",\n  \"receita_liquida\": \"1000000.00\",\n  \"aliquotas_atuais\": {\n    \"pis\": 1.65,\n    \"cofins\": 7.6,\n    \"icms\": 18,\n    \"ipi\": 5,\n    \"is\": 0\n  },\n  \"fator_beneficio_ibs\": 1,\n  \"fator_beneficio_cbs\": 1\n}\n\"\"\", System.Text.Encoding.UTF8, \"application/json\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/reforma/simulacoes/transicao\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .header(\"Content-Type\", \"application/json\")\n                .method(\"POST\", HttpRequest.BodyPublishers.ofString(\"\"\"\n{\n  \"perfil\": \"industria\",\n  \"receita_liquida\": \"1000000.00\",\n  \"aliquotas_atuais\": {\n    \"pis\": 1.65,\n    \"cofins\": 7.6,\n    \"icms\": 18,\n    \"ipi\": 5,\n    \"is\": 0\n  },\n  \"fator_beneficio_ibs\": 1,\n  \"fator_beneficio_cbs\": 1\n}\n\"\"\"))\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Corpo: TStringStream;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/reforma/simulacoes/transicao';\n  Http := THTTPClient.Create;\n  try\n    Corpo := TStringStream.Create('{\"perfil\":\"industria\",\"receita_liquida\":\"1000000.00\",\"aliquotas_atuais\":{\"pis\":1.65,\"cofins\":7.6,\"icms\":18,\"ipi\":5,\"is\":0},\"fator_beneficio_ibs\":1,\"fator_beneficio_cbs\":1}', TEncoding.UTF8);\n    try\n      Resp := Http.Post(Url, Corpo, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY')), TNetHeader.Create('Content-Type', 'application/json')]);\n    finally\n      Corpo.Free;\n    end;\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/reforma/simulacoes/regimes": {
      "post": {
        "operationId": "simularRegimes",
        "summary": "Comparação entre Simples, Presumido e Real",
        "description": "Use para comparar a carga tributária mensal de uma empresa no Simples Nacional, no Lucro Presumido e no Lucro Real, no ano de referência da Reforma, com os dados informados. A resposta traz o resultado de cada regime (carga mensal, carga sobre o faturamento e valor por tributo), o veredito, os `avisos` (o que informar para um resultado mais preciso) e as versões das tabelas e premissas. Não traz a memória de cálculo nem valores intermediários da conta.\n\nA chamada é síncrona e não guarda o pedido. O regime híbrido do Simples não faz parte da v1.\n\n**Quando não usar:** para decidir sozinho o regime da empresa: quando a diferença é pequena o veredito é `avaliar`, que significa procurar um contador. Também não serve para apurar imposto de período real.",
        "tags": [
          "Reforma: simulações"
        ],
        "security": [
          {
            "bearerAuth": [
              "consultas:ler"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SimulacaoRegimesRequisicao"
              },
              "examples": {
                "comercio": {
                  "summary": "Comércio, os três regimes",
                  "value": {
                    "setor": "comercio",
                    "faturamento_mes": "200000.00",
                    "folha_mes": "30000.00",
                    "rbt12": "2400000.00",
                    "anexo_simples": "I",
                    "margem_percentual": 8,
                    "ano_referencia": 2033,
                    "regimes": [
                      "simples",
                      "presumido",
                      "real"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Carga por regime e veredito.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimulacaoRegimesResposta"
                },
                "examples": {
                  "comercio": {
                    "summary": "Comércio com faturamento de R$ 200 mil por mês",
                    "description": "Valores ilustrativos: não vêm de um cálculo real e não devem ser lidos como resultado fiscal.",
                    "value": {
                      "ano_referencia": 2033,
                      "versao_tabelas": "CGSN-140-2018",
                      "versao_premissas": "v0-2026-09-13",
                      "regimes": [
                        {
                          "regime": "simples",
                          "carga_mensal": "18000.00",
                          "carga_percentual": 9,
                          "tributos": [
                            {
                              "tributo": "PIS",
                              "valor": "400.00"
                            },
                            {
                              "tributo": "COFINS",
                              "valor": "1800.00"
                            }
                          ]
                        },
                        {
                          "regime": "presumido",
                          "carga_mensal": "23600.00",
                          "carga_percentual": 11.8,
                          "tributos": [
                            {
                              "tributo": "PIS",
                              "valor": "1300.00"
                            },
                            {
                              "tributo": "COFINS",
                              "valor": "6000.00"
                            }
                          ]
                        },
                        {
                          "regime": "real",
                          "carga_mensal": "23400.00",
                          "carga_percentual": 11.7,
                          "tributos": [
                            {
                              "tributo": "PIS",
                              "valor": "900.00"
                            },
                            {
                              "tributo": "COFINS",
                              "valor": "4150.00"
                            }
                          ]
                        }
                      ],
                      "veredito": {
                        "codigo": "ficar",
                        "texto": "FICAR NO SIMPLES",
                        "diferenca": "-5400.00"
                      },
                      "avisos": [
                        "Informe aliquota_icms_iss e percentual_credito_pis_cofins para um resultado mais preciso."
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "description": "Parâmetro inválido",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "parametro_invalido": {
                    "summary": "Parâmetro inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "Informe o anexo do Simples para comparar o Simples.",
                      "instance": "/v1/reforma/simulacoes/regimes",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "anexo_simples",
                          "mensagem": "Obrigatório quando regimes inclui simples."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/reforma/simulacoes/regimes\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"setor\": \"comercio\", \"faturamento_mes\": \"200000.00\", \"folha_mes\": \"30000.00\", \"rbt12\": \"2400000.00\", \"anexo_simples\": \"I\", \"margem_percentual\": 8, \"ano_referencia\": 2033, \"regimes\": [\"simples\", \"presumido\", \"real\"]}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/reforma/simulacoes/regimes`, {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    \"setor\": \"comercio\",\n    \"faturamento_mes\": \"200000.00\",\n    \"folha_mes\": \"30000.00\",\n    \"rbt12\": \"2400000.00\",\n    \"anexo_simples\": \"I\",\n    \"margem_percentual\": 8,\n    \"ano_referencia\": 2033,\n    \"regimes\": [\n      \"simples\",\n      \"presumido\",\n      \"real\"\n    ]\n  }),\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.post(\n    os.environ[\"RMF_API_URL\"] + \"/reforma/simulacoes/regimes\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    json={\n      \"setor\": \"comercio\",\n      \"faturamento_mes\": \"200000.00\",\n      \"folha_mes\": \"30000.00\",\n      \"rbt12\": \"2400000.00\",\n      \"anexo_simples\": \"I\",\n      \"margem_percentual\": 8,\n      \"ano_referencia\": 2033,\n      \"regimes\": [\n        \"simples\",\n        \"presumido\",\n        \"real\"\n      ]\n    },\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/reforma/simulacoes/regimes');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'POST',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => '{\"setor\":\"comercio\",\"faturamento_mes\":\"200000.00\",\"folha_mes\":\"30000.00\",\"rbt12\":\"2400000.00\",\"anexo_simples\":\"I\",\"margem_percentual\":8,\"ano_referencia\":2033,\"regimes\":[\"simples\",\"presumido\",\"real\"]}',\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Post, \"reforma/simulacoes/regimes\");\nreq.Content = new StringContent(\"\"\"\n{\n  \"setor\": \"comercio\",\n  \"faturamento_mes\": \"200000.00\",\n  \"folha_mes\": \"30000.00\",\n  \"rbt12\": \"2400000.00\",\n  \"anexo_simples\": \"I\",\n  \"margem_percentual\": 8,\n  \"ano_referencia\": 2033,\n  \"regimes\": [\n    \"simples\",\n    \"presumido\",\n    \"real\"\n  ]\n}\n\"\"\", System.Text.Encoding.UTF8, \"application/json\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/reforma/simulacoes/regimes\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .header(\"Content-Type\", \"application/json\")\n                .method(\"POST\", HttpRequest.BodyPublishers.ofString(\"\"\"\n{\n  \"setor\": \"comercio\",\n  \"faturamento_mes\": \"200000.00\",\n  \"folha_mes\": \"30000.00\",\n  \"rbt12\": \"2400000.00\",\n  \"anexo_simples\": \"I\",\n  \"margem_percentual\": 8,\n  \"ano_referencia\": 2033,\n  \"regimes\": [\n    \"simples\",\n    \"presumido\",\n    \"real\"\n  ]\n}\n\"\"\"))\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Corpo: TStringStream;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/reforma/simulacoes/regimes';\n  Http := THTTPClient.Create;\n  try\n    Corpo := TStringStream.Create('{\"setor\":\"comercio\",\"faturamento_mes\":\"200000.00\",\"folha_mes\":\"30000.00\",\"rbt12\":\"2400000.00\",\"anexo_simples\":\"I\",\"margem_percentual\":8,\"ano_referencia\":2033,\"regimes\":[\"simples\",\"presumido\",\"real\"]}', TEncoding.UTF8);\n    try\n      Resp := Http.Post(Url, Corpo, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY')), TNetHeader.Create('Content-Type', 'application/json')]);\n    finally\n      Corpo.Free;\n    end;\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/reforma/linha-do-tempo": {
      "get": {
        "operationId": "listarMarcosReforma",
        "summary": "Marcos e ações da Reforma por perfil",
        "description": "Use para listar os marcos da transição (início da CBS, fim do PIS e da COFINS, créditos, prazos) ano a ano, e o que fazer em cada um para o perfil de empresa informado. Não recebe nem guarda dado da empresa: o perfil é só o regime e o setor.\n\nO exemplo de resposta mostra só o ano de 2027, para ficar curto.\n\n**Quando não usar:** para saber a alíquota de um ano (use `listarAliquotasReforma`) ou o tratamento de um produto (use `consultarNcmReforma`).",
        "tags": [
          "Reforma: linha do tempo"
        ],
        "security": [
          {
            "bearerAuth": [
              "consultas:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "regime",
            "in": "query",
            "description": "Regime da empresa, para filtrar as ações. Sem `regime` e `setor`, vêm só as ações que valem para todos.",
            "schema": {
              "type": "string",
              "enum": [
                "simples",
                "presumido",
                "real"
              ]
            },
            "example": "real"
          },
          {
            "name": "setor",
            "in": "query",
            "description": "Setor da empresa, para filtrar as ações.",
            "schema": {
              "type": "string",
              "enum": [
                "industria",
                "comercio",
                "servicos"
              ]
            },
            "example": "servicos"
          },
          {
            "name": "ano_de",
            "in": "query",
            "description": "Primeiro ano.",
            "schema": {
              "type": "integer",
              "minimum": 2026,
              "maximum": 2033,
              "default": 2026
            },
            "example": 2027
          },
          {
            "name": "ano_ate",
            "in": "query",
            "description": "Último ano.",
            "schema": {
              "type": "integer",
              "minimum": 2026,
              "maximum": 2033,
              "default": 2033
            },
            "example": 2027
          }
        ],
        "responses": {
          "200": {
            "description": "Marcos por ano, marcos recorrentes e versão do catálogo.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinhaDoTempoReforma"
                },
                "examples": {
                  "real_servicos": {
                    "summary": "Lucro Real, serviços, ano de 2027",
                    "description": "Marcos e ações do catálogo da RMF (leitura de 13/09/2026). `titulo` e `resumo` são os textos públicos do catálogo (`titulo_publico` e `resumo_publico`, mantidos pela equipe). Como o catálogo ainda não tem esses campos, o texto público do M-2027-CBS-REDUCAO é ilustrativo; nos demais marcos, ele é igual ao texto atual.",
                    "value": {
                      "perfil": {
                        "regime": "real",
                        "setor": "servicos"
                      },
                      "anos": [
                        {
                          "ano": 2027,
                          "marcos": [
                            {
                              "codigo": "M-2027-CBS-COBRANCA",
                              "data_inicio": "2027-01-01",
                              "data_fim": null,
                              "tipo": "inicio_cobranca",
                              "titulo": "A CBS passa a ser cobrada",
                              "resumo": "A CBS passa a ser cobrada",
                              "tributos": [
                                "CBS"
                              ],
                              "base_legal": "ADCT art. 126, I, \"a\" (art. 195, V, CF)",
                              "confianca": "lei",
                              "tem_pendencia": false,
                              "acoes": []
                            },
                            {
                              "codigo": "M-2027-PISCOFINS-FIM",
                              "data_inicio": "2027-01-01",
                              "data_fim": null,
                              "tipo": "extincao",
                              "titulo": "Extinção do PIS e da COFINS",
                              "resumo": "Extinção do PIS e da COFINS: condicionada à instituição da CBS",
                              "tributos": [
                                "PIS",
                                "COFINS"
                              ],
                              "base_legal": "ADCT art. 126, II; LC 214/2025, art. 542 (revoga LC 7/1970 art. 3º, \"b\"; LC 70/1991 arts. 1º a 6º, 9º e 10; Lei 9.718 arts. 2º a 8º-B; e mais 20 diplomas), com produção de efeitos pelo art. 544, III",
                              "confianca": "lei",
                              "tem_pendencia": false,
                              "acoes": [
                                {
                                  "acao": "Levante os créditos de PIS/COFINS não aproveitados até 31/12/2026: eles só sobrevivem se cumprirem os requisitos da legislação vigente na data da extinção",
                                  "detalhe": null,
                                  "antecedencia_dias": 0,
                                  "base_legal": "ADCT art. 135",
                                  "confianca": "lei",
                                  "tem_pendencia": false
                                },
                                {
                                  "acao": "Reveja os contratos com preço \"com tributos inclusos\": a partir de 2027 a CBS é por fora e PIS/COFINS somem da composição",
                                  "detalhe": null,
                                  "antecedencia_dias": 0,
                                  "base_legal": null,
                                  "confianca": "premissa",
                                  "tem_pendencia": true
                                }
                              ]
                            },
                            {
                              "codigo": "M-2027-CBS-REDUCAO",
                              "data_inicio": "2027-01-01",
                              "data_fim": "2028-12-31",
                              "tipo": "inicio_cobranca",
                              "titulo": "CBS de 2027 e 2028: a lei prevê redução de 0,1 ponto",
                              "resumo": "Em 2027 e 2028 a lei prevê que a alíquota de referência da CBS seja reduzida em 0,1 ponto percentual (exceto para os combustíveis dos arts. 172 a 180), proporcional nos regimes de alíquota reduzida e aplicada nos regimes específicos",
                              "tributos": [
                                "CBS"
                              ],
                              "base_legal": "LC 214/2025, art. 347, caput e §1º, I e II (ADCT art. 127, p.u.)",
                              "confianca": "lei",
                              "tem_pendencia": false,
                              "acoes": []
                            },
                            {
                              "codigo": "M-2027-IBS-TESTE",
                              "data_inicio": "2027-01-01",
                              "data_fim": "2028-12-31",
                              "tipo": "ano_teste",
                              "titulo": "IBS cobrado a 0,05% estadual e 0,05% municipal em 2027 e 2028",
                              "resumo": "IBS cobrado à alíquota estadual de 0,05% e municipal de 0,05%, aplicadas com a respectiva redução nos regimes diferenciados e nos regimes específicos (exceto combustíveis dos arts. 172 a 180, em que incidem sobre o valor da operação no momento da incidência da CBS)",
                              "tributos": [
                                "IBS"
                              ],
                              "base_legal": "LC 214/2025, art. 344, caput e p.u., I a IV (ADCT art. 127)",
                              "confianca": "lei",
                              "tem_pendencia": false,
                              "acoes": []
                            },
                            {
                              "codigo": "M-2027-IBS-DEDUCAO",
                              "data_inicio": "2027-01-01",
                              "data_fim": "2028-12-31",
                              "tipo": "credito",
                              "titulo": "IBS do regime específico de combustíveis deduzido da CBS a recolher",
                              "resumo": "No período, o IBS recolhido nos termos do art. 344, p.u., III pode ser deduzido da CBS a recolher pelos contribuintes do regime específico de combustíveis",
                              "tributos": [
                                "IBS",
                                "CBS"
                              ],
                              "base_legal": "LC 214/2025, art. 347, §2º",
                              "confianca": "lei",
                              "tem_pendencia": false,
                              "acoes": []
                            },
                            {
                              "codigo": "M-2027-IS-COBRANCA",
                              "data_inicio": "2027-01-01",
                              "data_fim": null,
                              "tipo": "inicio_cobranca",
                              "titulo": "O Imposto Seletivo passa a ser cobrado",
                              "resumo": "O Imposto Seletivo passa a ser cobrado",
                              "tributos": [
                                "IS"
                              ],
                              "base_legal": "ADCT art. 126, I, \"b\" (art. 153, VIII, CF); LC 214/2025, art. 477, caput e §1º (\"A partir de 2027 … substituição da arrecadação do IPI pela arrecadação do Imposto Seletivo\")",
                              "confianca": "lei",
                              "tem_pendencia": false,
                              "acoes": []
                            },
                            {
                              "codigo": "M-2027-IPI-ZERO",
                              "data_inicio": "2027-01-01",
                              "data_fim": null,
                              "tipo": "extincao",
                              "titulo": "Alíquotas do IPI reduzidas a zero, exceto a industrialização incentivada na ZFM",
                              "resumo": "Alíquotas do IPI reduzidas a zero, exceto para produtos com industrialização incentivada na Zona Franca de Manaus; e o IPI deixa de incidir cumulativamente com o Imposto Seletivo",
                              "tributos": [
                                "IPI"
                              ],
                              "base_legal": "ADCT art. 126, III, \"a\" e \"b\"",
                              "confianca": "regulamento",
                              "tem_pendencia": true,
                              "acoes": []
                            },
                            {
                              "codigo": "M-2027-CRED-RESIDUAL",
                              "data_inicio": "2027-01-01",
                              "data_fim": null,
                              "tipo": "credito",
                              "titulo": "Créditos residuais de IPI, PIS e COFINS permanecem compensáveis ou ressarcíveis",
                              "resumo": "Créditos de IPI, PIS e COFINS não apropriados ou não utilizados até a extinção permanecem compensáveis com tributos federais, inclusive com a CBS, ou ressarcíveis em dinheiro, para os créditos que cumpram os requisitos da legislação vigente na data da extinção",
                              "tributos": [
                                "IPI",
                                "PIS",
                                "COFINS"
                              ],
                              "base_legal": "ADCT art. 135",
                              "confianca": "regulamento",
                              "tem_pendencia": true,
                              "acoes": [
                                {
                                  "acao": "Acompanhe a lei complementar que disciplinará a forma de utilização desses créditos",
                                  "detalhe": null,
                                  "antecedencia_dias": 0,
                                  "base_legal": "ADCT art. 135",
                                  "confianca": "regulamento",
                                  "tem_pendencia": true
                                }
                              ]
                            },
                            {
                              "codigo": "M-2027-SIMPLES-OPCAO",
                              "data_inicio": "2027-01-01",
                              "data_fim": null,
                              "tipo": "prazo",
                              "titulo": "Optante do Simples pode optar pelo regime regular de IBS e CBS",
                              "resumo": "Optante do Simples Nacional pode optar por apurar e recolher IBS e CBS pelo regime regular",
                              "tributos": [
                                "IBS",
                                "CBS"
                              ],
                              "base_legal": "LC 214/2025, art. 41, §§3º e 4º; trava do §5º (quem recebeu ressarcimento de créditos no ano-calendário corrente ou anterior, nos termos do art. 39, não pode sair do regime regular)",
                              "confianca": "regulamento",
                              "tem_pendencia": true,
                              "acoes": []
                            }
                          ],
                          "vigentes": []
                        }
                      ],
                      "recorrentes": [
                        {
                          "codigo": "M-ANUAL-TCU",
                          "titulo": "TCU envia ao Senado os cálculos das alíquotas de referência",
                          "resumo": "O TCU envia ao Senado os cálculos das alíquotas de referência até 15 de setembro do ano anterior ao de vigência",
                          "base_legal": "LC 214/2025, art. 349, §1º, I",
                          "confianca": "lei",
                          "mes_dia": "09-15",
                          "ano_inicio": 2026,
                          "ano_fim": 2034
                        },
                        {
                          "codigo": "M-ANUAL-SENADO",
                          "titulo": "Senado fixa as alíquotas de referência do ano seguinte",
                          "resumo": "O Senado fixa as alíquotas de referência e o redutor da administração pública até 31 de outubro do ano anterior: sem a anterioridade nonagesimal do art. 150, III, \"c\" da CF",
                          "base_legal": "LC 214/2025, art. 349, §1º, II; ADCT art. 130, §1º",
                          "confianca": "lei",
                          "mes_dia": "10-31",
                          "ano_inicio": 2026,
                          "ano_fim": 2034
                        },
                        {
                          "codigo": "M-ANUAL-SENADO-ATRASO",
                          "titulo": "Sem fixação até 22 de dezembro, valem as alíquotas calculadas pelo TCU",
                          "resumo": "Passado 22 de dezembro sem fixação, valem as alíquotas calculadas pelo TCU, e as do Senado só vigem a partir do segundo mês subsequente ao da fixação, observado o art. 150, III, \"b\"",
                          "base_legal": "LC 214/2025, art. 349, §2º, I e II",
                          "confianca": "lei",
                          "mes_dia": "12-22",
                          "ano_inicio": 2026,
                          "ano_fim": 2034
                        }
                      ],
                      "versao_catalogo": "v0-2026-09-13",
                      "total_marcos": 27
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "description": "Parâmetro inválido",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "parametro_invalido": {
                    "summary": "Parâmetro inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "ano_ate não pode ser menor que ano_de.",
                      "instance": "/v1/reforma/linha-do-tempo",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "ano_ate",
                          "mensagem": "Use um ano igual ou maior que ano_de."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/reforma/linha-do-tempo?regime=real&setor=servicos&ano_de=2027&ano_ate=2027\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/reforma/linha-do-tempo`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/reforma/linha-do-tempo\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/reforma/linha-do-tempo');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"reforma/linha-do-tempo\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/reforma/linha-do-tempo\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/reforma/linha-do-tempo';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/empresas": {
      "post": {
        "operationId": "criarEmpresa",
        "summary": "Cadastrar empresa",
        "description": "Use para cadastrar na sua conta uma empresa (pelo CNPJ) antes de enviar arquivos dela. O CNPJ pode ser alfanumérico.\n\nQuando o cadastro não é possível nesta conta, a resposta é 409 `empresa_indisponivel`, sem o motivo: fale com a equipe da RMF. Repetir o cadastro de uma empresa que já está na conta, com outra `Idempotency-Key`, devolve 409 `empresa_ja_cadastrada`.\n\n**Conta de parceiro:** informe `rede_id` para pôr a empresa numa rede da sua carteira (os ids aparecem em `rede` de `listarEmpresas`). Sem `rede_id`, a API cria uma rede nova na sua carteira para a empresa. Em conta de rede, `rede_id` não é aceito (422).\n\n**Limite:** até 50 cadastros por dia por conta (cota `empresas_dia` em `obterUso`); acima disso, 429 `cota_diaria_excedida` até o dia seguinte.\n\n**Quando não usar:** para empresas de terceiros que você não representa, nem para consultar dados da empresa na Receita: a API só guarda o que você cadastra.",
        "tags": [
          "Empresas"
        ],
        "security": [
          {
            "bearerAuth": [
              "empresas:escrever"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CriarEmpresaRequisicao"
              },
              "examples": {
                "empresa": {
                  "summary": "Conta de rede",
                  "value": {
                    "cnpj": "11222333000181",
                    "nome": "Empresa Exemplo Comércio Ltda"
                  }
                },
                "parceiro_com_rede": {
                  "summary": "Conta de parceiro, numa rede da carteira",
                  "value": {
                    "cnpj": "12ABC34501DE35",
                    "nome": "Empresa Alfa Exemplo Ltda",
                    "rede_id": "rede_01JD3M2A4C6E8G0J2K4N6Q8S0T"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Empresa cadastrada.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmpresaResumo"
                },
                "examples": {
                  "empresa": {
                    "summary": "Empresa criada em conta de rede",
                    "value": {
                      "id": "emp_01JC8F3Z6Q2W9X4B7N5M1RTHKA",
                      "cnpj": "11222333000181",
                      "nome": "Empresa Exemplo Comércio Ltda",
                      "criada_em": "2026-10-29T10:15:00-03:00"
                    }
                  },
                  "parceiro": {
                    "summary": "Empresa criada em conta de parceiro",
                    "value": {
                      "id": "emp_01JC8F3Z6Q2W9X4B7N5M1RTHKB",
                      "cnpj": "12ABC34501DE35",
                      "nome": "Empresa Alfa Exemplo Ltda",
                      "criada_em": "2026-10-29T10:18:00-03:00",
                      "rede": {
                        "id": "rede_01JD3M2A4C6E8G0J2K4N6Q8S0T",
                        "nome": "Grupo Alfa"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "`rede_id` que não é de uma rede da sua carteira.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "rede_nao_encontrada": {
                    "summary": "Rede não encontrada",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/rede_nao_encontrada",
                      "title": "Rede não encontrada",
                      "status": 404,
                      "detail": "Não há rede com este identificador na sua carteira. Confira os ids em GET /empresas.",
                      "instance": "/v1/empresas",
                      "codigo": "rede_nao_encontrada",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Cadastro não possível nesta conta, empresa já cadastrada ou chamada em andamento.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "empresa_indisponivel": {
                    "summary": "Empresa indisponível para esta conta",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/empresa_indisponivel",
                      "title": "Empresa indisponível",
                      "status": 409,
                      "detail": "Não foi possível cadastrar este CNPJ nesta conta. Fale com a equipe da RMF.",
                      "instance": "/v1/empresas",
                      "codigo": "empresa_indisponivel",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "empresa_ja_cadastrada": {
                    "summary": "Empresa já cadastrada na conta",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/empresa_ja_cadastrada",
                      "title": "Empresa já cadastrada",
                      "status": 409,
                      "detail": "Este CNPJ já está na sua conta.",
                      "instance": "/v1/empresas",
                      "codigo": "empresa_ja_cadastrada",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "idempotencia_em_andamento": {
                    "summary": "Chamada em andamento",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/idempotencia_em_andamento",
                      "title": "Chamada em andamento",
                      "status": 409,
                      "detail": "Outra chamada com a mesma Idempotency-Key ainda está sendo processada.",
                      "instance": "/v1/empresas",
                      "codigo": "idempotencia_em_andamento",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "CNPJ inválido, `rede_id` em conta de rede ou Idempotency-Key reutilizada com outro corpo.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "cnpj_dv_invalido": {
                    "summary": "CNPJ com dígito verificador inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "O CNPJ 12345678000199 não passou na validação.",
                      "instance": "/v1/empresas",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "cnpj",
                          "mensagem": "CNPJ com dígito verificador inválido."
                        }
                      ]
                    }
                  },
                  "cnpj_invalido": {
                    "summary": "CNPJ inválido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "O CNPJ 00000000000000 não é válido.",
                      "instance": "/v1/empresas",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "cnpj",
                          "mensagem": "Informe o CNPJ de uma empresa."
                        }
                      ]
                    }
                  },
                  "rede_id_em_conta_de_rede": {
                    "summary": "rede_id em conta de rede",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "rede_id só é aceito em conta de parceiro.",
                      "instance": "/v1/empresas",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "rede_id",
                          "mensagem": "Retire o campo; em conta de rede, a empresa entra na sua rede."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Limite por minuto ou cota de 50 cadastros por dia excedidos.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "cota_diaria_excedida": {
                    "summary": "Cota de cadastros do dia acabou",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/cota_diaria_excedida",
                      "title": "Cota diária excedida",
                      "status": 429,
                      "detail": "A conta já cadastrou 50 empresas hoje. Tente amanhã ou fale com a equipe da RMF.",
                      "instance": "/v1/empresas",
                      "codigo": "cota_diaria_excedida",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "limite_excedido": {
                    "summary": "Limite por minuto",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/limite_excedido",
                      "title": "Limite excedido",
                      "status": 429,
                      "detail": "Você passou do limite de chamadas por minuto. Tente de novo em 23 segundos.",
                      "instance": "/v1/empresas",
                      "codigo": "limite_excedido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/empresas\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Idempotency-Key: 8f1c2d3e-4b5a-4c6d-9e7f-0a1b2c3d4e5f\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"cnpj\": \"11222333000181\", \"nome\": \"Empresa Exemplo Comércio Ltda\"}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/empresas`, {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n  },\n  body: JSON.stringify({\n    \"cnpj\": \"11222333000181\",\n    \"nome\": \"Empresa Exemplo Comércio Ltda\"\n  }),\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport uuid\nimport requests\n\nresp = requests.post(\n    os.environ[\"RMF_API_URL\"] + \"/empresas\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"], \"Idempotency-Key\": str(uuid.uuid4())},\n    json={\n      \"cnpj\": \"11222333000181\",\n      \"nome\": \"Empresa Exemplo Comércio Ltda\"\n    },\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/empresas');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'POST',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n        'Content-Type: application/json',\n        'Idempotency-Key: ' . bin2hex(random_bytes(16)),\n    ],\n    CURLOPT_POSTFIELDS => '{\"cnpj\":\"11222333000181\",\"nome\":\"Empresa Exemplo Comércio Ltda\"}',\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Post, \"empresas\");\nreq.Headers.Add(\"Idempotency-Key\", Guid.NewGuid().ToString());\nreq.Content = new StringContent(\"\"\"\n{\n  \"cnpj\": \"11222333000181\",\n  \"nome\": \"Empresa Exemplo Comércio Ltda\"\n}\n\"\"\", System.Text.Encoding.UTF8, \"application/json\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/empresas\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .header(\"Content-Type\", \"application/json\")\n                .header(\"Idempotency-Key\", java.util.UUID.randomUUID().toString())\n                .method(\"POST\", HttpRequest.BodyPublishers.ofString(\"\"\"\n{\n  \"cnpj\": \"11222333000181\",\n  \"nome\": \"Empresa Exemplo Comércio Ltda\"\n}\n\"\"\"))\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Corpo: TStringStream;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/empresas';\n  Http := THTTPClient.Create;\n  try\n    Corpo := TStringStream.Create('{\"cnpj\":\"11222333000181\",\"nome\":\"Empresa Exemplo Comércio Ltda\"}', TEncoding.UTF8);\n    try\n      Resp := Http.Post(Url, Corpo, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY')), TNetHeader.Create('Content-Type', 'application/json'), TNetHeader.Create('Idempotency-Key', GUIDToString(TGUID.NewGuid).Trim(['{', '}']).ToLower)]);\n    finally\n      Corpo.Free;\n    end;\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      },
      "get": {
        "operationId": "listarEmpresas",
        "summary": "Listar empresas da conta",
        "description": "Use para listar as empresas da sua conta, em páginas. Em conta de rede, são as empresas da rede. Em conta de parceiro, é a carteira inteira: a união das redes que o parceiro atende, e cada empresa traz a `rede` em que está (o `rede.id` serve para `criarEmpresa`).\n\n**Quando não usar:** para ver os arquivos de uma empresa (use `obterEmpresa`, que traz a cobertura por competência).",
        "tags": [
          "Empresas"
        ],
        "security": [
          {
            "bearerAuth": [
              "empresas:ler"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de empresas.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaEmpresas"
                },
                "examples": {
                  "rede": {
                    "summary": "Conta de rede, matriz e filial",
                    "value": {
                      "dados": [
                        {
                          "id": "emp_01JC8F3Z6Q2W9X4B7N5M1RTHKA",
                          "cnpj": "11222333000181",
                          "nome": "Empresa Exemplo Comércio Ltda",
                          "criada_em": "2026-10-29T10:15:00-03:00"
                        },
                        {
                          "id": "emp_01JC8F3Z6Q2W9X4B7N5M1RTHKC",
                          "cnpj": "11222333000262",
                          "nome": null,
                          "criada_em": "2026-10-29T10:16:00-03:00"
                        }
                      ],
                      "proximo_cursor": null
                    }
                  },
                  "parceiro": {
                    "summary": "Conta de parceiro, duas redes da carteira",
                    "value": {
                      "dados": [
                        {
                          "id": "emp_01JC8F3Z6Q2W9X4B7N5M1RTHKA",
                          "cnpj": "11222333000181",
                          "nome": "Empresa Exemplo Comércio Ltda",
                          "criada_em": "2026-10-29T10:15:00-03:00",
                          "rede": {
                            "id": "rede_01JD3M2A4C6E8G0J2K4N6Q8S0V",
                            "nome": "Grupo Exemplo"
                          }
                        },
                        {
                          "id": "emp_01JC8F3Z6Q2W9X4B7N5M1RTHKB",
                          "cnpj": "12ABC34501DE35",
                          "nome": "Empresa Alfa Exemplo Ltda",
                          "criada_em": "2026-10-29T10:18:00-03:00",
                          "rede": {
                            "id": "rede_01JD3M2A4C6E8G0J2K4N6Q8S0T",
                            "nome": "Grupo Alfa"
                          }
                        }
                      ],
                      "proximo_cursor": null
                    }
                  },
                  "vazia": {
                    "summary": "Conta sem empresas",
                    "value": {
                      "dados": [],
                      "proximo_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "$ref": "#/components/responses/NaoProcessavel"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/empresas?limite=50\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/empresas`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/empresas\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/empresas');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"empresas\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/empresas\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/empresas';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/empresas/{cnpj}": {
      "get": {
        "operationId": "obterEmpresa",
        "summary": "Obter empresa com cobertura de arquivos",
        "description": "Use para ler uma empresa da conta e a cobertura de arquivos por competência: quais tipos de arquivo já foram lidos em cada mês. É o que diz de que período um relatório pode ser pedido. Um ZIP de XML aparece em cada mês do seu `periodo`.\n\n**Quando não usar:** para listar todas as empresas (use `listarEmpresas`). Um CNPJ que não é da sua conta responde 404, nunca 403; um CNPJ malformado também responde 404, como um que não existe.",
        "tags": [
          "Empresas"
        ],
        "security": [
          {
            "bearerAuth": [
              "empresas:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "cnpj",
            "in": "path",
            "description": "CNPJ da empresa, 14 caracteres, sem máscara.",
            "schema": {
              "$ref": "#/components/schemas/Cnpj"
            },
            "required": true,
            "example": "11222333000181"
          }
        ],
        "responses": {
          "200": {
            "description": "Empresa e cobertura.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Empresa"
                },
                "examples": {
                  "empresa": {
                    "summary": "Empresa com dois meses de cobertura",
                    "value": {
                      "id": "emp_01JC8F3Z6Q2W9X4B7N5M1RTHKA",
                      "cnpj": "11222333000181",
                      "nome": "Empresa Exemplo Comércio Ltda",
                      "criada_em": "2026-10-29T10:15:00-03:00",
                      "cobertura": [
                        {
                          "competencia": "2026-08",
                          "arquivos": [
                            {
                              "tipo": "efd_contribuicoes",
                              "estado": "lido",
                              "arquivo_id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNN"
                            }
                          ]
                        },
                        {
                          "competencia": "2026-09",
                          "arquivos": [
                            {
                              "tipo": "efd_contribuicoes",
                              "estado": "lido",
                              "arquivo_id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP"
                            },
                            {
                              "tipo": "efd_icms_ipi",
                              "estado": "na_fila",
                              "arquivo_id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNQ"
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Empresa não encontrada, ou CNPJ malformado.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Empresa não encontrada",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Empresa não encontrada",
                      "status": 404,
                      "detail": "Não há empresa com este CNPJ na sua conta.",
                      "instance": "/v1/empresas/12ABC34501DE35",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/empresas/11222333000181\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/empresas/11222333000181`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/empresas/11222333000181\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/empresas/11222333000181');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"empresas/11222333000181\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/empresas/11222333000181\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/empresas/11222333000181';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/arquivos": {
      "post": {
        "operationId": "enviarArquivo",
        "summary": "Enviar SPED ou ZIP de XML",
        "description": "Use para enviar um arquivo SPED (EFD ICMS/IPI ou EFD Contribuições) ou um ZIP de XML de NF-e ou CF-e, por `multipart/form-data` no campo `arquivo`, um arquivo por chamada. Tipo e período são detectados do conteúdo; o CNPJ, conforme o tipo (abaixo). Arquivo sem o registro `|0000|` legível (CNPJ e período) no início é recusado na hora, com 422 `arquivo_corrompido` (o mesmo no sandbox e na produção). No ZIP de XML, o `tipo` (`nfe_xml` ou `cfe_xml`) já vem na resposta do envio e a `competencia` é nula (o período vem das datas dos XML). A resposta é 202 com o `id` do arquivo e `Location` do trabalho de leitura; acompanhe por `obterArquivo`, `obterJob` ou pelos eventos `arquivo.lido` e `arquivo.rejeitado`.\n\n- **Mesmo conteúdo, mesma conta:** se esta conta já enviou o mesmo conteúdo (mesmo `sha256`), a resposta é 200 com o arquivo existente e `duplicado: true`. A deduplicação é por conta: um envio igual feito por outra conta não volta aqui.\n- **Período já lido:** se já existe SPED lido para o mesmo CNPJ e período, enviado por esta conta ou por outra que também tem a empresa, o novo arquivo termina em `periodo_ja_lido` e não é usado de novo (o evento `arquivo.lido` chega com esse `estado`). Uma retificadora substitui o anterior, que passa a `substituido` sem evento próprio: o campo `substitui` do arquivo novo é a forma de saber.\n- **Empresa do SPED:** o CNPJ é detectado do arquivo e precisa ser de uma empresa da conta; senão, 422 `empresa_nao_cadastrada` (cadastre com `criarEmpresa`). Se você informar `cnpj` ou `empresa_id` e não bater com o do arquivo, 409 `cnpj_divergente`.\n- **Empresa do ZIP de XML:** informe no multipart `cnpj` ou `empresa_id` de uma empresa da conta; o ZIP fica com essa empresa (`Arquivo.cnpj` é o informado). Ausente: 422 `parametro_invalido`; fora da conta: 422 `empresa_nao_cadastrada`. Nenhum XML é recusado pelo CNPJ de emitente ou destinatário: um ZIP de compras tem os fornecedores como emitentes, e um de vendas, os clientes como destinatários.\n- **Tamanho:** até 400 MB para SPED e 200 MB para ZIP de XML (20 MB no sandbox); acima, 413 `arquivo_grande_demais`. A API recusa pelo `Content-Length` antes de ler o corpo.\n- **ZIP de XML:** o conteúdo descompactado pode ter até 10 vezes o tamanho do ZIP e no máximo 2 GB, com até 50.000 XML; acima disso, 413 `arquivo_grande_demais`. ZIP que não abre ou com entrada de caminho relativo (`../`) ou absoluto é recusado com 422 `arquivo_corrompido`. Um ZIP por tipo de documento: o ZIP que mistura NF-e e CF-e termina `rejeitado` com o motivo `formato_nao_reconhecido`. O ZIP fica `lido` quando todos os XML foram processados; a contagem está em `xml` de `obterArquivo`. Os XML lidos ficam disponíveis para os relatórios que os usam, previstos para depois do lançamento.\n- **Recusas:** os envios recusados com 422 `empresa_nao_cadastrada` ou `arquivo_corrompido` contam na cota `recusas_envio_dia` (50 por dia por conta, número provisório); acima dela, 429 `limite_de_recusas_excedido` até o dia seguinte, com o `detail` dizendo qual recusa pesou. Os outros 422 não contam.\n\n**Quando não usar:** para NFC-e (modelo 65), que não faz parte da v1, ou para enviar vários arquivos de uma vez.",
        "tags": [
          "Arquivos"
        ],
        "security": [
          {
            "bearerAuth": [
              "arquivos:enviar"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/EnvioArquivo"
              },
              "examples": {
                "sped": {
                  "summary": "SPED da EFD Contribuições",
                  "description": "Envie o arquivo com `-F \"arquivo=@EFD-Contribuicoes-2026-09.txt\"`.",
                  "value": {
                    "arquivo": "(conteúdo binário do arquivo EFD-Contribuicoes-2026-09.txt)"
                  }
                },
                "zip_xml": {
                  "summary": "ZIP de XML de NF-e, com a empresa informada",
                  "description": "Envie com `-F \"arquivo=@NFe-2026-09.zip\" -F \"cnpj=11222333000181\"`.",
                  "value": {
                    "arquivo": "(conteúdo binário do arquivo NFe-2026-09.zip)",
                    "cnpj": "11222333000181"
                  }
                }
              },
              "encoding": {
                "arquivo": {
                  "contentType": "text/plain, application/zip"
                },
                "cnpj": {
                  "contentType": "text/plain"
                },
                "empresa_id": {
                  "contentType": "text/plain"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Esta conta já enviou o mesmo conteúdo: devolve o arquivo existente, com `duplicado: true`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Arquivo"
                },
                "examples": {
                  "duplicado": {
                    "summary": "Reenvio do mesmo arquivo pela mesma conta",
                    "value": {
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP",
                      "tipo": "efd_contribuicoes",
                      "cnpj": "11222333000181",
                      "competencia": "2026-09",
                      "periodo": {
                        "inicio": "2026-09-01",
                        "fim": "2026-09-30"
                      },
                      "estado": "lido",
                      "motivo": null,
                      "nome_original": "EFD-Contribuicoes-2026-09.txt",
                      "tamanho_bytes": 18452310,
                      "sha256": "9f2b5c7e1a3d4f60b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5",
                      "duplicado": true,
                      "enviado_por_esta_conta": true,
                      "substitui": null,
                      "xml": null,
                      "recebido_em": "2026-10-29T10:20:00-03:00",
                      "atualizado_em": "2026-10-29T10:24:41-03:00"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Arquivo recebido; a leitura segue em segundo plano.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "Location": {
                "description": "Endereço do trabalho assíncrono.",
                "schema": {
                  "type": "string"
                },
                "example": "/v1/jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Arquivo"
                },
                "examples": {
                  "recebido": {
                    "summary": "Arquivo recebido",
                    "value": {
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP",
                      "tipo": "efd_contribuicoes",
                      "cnpj": "11222333000181",
                      "competencia": "2026-09",
                      "periodo": {
                        "inicio": "2026-09-01",
                        "fim": "2026-09-30"
                      },
                      "estado": "recebido",
                      "motivo": null,
                      "nome_original": "EFD-Contribuicoes-2026-09.txt",
                      "tamanho_bytes": 18452310,
                      "sha256": "9f2b5c7e1a3d4f60b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5",
                      "duplicado": false,
                      "enviado_por_esta_conta": true,
                      "substitui": null,
                      "xml": null,
                      "recebido_em": "2026-10-29T10:20:00-03:00",
                      "atualizado_em": "2026-10-29T10:20:00-03:00"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "409": {
            "description": "CNPJ informado diferente do CNPJ do SPED, ou chamada com a mesma Idempotency-Key ainda em andamento.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "cnpj_divergente": {
                    "summary": "CNPJ informado não bate com o do SPED",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/cnpj_divergente",
                      "title": "CNPJ divergente",
                      "status": 409,
                      "detail": "O CNPJ informado (11222333000262) não é o CNPJ do SPED (11222333000181). Envie sem o campo cnpj ou com o CNPJ do arquivo.",
                      "instance": "/v1/arquivos",
                      "codigo": "cnpj_divergente",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "idempotencia_em_andamento": {
                    "summary": "Chamada em andamento",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/idempotencia_em_andamento",
                      "title": "Chamada em andamento",
                      "status": 409,
                      "detail": "Outra chamada com a mesma Idempotency-Key ainda está sendo processada.",
                      "instance": "/v1/arquivos",
                      "codigo": "idempotencia_em_andamento",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Arquivo grande demais, inclusive ZIP cujo conteúdo descompactado ou número de XML passa do limite.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "arquivo_grande_demais": {
                    "summary": "SPED acima de 400 MB",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/arquivo_grande_demais",
                      "title": "Arquivo grande demais",
                      "status": 413,
                      "detail": "O SPED tem mais de 400 MB.",
                      "instance": "/v1/arquivos",
                      "codigo": "arquivo_grande_demais",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "zip_descompactado_grande_demais": {
                    "summary": "ZIP que descompacta além do limite",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/arquivo_grande_demais",
                      "title": "Arquivo grande demais",
                      "status": 413,
                      "detail": "O conteúdo do ZIP passa de 10 vezes o tamanho do ZIP ou de 2 GB. Divida os XML em ZIPs menores.",
                      "instance": "/v1/arquivos",
                      "codigo": "arquivo_grande_demais",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Arquivo não reconhecido ou corrompido, empresa não cadastrada ou Idempotency-Key reutilizada.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "empresa_nao_cadastrada": {
                    "summary": "Empresa não cadastrada",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/empresa_nao_cadastrada",
                      "title": "Empresa não cadastrada",
                      "status": 422,
                      "detail": "O CNPJ do arquivo não está na sua conta. Cadastre-o com POST /empresas.",
                      "instance": "/v1/arquivos",
                      "codigo": "empresa_nao_cadastrada",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "zip_sem_empresa": {
                    "summary": "ZIP de XML sem a empresa informada",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                      "title": "Parâmetro inválido",
                      "status": 422,
                      "detail": "Para ZIP de XML, informe cnpj ou empresa_id de uma empresa da sua conta.",
                      "instance": "/v1/arquivos",
                      "codigo": "parametro_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "cnpj",
                          "mensagem": "Obrigatório para ZIP de XML (ou informe empresa_id)."
                        }
                      ]
                    }
                  },
                  "arquivo_corrompido": {
                    "summary": "ZIP com caminho proibido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/arquivo_corrompido",
                      "title": "Arquivo corrompido",
                      "status": 422,
                      "detail": "O ZIP tem uma entrada com caminho relativo ou absoluto. Gere o ZIP só com os XML, sem pastas fora dele.",
                      "instance": "/v1/arquivos",
                      "codigo": "arquivo_corrompido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "tipo_nao_suportado": {
                    "summary": "Tipo ainda não aceito",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/tipo_nao_suportado",
                      "title": "Tipo de arquivo não suportado",
                      "status": 422,
                      "detail": "ECD e ECF ainda não são aceitos.",
                      "instance": "/v1/arquivos",
                      "codigo": "tipo_nao_suportado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "idempotencia_conflito": {
                    "summary": "Idempotency-Key reutilizada com outro arquivo",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/idempotencia_conflito",
                      "title": "Idempotency-Key reutilizada com outro corpo",
                      "status": 422,
                      "detail": "Essa chave já foi usada com outro conteúdo.",
                      "instance": "/v1/arquivos",
                      "codigo": "idempotencia_conflito",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Limite por minuto ou limite diário de envios recusados excedidos.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "limite_de_recusas_excedido": {
                    "summary": "Muitos envios recusados hoje",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/limite_de_recusas_excedido",
                      "title": "Limite de recusas excedido",
                      "status": 429,
                      "detail": "A conta passou de 50 envios recusados hoje: 41 por empresa_nao_cadastrada e 9 por arquivo_corrompido. Cadastre as empresas com POST /empresas e envie de novo amanhã.",
                      "instance": "/v1/arquivos",
                      "codigo": "limite_de_recusas_excedido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "limite_excedido": {
                    "summary": "Limite por minuto",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/limite_excedido",
                      "title": "Limite excedido",
                      "status": 429,
                      "detail": "Você passou do limite de chamadas por minuto. Tente de novo em 23 segundos.",
                      "instance": "/v1/arquivos",
                      "codigo": "limite_excedido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL (1)",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/arquivos\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Idempotency-Key: 8f1c2d3e-4b5a-4c6d-9e7f-0a1b2c3d4e5f\" \\\n  -F \"arquivo=@EFD-Contribuicoes-2026-09.txt\""
          },
          {
            "lang": "shell",
            "label": "cURL (ZIP de XML)",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/arquivos\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Idempotency-Key: 2c4e6a8b-0d1f-4a3b-9c5d-7e9f1a2b3c4d\" \\\n  -F \"arquivo=@NFe-2026-09.zip\" \\\n  -F cnpj=11222333000181"
          },
          {
            "lang": "typescript",
            "label": "TypeScript (exemplo completo)",
            "source": "// Envia um SPED, espera o job de leitura (respeitando o Retry-After) e mostra o estado do arquivo.\n// Node 22.18 ou mais novo. Precisa de RMF_API_URL e RMF_API_KEY.\nimport { randomUUID } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\n\nconst BASE = process.env.RMF_API_URL;\nconst CHAVE = process.env.RMF_API_KEY;\nif (!BASE || !CHAVE) {\n  console.error(\"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\");\n  process.exit(2);\n}\nconst ARQUIVO = process.env.RMF_ARQUIVO ?? \"efd-contribuicoes-11222333000181-2025-01.txt\";\nconst TERMINAIS = [\"concluido\", \"concluido_sem_dados\", \"falhou\", \"cancelado\"];\n\nconst dormir = (s: number) => new Promise((r) => setTimeout(r, s * 1000));\n\nasync function chamar(caminho: string, init: RequestInit = {}): Promise<Response> {\n  for (let tentativa = 1; ; tentativa++) {\n    const resp = await fetch(`${BASE}${caminho}`, {\n      ...init,\n      headers: { Authorization: `Bearer ${CHAVE}`, ...init.headers },\n    });\n    // Nos GET, 429 espera o Retry-After e repete (o envio tem o seu laço, com a mesma Idempotency-Key).\n    if (resp.status === 429 && !init.body && tentativa < 5) {\n      await dormir(Number(resp.headers.get(\"Retry-After\") ?? \"1\"));\n      continue;\n    }\n    if (resp.status >= 400 && resp.status !== 429) {\n      const erro = await resp.json(); // application/problem+json\n      throw new Error(`${erro.codigo} (${erro.request_id}): ${erro.detail}`);\n    }\n    return resp;\n  }\n}\n\nasync function enviar(caminho: string): Promise<Response> {\n  // Um UUID por operação lógica; o MESMO UUID em todas as tentativas do envio.\n  const chave = randomUUID();\n  const conteudo = await readFile(caminho);\n  for (let tentativa = 1; tentativa <= 5; tentativa++) {\n    const form = new FormData();\n    form.append(\"arquivo\", new Blob([conteudo]), caminho.split(\"/\").pop());\n    const resp = await chamar(\"/arquivos\", { method: \"POST\", headers: { \"Idempotency-Key\": chave }, body: form });\n    if (resp.status !== 429) return resp;\n    await dormir(Number(resp.headers.get(\"Retry-After\") ?? \"1\"));\n  }\n  throw new Error(\"limite_excedido: tente mais tarde\");\n}\n\nconst resp = await enviar(ARQUIVO);\nconst arquivo = await resp.json();\n\n// 202: a leitura segue em segundo plano e o job vem em Location. 200: o mesmo conteúdo já foi enviado.\nconst location = resp.headers.get(\"Location\");\nlet estadoJob = \"-\";\nif (location) {\n  for (let i = 0; i < 90; i++) {\n    const job = await (await chamar(location.replace(/^\\/v1/, \"\"))).json();\n    estadoJob = job.estado;\n    if (job.estado === \"falhou\") throw new Error(`falhou: ${job.erro?.codigo}`);\n    if (TERMINAIS.includes(job.estado)) break;\n    await dormir(Number(resp.headers.get(\"Retry-After\") ?? \"2\"));\n  }\n}\n\nconst atual = await (await chamar(`/arquivos/${arquivo.id}`)).json();\nconsole.log(`arquivo=${arquivo.id} estado=${atual.estado} job=${estadoJob}`);\n"
          },
          {
            "lang": "python",
            "label": "Python (exemplo completo)",
            "source": "\"\"\"Envia um SPED, espera o job de leitura (respeitando o Retry-After) e mostra o estado do arquivo.\nPython 3.9 ou mais novo, com requests. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\"\"\"\nimport os\nimport sys\nimport time\nimport uuid\nfrom pathlib import Path\n\nimport requests\n\nBASE = os.environ.get(\"RMF_API_URL\")\nCHAVE = os.environ.get(\"RMF_API_KEY\")\nif not BASE or not CHAVE:\n    sys.exit(\"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\")\nARQUIVO = Path(os.environ.get(\"RMF_ARQUIVO\") or \"efd-contribuicoes-11222333000181-2025-01.txt\")\nTERMINAIS = {\"concluido\", \"concluido_sem_dados\", \"falhou\", \"cancelado\"}\nHEADERS = {\"Authorization\": f\"Bearer {CHAVE}\"}\n\n\ndef conferir(resp: requests.Response) -> requests.Response:\n    if resp.status_code >= 400:\n        erro = resp.json()  # application/problem+json\n        raise RuntimeError(f\"{erro['codigo']} ({erro['request_id']}): {erro.get('detail')}\")\n    return resp\n\n\ndef obter(caminho: str) -> requests.Response:\n    # GET com 429: espera o Retry-After e repete.\n    for _ in range(5):\n        resp = requests.get(BASE + caminho, headers=HEADERS, timeout=15)\n        if resp.status_code != 429:\n            return conferir(resp)\n        time.sleep(int(resp.headers.get(\"Retry-After\", \"1\")))\n    raise RuntimeError(\"limite_excedido: tente mais tarde\")\n\n\ndef enviar(caminho: Path) -> requests.Response:\n    # Um UUID por operação lógica; o MESMO UUID em todas as tentativas do envio.\n    chave = str(uuid.uuid4())\n    for _ in range(5):\n        with caminho.open(\"rb\") as f:\n            resp = requests.post(\n                f\"{BASE}/arquivos\",\n                headers={**HEADERS, \"Idempotency-Key\": chave},\n                files={\"arquivo\": (caminho.name, f)},\n                timeout=60,\n            )\n        if resp.status_code != 429:\n            return conferir(resp)\n        time.sleep(int(resp.headers.get(\"Retry-After\", \"1\")))\n    raise RuntimeError(\"limite_excedido: tente mais tarde\")\n\n\nresp = enviar(ARQUIVO)\narquivo = resp.json()\n\n# 202: a leitura segue em segundo plano e o job vem em Location. 200: o mesmo conteúdo já foi enviado.\nlocation = resp.headers.get(\"Location\")\nestado_job = \"-\"\nif location:\n    for _ in range(90):\n        r = obter(location.removeprefix(\"/v1\"))\n        job = r.json()\n        estado_job = job[\"estado\"]\n        if estado_job == \"falhou\":\n            raise RuntimeError(f\"falhou: {(job.get('erro') or {}).get('codigo')}\")\n        if estado_job in TERMINAIS:\n            break\n        time.sleep(int(r.headers.get(\"Retry-After\", \"2\")))\n\natual = obter(f\"/arquivos/{arquivo['id']}\").json()\nprint(f\"arquivo={arquivo['id']} estado={atual['estado']} job={estado_job}\")\n"
          },
          {
            "lang": "php",
            "label": "PHP (exemplo completo)",
            "source": "<?php\n// Envia um SPED, espera o job de leitura (respeitando o Retry-After) e mostra o estado do arquivo.\n// PHP 8.1 ou mais novo, extensão curl. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\n$base = getenv('RMF_API_URL');\n$chave = getenv('RMF_API_KEY');\nif (!$base || !$chave) {\n    fwrite(STDERR, \"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\\n\");\n    exit(2);\n}\n$arquivo = getenv('RMF_ARQUIVO') ?: 'efd-contribuicoes-11222333000181-2025-01.txt';\nconst TERMINAIS = ['concluido', 'concluido_sem_dados', 'falhou', 'cancelado'];\n\n/** GET com 429: espera o Retry-After e repete. Os outros métodos (o envio tem o seu laço) seguem direto. */\nfunction chamar(string $base, string $chave, string $metodo, string $caminho, array $extra = [], $corpo = null): array\n{\n    for ($tentativa = 1; ; $tentativa++) {\n        $r = chamarUma($base, $chave, $metodo, $caminho, $extra, $corpo);\n        if ($r[0] !== 429 || $metodo !== 'GET' || $tentativa >= 5) {\n            return $r;\n        }\n        sleep((int) ($r[2]['retry-after'] ?? 1));\n    }\n}\n\n/** Faz a chamada e devolve [status, corpo decodificado, cabeçalhos em minúsculas]. */\nfunction chamarUma(string $base, string $chave, string $metodo, string $caminho, array $extra = [], $corpo = null): array\n{\n    $cab = [];\n    $ch = curl_init($base . $caminho);\n    curl_setopt_array($ch, [\n        CURLOPT_CUSTOMREQUEST => $metodo,\n        CURLOPT_RETURNTRANSFER => true,\n        CURLOPT_HTTPHEADER => array_merge([\"Authorization: Bearer $chave\"], $extra),\n        CURLOPT_HEADERFUNCTION => function ($ch, $linha) use (&$cab) {\n            if (str_contains($linha, ':')) {\n                [$nome, $valor] = explode(':', $linha, 2);\n                $cab[strtolower(trim($nome))] = trim($valor);\n            }\n            return strlen($linha);\n        },\n    ]);\n    if ($corpo !== null) {\n        curl_setopt($ch, CURLOPT_POSTFIELDS, $corpo);\n    }\n    $texto = curl_exec($ch);\n    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\n    curl_close($ch);\n    $json = json_decode($texto, true);\n    if ($status >= 400 && $status !== 429) { // application/problem+json\n        throw new RuntimeException(\"{$json['codigo']} ({$json['request_id']}): {$json['detail']}\");\n    }\n    return [$status, $json, $cab];\n}\n\n// Um UUID por operação lógica; o MESMO UUID em todas as tentativas do envio.\n$b = random_bytes(16);\n$b[6] = chr((ord($b[6]) & 0x0f) | 0x40);\n$b[8] = chr((ord($b[8]) & 0x3f) | 0x80);\n$idempotencia = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));\n\nfor ($tentativa = 1; $tentativa <= 5; $tentativa++) {\n    [$status, $arq, $cab] = chamar($base, $chave, 'POST', '/arquivos', [\"Idempotency-Key: $idempotencia\"],\n        ['arquivo' => new CURLFile($arquivo, 'text/plain', basename($arquivo))]);\n    if ($status !== 429) {\n        break;\n    }\n    sleep((int) ($cab['retry-after'] ?? 1));\n}\nif ($status === 429) {\n    throw new RuntimeException('limite_excedido: tente mais tarde');\n}\n\n// 202: a leitura segue em segundo plano e o job vem em Location. 200: o mesmo conteúdo já foi enviado.\n$estadoJob = '-';\nif (isset($cab['location'])) {\n    for ($i = 0; $i < 90; $i++) {\n        [, $job, $cabJob] = chamar($base, $chave, 'GET', preg_replace('#^/v1#', '', $cab['location']));\n        $estadoJob = $job['estado'];\n        if ($estadoJob === 'falhou') {\n            throw new RuntimeException('falhou: ' . ($job['erro']['codigo'] ?? ''));\n        }\n        if (in_array($estadoJob, TERMINAIS, true)) {\n            break;\n        }\n        sleep((int) ($cabJob['retry-after'] ?? 2));\n    }\n}\n\n[, $atual] = chamar($base, $chave, 'GET', '/arquivos/' . $arq['id']);\necho \"arquivo={$arq['id']} estado={$atual['estado']} job=$estadoJob\", PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C# (exemplo completo)",
            "source": "// Envia um SPED, espera o job de leitura (respeitando o Retry-After) e mostra o estado do arquivo.\n// .NET 8 ou mais novo, top-level statements. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\nusing System.Net;\nusing System.Net.Http.Headers;\nusing System.Text.Json;\n\nvar baseUrl = Environment.GetEnvironmentVariable(\"RMF_API_URL\");\nvar chave = Environment.GetEnvironmentVariable(\"RMF_API_KEY\");\nif (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(chave))\n{\n    Console.Error.WriteLine(\"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\");\n    return 2;\n}\nvar arquivo = Environment.GetEnvironmentVariable(\"RMF_ARQUIVO\") ?? \"efd-contribuicoes-11222333000181-2025-01.txt\";\nstring[] terminais = { \"concluido\", \"concluido_sem_dados\", \"falhou\", \"cancelado\" };\n\nvar http = new HttpClient { BaseAddress = new Uri(baseUrl.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", chave);\n\n// Um UUID por operação lógica; o MESMO UUID em todas as tentativas do envio.\nvar idempotencia = Guid.NewGuid().ToString();\nHttpResponseMessage resp = null!;\nfor (var tentativa = 1; tentativa <= 5; tentativa++)\n{\n    using var form = new MultipartFormDataContent();\n    form.Add(new ByteArrayContent(await File.ReadAllBytesAsync(arquivo)), \"arquivo\", Path.GetFileName(arquivo));\n    using var req = new HttpRequestMessage(HttpMethod.Post, \"arquivos\") { Content = form };\n    req.Headers.Add(\"Idempotency-Key\", idempotencia);\n    resp = await http.SendAsync(req);\n    if (resp.StatusCode != HttpStatusCode.TooManyRequests) break;\n    await Task.Delay(resp.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1));\n}\nvar arq = await LerJson(resp);\n\n// 202: a leitura segue em segundo plano e o job vem em Location. 200: o mesmo conteúdo já foi enviado.\nvar estadoJob = \"-\";\nif (resp.Headers.Location is { } location)\n{\n    for (var i = 0; i < 90; i++)\n    {\n        using var r = await Obter(location.ToString().Replace(\"/v1/\", \"\"));\n        var job = await LerJson(r);\n        estadoJob = job.GetProperty(\"estado\").GetString();\n        if (estadoJob == \"falhou\") throw new Exception($\"falhou: {job.GetProperty(\"erro\").GetProperty(\"codigo\")}\");\n        if (terminais.Contains(estadoJob)) break;\n        await Task.Delay(r.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(2));\n    }\n}\n\nusing var ra = await Obter($\"arquivos/{arq.GetProperty(\"id\").GetString()}\");\nvar atual = await LerJson(ra);\nConsole.WriteLine($\"arquivo={arq.GetProperty(\"id\").GetString()} estado={atual.GetProperty(\"estado\").GetString()} job={estadoJob}\");\nreturn 0;\n\nasync Task<HttpResponseMessage> Obter(string caminho) // GET com 429: espera o Retry-After e repete\n{\n    for (var tentativa = 1; tentativa <= 5; tentativa++)\n    {\n        var r = await http.GetAsync(caminho);\n        if (r.StatusCode != HttpStatusCode.TooManyRequests) return r;\n        await Task.Delay(r.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1));\n        r.Dispose();\n    }\n    throw new Exception(\"limite_excedido: tente mais tarde\");\n}\n\nasync Task<JsonElement> LerJson(HttpResponseMessage r)\n{\n    using var doc = JsonDocument.Parse(await r.Content.ReadAsStringAsync());\n    var e = doc.RootElement.Clone();\n    if ((int)r.StatusCode >= 400) // application/problem+json\n        throw new Exception($\"{e.GetProperty(\"codigo\")} ({e.GetProperty(\"request_id\")}): {e.GetProperty(\"detail\")}\");\n    return e;\n}\n"
          },
          {
            "lang": "java",
            "label": "Java (exemplo completo)",
            "source": "// Envia um SPED, espera o job de leitura (respeitando o Retry-After) e mostra o estado do arquivo.\n// Java 17 ou mais novo, com Jackson (com.fasterxml.jackson.core:jackson-databind).\n// Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport java.io.ByteArrayOutputStream;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.List;\nimport java.util.UUID;\n\npublic class Envio {\n    static final String BASE = System.getenv(\"RMF_API_URL\");\n    static final String CHAVE = System.getenv(\"RMF_API_KEY\");\n    static final List<String> TERMINAIS = List.of(\"concluido\", \"concluido_sem_dados\", \"falhou\", \"cancelado\");\n    static final HttpClient HTTP = HttpClient.newHttpClient();\n    static final ObjectMapper JSON = new ObjectMapper();\n\n    static HttpResponse<String> conferir(HttpResponse<String> resp) throws Exception {\n        if (resp.statusCode() >= 400) { // application/problem+json\n            var e = JSON.readTree(resp.body());\n            throw new RuntimeException(e.path(\"codigo\").asText() + \" (\" + e.path(\"request_id\").asText()\n                    + \"): \" + e.path(\"detail\").asText());\n        }\n        return resp;\n    }\n\n    static HttpResponse<String> get(String caminho) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(BASE + caminho))\n                .header(\"Authorization\", \"Bearer \" + CHAVE).GET().build();\n        for (int tentativa = 1; tentativa <= 5; tentativa++) {\n            var resp = HTTP.send(req, HttpResponse.BodyHandlers.ofString());\n            if (resp.statusCode() != 429) return conferir(resp);\n            Thread.sleep(espera(resp, 1) * 1000); // 429: espera o Retry-After e repete\n        }\n        throw new RuntimeException(\"limite_excedido: tente mais tarde\");\n    }\n\n    static long espera(HttpResponse<?> resp, long padrao) {\n        return Long.parseLong(resp.headers().firstValue(\"Retry-After\").orElse(String.valueOf(padrao)));\n    }\n\n    static HttpResponse<String> enviar(Path arquivo) throws Exception {\n        // Um UUID por operação lógica; o MESMO UUID em todas as tentativas do envio.\n        var idempotencia = UUID.randomUUID().toString();\n        var limite = \"rmf\" + UUID.randomUUID().toString().replace(\"-\", \"\");\n        var corpo = new ByteArrayOutputStream();\n        corpo.write((\"--\" + limite + \"\\r\\nContent-Disposition: form-data; name=\\\"arquivo\\\"; filename=\\\"\"\n                + arquivo.getFileName() + \"\\\"\\r\\nContent-Type: text/plain\\r\\n\\r\\n\").getBytes(StandardCharsets.UTF_8));\n        corpo.write(Files.readAllBytes(arquivo));\n        corpo.write((\"\\r\\n--\" + limite + \"--\\r\\n\").getBytes(StandardCharsets.UTF_8));\n        for (int tentativa = 1; tentativa <= 5; tentativa++) {\n            var req = HttpRequest.newBuilder(URI.create(BASE + \"/arquivos\"))\n                    .header(\"Authorization\", \"Bearer \" + CHAVE)\n                    .header(\"Idempotency-Key\", idempotencia)\n                    .header(\"Content-Type\", \"multipart/form-data; boundary=\" + limite)\n                    .POST(HttpRequest.BodyPublishers.ofByteArray(corpo.toByteArray())).build();\n            var resp = HTTP.send(req, HttpResponse.BodyHandlers.ofString());\n            if (resp.statusCode() != 429) return conferir(resp);\n            Thread.sleep(espera(resp, 1) * 1000);\n        }\n        throw new RuntimeException(\"limite_excedido: tente mais tarde\");\n    }\n\n    public static void main(String[] args) throws Exception {\n        if (BASE == null || CHAVE == null) {\n            System.err.println(\"defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\");\n            System.exit(2);\n        }\n        var caminho = Path.of(System.getenv().getOrDefault(\"RMF_ARQUIVO\", \"efd-contribuicoes-11222333000181-2025-01.txt\"));\n        var resp = enviar(caminho);\n        JsonNode arquivo = JSON.readTree(resp.body());\n\n        // 202: a leitura segue em segundo plano e o job vem em Location. 200: o mesmo conteúdo já foi enviado.\n        var estadoJob = \"-\";\n        var location = resp.headers().firstValue(\"Location\");\n        if (location.isPresent()) {\n            for (int i = 0; i < 90; i++) {\n                var r = get(location.get().replaceFirst(\"^/v1\", \"\"));\n                var job = JSON.readTree(r.body());\n                estadoJob = job.path(\"estado\").asText();\n                if (estadoJob.equals(\"falhou\")) {\n                    throw new RuntimeException(\"falhou: \" + job.path(\"erro\").path(\"codigo\").asText());\n                }\n                if (TERMINAIS.contains(estadoJob)) break;\n                Thread.sleep(espera(r, 2) * 1000);\n            }\n        }\n\n        var atual = JSON.readTree(get(\"/arquivos/\" + arquivo.path(\"id\").asText()).body());\n        System.out.println(\"arquivo=\" + arquivo.path(\"id\").asText() + \" estado=\" + atual.path(\"estado\").asText()\n                + \" job=\" + estadoJob);\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi (exemplo completo)",
            "source": "program Envio;\n// Envia um SPED, espera o job de leitura (respeitando o Retry-After) e mostra o estado do arquivo.\n// Delphi 10.3 ou mais novo. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.\n// Exemplo escrito e revisado à mão: não roda no CI (não existe compilador Delphi em container).\n\n{$APPTYPE CONSOLE}\n\nuses\n  System.SysUtils, System.Classes, System.JSON, System.Net.HttpClient, System.Net.URLClient,\n  System.Net.Mime;\n\nvar\n  Base, Chave, Caminho: string;\n  Http: THTTPClient;\n\nprocedure Conferir(const AResp: IHTTPResponse);\nvar\n  Erro: TJSONObject;\nbegin\n  if AResp.StatusCode < 400 then Exit;\n  Erro := TJSONObject.ParseJSONValue(AResp.ContentAsString(TEncoding.UTF8)) as TJSONObject; // problem+json\n  try\n    raise Exception.CreateFmt('%s (%s): %s', [Erro.GetValue<string>('codigo'),\n      Erro.GetValue<string>('request_id'), Erro.GetValue<string>('detail')]);\n  finally\n    Erro.Free;\n  end;\nend;\n\nfunction Enviar(const ACaminho: string): IHTTPResponse;\nvar\n  Idempotencia: string;\n  Form: TMultipartFormData;\n  Tentativa: Integer;\nbegin\n  // Um UUID por operação lógica; o MESMO UUID em todas as tentativas do envio.\n  Idempotencia := GUIDToString(TGUID.NewGuid).Trim(['{', '}']).ToLower;\n  for Tentativa := 1 to 5 do\n  begin\n    Form := TMultipartFormData.Create;\n    try\n      Form.AddFile('arquivo', ACaminho, 'text/plain');\n      Result := Http.Post(Base + '/arquivos', Form, nil,\n        [TNetHeader.Create('Idempotency-Key', Idempotencia)]);\n    finally\n      Form.Free;\n    end;\n    if Result.StatusCode <> 429 then\n    begin\n      Conferir(Result);\n      Exit;\n    end;\n    Sleep(StrToIntDef(Result.HeaderValue['Retry-After'], 1) * 1000);\n  end;\n  raise Exception.Create('limite_excedido: tente mais tarde');\nend;\n\nfunction Obter(const ACaminho: string; out AResp: IHTTPResponse): TJSONObject;\nvar\n  Tentativa: Integer;\nbegin\n  for Tentativa := 1 to 5 do\n  begin\n    AResp := Http.Get(Base + ACaminho);\n    if AResp.StatusCode <> 429 then Break;\n    Sleep(StrToIntDef(AResp.HeaderValue['Retry-After'], 1) * 1000); // 429: espera o Retry-After e repete\n  end;\n  Conferir(AResp);\n  Result := TJSONObject.ParseJSONValue(AResp.ContentAsString(TEncoding.UTF8)) as TJSONObject;\nend;\n\nvar\n  Resp, RJob: IHTTPResponse;\n  Arquivo, Job, Atual: TJSONObject;\n  Location, EstadoJob, ArquivoId: string;\n  I: Integer;\nbegin\n  Base := GetEnvironmentVariable('RMF_API_URL');\n  Chave := GetEnvironmentVariable('RMF_API_KEY');\n  if (Base = '') or (Chave = '') then\n  begin\n    Writeln(ErrOutput, 'defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY');\n    Halt(2);\n  end;\n  Caminho := GetEnvironmentVariable('RMF_ARQUIVO');\n  if Caminho = '' then Caminho := 'efd-contribuicoes-11222333000181-2025-01.txt';\n\n  Http := THTTPClient.Create;\n  try\n    Http.CustomHeaders['Authorization'] := 'Bearer ' + Chave;\n    Resp := Enviar(Caminho);\n    Arquivo := TJSONObject.ParseJSONValue(Resp.ContentAsString(TEncoding.UTF8)) as TJSONObject;\n    try\n      ArquivoId := Arquivo.GetValue<string>('id');\n      // 202: a leitura segue em segundo plano e o job vem em Location. 200: o mesmo conteúdo já foi enviado.\n      Location := Resp.HeaderValue['Location'];\n      EstadoJob := '-';\n      if Location <> '' then\n        for I := 1 to 90 do\n        begin\n          Job := Obter(Location.Replace('/v1', '', []), RJob);\n          try\n            EstadoJob := Job.GetValue<string>('estado');\n            if EstadoJob = 'falhou' then\n              raise Exception.Create('falhou: ' + Job.GetValue<TJSONObject>('erro').GetValue<string>('codigo'));\n          finally\n            Job.Free;\n          end;\n          if (EstadoJob = 'concluido') or (EstadoJob = 'concluido_sem_dados') or (EstadoJob = 'cancelado') then\n            Break;\n          Sleep(StrToIntDef(RJob.HeaderValue['Retry-After'], 2) * 1000);\n        end;\n\n      Atual := Obter('/arquivos/' + ArquivoId, Resp);\n      try\n        Writeln('arquivo=', ArquivoId, ' estado=', Atual.GetValue<string>('estado'), ' job=', EstadoJob);\n      finally\n        Atual.Free;\n      end;\n    finally\n      Arquivo.Free;\n    end;\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      },
      "get": {
        "operationId": "listarArquivos",
        "summary": "Listar arquivos",
        "description": "Use para listar os arquivos das empresas da conta, com filtros por empresa, competência, tipo e estado, em páginas. No v1 a lista mostra os arquivos de cada empresa que está no escopo da conta, inclusive os enviados por outra conta que também a tem; `enviado_por_esta_conta` diz quem enviou, sem identificar a outra conta. `obterArquivo` segue a mesma regra.\n\n**Quando não usar:** para ler o estado de um arquivo que você acabou de enviar (use `obterArquivo`).",
        "tags": [
          "Arquivos"
        ],
        "security": [
          {
            "bearerAuth": [
              "arquivos:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "cnpj",
            "in": "query",
            "description": "Só arquivos desta empresa.",
            "schema": {
              "$ref": "#/components/schemas/Cnpj"
            },
            "example": "11222333000181"
          },
          {
            "name": "competencia",
            "in": "query",
            "description": "Só arquivos desta competência. Inclui os ZIP de XML cujo `periodo` contém o mês.",
            "schema": {
              "$ref": "#/components/schemas/Competencia"
            },
            "example": "2026-09"
          },
          {
            "name": "tipo",
            "in": "query",
            "description": "Só arquivos deste tipo.",
            "schema": {
              "$ref": "#/components/schemas/TipoArquivo"
            },
            "example": "efd_contribuicoes"
          },
          {
            "name": "estado",
            "in": "query",
            "description": "Só arquivos neste estado.",
            "schema": {
              "$ref": "#/components/schemas/EstadoArquivo"
            },
            "example": "lido"
          },
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de arquivos.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaArquivos"
                },
                "examples": {
                  "lista": {
                    "summary": "Um arquivo lido",
                    "value": {
                      "dados": [
                        {
                          "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP",
                          "tipo": "efd_contribuicoes",
                          "cnpj": "11222333000181",
                          "competencia": "2026-09",
                          "periodo": {
                            "inicio": "2026-09-01",
                            "fim": "2026-09-30"
                          },
                          "estado": "lido",
                          "motivo": null,
                          "nome_original": "EFD-Contribuicoes-2026-09.txt",
                          "tamanho_bytes": 18452310,
                          "sha256": "9f2b5c7e1a3d4f60b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5",
                          "duplicado": false,
                          "enviado_por_esta_conta": true,
                          "substitui": null,
                          "xml": null,
                          "recebido_em": "2026-10-29T10:20:00-03:00",
                          "atualizado_em": "2026-10-29T10:24:41-03:00"
                        }
                      ],
                      "proximo_cursor": null
                    }
                  },
                  "vazia": {
                    "summary": "Nenhum arquivo",
                    "value": {
                      "dados": [],
                      "proximo_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "$ref": "#/components/responses/NaoProcessavel"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/arquivos?cnpj=11222333000181&competencia=2026-09&estado=lido\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/arquivos`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/arquivos\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/arquivos');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"arquivos\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/arquivos\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/arquivos';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/arquivos/{id}": {
      "get": {
        "operationId": "obterArquivo",
        "summary": "Obter arquivo e estado da leitura",
        "description": "Use para acompanhar um arquivo enviado: estado da leitura e, se foi recusado, o `motivo`. Consulte até um estado terminal ou use os webhooks. Se a leitura parar do nosso lado, ela é retomada de onde parou; não é preciso reenviar.\n\n**Eventos e estados terminais:** `lido` e `periodo_ja_lido` disparam `arquivo.lido`, com o `estado` no corpo; o trabalho de leitura fica `concluido` no primeiro caso e `concluido_sem_dados` no segundo. `rejeitado` dispara `arquivo.rejeitado` (trabalho `falhou`). `substituido` não dispara evento: o arquivo novo traz o antigo em `substitui`.\n\n**ZIP de XML:** o campo `xml` traz a contagem de XML lidos, duplicados (chave já conhecida pela RMF, inclusive por outro envio) e com erro. O ZIP fica `lido`, e o evento `arquivo.lido` dispara, quando todos os XML foram processados (`lidos + duplicados + com_erro = total`), mesmo que alguns tenham erro. Se nenhum XML pôde ser lido, o arquivo vai para `rejeitado` com o motivo `nenhum_xml_valido` e dispara `arquivo.rejeitado`; ZIP que mistura NF-e e CF-e vai para `rejeitado` com `formato_nao_reconhecido`. Os XML lidos ficam disponíveis para os relatórios que os usam, previstos para depois do lançamento.\n\n**Quando não usar:** para listar vários arquivos (use `listarArquivos`). Arquivo que não é de uma empresa da sua conta responde 404.",
        "tags": [
          "Arquivos"
        ],
        "security": [
          {
            "bearerAuth": [
              "arquivos:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Identificador do arquivo.",
            "schema": {
              "$ref": "#/components/schemas/ArquivoId"
            },
            "required": true,
            "example": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP"
          }
        ],
        "responses": {
          "200": {
            "description": "Arquivo.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Arquivo"
                },
                "examples": {
                  "lido": {
                    "summary": "SPED lido",
                    "value": {
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP",
                      "tipo": "efd_contribuicoes",
                      "cnpj": "11222333000181",
                      "competencia": "2026-09",
                      "periodo": {
                        "inicio": "2026-09-01",
                        "fim": "2026-09-30"
                      },
                      "estado": "lido",
                      "motivo": null,
                      "nome_original": "EFD-Contribuicoes-2026-09.txt",
                      "tamanho_bytes": 18452310,
                      "sha256": "9f2b5c7e1a3d4f60b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5",
                      "duplicado": false,
                      "enviado_por_esta_conta": true,
                      "substitui": null,
                      "xml": null,
                      "recebido_em": "2026-10-29T10:20:00-03:00",
                      "atualizado_em": "2026-10-29T10:24:41-03:00"
                    }
                  },
                  "periodo_ja_lido": {
                    "summary": "SPED de um período que outra conta já tinha enviado",
                    "description": "Esta conta enviou o SPED de um período que já estava lido, porque outra conta que também tem a empresa tinha enviado o mesmo SPED antes. A deduplicação é por conta, então o envio criou um arquivo novo, que termina em `periodo_ja_lido` e não é usado. Valores ilustrativos.",
                    "value": {
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNS",
                      "tipo": "efd_contribuicoes",
                      "cnpj": "11222333000181",
                      "competencia": "2026-09",
                      "periodo": {
                        "inicio": "2026-09-01",
                        "fim": "2026-09-30"
                      },
                      "estado": "periodo_ja_lido",
                      "motivo": null,
                      "nome_original": "EFDC_092026.txt",
                      "tamanho_bytes": 18452310,
                      "sha256": "9f2b5c7e1a3d4f60b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5",
                      "duplicado": false,
                      "enviado_por_esta_conta": true,
                      "substitui": null,
                      "xml": null,
                      "recebido_em": "2026-10-30T09:00:00-03:00",
                      "atualizado_em": "2026-10-30T09:03:10-03:00"
                    }
                  },
                  "zip_lendo": {
                    "summary": "ZIP de XML em leitura",
                    "description": "ZIP enviado com `cnpj=11222333000181`: o `cnpj` é o informado no envio, e nenhum XML é recusado pelo CNPJ de emitente ou destinatário. Valores ilustrativos.",
                    "value": {
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNR",
                      "tipo": "nfe_xml",
                      "cnpj": "11222333000181",
                      "competencia": null,
                      "periodo": {
                        "inicio": "2026-09-01",
                        "fim": "2026-09-30"
                      },
                      "estado": "lendo",
                      "motivo": null,
                      "nome_original": "NFe-2026-09.zip",
                      "tamanho_bytes": 52428800,
                      "sha256": "4a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9",
                      "duplicado": false,
                      "enviado_por_esta_conta": true,
                      "substitui": null,
                      "xml": {
                        "total": 1840,
                        "lidos": 1200,
                        "duplicados": 30,
                        "com_erro": 2
                      },
                      "recebido_em": "2026-10-29T10:30:00-03:00",
                      "atualizado_em": "2026-10-29T10:41:00-03:00"
                    }
                  },
                  "zip_lido": {
                    "summary": "ZIP de XML lido, com alguns XML com erro",
                    "description": "ZIP enviado com `cnpj=11222333000181` (o `cnpj` é o informado). Todos os XML foram processados (1795 + 41 + 4 = 1840): o ZIP fica `lido` e o evento `arquivo.lido` dispara. Os 41 duplicados são XML de chave já conhecida pela RMF, inclusive por outro envio. Valores ilustrativos.",
                    "value": {
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNR",
                      "tipo": "nfe_xml",
                      "cnpj": "11222333000181",
                      "competencia": null,
                      "periodo": {
                        "inicio": "2026-09-01",
                        "fim": "2026-09-30"
                      },
                      "estado": "lido",
                      "motivo": null,
                      "nome_original": "NFe-2026-09.zip",
                      "tamanho_bytes": 52428800,
                      "sha256": "4a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9",
                      "duplicado": false,
                      "enviado_por_esta_conta": true,
                      "substitui": null,
                      "xml": {
                        "total": 1840,
                        "lidos": 1795,
                        "duplicados": 41,
                        "com_erro": 4
                      },
                      "recebido_em": "2026-10-29T10:30:00-03:00",
                      "atualizado_em": "2026-10-29T10:52:30-03:00"
                    }
                  },
                  "rejeitado": {
                    "summary": "Arquivo rejeitado",
                    "value": {
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP",
                      "tipo": "efd_contribuicoes",
                      "cnpj": "11222333000181",
                      "competencia": "2026-09",
                      "periodo": {
                        "inicio": "2026-09-01",
                        "fim": "2026-09-30"
                      },
                      "estado": "rejeitado",
                      "motivo": {
                        "codigo": "sem_movimento",
                        "mensagem": "O arquivo não tem movimento no período. Nada a fazer."
                      },
                      "nome_original": "EFD-Contribuicoes-2026-09.txt",
                      "tamanho_bytes": 18452310,
                      "sha256": "9f2b5c7e1a3d4f60b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5",
                      "duplicado": false,
                      "enviado_por_esta_conta": true,
                      "substitui": null,
                      "xml": null,
                      "recebido_em": "2026-10-29T10:20:00-03:00",
                      "atualizado_em": "2026-10-29T10:24:41-03:00"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Arquivo não encontrado",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Arquivo não encontrado",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Arquivo não encontrado",
                      "status": 404,
                      "detail": "Não há arquivo com este identificador nas empresas da sua conta.",
                      "instance": "/v1/arquivos/arq_01JC8F4P0D5S3Y8E2G6H7JKMNP",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/arquivos/arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/arquivos/arq_01JC8F4P0D5S3Y8E2G6H7JKMNP`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/arquivos/arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/arquivos/arq_01JC8F4P0D5S3Y8E2G6H7JKMNP');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"arquivos/arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/arquivos/arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/arquivos/arq_01JC8F4P0D5S3Y8E2G6H7JKMNP';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/relatorios/tipos": {
      "get": {
        "operationId": "listarTiposRelatorio",
        "summary": "Catálogo de tipos de relatório",
        "description": "Use para descobrir quais relatórios existem, de que arquivos cada um precisa, quais opções aceita, e se já está disponível para a sua conta. Consulte antes de `criarRelatorio`, em vez de fixar a lista no código: ela cresce a cada fase e depende do plano.\n\nNenhum tipo do lançamento v1 lê XML: os XML lidos (ZIP de NF-e e CF-e) ficam disponíveis para os relatórios que os usam, previstos para depois do lançamento.\n\nNo v1 o catálogo traz só os tipos do lançamento que o plano da conta permite. Os campos `tese` e `arquivos_necessarios` do exemplo são ilustrativos.\n\n**Quando não usar:** para ler um relatório já criado (use `obterRelatorio`).",
        "tags": [
          "Relatórios"
        ],
        "security": [
          {
            "bearerAuth": [
              "relatorios:ler"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo de tipos.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaTiposRelatorio"
                },
                "examples": {
                  "catalogo": {
                    "summary": "Tipos do catálogo",
                    "description": "Valores ilustrativos: não vêm de um cálculo real e não devem ser lidos como resultado fiscal. A situação da tese e a descrição de cada tipo dependem de validação jurídica; a `descricao` do `icms_st` é ilustrativa até o texto do jurídico. O plano desta conta não inclui a planilha de resultado, por isso `planilha_sped` não aparece.",
                    "value": {
                      "dados": [
                        {
                          "tipo": "icms_st",
                          "nome": "Crédito de ICMS-ST",
                          "descricao": "Crédito de PIS e COFINS sobre o ICMS-ST das compras, por competência, com a alíquota de ICMS da base de alíquotas da RMF.",
                          "arquivos_necessarios": [
                            "efd_contribuicoes"
                          ],
                          "arquivos_exigencia": "todos",
                          "parametros": [
                            {
                              "nome": "periodicidade_saida",
                              "tipo": "enum",
                              "obrigatorio": false,
                              "valores": [
                                "mensal",
                                "anual"
                              ],
                              "descricao": "Como a planilha agrupa o resultado."
                            }
                          ],
                          "tese": "pendente",
                          "disponivel": true,
                          "motivo_indisponivel": null
                        },
                        {
                          "tipo": "diagnostico_federal",
                          "nome": "Diagnóstico federal",
                          "descricao": "Ocorrências de inconsistência de PIS e COFINS no período, com a contagem por ocorrência. As ocorrências de revisão manual ficam fora da API.",
                          "arquivos_necessarios": [
                            "efd_icms_ipi",
                            "efd_contribuicoes"
                          ],
                          "arquivos_exigencia": "todos",
                          "parametros": [],
                          "tese": "nao_se_aplica",
                          "disponivel": true,
                          "motivo_indisponivel": null
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/relatorios/tipos\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/relatorios/tipos`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/relatorios/tipos\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/relatorios/tipos');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"relatorios/tipos\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/relatorios/tipos\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/relatorios/tipos';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/relatorios": {
      "post": {
        "operationId": "criarRelatorio",
        "summary": "Criar relatório",
        "description": "Use para pedir um relatório de um tipo do catálogo. A resposta é 202 com o relatório (`rel_`) e o trabalho (`job_`); `Location` aponta para o trabalho. Acompanhe por `obterRelatorio`, `obterJob` ou pelos eventos `relatorio.concluido` e `relatorio.falhou`.\n\nO pedido tem duas formas:\n\n- **Pela empresa e período:** `{tipo, cnpj, periodo}` ou `{tipo, empresa_id, periodo}`. No v1 os relatórios só aceitam empresa com CNPJ numérico (14 dígitos); um CNPJ alfanumérico responde 422 `parametro_invalido` (campo `cnpj`). Os arquivos das competências pedidas precisam estar lidos (`lido`); se faltar algum, o relatório falha com `arquivo_faltando`. Confira a cobertura em `obterEmpresa` antes.\n- **Pelo id do arquivo:** `{tipo, arquivo_id}`, com `periodo` opcional. Sem `periodo`, vale o período do arquivo; com ele, o período precisa caber no do arquivo (senão, 422 `periodo_invalido`). Quando o tipo precisa de outros arquivos além desse, ele usa os arquivos lidos da mesma empresa no período. Erros próprios desta forma: 404 `arquivo_nao_encontrado` (o arquivo não existe ou não é de uma empresa da sua conta), 409 `arquivo_nao_processado` (o arquivo ainda não está `lido`; espere o evento `arquivo.lido` ou, se ele foi rejeitado, envie um arquivo corrigido) e 422 `tipo_incompativel_com_arquivo` (o tipo não usa esse tipo de arquivo; veja `arquivos_necessarios` no catálogo).\n\nUm relatório sem nenhum dado no período termina em `concluido_sem_dados`. Quando concluído, ele traz o resumo em JSON e, se o plano da conta incluir, a planilha de resultado.\n\n**Quando não usar:** para tipos que não estão no catálogo da sua conta (422 `tipo_indisponivel`; o `detail` diz o motivo) ou para repetir uma chamada que já foi feita: reenvie com a mesma `Idempotency-Key` em vez de criar outro.",
        "tags": [
          "Relatórios"
        ],
        "security": [
          {
            "bearerAuth": [
              "relatorios:criar"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CriarRelatorioRequisicao"
              },
              "examples": {
                "icms_st": {
                  "summary": "ICMS-ST de janeiro a setembro de 2026",
                  "value": {
                    "tipo": "icms_st",
                    "cnpj": "11222333000181",
                    "periodo": {
                      "inicio": "2026-01",
                      "fim": "2026-09"
                    },
                    "opcoes": {
                      "periodicidade_saida": "mensal"
                    }
                  }
                },
                "planilha_por_empresa_id": {
                  "summary": "Planilha do SPED, pelo id da empresa",
                  "value": {
                    "tipo": "planilha_sped",
                    "empresa_id": "emp_01JC8F3Z6Q2W9X4B7N5M1RTHKB",
                    "periodo": {
                      "inicio": "2026-01",
                      "fim": "2026-09"
                    }
                  }
                },
                "diagnostico_por_arquivo": {
                  "summary": "Diagnóstico federal, pelo id do arquivo",
                  "description": "Sem `periodo`, vale o período do arquivo (setembro de 2026).",
                  "value": {
                    "tipo": "diagnostico_federal",
                    "arquivo_id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Relatório aceito.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "Location": {
                "description": "Endereço do trabalho assíncrono.",
                "schema": {
                  "type": "string"
                },
                "example": "/v1/jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Relatorio"
                },
                "examples": {
                  "criado": {
                    "summary": "Relatório na fila",
                    "value": {
                      "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK",
                      "job_id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                      "tipo": "icms_st",
                      "cnpj": "11222333000181",
                      "periodo": {
                        "inicio": "2026-01",
                        "fim": "2026-09"
                      },
                      "opcoes": {
                        "periodicidade_saida": "mensal"
                      },
                      "estado": "na_fila",
                      "criado_em": "2026-10-29T11:30:00-03:00",
                      "concluido_em": null,
                      "arquivos": [],
                      "avisos": [],
                      "erro": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Empresa ou arquivo não encontrado (não existe ou não é de uma empresa da sua conta).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Empresa não encontrada",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Empresa não encontrada",
                      "status": 404,
                      "detail": "Não há empresa com este CNPJ na sua conta. Cadastre-a com POST /empresas.",
                      "instance": "/v1/relatorios",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "arquivo_nao_encontrado": {
                    "summary": "Arquivo não encontrado",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/arquivo_nao_encontrado",
                      "title": "Arquivo não encontrado",
                      "status": 404,
                      "detail": "Não há arquivo com este identificador na sua conta. Confira o arquivo_id em GET /arquivos.",
                      "instance": "/v1/relatorios",
                      "codigo": "arquivo_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Arquivo ainda não lido ou chamada com a mesma Idempotency-Key em andamento.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "arquivo_nao_processado": {
                    "summary": "Arquivo ainda não lido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/arquivo_nao_processado",
                      "title": "Arquivo ainda não lido",
                      "status": 409,
                      "detail": "O arquivo ainda está em leitura. Espere o evento arquivo.lido ou consulte GET /arquivos/{id} e peça o relatório de novo.",
                      "instance": "/v1/relatorios",
                      "codigo": "arquivo_nao_processado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "idempotencia_em_andamento": {
                    "summary": "Chamada em andamento",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/idempotencia_em_andamento",
                      "title": "Chamada em andamento",
                      "status": 409,
                      "detail": "Outra chamada com a mesma Idempotency-Key ainda está sendo processada. Tente de novo em instantes.",
                      "instance": "/v1/relatorios",
                      "codigo": "idempotencia_em_andamento",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Tipo indisponível, tipo que não usa o arquivo informado, período inválido, opção desconhecida ou Idempotency-Key reutilizada.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "tipo_incompativel_com_arquivo": {
                    "summary": "Tipo não usa este arquivo",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/tipo_incompativel_com_arquivo",
                      "title": "Tipo incompatível com o arquivo",
                      "status": 422,
                      "detail": "O relatório planilha_sped não usa ZIP de XML. Veja arquivos_necessarios em GET /relatorios/tipos e informe um SPED.",
                      "instance": "/v1/relatorios",
                      "codigo": "tipo_incompativel_com_arquivo",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "arquivo_id",
                          "mensagem": "Informe um arquivo de um dos tipos de arquivos_necessarios."
                        }
                      ]
                    }
                  },
                  "tipo_indisponivel": {
                    "summary": "Tipo indisponível",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/tipo_indisponivel",
                      "title": "Tipo indisponível",
                      "status": 422,
                      "detail": "O tipo exclusao_iss ainda não está disponível para a sua conta.",
                      "instance": "/v1/relatorios",
                      "codigo": "tipo_indisponivel",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "tipo",
                          "mensagem": "Escolha um tipo de GET /relatorios/tipos."
                        }
                      ]
                    }
                  },
                  "idempotencia_conflito": {
                    "summary": "Idempotency-Key reutilizada com outro corpo",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/idempotencia_conflito",
                      "title": "Idempotency-Key reutilizada com outro corpo",
                      "status": 422,
                      "detail": "Essa chave já foi usada com outro corpo nas últimas 24 horas.",
                      "instance": "/v1/relatorios",
                      "codigo": "idempotencia_conflito",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  },
                  "periodo_invalido": {
                    "summary": "Período invertido",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/periodo_invalido",
                      "title": "Período inválido",
                      "status": 422,
                      "detail": "O fim não pode ser antes do início.",
                      "instance": "/v1/relatorios",
                      "codigo": "periodo_invalido",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "periodo.fim",
                          "mensagem": "Use uma competência igual ou posterior a periodo.inicio."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL (1)",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/relatorios\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Idempotency-Key: 8f1c2d3e-4b5a-4c6d-9e7f-0a1b2c3d4e5f\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"tipo\": \"icms_st\", \"cnpj\": \"11222333000181\", \"periodo\": {\"inicio\": \"2026-01\", \"fim\": \"2026-09\"}, \"opcoes\": {\"periodicidade_saida\": \"mensal\"}}'"
          },
          {
            "lang": "shell",
            "label": "cURL (pelo id do arquivo)",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/relatorios\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Idempotency-Key: 3b9e7a1c-5d2f-4e8a-b6c4-1f0d9e8c7b6a\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"tipo\": \"diagnostico_federal\", \"arquivo_id\": \"arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\"}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/relatorios`, {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n  },\n  body: JSON.stringify({\n    \"tipo\": \"icms_st\",\n    \"cnpj\": \"11222333000181\",\n    \"periodo\": {\n      \"inicio\": \"2026-01\",\n      \"fim\": \"2026-09\"\n    },\n    \"opcoes\": {\n      \"periodicidade_saida\": \"mensal\"\n    }\n  }),\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport uuid\nimport requests\n\nresp = requests.post(\n    os.environ[\"RMF_API_URL\"] + \"/relatorios\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"], \"Idempotency-Key\": str(uuid.uuid4())},\n    json={\n      \"tipo\": \"icms_st\",\n      \"cnpj\": \"11222333000181\",\n      \"periodo\": {\n        \"inicio\": \"2026-01\",\n        \"fim\": \"2026-09\"\n      },\n      \"opcoes\": {\n        \"periodicidade_saida\": \"mensal\"\n      }\n    },\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/relatorios');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'POST',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n        'Content-Type: application/json',\n        'Idempotency-Key: ' . bin2hex(random_bytes(16)),\n    ],\n    CURLOPT_POSTFIELDS => '{\"tipo\":\"icms_st\",\"cnpj\":\"11222333000181\",\"periodo\":{\"inicio\":\"2026-01\",\"fim\":\"2026-09\"},\"opcoes\":{\"periodicidade_saida\":\"mensal\"}}',\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Post, \"relatorios\");\nreq.Headers.Add(\"Idempotency-Key\", Guid.NewGuid().ToString());\nreq.Content = new StringContent(\"\"\"\n{\n  \"tipo\": \"icms_st\",\n  \"cnpj\": \"11222333000181\",\n  \"periodo\": {\n    \"inicio\": \"2026-01\",\n    \"fim\": \"2026-09\"\n  },\n  \"opcoes\": {\n    \"periodicidade_saida\": \"mensal\"\n  }\n}\n\"\"\", System.Text.Encoding.UTF8, \"application/json\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/relatorios\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .header(\"Content-Type\", \"application/json\")\n                .header(\"Idempotency-Key\", java.util.UUID.randomUUID().toString())\n                .method(\"POST\", HttpRequest.BodyPublishers.ofString(\"\"\"\n{\n  \"tipo\": \"icms_st\",\n  \"cnpj\": \"11222333000181\",\n  \"periodo\": {\n    \"inicio\": \"2026-01\",\n    \"fim\": \"2026-09\"\n  },\n  \"opcoes\": {\n    \"periodicidade_saida\": \"mensal\"\n  }\n}\n\"\"\"))\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Corpo: TStringStream;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/relatorios';\n  Http := THTTPClient.Create;\n  try\n    Corpo := TStringStream.Create('{\"tipo\":\"icms_st\",\"cnpj\":\"11222333000181\",\"periodo\":{\"inicio\":\"2026-01\",\"fim\":\"2026-09\"},\"opcoes\":{\"periodicidade_saida\":\"mensal\"}}', TEncoding.UTF8);\n    try\n      Resp := Http.Post(Url, Corpo, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY')), TNetHeader.Create('Content-Type', 'application/json'), TNetHeader.Create('Idempotency-Key', GUIDToString(TGUID.NewGuid).Trim(['{', '}']).ToLower)]);\n    finally\n      Corpo.Free;\n    end;\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      },
      "get": {
        "operationId": "listarRelatorios",
        "summary": "Listar relatórios",
        "description": "Use para listar os relatórios da conta, com filtros por empresa, tipo e estado, em páginas. A lista traz o resumo dos concluídos.\n\n**Quando não usar:** para acompanhar um relatório que você acabou de criar (use `obterRelatorio`).",
        "tags": [
          "Relatórios"
        ],
        "security": [
          {
            "bearerAuth": [
              "relatorios:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "cnpj",
            "in": "query",
            "description": "Só relatórios desta empresa.",
            "schema": {
              "$ref": "#/components/schemas/Cnpj"
            },
            "example": "11222333000181"
          },
          {
            "name": "tipo",
            "in": "query",
            "description": "Só relatórios deste tipo.",
            "schema": {
              "$ref": "#/components/schemas/TipoRelatorioId"
            },
            "example": "icms_st"
          },
          {
            "name": "estado",
            "in": "query",
            "description": "Só relatórios neste estado.",
            "schema": {
              "$ref": "#/components/schemas/JobEstado"
            },
            "example": "concluido"
          },
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de relatórios.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaRelatorios"
                },
                "examples": {
                  "lista": {
                    "summary": "Um relatório concluído",
                    "description": "Valores ilustrativos: não vêm de um cálculo real e não devem ser lidos como resultado fiscal.",
                    "value": {
                      "dados": [
                        {
                          "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK",
                          "job_id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                          "tipo": "icms_st",
                          "cnpj": "11222333000181",
                          "periodo": {
                            "inicio": "2026-01",
                            "fim": "2026-09"
                          },
                          "opcoes": {
                            "periodicidade_saida": "mensal"
                          },
                          "estado": "concluido",
                          "criado_em": "2026-10-29T11:30:00-03:00",
                          "concluido_em": "2026-10-29T11:41:12-03:00",
                          "resumo": {
                            "tipo": "icms_st",
                            "tese": "pendente",
                            "total_credito": "18234.57",
                            "por_competencia": [
                              {
                                "competencia": "2026-01",
                                "total_credito": "2011.40"
                              }
                            ],
                            "versao_base_aliquotas": "e964db7cb0e8"
                          },
                          "arquivos": [
                            {
                              "id": "rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK",
                              "tipo": "planilha_resultado",
                              "nome": "icms-st-2026-01-a-2026-09.xlsx",
                              "tipo_midia": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                              "tamanho_bytes": 482113
                            }
                          ],
                          "avisos": [
                            "Há NCMs com alíquota de ICMS a confirmar no período. Confira esses NCMs em GET /icms/aliquotas e fale com a equipe da RMF antes de usar o crédito em apuração."
                          ],
                          "erro": null
                        }
                      ],
                      "proximo_cursor": null
                    }
                  },
                  "vazia": {
                    "summary": "Nenhum relatório",
                    "value": {
                      "dados": [],
                      "proximo_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "$ref": "#/components/responses/NaoProcessavel"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/relatorios?cnpj=11222333000181&tipo=icms_st&estado=concluido\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/relatorios`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/relatorios\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/relatorios');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"relatorios\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/relatorios\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/relatorios';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/relatorios/{id}": {
      "get": {
        "operationId": "obterRelatorio",
        "summary": "Obter relatório e resumo",
        "description": "Use para ler o estado de um relatório e, quando `concluido`, o resumo em JSON (valor por competência, totais, situação da tese) com os avisos, e a lista de arquivos para baixar. O resumo vem sempre; a planilha de resultado só aparece em `arquivos` quando o plano da conta a inclui. O resumo e a planilha trazem só o resultado: sem fórmula, sem memória de cálculo e sem as colunas intermediárias da regra. Consulte respeitando `Retry-After` enquanto não terminar, ou use os webhooks.\n\n**Quando não usar:** para listar vários relatórios (use `listarRelatorios`) ou para baixar a planilha (use `baixarArquivoRelatorio`). Relatório de outra conta responde 404.",
        "tags": [
          "Relatórios"
        ],
        "security": [
          {
            "bearerAuth": [
              "relatorios:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Identificador do relatório.",
            "schema": {
              "$ref": "#/components/schemas/RelatorioId"
            },
            "required": true,
            "example": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
          }
        ],
        "responses": {
          "200": {
            "description": "Relatório.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Relatorio"
                },
                "examples": {
                  "concluido_icms_st": {
                    "summary": "ICMS-ST concluído, conta com a planilha no plano",
                    "description": "Valores ilustrativos: não vêm de um cálculo real e não devem ser lidos como resultado fiscal. O plano desta conta inclui a planilha de resultado, por isso ela aparece em `arquivos`.",
                    "value": {
                      "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK",
                      "job_id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                      "tipo": "icms_st",
                      "cnpj": "11222333000181",
                      "periodo": {
                        "inicio": "2026-01",
                        "fim": "2026-09"
                      },
                      "opcoes": {
                        "periodicidade_saida": "mensal"
                      },
                      "estado": "concluido",
                      "criado_em": "2026-10-29T11:30:00-03:00",
                      "concluido_em": "2026-10-29T11:41:12-03:00",
                      "resumo": {
                        "tipo": "icms_st",
                        "tese": "pendente",
                        "total_credito": "18234.57",
                        "por_competencia": [
                          {
                            "competencia": "2026-01",
                            "total_credito": "2011.40"
                          }
                        ],
                        "versao_base_aliquotas": "e964db7cb0e8"
                      },
                      "arquivos": [
                        {
                          "id": "rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK",
                          "tipo": "planilha_resultado",
                          "nome": "icms-st-2026-01-a-2026-09.xlsx",
                          "tipo_midia": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                          "tamanho_bytes": 482113
                        }
                      ],
                      "avisos": [
                        "Há NCMs com alíquota de ICMS a confirmar no período. Confira esses NCMs em GET /icms/aliquotas e fale com a equipe da RMF antes de usar o crédito em apuração."
                      ],
                      "erro": null
                    }
                  },
                  "concluido_diagnostico": {
                    "summary": "Diagnóstico federal pedido pelo id do arquivo, conta sem a planilha no plano",
                    "description": "Valores ilustrativos: não vêm de um cálculo real e não devem ser lidos como resultado fiscal. Pedido com `arquivo_id` e sem `periodo`: vale o período do arquivo. O plano desta conta não inclui a planilha de resultado, por isso `arquivos` vem vazio; o resumo vem sempre.",
                    "value": {
                      "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJM",
                      "job_id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ1",
                      "tipo": "diagnostico_federal",
                      "cnpj": "11222333000181",
                      "arquivo_id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP",
                      "periodo": {
                        "inicio": "2026-09",
                        "fim": "2026-09"
                      },
                      "opcoes": {},
                      "estado": "concluido",
                      "criado_em": "2026-10-29T11:30:00-03:00",
                      "concluido_em": "2026-10-29T11:41:12-03:00",
                      "resumo": {
                        "tipo": "diagnostico_federal",
                        "tese": "nao_se_aplica",
                        "total_ocorrencias": 2,
                        "total_achados": 15,
                        "ocorrencias": [
                          {
                            "codigo": "oc_001",
                            "titulo": "CST x alíquota divergentes",
                            "nivel": "alto",
                            "quantidade": 14,
                            "valor_envolvido": "53210.00",
                            "por_competencia": [
                              {
                                "competencia": "2026-09",
                                "quantidade": 14,
                                "valor_envolvido": "53210.00"
                              }
                            ],
                            "sem_competencia": null
                          },
                          {
                            "codigo": "oc_008",
                            "titulo": "Alerta de recebimento de arquivos sem movimento",
                            "nivel": "baixo",
                            "quantidade": 1,
                            "valor_envolvido": null,
                            "por_competencia": [
                              {
                                "competencia": "2026-09",
                                "quantidade": 1,
                                "valor_envolvido": null
                              }
                            ],
                            "sem_competencia": null
                          }
                        ]
                      },
                      "arquivos": [],
                      "avisos": [],
                      "erro": null
                    }
                  },
                  "concluido_sem_dados": {
                    "summary": "ICMS-ST sem crédito no período",
                    "description": "Estado `concluido_sem_dados`: o resumo vem com o total zerado e `por_competencia` vazio, e `arquivos` vem vazio. Valores ilustrativos.",
                    "value": {
                      "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJN",
                      "job_id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ2",
                      "tipo": "icms_st",
                      "cnpj": "11222333000262",
                      "periodo": {
                        "inicio": "2026-01",
                        "fim": "2026-03"
                      },
                      "opcoes": {},
                      "estado": "concluido_sem_dados",
                      "criado_em": "2026-10-29T12:00:00-03:00",
                      "concluido_em": "2026-10-29T12:04:30-03:00",
                      "resumo": {
                        "tipo": "icms_st",
                        "tese": "pendente",
                        "total_credito": "0.00",
                        "por_competencia": [],
                        "versao_base_aliquotas": "e964db7cb0e8"
                      },
                      "arquivos": [],
                      "avisos": [],
                      "erro": null
                    }
                  },
                  "processando": {
                    "summary": "Ainda na fila",
                    "value": {
                      "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK",
                      "job_id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                      "tipo": "icms_st",
                      "cnpj": "11222333000181",
                      "periodo": {
                        "inicio": "2026-01",
                        "fim": "2026-09"
                      },
                      "opcoes": {
                        "periodicidade_saida": "mensal"
                      },
                      "estado": "na_fila",
                      "criado_em": "2026-10-29T11:30:00-03:00",
                      "concluido_em": null,
                      "arquivos": [],
                      "avisos": [],
                      "erro": null
                    }
                  },
                  "falhou": {
                    "summary": "Falhou por falta de arquivo",
                    "value": {
                      "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK",
                      "job_id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                      "tipo": "icms_st",
                      "cnpj": "11222333000181",
                      "periodo": {
                        "inicio": "2026-01",
                        "fim": "2026-09"
                      },
                      "opcoes": {
                        "periodicidade_saida": "mensal"
                      },
                      "estado": "falhou",
                      "criado_em": "2026-10-29T11:30:00-03:00",
                      "concluido_em": "2026-10-29T11:32:02-03:00",
                      "arquivos": [],
                      "avisos": [],
                      "erro": {
                        "codigo": "arquivo_faltando",
                        "mensagem": "Falta a EFD Contribuições de 2026-04."
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Relatório não encontrado",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Relatório não encontrado",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Relatório não encontrado",
                      "status": 404,
                      "detail": "Não há relatório com este identificador na sua conta.",
                      "instance": "/v1/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/relatorios/{id}/arquivos/{arquivo_id}": {
      "get": {
        "operationId": "baixarArquivoRelatorio",
        "summary": "Baixar a planilha de resultado do relatório",
        "description": "Use para baixar a planilha de resultado (`.xlsx`) de um relatório concluído. O `arquivo_id` vem de `arquivos[].id` do relatório, com `tipo: planilha_resultado`. A resposta é o próprio arquivo, com `Content-Disposition`.\n\nA planilha traz só o resultado: sem fórmula e sem as colunas intermediárias da regra. Ela só existe quando o plano da conta inclui a planilha de resultado; sem isso, `arquivos` vem vazio e não há o que baixar. O resumo em JSON de `obterRelatorio` vem sempre, com ou sem a planilha.\n\n**Quando não usar:** enquanto o relatório não estiver `concluido` (responde 409 `estado_incompativel`) ou para ler o resultado em código (use o resumo de `obterRelatorio`).",
        "tags": [
          "Relatórios"
        ],
        "security": [
          {
            "bearerAuth": [
              "relatorios:ler"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Identificador do relatório.",
            "schema": {
              "$ref": "#/components/schemas/RelatorioId"
            },
            "required": true,
            "example": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
          },
          {
            "name": "arquivo_id",
            "in": "path",
            "description": "Identificador do arquivo do relatório.",
            "schema": {
              "$ref": "#/components/schemas/RelatorioArquivoId"
            },
            "required": true,
            "example": "rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK"
          }
        ],
        "responses": {
          "200": {
            "description": "Arquivo da planilha.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "Content-Disposition": {
                "description": "Nome do arquivo.",
                "schema": {
                  "type": "string"
                },
                "example": "attachment; filename=\"icms-st-2026-01-a-2026-09.xlsx\""
              }
            },
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                },
                "examples": {
                  "planilha": {
                    "summary": "Planilha em binário",
                    "description": "Conteúdo binário do arquivo .xlsx.",
                    "value": "(bytes do arquivo)"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Arquivo não encontrado",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Arquivo não encontrado",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Arquivo não encontrado",
                      "status": 404,
                      "detail": "O relatório ou o arquivo não existe nesta conta.",
                      "instance": "/v1/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/arquivos/rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Relatório ainda não concluído",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "estado_incompativel": {
                    "summary": "Relatório ainda não concluído",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/estado_incompativel",
                      "title": "Relatório ainda não concluído",
                      "status": 409,
                      "detail": "O relatório está em processamento; não há arquivo para baixar.",
                      "instance": "/v1/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/arquivos/rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK",
                      "codigo": "estado_incompativel",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/arquivos/rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -o relatorio.xlsx"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/arquivos/rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/arquivos/rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/arquivos/rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/arquivos/rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/arquivos/rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/arquivos/rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/relatorios/{id}/cancelar": {
      "post": {
        "operationId": "cancelarRelatorio",
        "summary": "Cancelar relatório",
        "description": "Use para cancelar um relatório que ainda não terminou (`na_fila` ou `processando`). O relatório passa a `cancelado`.\n\n**Quando não usar:** para relatório que já terminou, em qualquer estado terminal: responde 409 `relatorio_ja_terminou`. Também não apaga relatórios prontos.",
        "tags": [
          "Relatórios"
        ],
        "security": [
          {
            "bearerAuth": [
              "relatorios:criar"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Identificador do relatório.",
            "schema": {
              "$ref": "#/components/schemas/RelatorioId"
            },
            "required": true,
            "example": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
          }
        ],
        "responses": {
          "200": {
            "description": "Relatório cancelado.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Relatorio"
                },
                "examples": {
                  "cancelado": {
                    "summary": "Relatório cancelado",
                    "value": {
                      "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK",
                      "job_id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                      "tipo": "icms_st",
                      "cnpj": "11222333000181",
                      "periodo": {
                        "inicio": "2026-01",
                        "fim": "2026-09"
                      },
                      "opcoes": {
                        "periodicidade_saida": "mensal"
                      },
                      "estado": "cancelado",
                      "criado_em": "2026-10-29T11:30:00-03:00",
                      "concluido_em": "2026-10-29T11:31:00-03:00",
                      "arquivos": [],
                      "avisos": [],
                      "erro": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Relatório não encontrado",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Relatório não encontrado",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Relatório não encontrado",
                      "status": 404,
                      "detail": "Não há relatório com este identificador na sua conta.",
                      "instance": "/v1/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/cancelar",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Relatório já terminou",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "relatorio_ja_terminou": {
                    "summary": "Relatório já terminou",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/relatorio_ja_terminou",
                      "title": "Relatório já terminou",
                      "status": 409,
                      "detail": "O relatório já está concluído e não pode ser cancelado.",
                      "instance": "/v1/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/cancelar",
                      "codigo": "relatorio_ja_terminou",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/cancelar\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/cancelar`, {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.post(\n    os.environ[\"RMF_API_URL\"] + \"/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/cancelar\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/cancelar');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'POST',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Post, \"relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/cancelar\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/cancelar\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"POST\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/relatorios/rel_01JC8F6A9V1T4B7C2D5E8FGHJK/cancelar';\n  Http := THTTPClient.Create;\n  try\n    Corpo := TStringStream.Create('', TEncoding.UTF8);\n    try\n      Resp := Http.Post(Url, Corpo, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    finally\n      Corpo.Free;\n    end;\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/jobs/{id}": {
      "get": {
        "operationId": "obterJob",
        "summary": "Estado de um trabalho assíncrono",
        "description": "Use para acompanhar qualquer trabalho que uma operação devolveu em `Location` (leitura de arquivo, relatório e, depois do lançamento, exclusão de arquivo, consulta na Receita e lote de notas). Enquanto o estado não for terminal a resposta traz `Retry-After`: espere esse tempo antes de consultar de novo. Prefira os webhooks quando puder.\n\nA chave precisa do escopo do recurso que o trabalho produz (`arquivos:ler`, `relatorios:ler`, `captura:receita` ou `captura:notas`).\n\n**Quando não usar:** para ler o resultado do trabalho: use o recurso de destino (`obterArquivo`, `obterRelatorio`, `obterConsultaReceita`, `obterLoteNotas`). Trabalho de outra conta responde 404.",
        "tags": [
          "Jobs"
        ],
        "security": [
          {
            "bearerAuth": [
              "arquivos:ler"
            ]
          },
          {
            "bearerAuth": [
              "relatorios:ler"
            ]
          },
          {
            "bearerAuth": [
              "captura:receita"
            ]
          },
          {
            "bearerAuth": [
              "captura:notas"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Identificador do trabalho, recebido em `Location` ou em `job_id`.",
            "schema": {
              "$ref": "#/components/schemas/JobId"
            },
            "required": true,
            "example": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0"
          }
        ],
        "responses": {
          "200": {
            "description": "Estado do trabalho.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                },
                "examples": {
                  "processando": {
                    "summary": "Em andamento",
                    "value": {
                      "id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                      "estado": "processando",
                      "tipo": "relatorio",
                      "progresso": {
                        "percentual": 40
                      },
                      "recurso": {
                        "tipo": "relatorio",
                        "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
                      },
                      "erro": null,
                      "criado_em": "2026-10-29T11:30:00-03:00",
                      "atualizado_em": "2026-10-29T11:35:00-03:00",
                      "concluido_em": null
                    }
                  },
                  "concluido": {
                    "summary": "Concluído",
                    "value": {
                      "id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                      "estado": "concluido",
                      "tipo": "relatorio",
                      "progresso": {
                        "percentual": 100
                      },
                      "recurso": {
                        "tipo": "relatorio",
                        "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
                      },
                      "erro": null,
                      "criado_em": "2026-10-29T11:30:00-03:00",
                      "atualizado_em": "2026-10-29T11:41:12-03:00",
                      "concluido_em": "2026-10-29T11:41:12-03:00"
                    }
                  },
                  "falhou": {
                    "summary": "Falhou",
                    "value": {
                      "id": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                      "estado": "falhou",
                      "tipo": "relatorio",
                      "progresso": null,
                      "recurso": {
                        "tipo": "relatorio",
                        "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
                      },
                      "erro": {
                        "codigo": "arquivo_faltando",
                        "mensagem": "Falta a EFD Contribuições de 2026-04."
                      },
                      "criado_em": "2026-10-29T11:30:00-03:00",
                      "atualizado_em": "2026-10-29T11:32:02-03:00",
                      "concluido_em": "2026-10-29T11:32:02-03:00"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Trabalho não encontrado",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Trabalho não encontrado",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Trabalho não encontrado",
                      "status": 404,
                      "detail": "Não há trabalho com este identificador na sua conta.",
                      "instance": "/v1/jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/jobs/job_01JC8F6B3N7Q1R5S9T2V4WXYZ0';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/webhooks": {
      "post": {
        "operationId": "criarWebhook",
        "summary": "Criar webhook",
        "description": "Use para receber eventos (arquivo lido ou rejeitado, relatório concluído ou falho e, depois do lançamento, consulta à Receita e lote de NF-e concluídos) em um endereço seu, em vez de consultar o estado repetidamente. A resposta traz o segredo `whsec_` **uma única vez**: guarde-o para validar a assinatura das entregas. A URL: HTTPS na porta 443, nome público; a URL é conferida de novo a cada entrega, e a entrega é descartada se o nome passar a resolver para endereço interno. No sandbox, até 10 entregas por dia. Assinar um evento da captura sem a captura no plano responde 403 `fora_do_plano`.\n\n**Quando não usar:** para um endereço que não é seu ou que não valida a assinatura: qualquer um que conheça a URL poderia forjar eventos. Os eventos não carregam dado fiscal, de propósito.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:gerenciar"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CriarWebhookRequisicao"
              },
              "examples": {
                "webhook": {
                  "summary": "Três eventos",
                  "value": {
                    "url": "https://integracao.exemplo.com.br/rmf/eventos",
                    "eventos": [
                      "arquivo.lido",
                      "relatorio.concluido",
                      "relatorio.falhou"
                    ],
                    "descricao": "Integração com o ERP"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook criado, com o segredo.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCriado"
                },
                "examples": {
                  "criado": {
                    "summary": "Webhook criado",
                    "description": "O segredo é ilustrativo.",
                    "value": {
                      "id": "whk_01JC8F7C5P9R2T4V6X8Z1ABCDE",
                      "url": "https://integracao.exemplo.com.br/rmf/eventos",
                      "eventos": [
                        "arquivo.lido",
                        "relatorio.concluido",
                        "relatorio.falhou"
                      ],
                      "descricao": "Integração com o ERP",
                      "ativo": true,
                      "criado_em": "2026-10-29T12:00:00-03:00",
                      "segredo": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "409": {
            "$ref": "#/components/responses/Conflito"
          },
          "422": {
            "description": "URL fora da regra, evento desconhecido ou Idempotency-Key reutilizada.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "url_invalida": {
                    "summary": "URL inválida",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/url_invalida",
                      "title": "URL inválida",
                      "status": 422,
                      "detail": "A URL do webhook não segue a regra: HTTPS na porta 443, nome público; a URL é conferida de novo a cada entrega, e a entrega é descartada se o nome passar a resolver para endereço interno.",
                      "instance": "/v1/webhooks",
                      "codigo": "url_invalida",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                      "erros": [
                        {
                          "campo": "url",
                          "mensagem": "Use https:// na porta 443, com um nome público."
                        }
                      ]
                    }
                  },
                  "idempotencia_conflito": {
                    "summary": "Idempotency-Key reutilizada com outro corpo",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/idempotencia_conflito",
                      "title": "Idempotency-Key reutilizada com outro corpo",
                      "status": 422,
                      "detail": "Essa chave já foi usada com outro corpo nas últimas 24 horas.",
                      "instance": "/v1/webhooks",
                      "codigo": "idempotencia_conflito",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/webhooks\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Idempotency-Key: 8f1c2d3e-4b5a-4c6d-9e7f-0a1b2c3d4e5f\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\": \"https://integracao.exemplo.com.br/rmf/eventos\", \"eventos\": [\"arquivo.lido\", \"relatorio.concluido\", \"relatorio.falhou\"], \"descricao\": \"Integração com o ERP\"}'"
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/webhooks`, {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n  },\n  body: JSON.stringify({\n    \"url\": \"https://integracao.exemplo.com.br/rmf/eventos\",\n    \"eventos\": [\n      \"arquivo.lido\",\n      \"relatorio.concluido\",\n      \"relatorio.falhou\"\n    ],\n    \"descricao\": \"Integração com o ERP\"\n  }),\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport uuid\nimport requests\n\nresp = requests.post(\n    os.environ[\"RMF_API_URL\"] + \"/webhooks\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"], \"Idempotency-Key\": str(uuid.uuid4())},\n    json={\n      \"url\": \"https://integracao.exemplo.com.br/rmf/eventos\",\n      \"eventos\": [\n        \"arquivo.lido\",\n        \"relatorio.concluido\",\n        \"relatorio.falhou\"\n      ],\n      \"descricao\": \"Integração com o ERP\"\n    },\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/webhooks');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'POST',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n        'Content-Type: application/json',\n        'Idempotency-Key: ' . bin2hex(random_bytes(16)),\n    ],\n    CURLOPT_POSTFIELDS => '{\"url\":\"https://integracao.exemplo.com.br/rmf/eventos\",\"eventos\":[\"arquivo.lido\",\"relatorio.concluido\",\"relatorio.falhou\"],\"descricao\":\"Integração com o ERP\"}',\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Post, \"webhooks\");\nreq.Headers.Add(\"Idempotency-Key\", Guid.NewGuid().ToString());\nreq.Content = new StringContent(\"\"\"\n{\n  \"url\": \"https://integracao.exemplo.com.br/rmf/eventos\",\n  \"eventos\": [\n    \"arquivo.lido\",\n    \"relatorio.concluido\",\n    \"relatorio.falhou\"\n  ],\n  \"descricao\": \"Integração com o ERP\"\n}\n\"\"\", System.Text.Encoding.UTF8, \"application/json\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/webhooks\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .header(\"Content-Type\", \"application/json\")\n                .header(\"Idempotency-Key\", java.util.UUID.randomUUID().toString())\n                .method(\"POST\", HttpRequest.BodyPublishers.ofString(\"\"\"\n{\n  \"url\": \"https://integracao.exemplo.com.br/rmf/eventos\",\n  \"eventos\": [\n    \"arquivo.lido\",\n    \"relatorio.concluido\",\n    \"relatorio.falhou\"\n  ],\n  \"descricao\": \"Integração com o ERP\"\n}\n\"\"\"))\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Corpo: TStringStream;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/webhooks';\n  Http := THTTPClient.Create;\n  try\n    Corpo := TStringStream.Create('{\"url\":\"https://integracao.exemplo.com.br/rmf/eventos\",\"eventos\":[\"arquivo.lido\",\"relatorio.concluido\",\"relatorio.falhou\"],\"descricao\":\"Integração com o ERP\"}', TEncoding.UTF8);\n    try\n      Resp := Http.Post(Url, Corpo, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY')), TNetHeader.Create('Content-Type', 'application/json'), TNetHeader.Create('Idempotency-Key', GUIDToString(TGUID.NewGuid).Trim(['{', '}']).ToLower)]);\n    finally\n      Corpo.Free;\n    end;\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      },
      "get": {
        "operationId": "listarWebhooks",
        "summary": "Listar webhooks",
        "description": "Use para ver os webhooks da conta. O segredo nunca é devolvido aqui.\n\n**Quando não usar:** para recuperar um segredo perdido: não há como; exclua o webhook e crie outro.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:gerenciar"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de webhooks.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaWebhooks"
                },
                "examples": {
                  "lista": {
                    "summary": "Um webhook",
                    "value": {
                      "dados": [
                        {
                          "id": "whk_01JC8F7C5P9R2T4V6X8Z1ABCDE",
                          "url": "https://integracao.exemplo.com.br/rmf/eventos",
                          "eventos": [
                            "arquivo.lido",
                            "relatorio.concluido",
                            "relatorio.falhou"
                          ],
                          "descricao": "Integração com o ERP",
                          "ativo": true,
                          "criado_em": "2026-10-29T12:00:00-03:00"
                        }
                      ],
                      "proximo_cursor": null
                    }
                  },
                  "vazia": {
                    "summary": "Nenhum webhook",
                    "value": {
                      "dados": [],
                      "proximo_cursor": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "422": {
            "$ref": "#/components/responses/NaoProcessavel"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/webhooks\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/webhooks`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/webhooks\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/webhooks');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"webhooks\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/webhooks\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/webhooks';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/webhooks/{id}": {
      "delete": {
        "operationId": "excluirWebhook",
        "summary": "Excluir webhook",
        "description": "Use para parar de receber eventos em um endereço (e para trocar de segredo: exclua e crie outro). A exclusão é imediata.\n\n**Quando não usar:** para pausar por pouco tempo: os eventos gerados enquanto não há webhook não são entregues sozinhos depois. Eles ficam guardados 30 dias e podem ser reenviados um a um com `reenviarEvento`.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:gerenciar"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Identificador do webhook.",
            "schema": {
              "$ref": "#/components/schemas/WebhookId"
            },
            "required": true,
            "example": "whk_01JC8F7C5P9R2T4V6X8Z1ABCDE"
          }
        ],
        "responses": {
          "204": {
            "description": "Webhook excluído.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Webhook não encontrado",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Webhook não encontrado",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Webhook não encontrado",
                      "status": 404,
                      "detail": "Não há webhook com este identificador na sua conta.",
                      "instance": "/v1/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X DELETE \"$RMF_API_URL/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE`, {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.delete(\n    os.environ[\"RMF_API_URL\"] + \"/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'DELETE',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Delete, \"webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"DELETE\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Delete(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/webhooks/{id}/teste": {
      "post": {
        "operationId": "testarWebhook",
        "summary": "Enviar evento de teste",
        "description": "Use para conferir se o seu endereço recebe e valida a assinatura: a RMF envia um evento do tipo `teste` ao webhook. A resposta confirma o enfileiramento; o resultado da entrega você vê no seu servidor.\n\n**Quando não usar:** para repetir um evento real perdido (use `reenviarEvento`).",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:gerenciar"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Identificador do webhook.",
            "schema": {
              "$ref": "#/components/schemas/WebhookId"
            },
            "required": true,
            "example": "whk_01JC8F7C5P9R2T4V6X8Z1ABCDE"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Evento de teste enfileirado.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventoEnfileirado"
                },
                "examples": {
                  "teste": {
                    "summary": "Evento de teste",
                    "value": {
                      "evento_id": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN",
                      "tipo": "teste",
                      "enfileirado_em": "2026-10-29T12:05:00-03:00"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Webhook não encontrado",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Webhook não encontrado",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Webhook não encontrado",
                      "status": 404,
                      "detail": "Não há webhook com este identificador na sua conta.",
                      "instance": "/v1/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE/teste",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflito"
          },
          "422": {
            "$ref": "#/components/responses/NaoProcessavel"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE/teste\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Idempotency-Key: 8f1c2d3e-4b5a-4c6d-9e7f-0a1b2c3d4e5f\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE/teste`, {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n    \"Idempotency-Key\": crypto.randomUUID(),\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport uuid\nimport requests\n\nresp = requests.post(\n    os.environ[\"RMF_API_URL\"] + \"/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE/teste\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"], \"Idempotency-Key\": str(uuid.uuid4())},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE/teste');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'POST',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n        'Idempotency-Key: ' . bin2hex(random_bytes(16)),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Post, \"webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE/teste\");\nreq.Headers.Add(\"Idempotency-Key\", Guid.NewGuid().ToString());\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE/teste\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .header(\"Idempotency-Key\", java.util.UUID.randomUUID().toString())\n                .method(\"POST\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/webhooks/whk_01JC8F7C5P9R2T4V6X8Z1ABCDE/teste';\n  Http := THTTPClient.Create;\n  try\n    Corpo := TStringStream.Create('', TEncoding.UTF8);\n    try\n      Resp := Http.Post(Url, Corpo, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY')), TNetHeader.Create('Idempotency-Key', GUIDToString(TGUID.NewGuid).Trim(['{', '}']).ToLower)]);\n    finally\n      Corpo.Free;\n    end;\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/eventos/{id}/reenviar": {
      "post": {
        "operationId": "reenviarEvento",
        "summary": "Reenviar evento",
        "description": "Use quando uma entrega se perdeu (seu servidor ficou fora do ar, por exemplo): o evento é enviado de novo, com o mesmo `id`, a todos os webhooks ativos que o assinam. O `id` do evento é o `webhook-id` da entrega original.\n\nOs eventos da conta ficam guardados 30 dias. Qualquer evento da conta nesse prazo pode ser reenviado, inclusive um gerado quando não havia webhook. Teto de 100 reenvios por dia por conta (cota `reenvios_dia` em `obterUso`); acima, 429 `cota_diaria_excedida`.\n\n**Quando não usar:** para eventos com mais de 30 dias (404) ou que nunca foram gerados.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "bearerAuth": [
              "webhooks:gerenciar"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "Identificador do evento.",
            "schema": {
              "$ref": "#/components/schemas/EventoId"
            },
            "required": true,
            "example": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Reenvio enfileirado.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventoEnfileirado"
                },
                "examples": {
                  "reenvio": {
                    "summary": "Reenvio de relatorio.concluido",
                    "value": {
                      "evento_id": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN",
                      "tipo": "relatorio.concluido",
                      "enfileirado_em": "2026-10-29T12:05:00-03:00"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RequisicaoInvalida"
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "404": {
            "description": "Evento não encontrado",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                },
                "examples": {
                  "recurso_nao_encontrado": {
                    "summary": "Evento não encontrado",
                    "value": {
                      "type": "https://docs.rmfestrategia.com/erros/recurso_nao_encontrado",
                      "title": "Evento não encontrado",
                      "status": 404,
                      "detail": "Não há evento com este identificador na sua conta nos últimos 30 dias.",
                      "instance": "/v1/eventos/evt_01JC8F8D7S2T5W8Y1B4E7GHKMN/reenviar",
                      "codigo": "recurso_nao_encontrado",
                      "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/Conflito"
          },
          "422": {
            "$ref": "#/components/responses/NaoProcessavel"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X POST \"$RMF_API_URL/eventos/evt_01JC8F8D7S2T5W8Y1B4E7GHKMN/reenviar\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\" \\\n  -H \"Idempotency-Key: 8f1c2d3e-4b5a-4c6d-9e7f-0a1b2c3d4e5f\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/eventos/evt_01JC8F8D7S2T5W8Y1B4E7GHKMN/reenviar`, {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n    \"Idempotency-Key\": crypto.randomUUID(),\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport uuid\nimport requests\n\nresp = requests.post(\n    os.environ[\"RMF_API_URL\"] + \"/eventos/evt_01JC8F8D7S2T5W8Y1B4E7GHKMN/reenviar\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"], \"Idempotency-Key\": str(uuid.uuid4())},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/eventos/evt_01JC8F8D7S2T5W8Y1B4E7GHKMN/reenviar');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'POST',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n        'Idempotency-Key: ' . bin2hex(random_bytes(16)),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Post, \"eventos/evt_01JC8F8D7S2T5W8Y1B4E7GHKMN/reenviar\");\nreq.Headers.Add(\"Idempotency-Key\", Guid.NewGuid().ToString());\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/eventos/evt_01JC8F8D7S2T5W8Y1B4E7GHKMN/reenviar\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .header(\"Idempotency-Key\", java.util.UUID.randomUUID().toString())\n                .method(\"POST\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/eventos/evt_01JC8F8D7S2T5W8Y1B4E7GHKMN/reenviar';\n  Http := THTTPClient.Create;\n  try\n    Corpo := TStringStream.Create('', TEncoding.UTF8);\n    try\n      Resp := Http.Post(Url, Corpo, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY')), TNetHeader.Create('Idempotency-Key', GUIDToString(TGUID.NewGuid).Trim(['{', '}']).ToLower)]);\n    finally\n      Corpo.Free;\n    end;\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/conta": {
      "get": {
        "operationId": "obterConta",
        "summary": "Tipo e plano da conta",
        "description": "Use para saber o que a sua integração pode chamar: o tipo da conta (`rede` ou `parceiro`), o plano, os produtos e escopos que ele libera, se a planilha de resultado dos relatórios está incluída, quais capturas estão liberadas e o ambiente da chave usada (`live` ou `test`).\n\nToda conta nasce com o espelho no sandbox: a equipe da RMF cria as duas juntas, e `obterConta` no sandbox devolve o mesmo tipo e o mesmo plano, com `ambiente: test`.\n\nEm conta de `rede`, a API enxerga as empresas da rede. Em conta de `parceiro`, enxerga a carteira inteira, que é a união das redes que o parceiro atende. Escopo fora do plano responde 403 `fora_do_plano`, que é diferente de `escopo_insuficiente`: o primeiro se resolve mudando o plano com a equipe da RMF, o segundo usando uma chave com o escopo.\n\n**Quando não usar:** para ver o consumo das cotas (use `obterUso`) ou as chaves (use `listarChaves`). O plano não muda pela API: fale com a equipe da RMF.",
        "tags": [
          "Conta"
        ],
        "security": [
          {
            "bearerAuth": [
              "conta:ler"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Conta e plano.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Conta"
                },
                "examples": {
                  "parceiro": {
                    "summary": "Conta de parceiro no sandbox, sem planilha e sem captura",
                    "description": "Nome da conta e do plano ilustrativos, até o plano comercial ser definido.",
                    "value": {
                      "tipo": "parceiro",
                      "nome": "Contabilidade Exemplo",
                      "ambiente": "test",
                      "plano": {
                        "nome": "Parceiro (ilustrativo)",
                        "produtos": [
                          "consultas_icms",
                          "consultas_reforma",
                          "arquivos",
                          "relatorios"
                        ],
                        "escopos_permitidos": [
                          "consultas:ler",
                          "empresas:escrever",
                          "empresas:ler",
                          "arquivos:enviar",
                          "arquivos:ler",
                          "relatorios:criar",
                          "relatorios:ler",
                          "webhooks:gerenciar",
                          "conta:ler"
                        ],
                        "planilha_resultado": false,
                        "capturas": []
                      }
                    }
                  },
                  "rede": {
                    "summary": "Conta de rede em produção, com planilha e captura na Receita",
                    "description": "Nome da conta e do plano ilustrativos, até o plano comercial ser definido. A captura vem depois do lançamento, por plano.",
                    "value": {
                      "tipo": "rede",
                      "nome": "Empresa Exemplo Comércio Ltda",
                      "ambiente": "live",
                      "plano": {
                        "nome": "Rede completo (ilustrativo)",
                        "produtos": [
                          "consultas_icms",
                          "consultas_reforma",
                          "arquivos",
                          "relatorios",
                          "captura"
                        ],
                        "escopos_permitidos": [
                          "consultas:ler",
                          "empresas:escrever",
                          "empresas:ler",
                          "arquivos:enviar",
                          "arquivos:ler",
                          "relatorios:criar",
                          "relatorios:ler",
                          "webhooks:gerenciar",
                          "conta:ler",
                          "captura:receita"
                        ],
                        "planilha_resultado": true,
                        "capturas": [
                          "receita"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/conta\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/conta`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/conta\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/conta');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"conta\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/conta\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/conta';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/conta/uso": {
      "get": {
        "operationId": "obterUso",
        "summary": "Consumo de cotas do dia e do mês",
        "description": "Use para saber quanto da cota diária e mensal já foi usado, antes de receber um 429 `cota_diaria_excedida`. Vale para a conta toda, somando todas as chaves.\n\n**Quando não usar:** para saber o limite por minuto: ele vem nos cabeçalhos `RateLimit-Policy` e `RateLimit` de toda resposta.",
        "tags": [
          "Conta"
        ],
        "security": [
          {
            "bearerAuth": [
              "conta:ler"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Consumo atual.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsoConta"
                },
                "examples": {
                  "uso": {
                    "summary": "Duas cotas",
                    "description": "Os nomes e os limites das cotas são ilustrativos, até o plano comercial ser definido.",
                    "value": {
                      "data": "2026-10-29",
                      "mes": "2026-10",
                      "cotas": [
                        {
                          "cota": "consultas_dia",
                          "periodo": "dia",
                          "limite": 5000,
                          "usado": 1240,
                          "restante": 3760,
                          "renova_em": "2026-10-30T00:00:00-03:00"
                        },
                        {
                          "cota": "relatorios_mes",
                          "periodo": "mes",
                          "limite": 100,
                          "usado": 7,
                          "restante": 93,
                          "renova_em": "2026-11-01T00:00:00-03:00"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/conta/uso\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/conta/uso`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/conta/uso\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/conta/uso');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"conta/uso\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/conta/uso\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/conta/uso';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    },
    "/conta/chaves": {
      "get": {
        "operationId": "listarChaves",
        "summary": "Listar chaves da conta",
        "description": "Use para ver as chaves da conta neste ambiente: prefixo, nome, escopos, último uso e vencimento. A chave completa nunca é devolvida.\n\n**Quando não usar:** para pedir, trocar ou revogar chaves. A chave é emitida pela equipe da RMF; peça pelo seu contato comercial. Trocas e revogações também são pedidas à equipe.",
        "tags": [
          "Conta"
        ],
        "security": [
          {
            "bearerAuth": [
              "conta:ler"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Chaves da conta.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaChaves"
                },
                "examples": {
                  "chaves": {
                    "summary": "Uma chave de teste",
                    "value": {
                      "dados": [
                        {
                          "id": "chv_01JC8F9E2M5P8S1V4X7Z0CDFGH",
                          "prefixo": "rmf_test_01JC8F9E2M5P8S1V4X7Z0CDFGH",
                          "nome": "Integração do ERP",
                          "ambiente": "test",
                          "escopos": [
                            "consultas:ler",
                            "conta:ler"
                          ],
                          "criada_em": "2026-10-20T09:00:00-03:00",
                          "ultimo_uso_em": "2026-10-29T11:30:00-03:00",
                          "vence_em": "2027-10-20T09:00:00-03:00"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/NaoAutenticado"
          },
          "403": {
            "$ref": "#/components/responses/ProibidoEscopo"
          },
          "429": {
            "$ref": "#/components/responses/LimiteExcedido"
          },
          "500": {
            "$ref": "#/components/responses/ErroInterno"
          },
          "503": {
            "$ref": "#/components/responses/ServicoIndisponivel"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# Comece pelo sandbox: export RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\ncurl -X GET \"$RMF_API_URL/conta/chaves\" \\\n  -H \"Authorization: Bearer $RMF_API_KEY\""
          },
          {
            "lang": "typescript",
            "label": "TypeScript",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nconst resp = await fetch(`${process.env.RMF_API_URL}/conta/chaves`, {\n  method: \"GET\",\n  headers: {\n    Authorization: `Bearer ${process.env.RMF_API_KEY}`,\n  },\n});\nconsole.log(resp.status, await resp.json());\n"
          },
          {
            "lang": "python",
            "label": "Python",
            "source": "# Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport os\nimport requests\n\nresp = requests.get(\n    os.environ[\"RMF_API_URL\"] + \"/conta/chaves\",\n    headers={\"Authorization\": \"Bearer \" + os.environ[\"RMF_API_KEY\"]},\n    timeout=15,\n)\nprint(resp.status_code, resp.json())\n"
          },
          {
            "lang": "php",
            "label": "PHP",
            "source": "<?php\n// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n$ch = curl_init(getenv('RMF_API_URL') . '/conta/chaves');\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST => 'GET',\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER => [\n        'Authorization: Bearer ' . getenv('RMF_API_KEY'),\n    ],\n]);\n$corpo = curl_exec($ch);\necho curl_getinfo($ch, CURLINFO_RESPONSE_CODE), ' ', $corpo, PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C#",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nusing System.Net.Http.Headers;\n\nvar http = new HttpClient { BaseAddress = new Uri(Environment.GetEnvironmentVariable(\"RMF_API_URL\")!.TrimEnd('/') + \"/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", Environment.GetEnvironmentVariable(\"RMF_API_KEY\"));\nusing var req = new HttpRequestMessage(HttpMethod.Get, \"conta/chaves\");\nusing var resp = await http.SendAsync(req);\nConsole.WriteLine($\"{(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}\");\n"
          },
          {
            "lang": "java",
            "label": "Java",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\n\npublic class Chamada {\n    public static void main(String[] args) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(System.getenv(\"RMF_API_URL\") + \"/conta/chaves\"))\n                .header(\"Authorization\", \"Bearer \" + System.getenv(\"RMF_API_KEY\"))\n                .method(\"GET\", HttpRequest.BodyPublishers.noBody())\n                .build();\n        var resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());\n        System.out.println(resp.statusCode() + \" \" + resp.body());\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi",
            "source": "// Comece pelo sandbox: RMF_API_URL=https://sandbox.api.rmfestrategia.com/v1\n// Delphi 10.3 ou mais novo. Exemplo gerado do contrato e revisado à mão: não roda no CI.\nuses\n  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.URLClient;\n\nvar\n  Http: THTTPClient;\n  Resp: IHTTPResponse;\n  Url: string;\nbegin\n  Url := GetEnvironmentVariable('RMF_API_URL') + '/conta/chaves';\n  Http := THTTPClient.Create;\n  try\n    Resp := Http.Get(Url, nil, [TNetHeader.Create('Authorization', 'Bearer ' + GetEnvironmentVariable('RMF_API_KEY'))]);\n    Writeln(Resp.StatusCode, ' ', Resp.ContentAsString(TEncoding.UTF8));\n  finally\n    Http.Free;\n  end;\nend.\n"
          }
        ]
      }
    }
  },
  "webhooks": {
    "arquivoLido": {
      "post": {
        "operationId": "receberEventoArquivoLido",
        "summary": "Evento arquivo.lido",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "description": "Enviado quando a leitura de um arquivo termina sem recusa, em `lido` ou em `periodo_ja_lido`; o `estado` do corpo diz qual. Em `periodo_ja_lido`, o arquivo não foi usado (já havia um SPED lido para o mesmo CNPJ e período), e o trabalho de leitura fica `concluido_sem_dados`. Num ZIP de XML, dispara quando todos os XML foram processados (`xml.lidos + xml.duplicados + xml.com_erro = xml.total` em `obterArquivo`), mesmo que alguns tenham erro; se nenhum XML pôde ser lido, chega `arquivo.rejeitado`. Um arquivo que passa a `substituido` não gera evento: o arquivo novo traz o antigo em `substitui`.\n\n**Quando não usar:** esta é a entrega que a RMF faz no seu endereço; você não chama esta operação, você a implementa no seu servidor.\n\n**Como validar a entrega (Standard Webhooks).** Monte o texto `webhook-id` + `.` + `webhook-timestamp` + `.` + corpo bruto (os bytes exatos recebidos, antes de interpretar o JSON). Calcule o HMAC-SHA256 desse texto usando como chave os bytes do segredo `whsec_...` (a parte depois do prefixo `whsec_`, decodificada de Base64) e compare, em tempo constante, com cada assinatura `v1,...` do cabeçalho `webhook-signature`.\n\nO corpo leva só o identificador, o tipo do evento e o estado: nunca dado fiscal. Para ler o resultado, chame `obterArquivo` ou `obterRelatorio` com o `id` do recurso.\n\nResponda com qualquer 2xx para confirmar. Entrega perdida pode ser repetida com `reenviarEvento`; trate `webhook-id` como chave de deduplicação.",
        "parameters": [
          {
            "name": "webhook-id",
            "in": "header",
            "description": "Identificador único da entrega (igual a `id` do evento no corpo). Use para descartar entregas repetidas.",
            "schema": {
              "type": "string"
            },
            "required": true,
            "example": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN"
          },
          {
            "name": "webhook-timestamp",
            "in": "header",
            "description": "Momento do envio, em segundos desde 1970 (UTC). Recuse entregas com horário muito distante do seu relógio.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "required": true,
            "example": "1793284930"
          },
          {
            "name": "webhook-signature",
            "in": "header",
            "description": "Assinatura HMAC-SHA256 em Base64, no formato `v1,<assinatura>`. Pode vir mais de uma, separadas por espaço (durante a troca de segredo). Veja o texto da operação.",
            "schema": {
              "type": "string"
            },
            "required": true,
            "example": "v1,Zm9vYmFyYmF6cXV4Zm9vYmFyYmF6cXV4Zm9vYmFyYmE="
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventoWebhook"
              },
              "examples": {
                "evento": {
                  "summary": "Evento arquivo.lido",
                  "value": {
                    "id": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN",
                    "tipo": "arquivo.lido",
                    "criado_em": "2026-10-29T11:41:12-03:00",
                    "recurso": {
                      "tipo": "arquivo",
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP"
                    },
                    "estado": "lido"
                  }
                },
                "periodo_ja_lido": {
                  "summary": "Evento arquivo.lido de um arquivo que não foi usado",
                  "description": "O período já estava lido; o arquivo terminou em `periodo_ja_lido`.",
                  "value": {
                    "id": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMP",
                    "tipo": "arquivo.lido",
                    "criado_em": "2026-10-30T09:03:10-03:00",
                    "recurso": {
                      "tipo": "arquivo",
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNS"
                    },
                    "estado": "periodo_ja_lido"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Entrega confirmada. Qualquer resposta 2xx vale."
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "#!/bin/sh\n# Verifica a assinatura de um webhook (Standard Webhooks, HMAC-SHA256). Roda offline, com um vetor fixo.\n# Requer openssl. Em produção, leia o corpo BRUTO recebido e o segredo do seu cofre.\nset -eu\n\nSEGREDO='whsec_cm1mLXZldG9yLWRlLXRlc3RlLW5hby1lLXNlZ3JlZG8='\nWEBHOOK_ID='evt_01JC8F8D7S2T5W8Y1B4E7GHKMN'\nWEBHOOK_TIMESTAMP='1793284930'\nWEBHOOK_SIGNATURE='v1,3uSpSrd3OHruCljCqZp0mkughVpJT/ydTLRFDED/vSg='\nCORPO='{\"id\":\"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\",\"tipo\":\"arquivo.lido\",\"criado_em\":\"2026-10-29T11:41:12-03:00\",\"recurso\":{\"tipo\":\"arquivo\",\"id\":\"arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\"},\"estado\":\"lido\"}'\nAGORA=1793284990 # no seu servidor: $(date +%s)\n\nverificar() { # verificar <segredo> <id> <timestamp> <assinaturas> <corpo> <agora>\n  # recusa horário distante do seu relógio (5 minutos)\n  atraso=$(($6 - $3))\n  [ \"${atraso#-}\" -le 300 ] || return 1\n  # a chave são os bytes do segredo depois de \"whsec_\", decodificados de Base64\n  chave_hex=$(printf '%s' \"${1#whsec_}\" | base64 -d | od -An -v -tx1 | tr -d ' \\n')\n  esperada=$(printf '%s.%s.%s' \"$2\" \"$3\" \"$5\" |\n    openssl dgst -sha256 -mac HMAC -macopt \"hexkey:$chave_hex\" -binary | base64)\n  # o cabeçalho pode trazer várias assinaturas \"v1,<base64>\" separadas por espaço\n  for item in $4; do\n    [ \"${item%%,*}\" = v1 ] && [ \"${item#v1,}\" = \"$esperada\" ] && return 0\n  done\n  return 1\n}\n\nif verificar \"$SEGREDO\" \"$WEBHOOK_ID\" \"$WEBHOOK_TIMESTAMP\" \"$WEBHOOK_SIGNATURE\" \"$CORPO\" \"$AGORA\"; then\n  echo \"assinatura=valida\"\nelse\n  echo \"assinatura=invalida\" >&2\n  exit 1\nfi\nif verificar \"$SEGREDO\" \"$WEBHOOK_ID\" \"$WEBHOOK_TIMESTAMP\" \"$WEBHOOK_SIGNATURE\" \"${CORPO}x\" \"$AGORA\"; then\n  echo \"corpo adulterado foi aceito\" >&2\n  exit 1\nfi\necho \"adulterado=recusado\"\n"
          },
          {
            "lang": "typescript",
            "label": "TypeScript (exemplo completo)",
            "source": "// Verifica a assinatura de um webhook (Standard Webhooks, HMAC-SHA256). Roda offline, com um vetor fixo.\n// Node 22.18 ou mais novo. Em produção, use o corpo BRUTO recebido e o segredo do seu cofre.\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst SEGREDO = \"whsec_cm1mLXZldG9yLWRlLXRlc3RlLW5hby1lLXNlZ3JlZG8=\";\nconst WEBHOOK_ID = \"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\";\nconst WEBHOOK_TIMESTAMP = \"1793284930\";\nconst WEBHOOK_SIGNATURE = \"v1,3uSpSrd3OHruCljCqZp0mkughVpJT/ydTLRFDED/vSg=\";\nconst CORPO =\n  '{\"id\":\"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\",\"tipo\":\"arquivo.lido\",\"criado_em\":\"2026-10-29T11:41:12-03:00\",\"recurso\":{\"tipo\":\"arquivo\",\"id\":\"arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\"},\"estado\":\"lido\"}';\nconst AGORA = 1793284990; // no seu servidor: Math.floor(Date.now() / 1000)\n\nfunction verificar(segredo: string, id: string, timestamp: string, assinaturas: string, corpo: string, agora: number): boolean {\n  // recusa horário distante do seu relógio (5 minutos)\n  if (Math.abs(agora - Number(timestamp)) > 300) return false;\n  // a chave são os bytes do segredo depois de \"whsec_\", decodificados de Base64\n  const chave = Buffer.from(segredo.replace(/^whsec_/, \"\"), \"base64\");\n  const esperada = createHmac(\"sha256\", chave).update(`${id}.${timestamp}.${corpo}`).digest();\n  // o cabeçalho pode trazer várias assinaturas \"v1,<base64>\" separadas por espaço\n  return assinaturas.split(\" \").some((item) => {\n    const [versao, valor] = item.split(\",\");\n    if (versao !== \"v1\" || !valor) return false;\n    const recebida = Buffer.from(valor, \"base64\");\n    return recebida.length === esperada.length && timingSafeEqual(recebida, esperada);\n  });\n}\n\nif (!verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO, AGORA)) {\n  console.error(\"assinatura=invalida\");\n  process.exit(1);\n}\nconsole.log(\"assinatura=valida\");\nif (verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO + \"x\", AGORA)) {\n  console.error(\"corpo adulterado foi aceito\");\n  process.exit(1);\n}\nconsole.log(\"adulterado=recusado\");\n"
          },
          {
            "lang": "python",
            "label": "Python (exemplo completo)",
            "source": "\"\"\"Verifica a assinatura de um webhook (Standard Webhooks, HMAC-SHA256). Roda offline, com um vetor fixo.\nPython 3.9 ou mais novo, só biblioteca padrão. Em produção, use o corpo BRUTO recebido e o segredo do seu cofre.\"\"\"\nimport base64\nimport hashlib\nimport hmac\nimport sys\n\nSEGREDO = \"whsec_cm1mLXZldG9yLWRlLXRlc3RlLW5hby1lLXNlZ3JlZG8=\"\nWEBHOOK_ID = \"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\"\nWEBHOOK_TIMESTAMP = \"1793284930\"\nWEBHOOK_SIGNATURE = \"v1,3uSpSrd3OHruCljCqZp0mkughVpJT/ydTLRFDED/vSg=\"\nCORPO = (\n    '{\"id\":\"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\",\"tipo\":\"arquivo.lido\",\"criado_em\":\"2026-10-29T11:41:12-03:00\",'\n    '\"recurso\":{\"tipo\":\"arquivo\",\"id\":\"arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\"},\"estado\":\"lido\"}'\n)\nAGORA = 1793284990  # no seu servidor: int(time.time())\n\n\ndef verificar(segredo: str, id_: str, timestamp: str, assinaturas: str, corpo: str, agora: int) -> bool:\n    # recusa horário distante do seu relógio (5 minutos)\n    if abs(agora - int(timestamp)) > 300:\n        return False\n    # a chave são os bytes do segredo depois de \"whsec_\", decodificados de Base64\n    chave = base64.b64decode(segredo.removeprefix(\"whsec_\"))\n    esperada = hmac.new(chave, f\"{id_}.{timestamp}.{corpo}\".encode(), hashlib.sha256).digest()\n    # o cabeçalho pode trazer várias assinaturas \"v1,<base64>\" separadas por espaço\n    for item in assinaturas.split(\" \"):\n        versao, _, valor = item.partition(\",\")\n        if versao == \"v1\" and hmac.compare_digest(base64.b64decode(valor), esperada):\n            return True\n    return False\n\n\nif not verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO, AGORA):\n    sys.exit(\"assinatura=invalida\")\nprint(\"assinatura=valida\")\nif verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO + \"x\", AGORA):\n    sys.exit(\"corpo adulterado foi aceito\")\nprint(\"adulterado=recusado\")\n"
          },
          {
            "lang": "php",
            "label": "PHP (exemplo completo)",
            "source": "<?php\n// Verifica a assinatura de um webhook (Standard Webhooks, HMAC-SHA256). Roda offline, com um vetor fixo.\n// PHP 8.1 ou mais novo. Em produção, use o corpo BRUTO recebido (file_get_contents('php://input')) e o segredo do seu cofre.\nconst SEGREDO = 'whsec_cm1mLXZldG9yLWRlLXRlc3RlLW5hby1lLXNlZ3JlZG8=';\nconst WEBHOOK_ID = 'evt_01JC8F8D7S2T5W8Y1B4E7GHKMN';\nconst WEBHOOK_TIMESTAMP = '1793284930';\nconst WEBHOOK_SIGNATURE = 'v1,3uSpSrd3OHruCljCqZp0mkughVpJT/ydTLRFDED/vSg=';\nconst CORPO = '{\"id\":\"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\",\"tipo\":\"arquivo.lido\",\"criado_em\":\"2026-10-29T11:41:12-03:00\",\"recurso\":{\"tipo\":\"arquivo\",\"id\":\"arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\"},\"estado\":\"lido\"}';\nconst AGORA = 1793284990; // no seu servidor: time()\n\nfunction verificar(string $segredo, string $id, string $timestamp, string $assinaturas, string $corpo, int $agora): bool\n{\n    // recusa horário distante do seu relógio (5 minutos)\n    if (abs($agora - (int) $timestamp) > 300) {\n        return false;\n    }\n    // a chave são os bytes do segredo depois de \"whsec_\", decodificados de Base64\n    $chave = base64_decode(substr($segredo, strlen('whsec_')));\n    $esperada = base64_encode(hash_hmac('sha256', \"$id.$timestamp.$corpo\", $chave, true));\n    // o cabeçalho pode trazer várias assinaturas \"v1,<base64>\" separadas por espaço\n    foreach (explode(' ', $assinaturas) as $item) {\n        [$versao, $valor] = array_pad(explode(',', $item, 2), 2, '');\n        if ($versao === 'v1' && hash_equals($esperada, $valor)) {\n            return true;\n        }\n    }\n    return false;\n}\n\nif (!verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO, AGORA)) {\n    fwrite(STDERR, \"assinatura=invalida\\n\");\n    exit(1);\n}\necho \"assinatura=valida\", PHP_EOL;\nif (verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO . 'x', AGORA)) {\n    fwrite(STDERR, \"corpo adulterado foi aceito\\n\");\n    exit(1);\n}\necho \"adulterado=recusado\", PHP_EOL;\n"
          },
          {
            "lang": "csharp",
            "label": "C# (exemplo completo)",
            "source": "// Verifica a assinatura de um webhook (Standard Webhooks, HMAC-SHA256). Roda offline, com um vetor fixo.\n// .NET 8 ou mais novo. Em produção, use o corpo BRUTO recebido e o segredo do seu cofre.\nusing System.Security.Cryptography;\nusing System.Text;\n\nconst string Segredo = \"whsec_cm1mLXZldG9yLWRlLXRlc3RlLW5hby1lLXNlZ3JlZG8=\";\nconst string WebhookId = \"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\";\nconst string WebhookTimestamp = \"1793284930\";\nconst string WebhookSignature = \"v1,3uSpSrd3OHruCljCqZp0mkughVpJT/ydTLRFDED/vSg=\";\nconst string Corpo = \"{\\\"id\\\":\\\"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\\\",\\\"tipo\\\":\\\"arquivo.lido\\\",\\\"criado_em\\\":\\\"2026-10-29T11:41:12-03:00\\\",\\\"recurso\\\":{\\\"tipo\\\":\\\"arquivo\\\",\\\"id\\\":\\\"arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\\\"},\\\"estado\\\":\\\"lido\\\"}\";\nconst long Agora = 1793284990; // no seu servidor: DateTimeOffset.UtcNow.ToUnixTimeSeconds()\n\nif (!Verificar(Segredo, WebhookId, WebhookTimestamp, WebhookSignature, Corpo, Agora))\n{\n    Console.Error.WriteLine(\"assinatura=invalida\");\n    return 1;\n}\nConsole.WriteLine(\"assinatura=valida\");\nif (Verificar(Segredo, WebhookId, WebhookTimestamp, WebhookSignature, Corpo + \"x\", Agora))\n{\n    Console.Error.WriteLine(\"corpo adulterado foi aceito\");\n    return 1;\n}\nConsole.WriteLine(\"adulterado=recusado\");\nreturn 0;\n\nstatic bool Verificar(string segredo, string id, string timestamp, string assinaturas, string corpo, long agora)\n{\n    // recusa horário distante do seu relógio (5 minutos)\n    if (Math.Abs(agora - long.Parse(timestamp)) > 300) return false;\n    // a chave são os bytes do segredo depois de \"whsec_\", decodificados de Base64\n    var chave = Convert.FromBase64String(segredo[\"whsec_\".Length..]);\n    var esperada = HMACSHA256.HashData(chave, Encoding.UTF8.GetBytes($\"{id}.{timestamp}.{corpo}\"));\n    // o cabeçalho pode trazer várias assinaturas \"v1,<base64>\" separadas por espaço\n    foreach (var item in assinaturas.Split(' '))\n    {\n        var partes = item.Split(',', 2);\n        if (partes.Length == 2 && partes[0] == \"v1\"\n            && CryptographicOperations.FixedTimeEquals(Convert.FromBase64String(partes[1]), esperada))\n            return true;\n    }\n    return false;\n}\n"
          },
          {
            "lang": "java",
            "label": "Java (exemplo completo)",
            "source": "// Verifica a assinatura de um webhook (Standard Webhooks, HMAC-SHA256). Roda offline, com um vetor fixo.\n// Java 17 ou mais novo, só biblioteca padrão. Em produção, use o corpo BRUTO recebido e o segredo do seu cofre.\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.util.Base64;\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\n\npublic class Webhook {\n    static final String SEGREDO = \"whsec_cm1mLXZldG9yLWRlLXRlc3RlLW5hby1lLXNlZ3JlZG8=\";\n    static final String WEBHOOK_ID = \"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\";\n    static final String WEBHOOK_TIMESTAMP = \"1793284930\";\n    static final String WEBHOOK_SIGNATURE = \"v1,3uSpSrd3OHruCljCqZp0mkughVpJT/ydTLRFDED/vSg=\";\n    static final String CORPO = \"{\\\"id\\\":\\\"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\\\",\\\"tipo\\\":\\\"arquivo.lido\\\",\\\"criado_em\\\":\\\"2026-10-29T11:41:12-03:00\\\",\\\"recurso\\\":{\\\"tipo\\\":\\\"arquivo\\\",\\\"id\\\":\\\"arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\\\"},\\\"estado\\\":\\\"lido\\\"}\";\n    static final long AGORA = 1793284990L; // no seu servidor: System.currentTimeMillis() / 1000\n\n    static boolean verificar(String segredo, String id, String timestamp, String assinaturas, String corpo, long agora)\n            throws Exception {\n        // recusa horário distante do seu relógio (5 minutos)\n        if (Math.abs(agora - Long.parseLong(timestamp)) > 300) return false;\n        // a chave são os bytes do segredo depois de \"whsec_\", decodificados de Base64\n        var chave = Base64.getDecoder().decode(segredo.substring(\"whsec_\".length()));\n        var mac = Mac.getInstance(\"HmacSHA256\");\n        mac.init(new SecretKeySpec(chave, \"HmacSHA256\"));\n        var esperada = mac.doFinal((id + \".\" + timestamp + \".\" + corpo).getBytes(StandardCharsets.UTF_8));\n        // o cabeçalho pode trazer várias assinaturas \"v1,<base64>\" separadas por espaço\n        for (var item : assinaturas.split(\" \")) {\n            var partes = item.split(\",\", 2);\n            if (partes.length == 2 && partes[0].equals(\"v1\")\n                    && MessageDigest.isEqual(Base64.getDecoder().decode(partes[1]), esperada)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public static void main(String[] args) throws Exception {\n        if (!verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO, AGORA)) {\n            System.err.println(\"assinatura=invalida\");\n            System.exit(1);\n        }\n        System.out.println(\"assinatura=valida\");\n        if (verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO + \"x\", AGORA)) {\n            System.err.println(\"corpo adulterado foi aceito\");\n            System.exit(1);\n        }\n        System.out.println(\"adulterado=recusado\");\n    }\n}\n"
          },
          {
            "lang": "pascal",
            "label": "Delphi (exemplo completo)",
            "source": "program Webhook;\n// Verifica a assinatura de um webhook (Standard Webhooks, HMAC-SHA256). Roda offline, com um vetor fixo.\n// Delphi 10.3 ou mais novo. Em produção, use o corpo BRUTO recebido e o segredo do seu cofre.\n// Exemplo escrito e revisado à mão: não roda no CI (não existe compilador Delphi em container).\n\n{$APPTYPE CONSOLE}\n\nuses\n  System.SysUtils, System.Hash, System.NetEncoding;\n\nconst\n  SEGREDO = 'whsec_cm1mLXZldG9yLWRlLXRlc3RlLW5hby1lLXNlZ3JlZG8=';\n  WEBHOOK_ID = 'evt_01JC8F8D7S2T5W8Y1B4E7GHKMN';\n  WEBHOOK_TIMESTAMP = '1793284930';\n  WEBHOOK_SIGNATURE = 'v1,3uSpSrd3OHruCljCqZp0mkughVpJT/ydTLRFDED/vSg=';\n  CORPO = '{\"id\":\"evt_01JC8F8D7S2T5W8Y1B4E7GHKMN\",\"tipo\":\"arquivo.lido\",\"criado_em\":\"2026-10-29T11:41:12-03:00\",\"recurso\":{\"tipo\":\"arquivo\",\"id\":\"arq_01JC8F4P0D5S3Y8E2G6H7JKMNP\"},\"estado\":\"lido\"}';\n  AGORA: Int64 = 1793284990; // no seu servidor: DateTimeToUnix(Now, False)\n\n// Comparação em tempo constante.\nfunction IguaisEmTempoConstante(const A, B: string): Boolean;\nvar\n  I, Diferenca: Integer;\nbegin\n  Diferenca := Length(A) xor Length(B);\n  for I := 1 to Length(A) do\n    if I <= Length(B) then\n      Diferenca := Diferenca or (Ord(A[I]) xor Ord(B[I]));\n  Result := Diferenca = 0;\nend;\n\nfunction Verificar(const ASegredo, AId, ATimestamp, AAssinaturas, ACorpo: string; AAgora: Int64): Boolean;\nvar\n  Chave, Esperada: TBytes;\n  EsperadaB64, Item: string;\n  Partes: TArray<string>;\nbegin\n  Result := False;\n  // recusa horário distante do seu relógio (5 minutos)\n  if Abs(AAgora - StrToInt64(ATimestamp)) > 300 then Exit;\n  // a chave são os bytes do segredo depois de \"whsec_\", decodificados de Base64\n  Chave := TNetEncoding.Base64.DecodeStringToBytes(ASegredo.Substring(Length('whsec_')));\n  Esperada := THashSHA2.GetHMACAsBytes(TEncoding.UTF8.GetBytes(AId + '.' + ATimestamp + '.' + ACorpo),\n    Chave, THashSHA2.TSHA2Version.SHA256);\n  EsperadaB64 := TNetEncoding.Base64.EncodeBytesToString(Esperada);\n  // o cabeçalho pode trazer várias assinaturas \"v1,<base64>\" separadas por espaço\n  for Item in AAssinaturas.Split([' ']) do\n  begin\n    Partes := Item.Split([','], 2);\n    if (Length(Partes) = 2) and (Partes[0] = 'v1') and IguaisEmTempoConstante(Partes[1], EsperadaB64) then\n      Exit(True);\n  end;\nend;\n\nbegin\n  if not Verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO, AGORA) then\n  begin\n    Writeln(ErrOutput, 'assinatura=invalida');\n    Halt(1);\n  end;\n  Writeln('assinatura=valida');\n  if Verificar(SEGREDO, WEBHOOK_ID, WEBHOOK_TIMESTAMP, WEBHOOK_SIGNATURE, CORPO + 'x', AGORA) then\n  begin\n    Writeln(ErrOutput, 'corpo adulterado foi aceito');\n    Halt(1);\n  end;\n  Writeln('adulterado=recusado');\nend.\n"
          }
        ]
      }
    },
    "arquivoRejeitado": {
      "post": {
        "operationId": "receberEventoArquivoRejeitado",
        "summary": "Evento arquivo.rejeitado",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "description": "Enviado quando um arquivo é recusado. O motivo está em `obterArquivo`.\n\n**Quando não usar:** esta é a entrega que a RMF faz no seu endereço; você não chama esta operação, você a implementa no seu servidor.\n\n**Como validar a entrega (Standard Webhooks).** Monte o texto `webhook-id` + `.` + `webhook-timestamp` + `.` + corpo bruto (os bytes exatos recebidos, antes de interpretar o JSON). Calcule o HMAC-SHA256 desse texto usando como chave os bytes do segredo `whsec_...` (a parte depois do prefixo `whsec_`, decodificada de Base64) e compare, em tempo constante, com cada assinatura `v1,...` do cabeçalho `webhook-signature`.\n\nO corpo leva só o identificador, o tipo do evento e o estado: nunca dado fiscal. Para ler o resultado, chame `obterArquivo` ou `obterRelatorio` com o `id` do recurso.\n\nResponda com qualquer 2xx para confirmar. Entrega perdida pode ser repetida com `reenviarEvento`; trate `webhook-id` como chave de deduplicação.",
        "parameters": [
          {
            "name": "webhook-id",
            "in": "header",
            "description": "Identificador único da entrega (igual a `id` do evento no corpo). Use para descartar entregas repetidas.",
            "schema": {
              "type": "string"
            },
            "required": true,
            "example": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN"
          },
          {
            "name": "webhook-timestamp",
            "in": "header",
            "description": "Momento do envio, em segundos desde 1970 (UTC). Recuse entregas com horário muito distante do seu relógio.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "required": true,
            "example": "1793284930"
          },
          {
            "name": "webhook-signature",
            "in": "header",
            "description": "Assinatura HMAC-SHA256 em Base64, no formato `v1,<assinatura>`. Pode vir mais de uma, separadas por espaço (durante a troca de segredo). Veja o texto da operação.",
            "schema": {
              "type": "string"
            },
            "required": true,
            "example": "v1,Zm9vYmFyYmF6cXV4Zm9vYmFyYmF6cXV4Zm9vYmFyYmE="
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventoWebhook"
              },
              "examples": {
                "evento": {
                  "summary": "Evento arquivo.rejeitado",
                  "value": {
                    "id": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN",
                    "tipo": "arquivo.rejeitado",
                    "criado_em": "2026-10-29T11:41:12-03:00",
                    "recurso": {
                      "tipo": "arquivo",
                      "id": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP"
                    },
                    "estado": "rejeitado"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Entrega confirmada. Qualquer resposta 2xx vale."
          }
        }
      }
    },
    "relatorioConcluido": {
      "post": {
        "operationId": "receberEventoRelatorioConcluido",
        "summary": "Evento relatorio.concluido",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "description": "Enviado quando um relatório termina, com `estado` `concluido` ou `concluido_sem_dados`.\n\n**Quando não usar:** esta é a entrega que a RMF faz no seu endereço; você não chama esta operação, você a implementa no seu servidor.\n\n**Como validar a entrega (Standard Webhooks).** Monte o texto `webhook-id` + `.` + `webhook-timestamp` + `.` + corpo bruto (os bytes exatos recebidos, antes de interpretar o JSON). Calcule o HMAC-SHA256 desse texto usando como chave os bytes do segredo `whsec_...` (a parte depois do prefixo `whsec_`, decodificada de Base64) e compare, em tempo constante, com cada assinatura `v1,...` do cabeçalho `webhook-signature`.\n\nO corpo leva só o identificador, o tipo do evento e o estado: nunca dado fiscal. Para ler o resultado, chame `obterArquivo` ou `obterRelatorio` com o `id` do recurso.\n\nResponda com qualquer 2xx para confirmar. Entrega perdida pode ser repetida com `reenviarEvento`; trate `webhook-id` como chave de deduplicação.",
        "parameters": [
          {
            "name": "webhook-id",
            "in": "header",
            "description": "Identificador único da entrega (igual a `id` do evento no corpo). Use para descartar entregas repetidas.",
            "schema": {
              "type": "string"
            },
            "required": true,
            "example": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN"
          },
          {
            "name": "webhook-timestamp",
            "in": "header",
            "description": "Momento do envio, em segundos desde 1970 (UTC). Recuse entregas com horário muito distante do seu relógio.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "required": true,
            "example": "1793284930"
          },
          {
            "name": "webhook-signature",
            "in": "header",
            "description": "Assinatura HMAC-SHA256 em Base64, no formato `v1,<assinatura>`. Pode vir mais de uma, separadas por espaço (durante a troca de segredo). Veja o texto da operação.",
            "schema": {
              "type": "string"
            },
            "required": true,
            "example": "v1,Zm9vYmFyYmF6cXV4Zm9vYmFyYmF6cXV4Zm9vYmFyYmE="
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventoWebhook"
              },
              "examples": {
                "evento": {
                  "summary": "Evento relatorio.concluido",
                  "value": {
                    "id": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN",
                    "tipo": "relatorio.concluido",
                    "criado_em": "2026-10-29T11:41:12-03:00",
                    "recurso": {
                      "tipo": "relatorio",
                      "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
                    },
                    "estado": "concluido"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Entrega confirmada. Qualquer resposta 2xx vale."
          }
        }
      }
    },
    "relatorioFalhou": {
      "post": {
        "operationId": "receberEventoRelatorioFalhou",
        "summary": "Evento relatorio.falhou",
        "tags": [
          "Webhooks"
        ],
        "security": [],
        "description": "Enviado quando um relatório termina com erro. O erro está em `obterRelatorio`.\n\n**Quando não usar:** esta é a entrega que a RMF faz no seu endereço; você não chama esta operação, você a implementa no seu servidor.\n\n**Como validar a entrega (Standard Webhooks).** Monte o texto `webhook-id` + `.` + `webhook-timestamp` + `.` + corpo bruto (os bytes exatos recebidos, antes de interpretar o JSON). Calcule o HMAC-SHA256 desse texto usando como chave os bytes do segredo `whsec_...` (a parte depois do prefixo `whsec_`, decodificada de Base64) e compare, em tempo constante, com cada assinatura `v1,...` do cabeçalho `webhook-signature`.\n\nO corpo leva só o identificador, o tipo do evento e o estado: nunca dado fiscal. Para ler o resultado, chame `obterArquivo` ou `obterRelatorio` com o `id` do recurso.\n\nResponda com qualquer 2xx para confirmar. Entrega perdida pode ser repetida com `reenviarEvento`; trate `webhook-id` como chave de deduplicação.",
        "parameters": [
          {
            "name": "webhook-id",
            "in": "header",
            "description": "Identificador único da entrega (igual a `id` do evento no corpo). Use para descartar entregas repetidas.",
            "schema": {
              "type": "string"
            },
            "required": true,
            "example": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN"
          },
          {
            "name": "webhook-timestamp",
            "in": "header",
            "description": "Momento do envio, em segundos desde 1970 (UTC). Recuse entregas com horário muito distante do seu relógio.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "required": true,
            "example": "1793284930"
          },
          {
            "name": "webhook-signature",
            "in": "header",
            "description": "Assinatura HMAC-SHA256 em Base64, no formato `v1,<assinatura>`. Pode vir mais de uma, separadas por espaço (durante a troca de segredo). Veja o texto da operação.",
            "schema": {
              "type": "string"
            },
            "required": true,
            "example": "v1,Zm9vYmFyYmF6cXV4Zm9vYmFyYmF6cXV4Zm9vYmFyYmE="
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventoWebhook"
              },
              "examples": {
                "evento": {
                  "summary": "Evento relatorio.falhou",
                  "value": {
                    "id": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN",
                    "tipo": "relatorio.falhou",
                    "criado_em": "2026-10-29T11:41:12-03:00",
                    "recurso": {
                      "tipo": "relatorio",
                      "id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
                    },
                    "estado": "falhou"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Entrega confirmada. Qualquer resposta 2xx vale."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "rmf_live_ ou rmf_test_, 75 caracteres",
        "description": "Chave de API opaca no cabeçalho `Authorization: Bearer <chave>`. Nunca na URL.\n\n**Emissão.** A chave é emitida pela equipe da RMF; peça pelo seu contato comercial. Não há cadastro nem emissão de chave pelo portal ou pela API. Para revogar ou trocar uma chave, peça também à equipe. A chave completa é entregue uma vez e não pode ser recuperada depois.\n\n**Formato da chave:** começa com `rmf_live_` (produção) ou `rmf_test_` (sandbox) e tem sempre 75 caracteres. Trate o resto como opaco: não monte, não corte e não valide além do prefixo e do comprimento. O início da chave (prefixo e identificador) aparece em `GET /conta/chaves`. Chave `rmf_test_` não funciona em produção e a `rmf_live_` não funciona no sandbox.\n\n**Padrão para busca de segredo** (para ferramentas que procuram chaves vazadas em código e log): `rmf_(live|test)_[0-9A-HJKMNP-TV-Z]{26}_[0-9A-Za-z]{32}_[0-9A-Za-z]{6}`.\n\n**Escopos.** Cada chave carrega uma lista de escopos; a operação exige um deles, e sem ele a resposta é 403 `escopo_insuficiente`.\n\n| Escopo | Libera |\n|---|---|\n| `consultas:ler` | Consultas de ICMS e da Reforma Tributária |\n| `empresas:escrever` | Cadastrar empresas |\n| `empresas:ler` | Listar e ler empresas |\n| `arquivos:enviar` | Enviar arquivos (e, depois do lançamento, excluir) |\n| `arquivos:ler` | Listar e ler arquivos |\n| `relatorios:criar` | Criar e cancelar relatórios |\n| `relatorios:ler` | Ler relatórios, catálogo e baixar a planilha de resultado |\n| `webhooks:gerenciar` | Criar, listar, testar e excluir webhooks; reenviar eventos |\n| `conta:ler` | Ler a conta, o plano, o uso e as chaves |\n| `captura:receita` | Pedir e acompanhar consultas de arquivos na Receita (depois do lançamento, por plano) |\n| `captura:notas` | Pedir e acompanhar lotes de NF-e por chave (depois do lançamento, por plano) |\n| `captura:dfe` | Listar documentos fiscais emitidos contra as empresas e ver a validade dos certificados (depois do lançamento, por plano) |\n\n`GET /jobs/{id}` exige o escopo do recurso que o trabalho produz (`arquivos:ler`, `relatorios:ler`, `captura:receita` ou `captura:notas`).\n\n**Plano.** O plano da conta limita os escopos que as chaves podem receber. `conta:ler` e `empresas:ler` estão em todo plano; `webhooks:gerenciar` está nos planos com produto assíncrono (arquivos, relatórios ou captura). Operação de um produto que o plano não inclui responde 403 `fora_do_plano`, mesmo que a chave tenha o escopo (por exemplo, se o plano mudou depois que a chave foi criada). A diferença para `escopo_insuficiente`: em `fora_do_plano` a solução é mudar o plano com a equipe da RMF; em `escopo_insuficiente` o plano permite, e basta usar uma chave com o escopo. Quando as duas situações valem ao mesmo tempo, a resposta é `fora_do_plano`. Os escopos permitidos estão em `GET /conta`."
      }
    },
    "schemas": {
      "Ncm": {
        "type": "string",
        "pattern": "^[0-9]{8}$",
        "description": "NCM com 8 dígitos, sem ponto.",
        "example": "85414121"
      },
      "Uf": {
        "type": "string",
        "enum": [
          "AC",
          "AL",
          "AP",
          "AM",
          "BA",
          "CE",
          "DF",
          "ES",
          "GO",
          "MA",
          "MT",
          "MS",
          "MG",
          "PA",
          "PB",
          "PR",
          "PE",
          "PI",
          "RJ",
          "RN",
          "RS",
          "RO",
          "RR",
          "SC",
          "SP",
          "SE",
          "TO"
        ],
        "description": "Sigla da unidade da federação, em maiúsculas.",
        "example": "SP"
      },
      "Competencia": {
        "type": "string",
        "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$",
        "description": "Mês de competência no formato `AAAA-MM`.",
        "example": "2026-10"
      },
      "AliquotaPercentual": {
        "type": "number",
        "minimum": 0,
        "maximum": 100,
        "description": "Alíquota em percentual: `18` é 18%, `13.3` é 13,3%.",
        "example": 18
      },
      "OrigemAliquotaIcms": {
        "type": "string",
        "enum": [
          "lei",
          "padrao_uf",
          "pratica_mercado"
        ],
        "description": "De onde vem a alíquota.\n\n- `lei`: mapa da lei do estado, por NCM. Hoje só existe para SP.\n- `padrao_uf`: alíquota padrão do estado, usada quando não há mapa da lei para o NCM ou para o estado.\n- `pratica_mercado`: valor observado na prática de mercado, agregado de várias empresas e sem identificar nenhuma. Não substitui a lei: confirme antes de usar em apuração. Vem de um mínimo de redes distintas definido pela RMF; a RMF nunca expõe empresa, quantidade nem quais. A mesma célula (NCM, UF, mês) dá o mesmo valor para qualquer conta. Esse valor pode ainda não aparecer: depende do aval do jurídico.",
        "example": "lei"
      },
      "ConfiancaIcms": {
        "type": "string",
        "enum": [
          "confirmada",
          "provavel",
          "a_confirmar"
        ],
        "description": "Quanto a base confia no resultado do período inteiro. É a mais fraca entre a confiança na alíquota interna e a confiança na situação na ST; quando `na_st` é `false`, a incerteza da ST não entra e só a interna conta.\n\n- `confirmada`: a lei decide o NCM sem dúvida.\n- `provavel`: a lei decide, mas a descrição do produto não foi conferida (o NCM está na ST apenas pelo código).\n- `a_confirmar`: a base não decide sozinha; o valor é a melhor estimativa; confirme antes de usar em apuração.\n\nCombinações válidas (`origem` descreve a `aliquota_interna`; outras combinações não ocorrem):\n\n| `origem` | `na_st` | `confianca` | O que quer dizer |\n|---|---|---|---|\n| `lei` | `true` | `confirmada` | a lei decide a alíquota interna e o NCM está na lista de ST |\n| `lei` | `true` | `provavel` | a lei decide a alíquota interna; o NCM está na lista de ST pelo código, sem a descrição do produto conferida |\n| `lei` | `true` | `a_confirmar` | o NCM está (ou provavelmente está) na lista de ST, mas a base não decide sozinha a alíquota interna |\n| `lei` | `false` | `confirmada` | a lei decide a alíquota interna; o NCM está fora da lista de ST |\n| `lei` | `false` | `a_confirmar` | o NCM está fora da lista de ST, mas a base não decide sozinha a alíquota interna |\n| `padrao_uf` | `true` | `a_confirmar` | estado com mapa da lei (hoje SP): o NCM está na lista de ST, mas não tem regra própria no mapa; vale a alíquota padrão do estado como interna |\n| `padrao_uf` | `false` | `a_confirmar` | estado com mapa da lei (hoje SP), mas sem regra própria para o NCM: vale a alíquota padrão do estado, fora da lista de ST |\n| `padrao_uf` | `null` | `a_confirmar` | estado sem mapa da lei (hoje todos fora de SP): alíquota padrão do estado; lista de ST não avaliada |\n| `pratica_mercado` | `true`, `false` ou `null` | `a_confirmar` | a lei não decide sozinha e o valor vem da prática de mercado (pode ainda não aparecer) |\n\nO `texto_curto` diz qual parte deixa o resultado `a_confirmar` quando é a alíquota interna (\"alíquota interna a confirmar\").",
        "example": "confirmada"
      },
      "PeriodoAliquotaIcms": {
        "type": "object",
        "description": "Intervalo contínuo de competências em que a resposta é a mesma.",
        "required": [
          "inicio",
          "fim",
          "aliquota_interna",
          "aliquota_st",
          "na_st",
          "situacao_st",
          "origem",
          "confianca",
          "texto_curto"
        ],
        "properties": {
          "inicio": {
            "$ref": "#/components/schemas/Competencia",
            "description": "Primeira competência do período."
          },
          "fim": {
            "$ref": "#/components/schemas/Competencia",
            "description": "Última competência do período (inclusive)."
          },
          "aliquota_interna": {
            "$ref": "#/components/schemas/AliquotaPercentual",
            "description": "Alíquota interna da operação própria do NCM na UF, pela lei do estado. Quando não há mapa da lei para o NCM ou para o estado, é a alíquota padrão do estado. Vem sempre, dentro ou fora da ST."
          },
          "aliquota_st": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/AliquotaPercentual"
              },
              {
                "type": "null"
              }
            ],
            "description": "Alíquota que a substituição tributária usa para o NCM quando `na_st` é `true`. Pode ser diferente de `aliquota_interna` (em SP, alguns NCMs têm interna 12 e ST 18, por exemplo). `null` quando `na_st` é `false` (fora da ST) ou `null` (lista de ST não avaliada). Nunca vem `0` para dizer \"fora da ST\".",
            "example": 12
          },
          "na_st": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Se o NCM está na lista de substituição tributária no período. `true` para NCM na lista ou provável na lista; `false` para fora da lista; `null` quando a base não avalia a lista de ST naquele estado (hoje, qualquer UF diferente de SP).",
            "example": true
          },
          "situacao_st": {
            "type": "string",
            "description": "Texto da situação em relação à ST, para exibir ao usuário. Não use para decidir em código: use `na_st`.",
            "example": "Na lista de ST"
          },
          "origem": {
            "$ref": "#/components/schemas/OrigemAliquotaIcms"
          },
          "confianca": {
            "$ref": "#/components/schemas/ConfiancaIcms"
          },
          "texto_curto": {
            "type": "string",
            "description": "Frase única que resume origem, situação na ST e confiança, para exibir ao usuário. Não traz artigo de lei.",
            "example": "Lei do estado; na lista de ST; confirmada"
          }
        }
      },
      "ConsultaAliquotaIcms": {
        "type": "object",
        "required": [
          "ncm",
          "uf",
          "versao_base",
          "periodos"
        ],
        "description": "Alíquota do NCM na UF, mês a mês, agrupada em períodos iguais e em ordem cronológica.",
        "properties": {
          "ncm": {
            "$ref": "#/components/schemas/Ncm"
          },
          "uf": {
            "$ref": "#/components/schemas/Uf"
          },
          "versao_base": {
            "type": "string",
            "description": "Identificador da versão da base de alíquotas usada. Muda quando a base é atualizada; guarde-o junto do resultado.",
            "example": "e964db7cb0e8"
          },
          "periodos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PeriodoAliquotaIcms"
            }
          }
        }
      },
      "ErroCampo": {
        "type": "object",
        "required": [
          "campo",
          "mensagem"
        ],
        "properties": {
          "campo": {
            "type": "string",
            "description": "Nome do parâmetro ou caminho do campo no corpo.",
            "example": "ncm"
          },
          "mensagem": {
            "type": "string",
            "example": "Use 8 dígitos, sem ponto."
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "Erro no formato RFC 9457 (`application/problem+json`). O campo `codigo` é estável e faz parte do contrato: use-o no código, não o texto de `title` ou `detail`.",
        "required": [
          "type",
          "title",
          "status",
          "codigo",
          "request_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Endereço da página do erro no portal do desenvolvedor.",
            "example": "https://docs.rmfestrategia.com/erros/parametro_invalido"
          },
          "title": {
            "type": "string",
            "description": "Resumo curto do erro.",
            "example": "Parâmetro inválido"
          },
          "status": {
            "type": "integer",
            "description": "Código HTTP.",
            "example": 422
          },
          "detail": {
            "type": "string",
            "description": "Explicação para quem desenvolve a integração.",
            "example": "O NCM deve ter 8 dígitos, sem ponto."
          },
          "instance": {
            "type": "string",
            "description": "Caminho da chamada que gerou o erro.",
            "example": "/v1/icms/aliquotas"
          },
          "codigo": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_]*$",
            "description": "Código estável do erro, em `snake_case`. Catálogo único em todo o contrato.",
            "example": "parametro_invalido"
          },
          "request_id": {
            "type": "string",
            "description": "Identificador da chamada, igual ao cabeçalho `X-Request-Id`. Opaco, começa com `req_`; não dependa do resto do formato. No 413 que o servidor de entrada devolve antes de o pedido chegar à API (corpo sem tamanho declarado ou com tamanho falso), vem `req_` seguido de um UUID.",
            "example": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
          },
          "erros": {
            "type": "array",
            "description": "Detalhe por campo. Aparece em 422.",
            "items": {
              "$ref": "#/components/schemas/ErroCampo"
            }
          }
        }
      },
      "ItemConsultaIcms": {
        "type": "object",
        "required": [
          "ncm",
          "uf",
          "competencia"
        ],
        "description": "Um par a consultar. Os campos são texto livre no pedido de propósito: valor fora do formato não recusa o lote, só o item.",
        "properties": {
          "ncm": {
            "type": "string",
            "description": "NCM com 8 dígitos, sem ponto.",
            "example": "85414121"
          },
          "uf": {
            "type": "string",
            "description": "Sigla da UF, em maiúsculas.",
            "example": "SP"
          },
          "competencia": {
            "type": "string",
            "description": "Competência `AAAA-MM`.",
            "example": "2026-10"
          }
        }
      },
      "LoteConsultaIcms": {
        "type": "object",
        "required": [
          "itens"
        ],
        "properties": {
          "itens": {
            "type": "array",
            "minItems": 1,
            "maxItems": 500,
            "description": "Pares (NCM, UF, competência), de 1 a 500.",
            "items": {
              "$ref": "#/components/schemas/ItemConsultaIcms"
            }
          }
        }
      },
      "ResultadoItemIcms": {
        "type": "object",
        "required": [
          "indice",
          "ncm",
          "uf",
          "competencia"
        ],
        "description": "Resposta de um item do lote. Traz os campos de alíquota, ou `erro` quando o item não passou na validação (o lote não falha por causa de um item). Sem `erro`, `aliquota_interna`, `aliquota_st`, `na_st`, `situacao_st`, `origem`, `confianca` e `texto_curto` vêm sempre.",
        "properties": {
          "indice": {
            "type": "integer",
            "minimum": 0,
            "description": "Posição do item no pedido, a partir de 0.",
            "example": 0
          },
          "ncm": {
            "type": "string",
            "description": "NCM como enviado.",
            "example": "33049990"
          },
          "uf": {
            "type": "string",
            "description": "UF como enviada.",
            "example": "SP"
          },
          "competencia": {
            "type": "string",
            "description": "Competência como enviada.",
            "example": "2026-03"
          },
          "aliquota_interna": {
            "$ref": "#/components/schemas/AliquotaPercentual",
            "description": "Mesmo significado de `periodos[].aliquota_interna`. Presente sempre que o item não tem `erro`."
          },
          "aliquota_st": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/AliquotaPercentual"
              },
              {
                "type": "null"
              }
            ],
            "description": "Mesmo significado de `periodos[].aliquota_st`. `null` quando `na_st` é `false` ou `null`; nunca `0`.",
            "example": 18
          },
          "na_st": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Mesmo significado de `periodos[].na_st`.",
            "example": true
          },
          "situacao_st": {
            "type": "string",
            "example": "Na lista de ST"
          },
          "origem": {
            "$ref": "#/components/schemas/OrigemAliquotaIcms"
          },
          "confianca": {
            "$ref": "#/components/schemas/ConfiancaIcms"
          },
          "texto_curto": {
            "type": "string",
            "example": "Lei do estado; na lista de ST; confirmada"
          },
          "erro": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "codigo",
                  "mensagem"
                ],
                "properties": {
                  "codigo": {
                    "type": "string",
                    "enum": [
                      "ncm_invalido",
                      "uf_invalida",
                      "competencia_invalida"
                    ],
                    "description": "Por que o item foi recusado. O campo inválido está no nome do código.",
                    "example": "ncm_invalido"
                  },
                  "mensagem": {
                    "type": "string",
                    "example": "Use 8 dígitos, sem ponto."
                  }
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Preenchido só quando o item foi recusado."
          }
        }
      },
      "RespostaLoteIcms": {
        "type": "object",
        "required": [
          "versao_base",
          "resultados"
        ],
        "properties": {
          "versao_base": {
            "type": "string",
            "example": "e964db7cb0e8"
          },
          "resultados": {
            "type": "array",
            "description": "Uma entrada por item do pedido, na mesma ordem.",
            "items": {
              "$ref": "#/components/schemas/ResultadoItemIcms"
            }
          }
        }
      },
      "Data": {
        "type": "string",
        "format": "date",
        "description": "Data no formato `AAAA-MM-DD`.",
        "example": "2026-10-29"
      },
      "CoberturaIcmsUf": {
        "type": "object",
        "required": [
          "uf",
          "cobertura",
          "aliquota_padrao",
          "vigente_desde",
          "ncms_no_mapa"
        ],
        "properties": {
          "uf": {
            "$ref": "#/components/schemas/Uf"
          },
          "cobertura": {
            "type": "string",
            "enum": [
              "mapa_lei",
              "padrao_uf"
            ],
            "description": "`mapa_lei`: há mapa da lei por NCM (alíquota e lista de ST). `padrao_uf`: só a alíquota padrão do estado; toda resposta sai com confiança `a_confirmar`.",
            "example": "mapa_lei"
          },
          "aliquota_padrao": {
            "$ref": "#/components/schemas/AliquotaPercentual",
            "description": "Alíquota padrão do estado na competência de referência."
          },
          "vigente_desde": {
            "$ref": "#/components/schemas/Data",
            "description": "Início da vigência da alíquota padrão atual."
          },
          "ncms_no_mapa": {
            "type": "integer",
            "minimum": 0,
            "description": "Quantidade de NCMs com regra própria no mapa da lei. `0` quando a cobertura é só a padrão.",
            "example": 901
          }
        }
      },
      "CoberturaIcms": {
        "type": "object",
        "required": [
          "versao_base",
          "atualizada_em",
          "referencia",
          "dados"
        ],
        "properties": {
          "versao_base": {
            "type": "string",
            "example": "e964db7cb0e8"
          },
          "atualizada_em": {
            "$ref": "#/components/schemas/Data",
            "description": "Data da versão da base."
          },
          "referencia": {
            "$ref": "#/components/schemas/Competencia",
            "description": "Competência a que se refere a alíquota padrão."
          },
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CoberturaIcmsUf"
            }
          }
        }
      },
      "ReferenciaAliquotaReforma": {
        "type": [
          "object",
          "null"
        ],
        "description": "Alíquota de referência do ano usada para calcular `aliquota_ibs` e `aliquota_cbs` das linhas. `null` quando não há série para o ano.",
        "required": [
          "ano",
          "aliquota_ibs",
          "aliquota_cbs",
          "fonte"
        ],
        "properties": {
          "ano": {
            "type": "integer",
            "example": 2033
          },
          "aliquota_ibs": {
            "type": "number",
            "description": "IBS de referência, em percentual.",
            "example": 17.7
          },
          "aliquota_cbs": {
            "type": "number",
            "description": "CBS de referência, em percentual.",
            "example": 8.8
          },
          "fonte": {
            "type": "string",
            "example": "tabela de alíquotas de referência"
          }
        }
      },
      "BeneficioReforma": {
        "type": "string",
        "enum": [
          "aliquota_zero",
          "reducao_60",
          "reducao_30",
          "imposto_seletivo"
        ],
        "description": "Tipo de tratamento que a LC 214/2025 dá à linha da tabela.\n\n- `aliquota_zero`: alíquota reduzida a zero.\n- `reducao_60`: redução de 60% da alíquota.\n- `reducao_30`: redução de 30% da alíquota.\n- `imposto_seletivo`: bem ou serviço sujeito ao Imposto Seletivo.",
        "example": "aliquota_zero"
      },
      "CaminhoReforma": {
        "type": "string",
        "enum": [
          "exato",
          "herdado",
          "capitulo",
          "filhos"
        ],
        "description": "Como a linha casou com o código pedido.\n\n- `exato`: a linha é exatamente o código pedido.\n- `herdado`: a linha é de um código mais genérico (posição ou subposição) que contém o pedido.\n- `capitulo`: a lei cita o capítulo do código.\n- `filhos`: a linha é de um código mais específico dentro do pedido.",
        "example": "herdado"
      },
      "EnquadramentoReforma": {
        "type": "object",
        "description": "Uma linha da tabela de NCM e NBS da LC 214/2025. A tabela mostra o que a lei diz; não decide por você qual linha vale para o seu produto: leia `excecao` e `observacoes`.",
        "required": [
          "codigo",
          "codigo_exibicao",
          "tipo_codigo",
          "descricao",
          "lei",
          "anexo",
          "item",
          "beneficio"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "description": "Código sem pontuação (de 2 a 9 dígitos).",
            "example": "10062"
          },
          "codigo_exibicao": {
            "type": "string",
            "description": "Código como aparece na lei, com pontuação.",
            "example": "1006.2"
          },
          "tipo_codigo": {
            "type": "string",
            "enum": [
              "NCM",
              "NBS"
            ],
            "description": "NCM para mercadorias, NBS para serviços e intangíveis.",
            "example": "NCM"
          },
          "descricao": {
            "type": "string",
            "example": "Arroz das subposições"
          },
          "lei": {
            "type": "string",
            "example": "LC 214/2025"
          },
          "anexo": {
            "type": "string",
            "description": "Anexo (ou capítulo) da lei em que a linha está.",
            "example": "ANEXO I"
          },
          "item": {
            "type": "integer",
            "description": "Número do item dentro do anexo.",
            "example": 1
          },
          "grupo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Título do grupo no anexo."
          },
          "classificacao": {
            "type": [
              "string",
              "null"
            ],
            "description": "Classe da linha (por exemplo, produtos para alimentação humana)."
          },
          "beneficio": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/BeneficioReforma"
              },
              {
                "type": "null"
              }
            ],
            "description": "Tipo de tratamento da linha. `null` quando o tipo da linha não é um dos quatro mapeados."
          },
          "reducao_percentual": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Redução da alíquota em pontos percentuais. `null` nas linhas do Imposto Seletivo.",
            "example": 100
          },
          "aliquota_ibs": {
            "type": [
              "number",
              "null"
            ],
            "description": "Alíquota do IBS em percentual no ano de `referencia`, já com a redução da linha. `null` quando não se aplica à linha ou quando não há série de referência.",
            "example": 0
          },
          "aliquota_cbs": {
            "type": [
              "number",
              "null"
            ],
            "description": "Alíquota da CBS em percentual no ano de `referencia`, já com a redução da linha. `null` nas mesmas condições de `aliquota_ibs`.",
            "example": 0
          },
          "aliquota_is": {
            "type": [
              "number",
              "null"
            ],
            "description": "Alíquota do Imposto Seletivo em percentual, como consta na tabela de origem. Só nas linhas do Imposto Seletivo. A lei ordinária que fixa as alíquotas do IS ainda não foi publicada: trate como referência, não como alíquota legal.",
            "example": 30
          },
          "excecao": {
            "type": [
              "string",
              "null"
            ],
            "description": "Ressalva que tira parte dos produtos do código do benefício."
          },
          "observacoes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observações da lei sobre a linha."
          },
          "cst": {
            "type": [
              "string",
              "null"
            ],
            "description": "Código de Situação Tributária de IBS e CBS sugerido para o anexo e o tipo de benefício. `null` quando não há mapeamento.",
            "example": "200"
          },
          "cclasstrib": {
            "type": [
              "string",
              "null"
            ],
            "description": "Código de classificação tributária (cClassTrib) sugerido para o anexo e o tipo de benefício. `null` quando não há mapeamento.",
            "example": "200003"
          },
          "fundamento_legal": {
            "type": [
              "string",
              "null"
            ],
            "description": "Artigo que fundamenta o cClassTrib.",
            "example": "Art. 125"
          },
          "caminho": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CaminhoReforma"
              },
              {
                "type": "null"
              }
            ],
            "description": "Como a linha casou com a busca. `null` quando a listagem não foi feita por código."
          }
        }
      },
      "ConsultaNcmReforma": {
        "type": "object",
        "required": [
          "codigo",
          "tipo_codigo",
          "referencia",
          "enquadramentos"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "description": "Código pedido.",
            "example": "10062010"
          },
          "tipo_codigo": {
            "type": "string",
            "enum": [
              "NCM",
              "NBS"
            ],
            "example": "NCM"
          },
          "referencia": {
            "$ref": "#/components/schemas/ReferenciaAliquotaReforma"
          },
          "enquadramentos": {
            "type": "array",
            "description": "Linhas da tabela que alcançam o código, ordenadas da mais específica para a mais genérica. Lista vazia quer dizer que a tabela não traz benefício para o código; não confirma tributação integral.",
            "items": {
              "$ref": "#/components/schemas/EnquadramentoReforma"
            }
          }
        }
      },
      "ProximoCursor": {
        "type": [
          "string",
          "null"
        ],
        "description": "Cursor opaco da próxima página. `null` quando não há mais páginas. Envie-o em `?cursor=`, sem alterar. Só vale o cursor emitido pela API; um valor inventado ou alterado responde 422 `parametro_invalido`.",
        "example": "<valor opaco devolvido pela API>"
      },
      "BuscaNcmReforma": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor",
          "total"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EnquadramentoReforma"
            }
          },
          "proximo_cursor": {
            "$ref": "#/components/schemas/ProximoCursor"
          },
          "total": {
            "type": "integer",
            "description": "Total de linhas que casam com o filtro, em todas as páginas.",
            "example": 2
          },
          "referencia": {
            "$ref": "#/components/schemas/ReferenciaAliquotaReforma"
          }
        }
      },
      "AliquotaReformaAno": {
        "type": "object",
        "required": [
          "ano",
          "aliquota_cbs",
          "aliquota_ibs",
          "aliquota_is",
          "fonte",
          "base_legal"
        ],
        "properties": {
          "ano": {
            "type": "integer",
            "minimum": 2026,
            "maximum": 2033,
            "example": 2026
          },
          "aliquota_cbs": {
            "type": "number",
            "description": "Alíquota de referência da CBS no ano, em percentual.",
            "example": 0.9
          },
          "aliquota_ibs": {
            "type": "number",
            "description": "Alíquota de referência do IBS no ano, em percentual (soma de estadual e municipal).",
            "example": 0.1
          },
          "aliquota_is": {
            "type": [
              "number",
              "null"
            ],
            "description": "Alíquota de referência do Imposto Seletivo. `null` enquanto não houver alíquota legal para o ano.",
            "example": null
          },
          "fonte": {
            "type": "string",
            "description": "Origem do número.",
            "example": "tabela de alíquotas de referência"
          },
          "base_legal": {
            "type": "string",
            "example": "LC 214/2025, arts. 343 e 346 (ADCT art. 125)"
          },
          "vigencia_inicio": {
            "$ref": "#/components/schemas/Data",
            "description": "Início da vigência desta linha da série."
          }
        }
      },
      "ListaAliquotasReforma": {
        "type": "object",
        "required": [
          "dados"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "description": "Um item por ano, em ordem crescente.",
            "items": {
              "$ref": "#/components/schemas/AliquotaReformaAno"
            }
          }
        }
      },
      "ValorDecimal": {
        "type": "string",
        "pattern": "^-?[0-9]+(\\.[0-9]+)?$",
        "description": "Valor monetário como texto decimal com ponto, sem separador de milhar.",
        "example": "1234.56"
      },
      "AliquotasAtuais": {
        "type": "object",
        "description": "Alíquotas que a empresa paga hoje, em percentual.",
        "required": [
          "pis",
          "cofins",
          "is"
        ],
        "properties": {
          "pis": {
            "type": "number",
            "minimum": 0,
            "exclusiveMaximum": 100,
            "example": 1.65
          },
          "cofins": {
            "type": "number",
            "minimum": 0,
            "exclusiveMaximum": 100,
            "example": 7.6
          },
          "icms": {
            "type": "number",
            "minimum": 0,
            "exclusiveMaximum": 100,
            "description": "Obrigatório quando `perfil` é `industria`.",
            "example": 18
          },
          "ipi": {
            "type": "number",
            "minimum": 0,
            "exclusiveMaximum": 100,
            "description": "Obrigatório quando `perfil` é `industria`.",
            "example": 5
          },
          "iss": {
            "type": "number",
            "minimum": 0,
            "exclusiveMaximum": 100,
            "description": "Obrigatório quando `perfil` é `servicos`.",
            "example": 5
          },
          "is": {
            "type": "number",
            "minimum": 0,
            "exclusiveMaximum": 100,
            "description": "Imposto Seletivo; `0` se o produto não é sujeito.",
            "example": 0
          }
        }
      },
      "SimulacaoTransicaoRequisicao": {
        "type": "object",
        "required": [
          "perfil",
          "receita_liquida",
          "aliquotas_atuais"
        ],
        "properties": {
          "perfil": {
            "type": "string",
            "enum": [
              "industria",
              "servicos"
            ],
            "description": "`industria` inclui o comércio (ICMS e IPI); `servicos` usa o ISS.",
            "example": "industria"
          },
          "receita_liquida": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Receita líquida do período simulado, em reais."
          },
          "aliquotas_atuais": {
            "$ref": "#/components/schemas/AliquotasAtuais"
          },
          "fator_beneficio_ibs": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "default": 1,
            "description": "Fração da alíquota de referência do IBS efetivamente devida, pelo mix de produtos (1 = alíquota cheia).",
            "example": 1
          },
          "fator_beneficio_cbs": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "default": 1,
            "description": "Fração da alíquota de referência da CBS efetivamente devida, pelo mix de produtos (1 = alíquota cheia).",
            "example": 1
          }
        }
      },
      "AnoTransicao": {
        "type": "object",
        "required": [
          "ano",
          "valor_is",
          "valor_cbs",
          "valor_ibs",
          "total_nfe",
          "variacao_2026_percentual"
        ],
        "description": "Resultado de um ano. Tributo que já acabou naquele ano vem com valor `\"0.00\"`. Só valores finais; a memória de cálculo do ano não sai.",
        "properties": {
          "ano": {
            "type": "integer",
            "example": 2027
          },
          "valor_is": {
            "$ref": "#/components/schemas/ValorDecimal"
          },
          "valor_cbs": {
            "$ref": "#/components/schemas/ValorDecimal"
          },
          "valor_ibs": {
            "$ref": "#/components/schemas/ValorDecimal"
          },
          "valor_pis_cofins": {
            "$ref": "#/components/schemas/ValorDecimal"
          },
          "valor_icms": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Só no perfil `industria`."
          },
          "valor_ipi": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Só no perfil `industria`."
          },
          "valor_iss": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Só no perfil `servicos`."
          },
          "total_nfe": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Total da nota fiscal no ano."
          },
          "variacao_2026_percentual": {
            "type": "number",
            "description": "Variação do total da nota contra 2026, em percentual.",
            "example": 3.1
          }
        }
      },
      "SimulacaoTransicaoResposta": {
        "type": "object",
        "required": [
          "perfil",
          "resultados"
        ],
        "properties": {
          "perfil": {
            "type": "string",
            "enum": [
              "industria",
              "servicos"
            ]
          },
          "resultados": {
            "type": "array",
            "description": "Um item por ano, de 2026 a 2033.",
            "items": {
              "$ref": "#/components/schemas/AnoTransicao"
            }
          }
        }
      },
      "SimulacaoRegimesRequisicao": {
        "type": "object",
        "required": [
          "setor",
          "faturamento_mes"
        ],
        "properties": {
          "setor": {
            "type": "string",
            "enum": [
              "comercio",
              "servicos",
              "industria",
              "tecnologia",
              "saude",
              "educacao",
              "agronegocio"
            ],
            "description": "Setor da empresa. `saude`, `educacao` e `agronegocio` não têm presunção cadastrada no v1; pedir a comparação deles com `presumido` ou `real` responde 422 `parametro_invalido` (campo `setor`). Com só `simples` ela funciona.",
            "example": "comercio"
          },
          "faturamento_mes": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Faturamento médio mensal, em reais."
          },
          "folha_mes": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Folha de pagamento mensal. Sem ela, o resultado é menos preciso e a resposta traz um aviso."
          },
          "rbt12": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Receita bruta dos últimos 12 meses. Sem ela, o resultado é menos preciso e a resposta traz um aviso."
          },
          "anexo_simples": {
            "type": "string",
            "enum": [
              "I",
              "II",
              "III",
              "IV",
              "V"
            ],
            "description": "Anexo do Simples Nacional da atividade. Obrigatório quando `regimes` inclui `simples`, o que vale também quando `regimes` é omitido (o padrão são os três regimes). Sem ele, 422 `parametro_invalido`.",
            "example": "I"
          },
          "margem_percentual": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Margem de lucro, em percentual. Obrigatória quando `regimes` inclui `real`, o que vale também quando `regimes` é omitido (o padrão são os três regimes). Sem ela, 422 `parametro_invalido`.",
            "example": 8
          },
          "aliquota_icms_iss": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Alíquota de ICMS ou ISS da operação, em percentual. Sem ela, o resultado é menos preciso e a resposta traz um aviso."
          },
          "percentual_credito_pis_cofins": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Parcela do faturamento que gera crédito de PIS e COFINS, no Lucro Real."
          },
          "ano_referencia": {
            "type": "integer",
            "minimum": 2026,
            "maximum": 2033,
            "default": 2033,
            "description": "Ano da Reforma que a resposta devolve em `ano_referencia`. No v1 a comparação usa a lei de hoje (sem CBS nem IBS), então o ano não muda os valores dos regimes.",
            "example": 2033
          },
          "regimes": {
            "type": "array",
            "uniqueItems": true,
            "items": {
              "type": "string",
              "enum": [
                "simples",
                "presumido",
                "real"
              ]
            },
            "default": [
              "simples",
              "presumido",
              "real"
            ],
            "minItems": 2,
            "description": "Regimes a comparar. Padrão: os três. Informe pelo menos dois. Cada regime pede um campo: `simples` exige `anexo_simples` e `real` exige `margem_percentual`; sem eles a resposta é 422 `parametro_invalido` com o campo no `erros`. Para comparar só `presumido` e `real`, não precisa de anexo."
          }
        }
      },
      "ResultadoRegime": {
        "type": "object",
        "required": [
          "regime",
          "carga_mensal",
          "carga_percentual",
          "tributos"
        ],
        "properties": {
          "regime": {
            "type": "string",
            "enum": [
              "simples",
              "presumido",
              "real"
            ],
            "example": "presumido"
          },
          "carga_mensal": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Total mensal de tributos no regime."
          },
          "carga_percentual": {
            "type": "number",
            "description": "Carga total do regime sobre o faturamento informado, em percentual. É o resultado final, não uma alíquota aplicada na conta.",
            "example": 11.8
          },
          "tributos": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "tributo",
                "valor"
              ],
              "properties": {
                "tributo": {
                  "type": "string",
                  "description": "IRPJ, CSLL, PIS, COFINS, CPP, ICMS, IPI, ISS, CBS ou IBS.",
                  "example": "PIS"
                },
                "valor": {
                  "$ref": "#/components/schemas/ValorDecimal"
                }
              }
            }
          }
        }
      },
      "SimulacaoRegimesResposta": {
        "type": "object",
        "required": [
          "ano_referencia",
          "versao_tabelas",
          "versao_premissas",
          "regimes",
          "veredito",
          "avisos"
        ],
        "properties": {
          "ano_referencia": {
            "type": "integer",
            "example": 2033
          },
          "versao_tabelas": {
            "type": "string",
            "description": "Versão das tabelas do Simples usadas.",
            "example": "CGSN-140-2018"
          },
          "versao_premissas": {
            "type": "string",
            "description": "Versão do conjunto de premissas da RMF usado na simulação. Guarde junto do resultado.",
            "example": "v0-2026-09-13"
          },
          "regimes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResultadoRegime"
            }
          },
          "veredito": {
            "type": "object",
            "required": [
              "codigo",
              "texto",
              "diferenca"
            ],
            "properties": {
              "codigo": {
                "type": "string",
                "enum": [
                  "ficar",
                  "migrar",
                  "avaliar",
                  "obrigado_lr"
                ],
                "description": "`avaliar`: a diferença é pequena demais para recomendar; procure um contador. `obrigado_lr`: a receita do ano anterior passa do limite do Presumido.",
                "example": "avaliar"
              },
              "texto": {
                "type": "string",
                "example": "AVALIE COM CONTADOR"
              },
              "diferenca": {
                "$ref": "#/components/schemas/ValorDecimal",
                "description": "Diferença mensal entre os dois regimes comparados."
              }
            }
          },
          "avisos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "O que fazer para melhorar o resultado ou com que cuidado usá-lo: por exemplo, que campo informar. Não descrevem a conta."
          }
        }
      },
      "MarcoReforma": {
        "type": "object",
        "required": [
          "codigo",
          "data_inicio",
          "tipo",
          "titulo",
          "resumo",
          "tributos",
          "base_legal",
          "confianca",
          "tem_pendencia",
          "acoes"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "example": "M-2027-CBS-COBRANCA"
          },
          "data_inicio": {
            "$ref": "#/components/schemas/Data"
          },
          "data_fim": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Data"
              },
              {
                "type": "null"
              }
            ]
          },
          "tipo": {
            "type": "string",
            "enum": [
              "ano_teste",
              "credito",
              "escalonamento",
              "extincao",
              "inicio_cobranca",
              "prazo",
              "regime_pleno"
            ],
            "example": "inicio_cobranca"
          },
          "titulo": {
            "type": "string",
            "description": "Título público do marco, mantido pela equipe da RMF no catálogo (`titulo_publico`). Descreve a lei, nunca como a plataforma calcula."
          },
          "resumo": {
            "type": "string",
            "description": "Resumo público do marco, mantido pela equipe da RMF no catálogo (`resumo_publico`)."
          },
          "tributos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "CBS"
            ]
          },
          "base_legal": {
            "type": "string"
          },
          "confianca": {
            "type": "string",
            "enum": [
              "lei",
              "regulamento"
            ],
            "description": "`lei`: previsto em texto de lei. `regulamento`: depende de regulamentação e pode mudar.",
            "example": "lei"
          },
          "tem_pendencia": {
            "type": "boolean",
            "description": "`true` quando há ponto do marco ainda em aberto na interpretação ou na regulamentação.",
            "example": false
          },
          "acoes": {
            "type": "array",
            "description": "O que fazer por causa do marco, só as ações que valem para o perfil informado (sem perfil, só as que valem para todos).",
            "items": {
              "type": "object",
              "required": [
                "acao",
                "antecedencia_dias",
                "confianca",
                "tem_pendencia"
              ],
              "properties": {
                "acao": {
                  "type": "string"
                },
                "detalhe": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "antecedencia_dias": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Com quantos dias de antecedência convém agir.",
                  "example": 0
                },
                "base_legal": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "confianca": {
                  "type": "string",
                  "enum": [
                    "lei",
                    "regulamento",
                    "premissa"
                  ],
                  "example": "lei"
                },
                "tem_pendencia": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "AnoLinhaDoTempo": {
        "type": "object",
        "required": [
          "ano",
          "marcos",
          "vigentes"
        ],
        "properties": {
          "ano": {
            "type": "integer",
            "example": 2027
          },
          "marcos": {
            "type": "array",
            "description": "Marcos que começam no ano.",
            "items": {
              "$ref": "#/components/schemas/MarcoReforma"
            }
          },
          "vigentes": {
            "type": "array",
            "description": "Marcos que começaram antes e ainda valem no ano.",
            "items": {
              "type": "object",
              "required": [
                "codigo",
                "titulo",
                "data_inicio",
                "data_fim"
              ],
              "properties": {
                "codigo": {
                  "type": "string"
                },
                "titulo": {
                  "type": "string"
                },
                "data_inicio": {
                  "$ref": "#/components/schemas/Data"
                },
                "data_fim": {
                  "$ref": "#/components/schemas/Data"
                }
              }
            }
          }
        }
      },
      "MarcoRecorrente": {
        "type": "object",
        "required": [
          "codigo",
          "titulo",
          "resumo",
          "base_legal",
          "confianca",
          "mes_dia",
          "ano_inicio",
          "ano_fim"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "example": "M-ANUAL-TCU"
          },
          "titulo": {
            "type": "string"
          },
          "resumo": {
            "type": "string"
          },
          "base_legal": {
            "type": "string"
          },
          "confianca": {
            "type": "string",
            "enum": [
              "lei",
              "regulamento"
            ]
          },
          "mes_dia": {
            "type": "string",
            "pattern": "^(0[1-9]|1[0-2])-[0-3][0-9]$",
            "description": "Dia do ano em que o marco se repete, `MM-DD`.",
            "example": "09-15"
          },
          "ano_inicio": {
            "type": "integer",
            "example": 2026
          },
          "ano_fim": {
            "type": "integer",
            "example": 2034
          }
        }
      },
      "LinhaDoTempoReforma": {
        "type": "object",
        "required": [
          "perfil",
          "anos",
          "recorrentes",
          "versao_catalogo",
          "total_marcos"
        ],
        "properties": {
          "perfil": {
            "type": "object",
            "required": [
              "regime",
              "setor"
            ],
            "properties": {
              "regime": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "simples",
                  "presumido",
                  "real",
                  null
                ],
                "description": "Regime usado no recorte; `null` quando não informado."
              },
              "setor": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "industria",
                  "comercio",
                  "servicos",
                  null
                ],
                "description": "Setor usado no recorte; `null` quando não informado."
              }
            }
          },
          "anos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AnoLinhaDoTempo"
            }
          },
          "recorrentes": {
            "type": "array",
            "description": "Marcos que se repetem todo ano.",
            "items": {
              "$ref": "#/components/schemas/MarcoRecorrente"
            }
          },
          "versao_catalogo": {
            "type": "string",
            "example": "v0-2026-09-13"
          },
          "total_marcos": {
            "type": "integer",
            "example": 27
          }
        }
      },
      "EmpresaId": {
        "type": "string",
        "pattern": "^emp_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Identificador da empresa.",
        "example": "emp_01JC8F3Z6Q2W9X4B7N5M1RTHKA"
      },
      "Cnpj": {
        "type": "string",
        "pattern": "^[0-9A-Z]{12}[0-9]{2}$",
        "minLength": 14,
        "maxLength": 14,
        "description": "CNPJ com 14 caracteres, sem máscara. Aceita o formato alfanumérico da Receita (12 caracteres de letras maiúsculas ou números, mais 2 dígitos verificadores).",
        "example": "11222333000181"
      },
      "DataHora": {
        "type": "string",
        "format": "date-time",
        "description": "Data e hora em ISO 8601 com fuso.",
        "example": "2026-10-29T14:32:10-03:00"
      },
      "RedeId": {
        "type": "string",
        "pattern": "^rede_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Identificador de uma rede da carteira de uma conta de parceiro.",
        "example": "rede_01JD3M2A4C6E8G0J2K4N6Q8S0T"
      },
      "RedeResumo": {
        "type": "object",
        "required": [
          "id",
          "nome"
        ],
        "description": "Rede da carteira de uma conta de parceiro.",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/RedeId"
          },
          "nome": {
            "type": "string",
            "example": "Grupo Alfa"
          }
        }
      },
      "EmpresaResumo": {
        "type": "object",
        "required": [
          "id",
          "cnpj",
          "criada_em"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/EmpresaId"
          },
          "cnpj": {
            "$ref": "#/components/schemas/Cnpj"
          },
          "nome": {
            "type": [
              "string",
              "null"
            ],
            "example": "Empresa Exemplo Comércio Ltda"
          },
          "criada_em": {
            "$ref": "#/components/schemas/DataHora"
          },
          "rede": {
            "$ref": "#/components/schemas/RedeResumo",
            "description": "Rede da carteira em que a empresa está. Só em conta de parceiro."
          }
        }
      },
      "ListaEmpresas": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmpresaResumo"
            }
          },
          "proximo_cursor": {
            "$ref": "#/components/schemas/ProximoCursor"
          }
        }
      },
      "CriarEmpresaRequisicao": {
        "type": "object",
        "required": [
          "cnpj"
        ],
        "properties": {
          "cnpj": {
            "$ref": "#/components/schemas/Cnpj"
          },
          "nome": {
            "type": "string",
            "maxLength": 150,
            "description": "Nome para identificar a empresa na sua conta (opcional).",
            "example": "Empresa Exemplo Comércio Ltda"
          },
          "rede_id": {
            "$ref": "#/components/schemas/RedeId",
            "description": "Só em conta de parceiro, opcional. Rede da carteira em que a empresa entra. Sem ele, a API cria uma rede nova na carteira."
          }
        }
      },
      "TipoArquivo": {
        "type": "string",
        "enum": [
          "efd_icms_ipi",
          "efd_contribuicoes",
          "ecd",
          "ecf",
          "nfe_xml",
          "cfe_xml"
        ],
        "description": "Tipo detectado do arquivo.\n\n- `efd_icms_ipi`: SPED EFD ICMS/IPI.\n- `efd_contribuicoes`: SPED EFD Contribuições.\n- `ecd` e `ecf`: aceitos depois da correção do tratamento de arquivo substituto; até lá o envio é recusado com `tipo_nao_suportado`.\n- `nfe_xml` e `cfe_xml`: ZIP com XML de NF-e ou de CF-e (parte do lançamento v1).",
        "example": "efd_contribuicoes"
      },
      "EstadoArquivo": {
        "type": "string",
        "enum": [
          "recebido",
          "na_fila",
          "lendo",
          "lido",
          "periodo_ja_lido",
          "substituido",
          "rejeitado"
        ],
        "description": "Estado do arquivo.\n\n- `recebido`: chegou e foi guardado.\n- `na_fila`: aguardando leitura.\n- `lendo`: em leitura (num ZIP, enquanto houver XML por processar).\n- `lido`: lido e disponível para relatórios. Num ZIP, todos os XML foram processados; veja `xml`.\n- `periodo_ja_lido`: já havia um SPED lido para o mesmo CNPJ e período (enviado por esta ou por outra conta que tem a empresa); este não foi usado. Dispara `arquivo.lido` com esse `estado`, e o trabalho de leitura fica `concluido_sem_dados`. Não confundir com o campo `duplicado`, que diz que esta conta já tinha enviado exatamente o mesmo conteúdo.\n- `substituido`: foi trocado por uma retificadora. Não há evento: o arquivo novo traz este em `substitui`.\n- `rejeitado`: não pôde ser lido; veja `motivo`.\n\nTerminais: `lido`, `periodo_ja_lido`, `substituido` e `rejeitado`.",
        "example": "lido"
      },
      "ArquivoId": {
        "type": "string",
        "pattern": "^arq_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Identificador de um arquivo enviado.",
        "example": "arq_01JC8F4P0D5S3Y8E2G6H7JKMNP"
      },
      "CoberturaCompetencia": {
        "type": "object",
        "required": [
          "competencia",
          "arquivos"
        ],
        "properties": {
          "competencia": {
            "$ref": "#/components/schemas/Competencia"
          },
          "arquivos": {
            "type": "array",
            "description": "Arquivo vigente de cada tipo na competência. Um ZIP de XML aparece em cada mês do seu `periodo`.",
            "items": {
              "type": "object",
              "required": [
                "tipo",
                "estado",
                "arquivo_id"
              ],
              "properties": {
                "tipo": {
                  "$ref": "#/components/schemas/TipoArquivo"
                },
                "estado": {
                  "$ref": "#/components/schemas/EstadoArquivo"
                },
                "arquivo_id": {
                  "$ref": "#/components/schemas/ArquivoId"
                }
              }
            }
          }
        }
      },
      "Empresa": {
        "allOf": [
          {
            "$ref": "#/components/schemas/EmpresaResumo"
          },
          {
            "type": "object",
            "required": [
              "cobertura"
            ],
            "properties": {
              "cobertura": {
                "type": "array",
                "description": "Por competência, os arquivos que a empresa já tem. Use para saber de que período um relatório poderá ser pedido.",
                "items": {
                  "$ref": "#/components/schemas/CoberturaCompetencia"
                }
              }
            }
          }
        ]
      },
      "PeriodoDatas": {
        "type": "object",
        "required": [
          "inicio",
          "fim"
        ],
        "properties": {
          "inicio": {
            "$ref": "#/components/schemas/Data"
          },
          "fim": {
            "$ref": "#/components/schemas/Data"
          }
        }
      },
      "LeituraXml": {
        "type": "object",
        "description": "Contagem dos XML de um ZIP. O ZIP termina quando `lidos + duplicados + com_erro = total`.",
        "required": [
          "total",
          "lidos",
          "duplicados",
          "com_erro"
        ],
        "properties": {
          "total": {
            "type": "integer",
            "minimum": 0,
            "description": "XML encontrados no ZIP.",
            "example": 1840
          },
          "lidos": {
            "type": "integer",
            "minimum": 0,
            "description": "XML lidos e disponíveis para relatórios.",
            "example": 1795
          },
          "duplicados": {
            "type": "integer",
            "minimum": 0,
            "description": "XML cuja chave já é conhecida pela RMF, inclusive por outro envio; não são lidos de novo.",
            "example": 41
          },
          "com_erro": {
            "type": "integer",
            "minimum": 0,
            "description": "XML que não puderam ser lidos (estrutura inválida ou documento que não é NF-e nem CF-e).",
            "example": 4
          }
        }
      },
      "Arquivo": {
        "type": "object",
        "required": [
          "id",
          "tipo",
          "estado",
          "nome_original",
          "tamanho_bytes",
          "sha256",
          "duplicado",
          "enviado_por_esta_conta",
          "recebido_em",
          "atualizado_em"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/ArquivoId"
          },
          "tipo": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TipoArquivo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Tipo detectado. `null` se ainda não foi possível detectar ou se o arquivo foi recusado antes disso."
          },
          "cnpj": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Cnpj"
              },
              {
                "type": "null"
              }
            ],
            "description": "No SPED, o CNPJ detectado dentro do arquivo. No ZIP de XML, o CNPJ da empresa informada no envio (`cnpj` ou `empresa_id`)."
          },
          "competencia": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Competencia"
              },
              {
                "type": "null"
              }
            ],
            "description": "Competência do SPED (mês do fim do período). `null` para ZIP de XML, que pode cobrir vários meses."
          },
          "periodo": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PeriodoDatas"
              },
              {
                "type": "null"
              }
            ],
            "description": "Período declarado no arquivo (no ZIP, da primeira à última data de emissão dos XML). O ZIP aparece na cobertura da empresa e no filtro `competencia` em cada mês deste período."
          },
          "estado": {
            "$ref": "#/components/schemas/EstadoArquivo"
          },
          "motivo": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "codigo",
                  "mensagem"
                ],
                "properties": {
                  "codigo": {
                    "type": "string",
                    "description": "Código estável do motivo.",
                    "example": "formato_nao_reconhecido"
                  },
                  "mensagem": {
                    "type": "string",
                    "example": "O arquivo não parece um SPED nem um ZIP de XML."
                  }
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Explica `rejeitado`. Códigos possíveis: `formato_nao_reconhecido`, `cnpj_invalido`, `contagem_de_linhas_divergente`, `sem_movimento`, `arquivo_corrompido`, `sem_contrato` (a empresa do arquivo não tem contrato para este produto; fale com a equipe), `leitura_interrompida` (a leitura parou do lado da RMF; envie de novo) e, no ZIP, `nenhum_xml_valido` (catálogo em `ErroTrabalho`)."
          },
          "nome_original": {
            "type": "string",
            "example": "EFD-Contribuicoes-2026-09.txt"
          },
          "tamanho_bytes": {
            "type": "integer",
            "minimum": 0,
            "example": 18452310
          },
          "sha256": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "Impressão digital do conteúdo. A mesma conta enviando o mesmo conteúdo de novo recebe `duplicado: true`.",
            "example": "9f2b5c7e1a3d4f60b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5"
          },
          "duplicado": {
            "type": "boolean",
            "description": "`true` quando o envio devolveu um arquivo que esta conta já tinha enviado (mesmo `sha256`). A deduplicação é por conta. Não confundir com o estado `periodo_ja_lido`, que trata de outro arquivo do mesmo CNPJ e período.",
            "example": false
          },
          "enviado_por_esta_conta": {
            "type": "boolean",
            "description": "`true` quando esta conta enviou o arquivo; `false` quando foi outra conta que também tem a empresa (a outra conta não é identificada). Todo arquivo de uma empresa fica visível a toda conta que tem a empresa.",
            "example": true
          },
          "substitui": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ArquivoId"
              },
              {
                "type": "null"
              }
            ],
            "description": "Arquivo que esta retificadora substituiu. É a forma de saber de um `substituido`, que não dispara evento."
          },
          "xml": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/LeituraXml"
              },
              {
                "type": "null"
              }
            ],
            "description": "Contagem dos XML, só em ZIP de XML (`nfe_xml`, `cfe_xml`). `null` em SPED."
          },
          "recebido_em": {
            "$ref": "#/components/schemas/DataHora"
          },
          "atualizado_em": {
            "$ref": "#/components/schemas/DataHora"
          }
        }
      },
      "ListaArquivos": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Arquivo"
            }
          },
          "proximo_cursor": {
            "$ref": "#/components/schemas/ProximoCursor"
          }
        }
      },
      "EnvioArquivo": {
        "type": "object",
        "description": "Campos do multipart. Para ZIP de XML, `cnpj` ou `empresa_id` é obrigatório (ausente: 422 `parametro_invalido`). Para SPED, os dois são opcionais e, se vierem, precisam bater com o CNPJ do arquivo (senão, 409 `cnpj_divergente`). Informe só um dos dois.",
        "required": [
          "arquivo"
        ],
        "properties": {
          "arquivo": {
            "type": "string",
            "format": "binary",
            "description": "SPED em texto (EFD ICMS/IPI ou EFD Contribuições, até 400 MB) ou ZIP de XML de NF-e ou CF-e (até 200 MB; descompactado até 10 vezes o ZIP e no máximo 2 GB; até 50.000 XML; sem entradas com caminho relativo ou absoluto; um ZIP por tipo de documento). Até 20 MB no sandbox. Um arquivo por chamada. Tipo e período são detectados do conteúdo; no SPED, o CNPJ também."
          },
          "cnpj": {
            "$ref": "#/components/schemas/Cnpj",
            "description": "Empresa da conta dona do ZIP de XML (obrigatório no ZIP, se não vier `empresa_id`). No SPED, opcional; se vier, precisa ser o CNPJ do arquivo."
          },
          "empresa_id": {
            "$ref": "#/components/schemas/EmpresaId",
            "description": "Alternativa a `cnpj`, com o id da empresa da conta."
          }
        }
      },
      "JobId": {
        "type": "string",
        "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Identificador de um trabalho assíncrono.",
        "example": "job_01JC8F6B3N7Q1R5S9T2V4WXYZ0"
      },
      "JobEstado": {
        "type": "string",
        "enum": [
          "na_fila",
          "processando",
          "concluido",
          "concluido_sem_dados",
          "falhou",
          "cancelado"
        ],
        "description": "Estado único de qualquer trabalho assíncrono.\n\n- `na_fila`: aceito, ainda não começou.\n- `processando`: em andamento.\n- `concluido`: terminou com resultado.\n- `concluido_sem_dados`: terminou, mas não havia dado para o pedido (por exemplo, nenhum crédito no período). Num relatório, o resumo vem com os totais zerados e listas vazias, e `arquivos` vem vazio. Na leitura de arquivo, é o estado do trabalho quando o arquivo termina em `periodo_ja_lido`.\n- `falhou`: terminou com erro; veja `erro`.\n- `cancelado`: cancelado a pedido.\n\nSão terminais: `concluido`, `concluido_sem_dados`, `falhou` e `cancelado`.",
        "example": "processando"
      },
      "ErroTrabalho": {
        "type": "object",
        "description": "Erro de um trabalho que terminou em `falhou` (job, relatório, consulta à Receita, lote de NF-e). Não é um `Problem`; vem dentro do recurso.",
        "required": [
          "codigo",
          "mensagem"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9_]*$",
            "description": "Código estável. Catálogo (novos códigos podem aparecer; trate um código desconhecido como falha genérica e guarde o `request_id`):\n\n| Código | Trabalho | O que fazer |\n|---|---|---|\n| `formato_nao_reconhecido` | leitura de arquivo | envie um SPED ou um ZIP de XML, um ZIP por tipo de documento (ZIP que mistura NF-e e CF-e cai aqui) |\n| `cnpj_invalido` | leitura de arquivo | confira o CNPJ dentro do arquivo |\n| `contagem_de_linhas_divergente` | leitura de arquivo | gere o arquivo de novo no programa de origem |\n| `sem_movimento` | leitura de arquivo | nada a fazer: o arquivo não tem movimento |\n| `arquivo_corrompido` | leitura de arquivo | envie o arquivo de novo |\n| `sem_contrato` | leitura de arquivo | a empresa do arquivo não tem contrato para este produto; fale com a equipe da RMF |\n| `leitura_interrompida` | leitura de arquivo | a leitura parou do lado da RMF; envie o arquivo de novo |\n| `nenhum_xml_valido` | leitura de ZIP de XML | nenhum XML do ZIP pôde ser lido; confira os XML e envie de novo |\n| `arquivo_faltando` | relatório | envie o arquivo da competência citada e peça o relatório de novo |\n| `receita_indisponivel` | consulta à Receita (depois do lançamento) | peça a consulta de novo mais tarde |\n| `procuracao_vencida` | consulta à Receita (depois do lançamento) | fale com a equipe da RMF para renovar a procuração ou o certificado |\n| `sefaz_indisponivel` | lote de NF-e (depois do lançamento) | peça o lote de novo mais tarde |\n| `erro_interno` | qualquer | peça de novo; se repetir, informe o `request_id` ao suporte |\n\nOs de leitura de arquivo (de `formato_nao_reconhecido` a `nenhum_xml_valido`) são também os códigos de `motivo` de um arquivo `rejeitado`.",
            "example": "arquivo_faltando"
          },
          "mensagem": {
            "type": "string",
            "description": "Texto para quem desenvolve a integração. Diz o que fazer; não use em código.",
            "example": "Falta a EFD Contribuições de 2026-04."
          }
        }
      },
      "Job": {
        "type": "object",
        "description": "Trabalho assíncrono. Consulte até chegar a um estado terminal, respeitando `Retry-After`, ou receba o evento por webhook.",
        "required": [
          "id",
          "estado",
          "tipo",
          "criado_em",
          "atualizado_em"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/JobId"
          },
          "estado": {
            "$ref": "#/components/schemas/JobEstado"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "leitura_arquivo",
              "exclusao_arquivo",
              "relatorio",
              "consulta_receita",
              "lote_notas"
            ],
            "description": "O que o trabalho faz. `exclusao_arquivo` (`excluirArquivo`), `consulta_receita` e `lote_notas` (captura) ficam para depois do lançamento.",
            "example": "relatorio"
          },
          "progresso": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "percentual"
                ],
                "properties": {
                  "percentual": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100,
                    "example": 40
                  }
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Progresso em percentual, quando o trabalho consegue medir. `null` quando não houver."
          },
          "recurso": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "tipo",
                  "id"
                ],
                "properties": {
                  "tipo": {
                    "type": "string",
                    "enum": [
                      "arquivo",
                      "relatorio",
                      "consulta_receita",
                      "lote_notas"
                    ],
                    "example": "relatorio"
                  },
                  "id": {
                    "type": "string",
                    "description": "Identificador do recurso de destino (`arq_`, `rel_` ou, na captura, `crf_` e `lnf_`).",
                    "example": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
                  }
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Recurso que o trabalho produz ou altera."
          },
          "erro": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ErroTrabalho"
              },
              {
                "type": "null"
              }
            ],
            "description": "Preenchido só quando `estado` é `falhou`."
          },
          "criado_em": {
            "$ref": "#/components/schemas/DataHora"
          },
          "atualizado_em": {
            "$ref": "#/components/schemas/DataHora"
          },
          "concluido_em": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DataHora"
              },
              {
                "type": "null"
              }
            ],
            "description": "Momento em que chegou a um estado terminal."
          }
        }
      },
      "TipoRelatorioId": {
        "type": "string",
        "enum": [
          "icms_st",
          "diagnostico_federal",
          "planilha_sped",
          "exclusao_iss",
          "exclusao_icms_livro",
          "exclusao_pis_cofins",
          "exclusao_difal",
          "ciap"
        ],
        "description": "Tipo de relatório. Por ordem de prontidão:\n\n- `icms_st`: crédito de PIS e COFINS sobre o ICMS-ST pago na compra, com a alíquota de ICMS da base de alíquotas da RMF. Texto ilustrativo até o jurídico aprovar a descrição da tese.\n- `diagnostico_federal`: ocorrências de inconsistência de PIS e COFINS (as de revisão manual ficam fora da API).\n- `planilha_sped`: o SPED em planilha de análise. É conversão de formato, não cálculo de crédito. Como a entrega é a planilha, só fica disponível quando o plano da conta inclui a planilha de resultado.\n- `exclusao_iss`: exclusão do ISS da base de PIS e COFINS.\n- `exclusao_icms_livro`: exclusão do ICMS da base de PIS e COFINS, pelo livro.\n- `exclusao_pis_cofins`: exclusão do PIS e da COFINS da própria base.\n- `exclusao_difal`: exclusão do DIFAL.\n- `ciap`: crédito do ativo permanente.\n\nSó `icms_st`, `diagnostico_federal` e `planilha_sped` fazem parte do lançamento v1; os demais são reservados e pedi-los responde 422 `tipo_indisponivel`. O catálogo (`listarTiposRelatorio`) só lista os tipos disponíveis para a conta.",
        "example": "icms_st"
      },
      "ParametroRelatorio": {
        "type": "object",
        "required": [
          "nome",
          "tipo",
          "obrigatorio",
          "descricao"
        ],
        "properties": {
          "nome": {
            "type": "string",
            "example": "periodicidade_saida"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "texto",
              "inteiro",
              "decimal",
              "booleano",
              "enum"
            ],
            "example": "enum"
          },
          "obrigatorio": {
            "type": "boolean",
            "example": false
          },
          "valores": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Valores aceitos, quando `tipo` é `enum`.",
            "example": [
              "mensal",
              "anual"
            ]
          },
          "descricao": {
            "type": "string"
          }
        }
      },
      "SituacaoTese": {
        "type": "string",
        "enum": [
          "julgada",
          "pendente",
          "nao_se_aplica"
        ],
        "description": "Situação da tese tributária por trás do relatório: `julgada`, `pendente` de julgamento, ou `nao_se_aplica` quando o relatório não depende de tese (conversão, diagnóstico). No catálogo é a situação atual; no resumo de um relatório, a situação no momento em que ele foi gerado. A situação e o texto da tese vêm do jurídico.",
        "example": "nao_se_aplica"
      },
      "TipoRelatorio": {
        "type": "object",
        "required": [
          "tipo",
          "nome",
          "descricao",
          "arquivos_necessarios",
          "arquivos_exigencia",
          "parametros",
          "tese",
          "disponivel",
          "motivo_indisponivel"
        ],
        "properties": {
          "tipo": {
            "$ref": "#/components/schemas/TipoRelatorioId"
          },
          "nome": {
            "type": "string",
            "example": "Crédito de ICMS-ST"
          },
          "descricao": {
            "type": "string",
            "description": "O que o relatório entrega. Enquanto o jurídico não aprova o texto da tese, a descrição dos tipos com tese é ilustrativa."
          },
          "arquivos_necessarios": {
            "type": "array",
            "description": "Tipos de arquivo que o relatório usa, lidos nas competências pedidas. Veja `arquivos_exigencia`.",
            "items": {
              "type": "string",
              "enum": [
                "efd_icms_ipi",
                "efd_contribuicoes",
                "ecd",
                "ecf",
                "nfe_xml",
                "cfe_xml"
              ]
            },
            "example": [
              "efd_contribuicoes"
            ]
          },
          "arquivos_exigencia": {
            "type": "string",
            "enum": [
              "todos",
              "qualquer_um"
            ],
            "description": "`todos`: cada tipo de `arquivos_necessarios` precisa estar lido em cada competência. `qualquer_um`: basta um deles (por exemplo, `planilha_sped` converte a EFD ICMS/IPI, a EFD Contribuições ou as duas).",
            "example": "todos"
          },
          "parametros": {
            "type": "array",
            "description": "Opções aceitas em `opcoes` ao criar o relatório.",
            "items": {
              "$ref": "#/components/schemas/ParametroRelatorio"
            }
          },
          "tese": {
            "$ref": "#/components/schemas/SituacaoTese"
          },
          "disponivel": {
            "type": "boolean",
            "description": "No v1 o catálogo só lista tipos disponíveis, então vem sempre `true`. Pedir um tipo que a conta não pode usar responde 422 `tipo_indisponivel`, e o `detail` diz o motivo.",
            "example": true
          },
          "motivo_indisponivel": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "fase_futura",
              "fora_do_plano",
              "exige_planilha_no_plano",
              null
            ],
            "description": "Reservado para quando o catálogo listar tipos indisponíveis: `fase_futura` (ainda não liberado), `fora_do_plano` (o plano da conta não inclui o tipo) ou `exige_planilha_no_plano` (a entrega do tipo é a planilha, e o plano não inclui a planilha de resultado). No v1 vem sempre `null`; o motivo de um pedido recusado vem no `detail` do 422 `tipo_indisponivel`.",
            "example": null
          }
        }
      },
      "ListaTiposRelatorio": {
        "type": "object",
        "required": [
          "dados"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TipoRelatorio"
            }
          }
        }
      },
      "RelatorioId": {
        "type": "string",
        "pattern": "^rel_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Identificador de um relatório.",
        "example": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
      },
      "PeriodoRelatorio": {
        "type": "object",
        "required": [
          "inicio",
          "fim"
        ],
        "properties": {
          "inicio": {
            "$ref": "#/components/schemas/Competencia",
            "description": "Primeira competência do relatório."
          },
          "fim": {
            "$ref": "#/components/schemas/Competencia",
            "description": "Última competência (inclusive). Não pode ser anterior a `inicio`."
          }
        }
      },
      "OpcoesRelatorio": {
        "type": "object",
        "additionalProperties": true,
        "description": "Opções do tipo, conforme `parametros` do catálogo. Opção desconhecida é recusada com 422.",
        "example": {
          "periodicidade_saida": "mensal"
        }
      },
      "ResumoIcmsSt": {
        "type": "object",
        "required": [
          "tipo",
          "tese",
          "total_credito",
          "por_competencia",
          "versao_base_aliquotas"
        ],
        "description": "Resumo do relatório de ICMS-ST. Traz o crédito total e o crédito por competência.",
        "properties": {
          "tipo": {
            "type": "string",
            "const": "icms_st"
          },
          "tese": {
            "$ref": "#/components/schemas/SituacaoTese"
          },
          "total_credito": {
            "$ref": "#/components/schemas/ValorDecimal",
            "description": "Crédito total do período."
          },
          "por_competencia": {
            "type": "array",
            "description": "Crédito de cada competência do período, em ordem cronológica.",
            "items": {
              "type": "object",
              "required": [
                "competencia",
                "total_credito"
              ],
              "properties": {
                "competencia": {
                  "$ref": "#/components/schemas/Competencia"
                },
                "total_credito": {
                  "$ref": "#/components/schemas/ValorDecimal"
                }
              }
            }
          },
          "versao_base_aliquotas": {
            "type": "string",
            "description": "Versão da base de alíquotas de ICMS usada, a mesma de `consultarAliquotaIcms`.",
            "example": "e964db7cb0e8"
          }
        }
      },
      "OcorrenciaDiagnostico": {
        "type": "object",
        "required": [
          "codigo",
          "titulo",
          "nivel",
          "quantidade",
          "valor_envolvido",
          "por_competencia",
          "sem_competencia"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "description": "Código da ocorrência, em minúsculas (`oc_001`). As ocorrências de revisão manual e as ocultas ao cliente (010, 011, 016, 018, 019 e 020) não aparecem.",
            "example": "oc_001"
          },
          "titulo": {
            "type": "string",
            "example": "CST x alíquota divergentes"
          },
          "nivel": {
            "type": "string",
            "enum": [
              "alto",
              "medio",
              "baixo"
            ],
            "description": "Gravidade da ocorrência.",
            "example": "alto"
          },
          "quantidade": {
            "type": "integer",
            "minimum": 0,
            "description": "Total de achados da ocorrência no período.",
            "example": 14
          },
          "valor_envolvido": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ValorDecimal"
              },
              {
                "type": "null"
              }
            ],
            "description": "Soma dos valores das operações com a ocorrência. `null` nas ocorrências que não medem valor."
          },
          "por_competencia": {
            "type": "array",
            "description": "Os achados por competência (quando o achado tem competência).",
            "items": {
              "type": "object",
              "required": [
                "competencia",
                "quantidade",
                "valor_envolvido"
              ],
              "properties": {
                "competencia": {
                  "$ref": "#/components/schemas/Competencia"
                },
                "quantidade": {
                  "type": "integer",
                  "minimum": 0
                },
                "valor_envolvido": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ValorDecimal"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          },
          "sem_competencia": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "quantidade",
                  "valor_envolvido"
                ],
                "properties": {
                  "quantidade": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "valor_envolvido": {
                    "oneOf": [
                      {
                        "$ref": "#/components/schemas/ValorDecimal"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "Achados que não têm competência (por exemplo, registro sem data). `null` quando todos têm."
          }
        }
      },
      "ResumoDiagnosticoFederal": {
        "type": "object",
        "required": [
          "tipo",
          "tese",
          "total_ocorrencias",
          "total_achados",
          "ocorrencias"
        ],
        "description": "Resumo do diagnóstico federal.",
        "properties": {
          "tipo": {
            "type": "string",
            "const": "diagnostico_federal"
          },
          "tese": {
            "$ref": "#/components/schemas/SituacaoTese"
          },
          "total_ocorrencias": {
            "type": "integer",
            "description": "Quantas ocorrências têm pelo menos um achado.",
            "example": 2
          },
          "total_achados": {
            "type": "integer",
            "description": "Soma dos achados de todas as ocorrências.",
            "example": 15
          },
          "ocorrencias": {
            "type": "array",
            "description": "Uma entrada por ocorrência com pelo menos um achado.",
            "items": {
              "$ref": "#/components/schemas/OcorrenciaDiagnostico"
            }
          }
        }
      },
      "ResumoPlanilhaSped": {
        "type": "object",
        "required": [
          "tipo",
          "tese",
          "arquivos_convertidos",
          "competencias"
        ],
        "description": "Resumo da planilha de análise do SPED. A planilha em si vem em `arquivos` (`planilha_resultado`). No v1 o relatório gera um export só, do primeiro tipo de SPED que tem arquivo lido no período (a EFD ICMS/IPI ou, se não houver, a EFD Contribuições); as `opcoes` não têm efeito.",
        "properties": {
          "tipo": {
            "type": "string",
            "const": "planilha_sped"
          },
          "tese": {
            "$ref": "#/components/schemas/SituacaoTese"
          },
          "arquivos_convertidos": {
            "type": "integer",
            "example": 3
          },
          "competencias": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Competencia"
            }
          }
        }
      },
      "ResumoRelatorio": {
        "description": "Resumo do relatório. O campo `tipo` diz qual das formas vale.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/ResumoIcmsSt"
          },
          {
            "$ref": "#/components/schemas/ResumoDiagnosticoFederal"
          },
          {
            "$ref": "#/components/schemas/ResumoPlanilhaSped"
          }
        ],
        "discriminator": {
          "propertyName": "tipo",
          "mapping": {
            "icms_st": "#/components/schemas/ResumoIcmsSt",
            "diagnostico_federal": "#/components/schemas/ResumoDiagnosticoFederal",
            "planilha_sped": "#/components/schemas/ResumoPlanilhaSped"
          }
        }
      },
      "RelatorioArquivoId": {
        "type": "string",
        "pattern": "^rarq_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Identificador de um arquivo gerado por um relatório.",
        "example": "rarq_01JC8FAF4Q7T0W3Y6B9D2EGHJK"
      },
      "ArquivoRelatorio": {
        "type": "object",
        "required": [
          "id",
          "tipo",
          "nome",
          "tipo_midia",
          "tamanho_bytes"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/RelatorioArquivoId"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "planilha_resultado"
            ],
            "description": "Tipo do arquivo. Hoje só `planilha_resultado`: planilha com o resultado, sem fórmula e sem as colunas intermediárias da regra. Só existe quando o plano da conta inclui a planilha de resultado.",
            "example": "planilha_resultado"
          },
          "nome": {
            "type": "string",
            "example": "icms-st-2026-01-a-2026-09.xlsx"
          },
          "tipo_midia": {
            "type": "string",
            "example": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
          },
          "tamanho_bytes": {
            "type": "integer",
            "example": 482113
          }
        }
      },
      "Relatorio": {
        "type": "object",
        "required": [
          "id",
          "job_id",
          "tipo",
          "cnpj",
          "periodo",
          "estado",
          "criado_em"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/RelatorioId"
          },
          "job_id": {
            "$ref": "#/components/schemas/JobId"
          },
          "tipo": {
            "$ref": "#/components/schemas/TipoRelatorioId"
          },
          "cnpj": {
            "$ref": "#/components/schemas/Cnpj"
          },
          "arquivo_id": {
            "$ref": "#/components/schemas/ArquivoId",
            "description": "Arquivo de origem. Só aparece quando o relatório foi pedido por `arquivo_id`."
          },
          "periodo": {
            "$ref": "#/components/schemas/PeriodoRelatorio",
            "description": "Período do relatório. No pedido por `arquivo_id` sem `periodo`, é o período do arquivo."
          },
          "opcoes": {
            "$ref": "#/components/schemas/OpcoesRelatorio"
          },
          "estado": {
            "$ref": "#/components/schemas/JobEstado"
          },
          "criado_em": {
            "$ref": "#/components/schemas/DataHora"
          },
          "concluido_em": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DataHora"
              },
              {
                "type": "null"
              }
            ]
          },
          "resumo": {
            "$ref": "#/components/schemas/ResumoRelatorio",
            "description": "Resumo em JSON, entregue sempre que `estado` é `concluido` ou `concluido_sem_dados`; nos outros estados o campo não vem. A forma depende de `tipo`. Traz o valor por competência, os totais e a situação da tese; não traz memória de cálculo nem colunas intermediárias. Em `concluido_sem_dados`, os totais vêm zerados e as listas vazias (`por_competencia: []`, `ocorrencias: []`, `competencias: []`), e `arquivos` vem vazio."
          },
          "arquivos": {
            "type": "array",
            "description": "Arquivos para baixar, só quando `estado` é `concluido` (em `concluido_sem_dados` vem vazio). Traz a `planilha_resultado` apenas se o plano da conta inclui a planilha; sem isso, vem vazio. A planilha completa de uso interno nunca é entregue.",
            "items": {
              "$ref": "#/components/schemas/ArquivoRelatorio"
            }
          },
          "avisos": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Pontos de atenção sobre o resultado, em texto. Dizem o que você deve conferir ou fazer, não como o valor foi calculado."
          },
          "erro": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ErroTrabalho"
              },
              {
                "type": "null"
              }
            ],
            "description": "Só quando `estado` é `falhou`. Códigos no catálogo de `ErroTrabalho`."
          }
        }
      },
      "ListaRelatorios": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Relatorio"
            }
          },
          "proximo_cursor": {
            "$ref": "#/components/schemas/ProximoCursor"
          }
        }
      },
      "CriarRelatorioPorEmpresa": {
        "type": "object",
        "title": "Pela empresa e período",
        "description": "Informe a empresa por `cnpj` ou por `empresa_id`, nunca os dois.",
        "additionalProperties": false,
        "required": [
          "tipo",
          "periodo"
        ],
        "oneOf": [
          {
            "required": [
              "cnpj"
            ],
            "properties": {
              "cnpj": {
                "$ref": "#/components/schemas/Cnpj"
              }
            }
          },
          {
            "required": [
              "empresa_id"
            ],
            "properties": {
              "empresa_id": {
                "$ref": "#/components/schemas/EmpresaId"
              }
            }
          }
        ],
        "properties": {
          "tipo": {
            "$ref": "#/components/schemas/TipoRelatorioId"
          },
          "cnpj": {
            "$ref": "#/components/schemas/Cnpj"
          },
          "empresa_id": {
            "$ref": "#/components/schemas/EmpresaId"
          },
          "periodo": {
            "$ref": "#/components/schemas/PeriodoRelatorio"
          },
          "opcoes": {
            "$ref": "#/components/schemas/OpcoesRelatorio"
          }
        }
      },
      "CriarRelatorioPorArquivo": {
        "type": "object",
        "title": "Pelo id do arquivo",
        "description": "Gera o relatório a partir de um arquivo enviado e já lido. Quando o tipo precisa de outros arquivos além desse, usa os arquivos lidos da mesma empresa no período.",
        "additionalProperties": false,
        "required": [
          "tipo",
          "arquivo_id"
        ],
        "properties": {
          "tipo": {
            "$ref": "#/components/schemas/TipoRelatorioId"
          },
          "arquivo_id": {
            "$ref": "#/components/schemas/ArquivoId",
            "description": "Arquivo enviado (`arq_`). Precisa ser da sua conta (senão, 404 `arquivo_nao_encontrado`), estar `lido` (senão, 409 `arquivo_nao_processado`) e ser de um tipo que o relatório usa (senão, 422 `tipo_incompativel_com_arquivo`)."
          },
          "periodo": {
            "$ref": "#/components/schemas/PeriodoRelatorio",
            "description": "Opcional. Sem ele, vale o período do arquivo; com ele, o período precisa caber no do arquivo (senão, 422 `periodo_invalido`)."
          },
          "opcoes": {
            "$ref": "#/components/schemas/OpcoesRelatorio"
          }
        }
      },
      "CriarRelatorioRequisicao": {
        "description": "Pedido de relatório, em uma de duas formas:\n\n- pela empresa e período: `tipo`, `cnpj` ou `empresa_id`, e `periodo`;\n- pelo id do arquivo: `tipo` e `arquivo_id`, com `periodo` opcional.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/CriarRelatorioPorEmpresa"
          },
          {
            "$ref": "#/components/schemas/CriarRelatorioPorArquivo"
          }
        ]
      },
      "WebhookId": {
        "type": "string",
        "pattern": "^whk_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Identificador de um webhook.",
        "example": "whk_01JC8F7C5P9R2T4V6X8Z1ABCDE"
      },
      "TipoEventoWebhook": {
        "type": "string",
        "enum": [
          "arquivo.lido",
          "arquivo.rejeitado",
          "relatorio.concluido",
          "relatorio.falhou",
          "consulta_receita.concluida",
          "lote_notas.concluido"
        ],
        "description": "Eventos que você pode assinar.\n\n- `arquivo.lido`: a leitura terminou em `lido` ou em `periodo_ja_lido` (o `estado` do corpo diz qual); num ZIP de XML, quando todos os XML foram processados. `substituido` não gera evento.\n- `arquivo.rejeitado`: o arquivo foi recusado.\n- `relatorio.concluido`: o relatório terminou (inclui `concluido_sem_dados`).\n- `relatorio.falhou`: o relatório terminou com erro.\n- `consulta_receita.concluida` (depois do lançamento, por plano): a consulta à Receita chegou a um estado terminal; o `estado` do corpo diz qual.\n- `lote_notas.concluido` (depois do lançamento, por plano): o lote de NF-e chegou a um estado terminal; o `estado` do corpo diz qual.\n\nOs eventos da captura só podem ser assinados com a captura no plano.",
        "example": "relatorio.concluido"
      },
      "Webhook": {
        "type": "object",
        "required": [
          "id",
          "url",
          "eventos",
          "ativo",
          "criado_em"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/WebhookId"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "Endereço que recebe as entregas: HTTPS na porta 443, nome público; a URL é conferida de novo a cada entrega, e a entrega é descartada se o nome passar a resolver para endereço interno.",
            "example": "https://integracao.exemplo.com.br/rmf/eventos"
          },
          "eventos": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/TipoEventoWebhook"
            }
          },
          "descricao": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 150,
            "example": "Integração com o ERP"
          },
          "ativo": {
            "type": "boolean",
            "example": true
          },
          "criado_em": {
            "$ref": "#/components/schemas/DataHora"
          }
        }
      },
      "ListaWebhooks": {
        "type": "object",
        "required": [
          "dados",
          "proximo_cursor"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          },
          "proximo_cursor": {
            "$ref": "#/components/schemas/ProximoCursor"
          }
        }
      },
      "CriarWebhookRequisicao": {
        "type": "object",
        "required": [
          "url",
          "eventos"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "HTTPS na porta 443, nome público; a URL é conferida de novo a cada entrega, e a entrega é descartada se o nome passar a resolver para endereço interno.",
            "example": "https://integracao.exemplo.com.br/rmf/eventos"
          },
          "eventos": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/TipoEventoWebhook"
            }
          },
          "descricao": {
            "type": "string",
            "maxLength": 150,
            "example": "Integração com o ERP"
          }
        }
      },
      "WebhookCriado": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "required": [
              "segredo"
            ],
            "properties": {
              "segredo": {
                "type": "string",
                "pattern": "^whsec_",
                "description": "Segredo de assinatura, mostrado **uma única vez**. Guarde-o: não há como recuperá-lo, só excluir o webhook e criar outro.",
                "example": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
              }
            }
          }
        ]
      },
      "EventoId": {
        "type": "string",
        "pattern": "^evt_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Identificador de um evento de webhook.",
        "example": "evt_01JC8F8D7S2T5W8Y1B4E7GHKMN"
      },
      "EventoEnfileirado": {
        "type": "object",
        "required": [
          "evento_id",
          "tipo",
          "enfileirado_em"
        ],
        "properties": {
          "evento_id": {
            "$ref": "#/components/schemas/EventoId"
          },
          "tipo": {
            "type": "string",
            "example": "teste"
          },
          "enfileirado_em": {
            "$ref": "#/components/schemas/DataHora"
          }
        }
      },
      "Escopo": {
        "type": "string",
        "enum": [
          "consultas:ler",
          "empresas:escrever",
          "empresas:ler",
          "arquivos:enviar",
          "arquivos:ler",
          "relatorios:criar",
          "relatorios:ler",
          "webhooks:gerenciar",
          "conta:ler",
          "captura:receita",
          "captura:notas",
          "captura:dfe"
        ],
        "description": "Escopo de chave. O que cada um libera está no esquema de segurança `bearerAuth`. Os três de captura vêm depois do lançamento e só existem em plano que libera a captura.",
        "example": "consultas:ler"
      },
      "PlanoConta": {
        "type": "object",
        "required": [
          "nome",
          "produtos",
          "escopos_permitidos",
          "planilha_resultado",
          "capturas"
        ],
        "description": "O que a conta pode usar. Muda só com a equipe da RMF, nunca pela API.",
        "properties": {
          "nome": {
            "type": "string",
            "description": "Nome do plano, para exibir. Não use em código.",
            "example": "Parceiro (ilustrativo)"
          },
          "produtos": {
            "type": "array",
            "uniqueItems": true,
            "description": "Produtos liberados.\n\n- `consultas_icms`: consultas de ICMS.\n- `consultas_reforma`: tabela, simulações e linha do tempo da Reforma.\n- `arquivos`: cadastro de empresas e envio de arquivos.\n- `relatorios`: relatórios sobre os arquivos lidos.\n- `captura`: consultas na Receita, NF-e por chave e DF-e (depois do lançamento); quais delas, em `capturas`.",
            "items": {
              "type": "string",
              "enum": [
                "consultas_icms",
                "consultas_reforma",
                "arquivos",
                "relatorios",
                "captura"
              ]
            },
            "example": [
              "consultas_icms",
              "consultas_reforma"
            ]
          },
          "escopos_permitidos": {
            "type": "array",
            "uniqueItems": true,
            "description": "Escopos que as chaves da conta podem receber. Chamada que exige escopo fora desta lista responde 403 `fora_do_plano`.",
            "items": {
              "$ref": "#/components/schemas/Escopo"
            },
            "example": [
              "consultas:ler",
              "conta:ler"
            ]
          },
          "planilha_resultado": {
            "type": "boolean",
            "description": "`true` quando os relatórios entregam também a planilha de resultado (sem fórmula e sem as colunas da regra). Com `false`, o relatório traz só o resumo em JSON e `arquivos` vem vazio.",
            "example": false
          },
          "capturas": {
            "type": "array",
            "uniqueItems": true,
            "description": "Capturas liberadas (depois do lançamento, por plano): `receita` (arquivos na Receita), `notas` (NF-e por chave) e `dfe` (documentos emitidos contra as empresas). Lista vazia quando o plano não tem captura.",
            "items": {
              "type": "string",
              "enum": [
                "receita",
                "notas",
                "dfe"
              ]
            },
            "example": []
          }
        }
      },
      "Conta": {
        "type": "object",
        "required": [
          "tipo",
          "nome",
          "ambiente",
          "plano"
        ],
        "description": "A conta de API dona da chave usada na chamada.",
        "properties": {
          "tipo": {
            "type": "string",
            "enum": [
              "rede",
              "parceiro"
            ],
            "description": "`rede`: cliente direto; a conta enxerga as empresas da rede. `parceiro`: enxerga a carteira, que é a união das redes que atende.",
            "example": "parceiro"
          },
          "nome": {
            "type": "string",
            "example": "Contabilidade Exemplo"
          },
          "ambiente": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "Ambiente da chave usada na chamada. `live` é produção; `test` é o sandbox, que espelha a conta com o mesmo plano.",
            "example": "test"
          },
          "plano": {
            "$ref": "#/components/schemas/PlanoConta"
          }
        }
      },
      "CotaUso": {
        "type": "object",
        "required": [
          "cota",
          "periodo",
          "limite",
          "usado",
          "restante",
          "renova_em"
        ],
        "properties": {
          "cota": {
            "type": "string",
            "enum": [
              "consultas_dia",
              "consultas_mes",
              "arquivos_mes",
              "relatorios_mes",
              "empresas_dia",
              "recusas_envio_dia",
              "reenvios_dia"
            ],
            "description": "Nome da cota. Os limites são ilustrativos até o plano comercial, menos estes, fixos no contrato. `consultas_dia` e `consultas_mes` gastam 1 por consulta e 1 por item de um lote de ICMS (um lote de 500 itens gasta 500):\n\n- `empresas_dia`: 50 cadastros de empresa por dia (`criarEmpresa`);\n- `recusas_envio_dia`: 50 envios de arquivo recusados por dia, contando só `empresa_nao_cadastrada` e `arquivo_corrompido` (`enviarArquivo`, número provisório);\n- `reenvios_dia`: 100 reenvios de evento por dia (`reenviarEvento`).",
            "example": "consultas_dia"
          },
          "periodo": {
            "type": "string",
            "enum": [
              "dia",
              "mes"
            ],
            "example": "dia"
          },
          "limite": {
            "type": "integer",
            "minimum": 0,
            "example": 5000
          },
          "usado": {
            "type": "integer",
            "minimum": 0,
            "example": 1240
          },
          "restante": {
            "type": "integer",
            "minimum": 0,
            "example": 3760
          },
          "renova_em": {
            "$ref": "#/components/schemas/DataHora"
          }
        }
      },
      "UsoConta": {
        "type": "object",
        "required": [
          "data",
          "mes",
          "cotas"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Data",
            "description": "Dia a que se refere o consumo diário."
          },
          "mes": {
            "$ref": "#/components/schemas/Competencia",
            "description": "Mês a que se refere o consumo mensal."
          },
          "cotas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CotaUso"
            }
          }
        }
      },
      "ChaveId": {
        "type": "string",
        "pattern": "^chv_[0-9A-HJKMNP-TV-Z]{26}$",
        "description": "Identificador de uma chave de API (não é o segredo).",
        "example": "chv_01JC8F9E2M5P8S1V4X7Z0CDFGH"
      },
      "ChaveApi": {
        "type": "object",
        "required": [
          "id",
          "prefixo",
          "nome",
          "ambiente",
          "escopos",
          "criada_em"
        ],
        "description": "Chave de API sem o segredo.",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/ChaveId"
          },
          "prefixo": {
            "type": "string",
            "description": "Início da chave, para reconhecê-la: `rmf_<ambiente>_<id>`. O segredo nunca é devolvido.",
            "example": "rmf_test_01JC8F9E2M5P8S1V4X7Z0CDFGH"
          },
          "nome": {
            "type": "string",
            "example": "Integração do ERP"
          },
          "ambiente": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "example": "test"
          },
          "escopos": {
            "type": "array",
            "description": "Escopos da chave. Sempre dentro de `plano.escopos_permitidos` de `obterConta`.",
            "items": {
              "$ref": "#/components/schemas/Escopo"
            },
            "example": [
              "consultas:ler",
              "conta:ler"
            ]
          },
          "criada_em": {
            "$ref": "#/components/schemas/DataHora"
          },
          "ultimo_uso_em": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DataHora"
              },
              {
                "type": "null"
              }
            ],
            "description": "Último uso da chave. `null` se nunca foi usada."
          },
          "vence_em": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DataHora"
              },
              {
                "type": "null"
              }
            ],
            "description": "Vencimento da chave. `null` se não vence."
          }
        }
      },
      "ListaChaves": {
        "type": "object",
        "required": [
          "dados"
        ],
        "properties": {
          "dados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChaveApi"
            }
          }
        }
      },
      "EventoWebhook": {
        "type": "object",
        "required": [
          "id",
          "tipo",
          "criado_em",
          "recurso",
          "estado"
        ],
        "description": "Corpo de toda entrega de webhook. Só identificadores e estado: nunca dado fiscal.",
        "properties": {
          "id": {
            "$ref": "#/components/schemas/EventoId"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "arquivo.lido",
              "arquivo.rejeitado",
              "relatorio.concluido",
              "relatorio.falhou",
              "consulta_receita.concluida",
              "lote_notas.concluido",
              "teste"
            ],
            "description": "Tipo do evento. `teste` só chega por `testarWebhook` e não pode ser assinado.",
            "example": "relatorio.concluido"
          },
          "criado_em": {
            "$ref": "#/components/schemas/DataHora"
          },
          "recurso": {
            "type": "object",
            "required": [
              "tipo",
              "id"
            ],
            "properties": {
              "tipo": {
                "type": "string",
                "enum": [
                  "arquivo",
                  "relatorio",
                  "consulta_receita",
                  "lote_notas",
                  "teste"
                ],
                "example": "relatorio"
              },
              "id": {
                "type": "string",
                "description": "`arq_`, `rel_` ou, na captura (depois do lançamento), `crf_` e `lnf_`.",
                "example": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK"
              }
            }
          },
          "estado": {
            "type": "string",
            "description": "Estado do recurso no momento do evento: um `EstadoArquivo` (`lido`, `periodo_ja_lido`, `rejeitado`) ou um `JobEstado` (`concluido`, `concluido_sem_dados`, `falhou`, `cancelado`).",
            "example": "concluido"
          }
        }
      }
    },
    "headers": {
      "XRequestId": {
        "description": "Identificador da chamada, gerado na entrada e reusado pela API do começo ao fim. Opaco, começa com `req_`. Informe-o ao pedir suporte.",
        "schema": {
          "type": "string"
        },
        "example": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
      },
      "RateLimitPolicy": {
        "description": "Política de limite da chave, no formato do rascunho atual do IETF (`draft-ietf-httpapi-ratelimit-headers`): nome da política, `q` (cota) e `w` (janela em segundos). Valor ilustrativo.",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";q=60;w=60"
      },
      "RateLimit": {
        "description": "Situação atual do limite, no mesmo rascunho: nome da política, `r` (restante) e `t` (segundos até renovar). Valor ilustrativo.",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";r=57;t=23"
      },
      "RetryAfter": {
        "description": "Segundos até a próxima tentativa.",
        "schema": {
          "type": "integer",
          "minimum": 0
        },
        "example": 23
      }
    },
    "responses": {
      "RequisicaoInvalida": {
        "description": "Requisição mal formada: corpo que não é JSON, cabeçalho ausente ou tipo de conteúdo errado.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "requisicao_invalida": {
                "summary": "Requisição inválida",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/requisicao_invalida",
                  "title": "Requisição inválida",
                  "status": 400,
                  "detail": "O corpo não é um JSON válido.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "requisicao_invalida",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              },
              "cabecalho_ausente": {
                "summary": "Cabeçalho ausente",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/cabecalho_ausente",
                  "title": "Cabeçalho ausente",
                  "status": 400,
                  "detail": "O cabeçalho Idempotency-Key é obrigatório neste POST.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "cabecalho_ausente",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              }
            }
          }
        }
      },
      "NaoAutenticado": {
        "description": "Chave ausente, inválida ou vencida.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "chave_ausente": {
                "summary": "Chave ausente",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/chave_ausente",
                  "title": "Chave ausente",
                  "status": 401,
                  "detail": "Envie o cabeçalho Authorization: Bearer <chave>.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "chave_ausente",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              },
              "chave_invalida": {
                "summary": "Chave inválida",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/chave_invalida",
                  "title": "Chave inválida",
                  "status": 401,
                  "detail": "A chave não foi reconhecida. Confira o ambiente: chaves rmf_test_ só valem no sandbox.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "chave_invalida",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              },
              "chave_vencida": {
                "summary": "Chave vencida",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/chave_vencida",
                  "title": "Chave vencida",
                  "status": 401,
                  "detail": "A chave passou da data de vencimento.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "chave_vencida",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              }
            }
          }
        }
      },
      "ProibidoEscopo": {
        "description": "A chave é válida, mas não pode chamar a operação. `escopo_insuficiente`: o plano permite, mas a chave não tem o escopo; use uma chave com o escopo. `fora_do_plano`: o produto não está no plano da conta; fale com a equipe da RMF para mudar o plano. Quando as duas valem, vem `fora_do_plano`.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "escopo_insuficiente": {
                "summary": "Escopo insuficiente",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/escopo_insuficiente",
                  "title": "Escopo insuficiente",
                  "status": 403,
                  "detail": "A operação exige o escopo consultas:ler. Use uma chave com esse escopo.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "escopo_insuficiente",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              },
              "fora_do_plano": {
                "summary": "Fora do plano da conta",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/fora_do_plano",
                  "title": "Fora do plano",
                  "status": 403,
                  "detail": "O plano da sua conta não inclui este produto. Veja GET /conta e fale com a equipe da RMF para mudar o plano.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "fora_do_plano",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              }
            }
          }
        }
      },
      "LimiteExcedido": {
        "description": "Limite de chamadas ou cota diária excedidos. Espere o tempo de `Retry-After`.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "limite_excedido": {
                "summary": "Limite excedido",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/limite_excedido",
                  "title": "Limite excedido",
                  "status": 429,
                  "detail": "Você passou do limite de chamadas por minuto. Tente de novo em 23 segundos.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "limite_excedido",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              },
              "cota_diaria_excedida": {
                "summary": "Cota diária excedida",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/cota_diaria_excedida",
                  "title": "Cota diária excedida",
                  "status": 429,
                  "detail": "A cota diária desta operação acabou. Veja GET /conta/uso.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "cota_diaria_excedida",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              }
            }
          }
        }
      },
      "ErroInterno": {
        "description": "Falha nossa. Tente de novo; se persistir, informe o `request_id` ao suporte.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "erro_interno": {
                "summary": "Erro interno",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/erro_interno",
                  "title": "Erro interno",
                  "status": 500,
                  "detail": "Não foi possível concluir a chamada. Informe o request_id ao suporte.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "erro_interno",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              }
            }
          }
        }
      },
      "ServicoIndisponivel": {
        "description": "Serviço fora do ar, reiniciando ou sem conseguir atender agora. Tente de novo com espera exponencial, respeitando `Retry-After`. Quem responde pode ser o servidor de entrada (`servico_indisponivel`, com a API fora do ar) ou a própria API (`erro_interno` com status 503, quando um serviço de apoio ou o banco está ocupado); nos dois casos a chamada pode ser repetida. Em POST, repita com a mesma `Idempotency-Key`.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "servico_indisponivel": {
                "summary": "Serviço indisponível (servidor de entrada)",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/servico_indisponivel",
                  "title": "Serviço temporariamente indisponível",
                  "status": 503,
                  "detail": "Estamos atualizando o serviço. Tente novamente em alguns instantes.",
                  "codigo": "servico_indisponivel",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              },
              "erro_interno_503": {
                "summary": "Serviço de apoio ocupado (a própria API)",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/erro_interno",
                  "title": "Serviço indisponível",
                  "status": 503,
                  "detail": "Serviço temporariamente indisponível. Tente de novo em instantes.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "erro_interno",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              }
            }
          }
        }
      },
      "NaoProcessavel": {
        "description": "A requisição é válida em formato, mas algum valor não é aceito. O campo `erros` lista cada problema.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "parametro_invalido": {
                "summary": "Parâmetro inválido",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/parametro_invalido",
                  "title": "Parâmetro inválido",
                  "status": 422,
                  "detail": "Um ou mais parâmetros não passaram na validação.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "parametro_invalido",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP",
                  "erros": [
                    {
                      "campo": "ncm",
                      "mensagem": "Use 8 dígitos, sem ponto."
                    }
                  ]
                }
              },
              "idempotencia_conflito": {
                "summary": "Idempotency-Key reutilizada com outro corpo",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/idempotencia_conflito",
                  "title": "Idempotency-Key reutilizada com outro corpo",
                  "status": 422,
                  "detail": "Essa Idempotency-Key já foi usada com um corpo diferente nas últimas 24 horas.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "idempotencia_conflito",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              }
            }
          }
        }
      },
      "Conflito": {
        "description": "Conflito com o estado atual do recurso ou com uma chamada em andamento.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "examples": {
              "idempotencia_em_andamento": {
                "summary": "Chamada em andamento",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/idempotencia_em_andamento",
                  "title": "Chamada em andamento",
                  "status": 409,
                  "detail": "Outra chamada com a mesma Idempotency-Key ainda está sendo processada. Tente de novo em instantes.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "idempotencia_em_andamento",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              },
              "estado_incompativel": {
                "summary": "Estado incompatível",
                "value": {
                  "type": "https://docs.rmfestrategia.com/erros/estado_incompativel",
                  "title": "Estado incompatível",
                  "status": 409,
                  "detail": "A operação não se aplica ao estado atual do recurso.",
                  "instance": "/v1/icms/aliquotas",
                  "codigo": "estado_incompativel",
                  "request_id": "req_01JC8FBG6R8V2X5Z9C3E7HKMNP"
                }
              }
            }
          }
        }
      }
    },
    "parameters": {
      "Limite": {
        "name": "limite",
        "in": "query",
        "required": false,
        "description": "Itens por página. Padrão 50, máximo 200.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        },
        "example": 50
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "Cursor devolvido em `proximo_cursor` da página anterior. É opaco: não monte, não interprete e não copie de um exemplo. Só vale o cursor que a própria API emitiu; qualquer outro valor responde 422 `parametro_invalido` (campo `cursor`). Deixe em branco para a primeira página.",
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Chave de idempotência, um texto único por operação lógica (recomendado: UUID v4, de 16 a 80 caracteres). Gere um valor por operação lógica e repita o MESMO valor em todas as retentativas dela. Guardada por 24 horas, por conta e por operação: a impressão digital inclui a conta, o método, o caminho e o corpo. No envio de arquivo (multipart), a impressão digital usa o sha256 do conteúdo do campo `arquivo` e dos demais campos, não o corpo multipart, cujo separador muda a cada tentativa. A chave usada por outra conta nunca colide com a sua.\n\n- Mesma chave e mesmo corpo: devolve a resposta original, sem repetir o efeito: o mesmo status e o mesmo corpo, inclusive o `request_id` da primeira chamada, com o cabeçalho `Idempotent-Replayed: true`. Vale também para o 409 de conflito de estado (por exemplo `empresa_ja_cadastrada`); falhas de validação, cota ou do servidor não são guardadas, e a chave fica livre para corrigir e tentar de novo.\n- Mesma chave e corpo diferente: 422 `idempotencia_conflito`.\n- Mesma chave enquanto a primeira chamada ainda processa: 409 `idempotencia_em_andamento`.",
        "schema": {
          "type": "string",
          "minLength": 16,
          "maxLength": 80
        },
        "example": "8f1c2d3e-4b5a-4c6d-9e7f-0a1b2c3d4e5f"
      }
    }
  }
}
