# Primeira chamada em 5 minutos

> Consulte a alíquota interna de ICMS de um NCM em SP pelo sandbox, leia a resposta e decida pela confiança.

Consulte a alíquota interna de ICMS do NCM `85414121` (iluminação LED) em SP, na competência `2026-10`, pelo sandbox. Nada do que você enviar lá chega à produção.

  5 minutos
  Escopo consultas:ler
  Ambiente: sandbox

## Antes de começar

- Uma chave de sandbox `rmf_test_` com o escopo `consultas:ler`. A equipe da RMF emite a chave: peça pelo seu contato comercial (veja [Como conseguir a chave](/comecar/chave)).
- A chave e o endereço numa variável de ambiente. Nenhum exemplo desta página leva a chave no código, e todos leem `RMF_API_URL` e `RMF_API_KEY`:

```bash
export RMF_API_URL="https://sandbox.api.rmfestrategia.com/v1"
export RMF_API_KEY="<sua chave do sandbox, 75 caracteres>"
```

:::danger[A chave é segredo]
Ela vai no cabeçalho `Authorization: Bearer`, nunca na URL, no código, em log ou em mensagem de commit. Chave `rmf_test_` só vale no sandbox; `rmf_live_`, só na produção.
:::

## 1. Faça a chamada

Um `GET /v1/icms/aliquotas` com o NCM, a UF e a competência em `inicio` e `fim`. Os exemplos tratam o 429: esperam o `Retry-After` e tentam de novo, até 5 vezes.

**Exemplos em 7 linguagens: Consulta com tratamento de 429**

**cURL** (`consulta.sh`)

```shell
#!/bin/sh
# Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).
# Precisa de RMF_API_URL (comece pelo sandbox) e RMF_API_KEY no ambiente. Requer curl e jq.
set -eu
: "${RMF_API_URL:?defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1)}"
: "${RMF_API_KEY:?defina RMF_API_KEY (chave rmf_test_ no sandbox)}"

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

for tentativa in 1 2 3 4 5; do
  # RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite), para ver a espera funcionar
  simular=""; [ "$tentativa" = 1 ] && [ "${RMF_SIMULAR_429:-}" = 1 ] && simular="X-Sandbox-Simular: limite"
  status=$(curl -sS -G "$RMF_API_URL/icms/aliquotas" \
    --data-urlencode ncm=85414121 --data-urlencode uf=SP \
    --data-urlencode inicio=2026-10 --data-urlencode fim=2026-10 \
    -H "Authorization: Bearer $RMF_API_KEY" -H 'Accept: application/json' ${simular:+-H "$simular"} \
    -D "$tmp/cabecalhos" -o "$tmp/corpo" -w '%{http_code}')
  if [ "$status" = 429 ]; then
    espera=$(awk -F': ' 'tolower($1)=="retry-after" {print int($2)}' "$tmp/cabecalhos")
    echo "429: aguardando ${espera:-1}s (tentativa $tentativa)" >&2
    sleep "${espera:-1}"
    continue
  fi
  if [ "$status" -ge 400 ]; then # application/problem+json
    jq -r '"\(.codigo) (\(.request_id)): \(.detail)"' "$tmp/corpo" >&2
    exit 1
  fi
  jq -r '.periodos[0] | "aliquota_interna=\(.aliquota_interna) aliquota_st=\(.aliquota_st) confianca=\(.confianca)"' "$tmp/corpo"
  exit 0
done
echo "limite_excedido: tente mais tarde" >&2
exit 1
```

**TypeScript** (`consulta.ts`)

```typescript
// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).
// Node 22.18 ou mais novo (fetch nativo; roda o .ts direto). Precisa de RMF_API_URL e RMF_API_KEY.
const BASE = process.env.RMF_API_URL;
const CHAVE = process.env.RMF_API_KEY;
if (!BASE || !CHAVE) {
  console.error("defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY");
  process.exit(2);
}

type Periodo = {
  inicio: string;
  fim: string;
  aliquota_interna: number;
  aliquota_st: number | null;
  na_st: boolean | null;
  confianca: "confirmada" | "provavel" | "a_confirmar";
};

async function consultarAliquota(ncm: string, uf: string, competencia: string): Promise<Periodo> {
  const url = new URL(`${BASE}/icms/aliquotas`);
  url.search = new URLSearchParams({ ncm, uf, inicio: competencia, fim: competencia }).toString();

  for (let tentativa = 1; tentativa <= 5; tentativa++) {
    // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)
    const simular = tentativa === 1 && process.env.RMF_SIMULAR_429 === "1" ? { "X-Sandbox-Simular": "limite" } : {};
    const resp = await fetch(url, {
      headers: { Authorization: `Bearer ${CHAVE}`, Accept: "application/json", ...simular },
    });
    if (resp.status === 429) {
      const espera = Number(resp.headers.get("Retry-After") ?? "1");
      console.error(`429: aguardando ${espera}s (tentativa ${tentativa})`);
      await new Promise((r) => setTimeout(r, espera * 1000));
      continue;
    }
    if (!resp.ok) {
      const erro = await resp.json(); // application/problem+json
      throw new Error(`${erro.codigo} (${erro.request_id}): ${erro.detail}`);
    }
    const corpo = await resp.json();
    return corpo.periodos[0];
  }
  throw new Error("limite_excedido: tente mais tarde");
}

const p = await consultarAliquota("85414121", "SP", "2026-10");
console.log(`aliquota_interna=${p.aliquota_interna} aliquota_st=${p.aliquota_st} confianca=${p.confianca}`);
```

**Python** (`consulta.py`)

```python
"""Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).
Python 3.9 ou mais novo, com requests. Precisa de RMF_API_URL e RMF_API_KEY no ambiente."""
import os
import sys
import time

import requests

BASE = os.environ.get("RMF_API_URL")
CHAVE = os.environ.get("RMF_API_KEY")
if not BASE or not CHAVE:
    sys.exit("defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY")


def consultar_aliquota(ncm: str, uf: str, competencia: str) -> dict:
    params = {"ncm": ncm, "uf": uf, "inicio": competencia, "fim": competencia}
    headers = {"Authorization": f"Bearer {CHAVE}", "Accept": "application/json"}
    for tentativa in range(1, 6):
        # RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)
        extra = {"X-Sandbox-Simular": "limite"} if tentativa == 1 and os.environ.get("RMF_SIMULAR_429") == "1" else {}
        resp = requests.get(f"{BASE}/icms/aliquotas", params=params, headers={**headers, **extra}, timeout=15)
        if resp.status_code == 429:
            espera = int(resp.headers.get("Retry-After", "1"))
            print(f"429: aguardando {espera}s (tentativa {tentativa})", file=sys.stderr)
            time.sleep(espera)
            continue
        if not resp.ok:
            erro = resp.json()  # application/problem+json
            raise RuntimeError(f"{erro['codigo']} ({erro['request_id']}): {erro.get('detail')}")
        return resp.json()["periodos"][0]
    raise RuntimeError("limite_excedido: tente mais tarde")


p = consultar_aliquota("85414121", "SP", "2026-10")
print(f"aliquota_interna={p['aliquota_interna']} aliquota_st={p['aliquota_st']} confianca={p['confianca']}")
```

**PHP** (`consulta.php`)

```php
<?php
// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).
// PHP 8.1 ou mais novo, extensão curl. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.
$base = getenv('RMF_API_URL');
$chave = getenv('RMF_API_KEY');
if (!$base || !$chave) {
    fwrite(STDERR, "defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY\n");
    exit(2);
}

function consultarAliquota(string $base, string $chave, string $ncm, string $uf, string $competencia): array
{
    $url = $base . '/icms/aliquotas?' . http_build_query([
        'ncm' => $ncm, 'uf' => $uf, 'inicio' => $competencia, 'fim' => $competencia,
    ]);
    for ($tentativa = 1; $tentativa <= 5; $tentativa++) {
        $retryAfter = 1;
        $ch = curl_init($url);
        $cabecalhos = ["Authorization: Bearer $chave", 'Accept: application/json'];
        // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)
        if ($tentativa === 1 && getenv('RMF_SIMULAR_429') === '1') {
            $cabecalhos[] = 'X-Sandbox-Simular: limite';
        }
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $cabecalhos,
            CURLOPT_HEADERFUNCTION => function ($ch, $linha) use (&$retryAfter) {
                if (stripos($linha, 'Retry-After:') === 0) {
                    $retryAfter = (int) trim(substr($linha, 12));
                }
                return strlen($linha);
            },
        ]);
        $corpo = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);

        if ($status === 429) {
            fwrite(STDERR, "429: aguardando {$retryAfter}s (tentativa $tentativa)\n");
            sleep($retryAfter);
            continue;
        }
        $json = json_decode($corpo, true);
        if ($status >= 400) { // application/problem+json
            throw new RuntimeException("{$json['codigo']} ({$json['request_id']}): {$json['detail']}");
        }
        return $json['periodos'][0];
    }
    throw new RuntimeException('limite_excedido: tente mais tarde');
}

$p = consultarAliquota($base, $chave, '85414121', 'SP', '2026-10');
echo 'aliquota_interna=', $p['aliquota_interna'], ' aliquota_st=', $p['aliquota_st'] ?? 'null',
    ' confianca=', $p['confianca'], PHP_EOL;
```

**C#** (`consulta.cs`)

```csharp
// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).
// .NET 8 ou mais novo, top-level statements. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.
using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;

var baseUrl = Environment.GetEnvironmentVariable("RMF_API_URL");
var chave = Environment.GetEnvironmentVariable("RMF_API_KEY");
if (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(chave))
{
    Console.Error.WriteLine("defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY");
    return 2;
}

var http = new HttpClient { BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", chave);

var p = await ConsultarAliquota("85414121", "SP", "2026-10");
Console.WriteLine($"aliquota_interna={p.GetProperty("aliquota_interna")} aliquota_st={p.GetProperty("aliquota_st")} confianca={p.GetProperty("confianca").GetString()}");
return 0;

async Task<JsonElement> ConsultarAliquota(string ncm, string uf, string competencia)
{
    var caminho = $"icms/aliquotas?ncm={ncm}&uf={uf}&inicio={competencia}&fim={competencia}";
    for (var tentativa = 1; tentativa <= 5; tentativa++)
    {
        // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)
        using var req = new HttpRequestMessage(HttpMethod.Get, caminho);
        if (tentativa == 1 && Environment.GetEnvironmentVariable("RMF_SIMULAR_429") == "1")
            req.Headers.Add("X-Sandbox-Simular", "limite");
        using var resp = await http.SendAsync(req);
        if (resp.StatusCode == HttpStatusCode.TooManyRequests)
        {
            var espera = resp.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(1);
            Console.Error.WriteLine($"429: aguardando {espera.TotalSeconds}s (tentativa {tentativa})");
            await Task.Delay(espera);
            continue;
        }
        using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
        if (!resp.IsSuccessStatusCode) // application/problem+json
        {
            var e = doc.RootElement;
            throw new Exception($"{e.GetProperty("codigo")} ({e.GetProperty("request_id")}): {e.GetProperty("detail")}");
        }
        return doc.RootElement.GetProperty("periodos")[0].Clone();
    }
    throw new Exception("limite_excedido: tente mais tarde");
}
```

**Java** (`Consulta.java`)

```java
// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).
// Java 17 ou mais novo, com Jackson (com.fasterxml.jackson.core:jackson-databind).
// Precisa de RMF_API_URL e RMF_API_KEY no ambiente.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Consulta {
    static final String BASE = System.getenv("RMF_API_URL");
    static final String CHAVE = System.getenv("RMF_API_KEY");
    static final HttpClient HTTP = HttpClient.newHttpClient();
    static final ObjectMapper JSON = new ObjectMapper();

    static JsonNode consultarAliquota(String ncm, String uf, String competencia) throws Exception {
        var uri = URI.create(BASE + "/icms/aliquotas?ncm=" + ncm + "&uf=" + uf
                + "&inicio=" + competencia + "&fim=" + competencia);
        for (int tentativa = 1; tentativa <= 5; tentativa++) {
            var pedido = HttpRequest.newBuilder(uri)
                    .header("Authorization", "Bearer " + CHAVE)
                    .header("Accept", "application/json");
            // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)
            if (tentativa == 1 && "1".equals(System.getenv("RMF_SIMULAR_429"))) {
                pedido.header("X-Sandbox-Simular", "limite");
            }
            var resp = HTTP.send(pedido.GET().build(), HttpResponse.BodyHandlers.ofString());
            if (resp.statusCode() == 429) {
                long espera = Long.parseLong(resp.headers().firstValue("Retry-After").orElse("1"));
                System.err.println("429: aguardando " + espera + "s (tentativa " + tentativa + ")");
                Thread.sleep(espera * 1000);
                continue;
            }
            var corpo = JSON.readTree(resp.body());
            if (resp.statusCode() >= 400) { // application/problem+json
                throw new RuntimeException(corpo.path("codigo").asText() + " ("
                        + corpo.path("request_id").asText() + "): " + corpo.path("detail").asText());
            }
            return corpo.path("periodos").get(0);
        }
        throw new RuntimeException("limite_excedido: tente mais tarde");
    }

    public static void main(String[] args) throws Exception {
        if (BASE == null || CHAVE == null) {
            System.err.println("defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY");
            System.exit(2);
        }
        var p = consultarAliquota("85414121", "SP", "2026-10");
        System.out.println("aliquota_interna=" + p.get("aliquota_interna") + " aliquota_st=" + p.get("aliquota_st")
                + " confianca=" + p.get("confianca").asText());
    }
}
```

**Delphi** (`Consulta.dpr`)

```pascal
program Consulta;
// Consulta a alíquota de ICMS de um NCM e trata o 429 (espera o Retry-After).
// Delphi 10.3 ou mais novo. Precisa de RMF_API_URL e RMF_API_KEY no ambiente.
// Exemplo escrito e revisado à mão: não roda no CI (não existe compilador Delphi em container).

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.JSON, System.Net.HttpClient, System.Net.URLClient;

function ConsultarAliquota(const ABase, AChave, ANcm, AUf, ACompetencia: string): TJSONObject;
var
  Http: THTTPClient;
  Resp: IHTTPResponse;
  Corpo: TJSONObject;
  Tentativa, Espera: Integer;
begin
  Http := THTTPClient.Create;
  try
    Http.CustomHeaders['Authorization'] := 'Bearer ' + AChave;
    Http.CustomHeaders['Accept'] := 'application/json';
    for Tentativa := 1 to 5 do
    begin
      // RMF_SIMULAR_429=1 pede ao sandbox um 429 na primeira tentativa (X-Sandbox-Simular: limite)
      if (Tentativa = 1) and (GetEnvironmentVariable('RMF_SIMULAR_429') = '1') then
        Http.CustomHeaders['X-Sandbox-Simular'] := 'limite'
      else
        Http.CustomHeaders['X-Sandbox-Simular'] := '';
      Resp := Http.Get(Format('%s/icms/aliquotas?ncm=%s&uf=%s&inicio=%s&fim=%s',
        [ABase, ANcm, AUf, ACompetencia, ACompetencia]));
      if Resp.StatusCode = 429 then
      begin
        Espera := StrToIntDef(Resp.HeaderValue['Retry-After'], 1);
        Writeln(ErrOutput, Format('429: aguardando %ds (tentativa %d)', [Espera, Tentativa]));
        Sleep(Espera * 1000);
        Continue;
      end;
      Corpo := TJSONObject.ParseJSONValue(Resp.ContentAsString(TEncoding.UTF8)) as TJSONObject;
      if Resp.StatusCode >= 400 then // application/problem+json
      try
        raise Exception.CreateFmt('%s (%s): %s', [Corpo.GetValue<string>('codigo'),
          Corpo.GetValue<string>('request_id'), Corpo.GetValue<string>('detail')]);
      finally
        Corpo.Free;
      end;
      Exit(Corpo);
    end;
    raise Exception.Create('limite_excedido: tente mais tarde');
  finally
    Http.Free;
  end;
end;

var
  Base, Chave: string;
  Resposta, Periodo: TJSONObject;
begin
  Base := GetEnvironmentVariable('RMF_API_URL');
  Chave := GetEnvironmentVariable('RMF_API_KEY');
  if (Base = '') or (Chave = '') then
  begin
    Writeln(ErrOutput, 'defina RMF_API_URL (sandbox: https://sandbox.api.rmfestrategia.com/v1) e RMF_API_KEY');
    Halt(2);
  end;
  Resposta := ConsultarAliquota(Base, Chave, '85414121', 'SP', '2026-10');
  try
    Periodo := Resposta.GetValue<TJSONArray>('periodos').Items[0] as TJSONObject;
    Writeln('aliquota_interna=', Periodo.GetValue('aliquota_interna').ToString,
      ' aliquota_st=', Periodo.GetValue('aliquota_st').ToString,
      ' confianca=', Periodo.GetValue<string>('confianca'));
  finally
    Resposta.Free;
  end;
end.
```


## 2. Leia a resposta

A resposta vem agrupada em períodos: um novo período começa quando alguma informação muda. Para uma competência só, vem um período.

```json title="200 OK, application/json"
{
  "ncm": "85414121",
  "uf": "SP",
  "versao_base": "e964db7cb0e8",
  "periodos": [
    {
      "inicio": "2026-10",
      "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"
    }
  ]
}
```

| Campo | Tipo | O que é |
|---|---|---|
| `aliquota_interna` | número | Alíquota interna da operação própria, pela lei do estado ou, sem mapa da lei, a padrão do estado. Vem sempre, dentro ou fora da ST |
| `aliquota_st` | número ou `null` | Alíquota que a substituição tributária usa quando `na_st` é `true` (pode ser diferente da interna); `null` fora da ST. Nunca vem `0` para dizer "fora da ST" |
| `na_st` | booleano ou `null` | Se o NCM está na lista de ST no período. `null` quando a lista não é avaliada no estado (hoje, fora de SP) |
| `situacao_st` | texto | Para exibir ao usuário. Para decidir no código, use `na_st` |
| `origem` | `lei`, `padrao_uf`, `pratica_mercado` | De onde vem a alíquota: a lei do estado, a alíquota padrão do estado ou a prática de mercado agregada e anonimizada |
| `confianca` | `confirmada`, `provavel`, `a_confirmar` | Quanto a base confia no resultado do período inteiro |
| `texto_curto` | texto | Uma frase com a origem, a situação na ST e a confiança, para exibir |
| `versao_base` | texto | Versão da base de alíquotas usada. Guarde junto do resultado |

A resposta traz o resultado, não a regra: sem artigo de lei e sem o caminho que levou ao valor.

## 3. Decida pela confiança

Leia `confianca` antes de usar o valor. Ela é a mais fraca entre a confiança na situação na ST e a confiança na alíquota interna.

A lei decide o NCM sem dúvida.
A lei decide, mas a descrição do produto não foi conferida: o NCM está na ST apenas pelo código.
A base não decide sozinha. O valor é a melhor estimativa: confirme antes de usar em apuração.

:::warning[Fora de SP, toda alíquota sai a_confirmar]
Hoje só SP tem mapa da lei. Os demais estados usam a alíquota padrão do estado, com `na_st` e `aliquota_st` nulos. A cobertura de cada UF está em `GET /v1/icms/cobertura`.
:::

## Se der erro

Erros vêm em `application/problem+json` (RFC 9457). Decida pelo `codigo`, nunca pelo texto, e guarde o `request_id` para falar com o suporte.

| HTTP | `codigo` | O que fazer |
|---|---|---|
| 401 | [`chave_ausente`](/erros/chave_ausente), [`chave_invalida`](/erros/chave_invalida) | Envie `Authorization: Bearer`. Confira o ambiente: chave `rmf_test_` só vale no sandbox |
| 403 | [`escopo_insuficiente`](/erros/escopo_insuficiente) | Use uma chave com o escopo `consultas:ler` |
| 403 | [`fora_do_plano`](/erros/fora_do_plano) | O produto não está no plano da conta. Fale com a equipe da RMF para mudar o plano |
| 422 | [`parametro_invalido`](/erros/parametro_invalido) | Veja `erros`: o NCM tem 8 dígitos, sem ponto; a competência é `AAAA-MM` |
| 429 | [`limite_excedido`](/erros/limite_excedido) | Espere o tempo de `Retry-After` e tente de novo |
| 503 | [`servico_indisponivel`](/erros/servico_indisponivel) | O serviço está fora do ar ou ocupado. Repita com espera exponencial, respeitando `Retry-After`. Em POST, repita com a mesma `Idempotency-Key` |

```http title="429, application/problem+json, Retry-After: 23"
{
  "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"
}
```

## Próximos passos

- [Conferir a alíquota de ICMS antes de emitir](/guias/conferir-aliquota-icms): lote de até 500 pares e cobertura por estado.
- [Autenticação e escopos](/comecar/autenticacao)
- [Use com IA](/use-com-ia): integrar com um assistente de código.
- [Referência da API](/referencia): todas as operações, com "testar agora" no sandbox.

:::info[Ambiente desta página]
Sandbox, banco próprio: `https://sandbox.api.rmfestrategia.com/v1`.
:::
