Primeira chamada em 5 minutos
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.
Antes de começar
- Uma chave de sandbox
rmf_test_com o escopoconsultas:ler. A equipe da RMF emite a chave: peça pelo seu contato comercial (veja Como conseguir a 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_URLeRMF_API_KEY:
export RMF_API_URL="https://sandbox.api.rmfestrategia.com/v1"
export RMF_API_KEY="<sua chave do sandbox, 75 caracteres>"
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.
- cURL
- TypeScript
- Python
- PHP
- C#
- Java
- Delphi
#!/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
// 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}`);
"""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 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;
// 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");
}
// 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());
}
}
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.
{
"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.
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, chave_invalida | Envie Authorization: Bearer. Confira o ambiente: chave rmf_test_ só vale no sandbox |
| 403 | escopo_insuficiente | Use uma chave com o escopo consultas:ler |
| 403 | 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 | Veja erros: o NCM tem 8 dígitos, sem ponto; a competência é AAAA-MM |
| 429 | limite_excedido | Espere o tempo de Retry-After e tente de novo |
| 503 | 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 |
{
"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: lote de até 500 pares e cobertura por estado.
- Autenticação e escopos
- Use com IA: integrar com um assistente de código.
- Referência da API: todas as operações, com "testar agora" no sandbox.
Sandbox, banco próprio: https://sandbox.api.rmfestrategia.com/v1.
Esta página em Markdown: https://docs.rmfestrategia.com/comecar/primeira-chamada.md