Pular para o conteúdo principal
Abrir no ChatGPTAbrir no Claude

Enviar o SPED e receber o relatório de ICMS-ST

O caminho completo, do arquivo ao resumo em JSON: cadastrar a empresa, enviar o SPED, esperar a leitura, pedir o relatório e ler o resultado.

Escopos empresas:escrever, arquivos:enviar, arquivos:ler, relatorios:criar, relatorios:ler

1. Cadastre a empresa​

O CNPJ do SPED precisa ser de uma empresa da conta; senão o envio responde 422 empresa_nao_cadastrada.

POST /v1/empresas, com Idempotency-Key
{ "cnpj": "11222333000181", "nome": "Empresa Exemplo Comércio Ltda" }

CNPJ que já está na conta responde 409 empresa_ja_cadastrada. Conta de parceiro informa também o rede_id da carteira.

2. Envie o arquivo e espere a leitura​

POST /v1/arquivos, multipart/form-data, campo arquivo, um arquivo por chamada, com Idempotency-Key. Tipo, CNPJ e período saem do próprio arquivo. A resposta é 202 com o arquivo e o Location do job de leitura; acompanhe com GET /v1/jobs/{id}, respeitando o Retry-After, ou receba o webhook.

envio.sh
#!/bin/sh
# Envia um SPED, espera o job de leitura (respeitando o Retry-After) e mostra o estado do arquivo.
# 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)}"
ARQUIVO=${RMF_ARQUIVO:-efd-contribuicoes-11222333000181-2025-01.txt}

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
# Um UUID por operação lógica; o MESMO UUID em todas as tentativas do envio.
chave=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)

espera() { awk -F': ' 'tolower($1)=="retry-after" {print int($2)}' "$tmp/cabecalhos"; }
get() { # get <caminho>: grava o corpo em $tmp/corpo e devolve o status; em 429, espera o Retry-After e repete
for _ in 1 2 3 4 5; do
s=$(curl -sS "$RMF_API_URL$1" -H "Authorization: Bearer $RMF_API_KEY" \
-D "$tmp/cabecalhos" -o "$tmp/corpo" -w '%{http_code}')
[ "$s" = 429 ] || break
pausa=$(espera)
sleep "${pausa:-1}"
done
echo "$s"
}
falha() { jq -r '"\(.codigo) (\(.request_id)): \(.detail)"' "$tmp/corpo" >&2; exit 1; }

status=0
for tentativa in 1 2 3 4 5; do
status=$(curl -sS "$RMF_API_URL/arquivos" -H "Authorization: Bearer $RMF_API_KEY" \
-H "Idempotency-Key: $chave" -F "arquivo=@$ARQUIVO" \
-D "$tmp/cabecalhos" -o "$tmp/corpo" -w '%{http_code}')
[ "$status" = 429 ] || break
pausa=$(espera)
sleep "${pausa:-1}"
done
[ "$status" -lt 400 ] || falha
arquivo_id=$(jq -r .id "$tmp/corpo")

# 202: a leitura segue em segundo plano e o job vem em Location. 200: o mesmo conteúdo já foi enviado.
job_id=$(awk -F': ' 'tolower($1)=="location" {sub(/\r$/, "", $2); n=split($2, p, "/"); print p[n]}' "$tmp/cabecalhos")
estado_job=-
if [ -n "$job_id" ]; then
for _ in $(seq 1 90); do
[ "$(get "/jobs/$job_id")" -lt 400 ] || falha
estado_job=$(jq -r .estado "$tmp/corpo")
case "$estado_job" in
concluido | concluido_sem_dados | cancelado) break ;;
falhou) jq -r '"falhou: \(.erro.codigo)"' "$tmp/corpo" >&2; exit 1 ;;
esac
pausa=$(espera)
sleep "${pausa:-2}"
done
fi

[ "$(get "/arquivos/$arquivo_id")" -lt 400 ] || falha
echo "arquivo=$arquivo_id estado=$(jq -r .estado "$tmp/corpo") job=$estado_job"
Exemplo testado no CI contra o sandbox
  • Mesmo conteúdo enviado de novo pela mesma conta: 200 com o arquivo existente e duplicado: true.
  • Período já lido para o mesmo CNPJ: o arquivo termina em periodo_ja_lido e não é usado de novo.
  • Uma retificadora substitui o arquivo anterior, que passa a substituido; o campo substitui do arquivo novo diz qual.
  • Estados do arquivo: recebido, na_fila, lendo, lido, periodo_ja_lido, substituido, rejeitado (com o motivo).

No sandbox, o limite do arquivo é 20 MB e os resultados são fixos para os arquivos de exemplo. Baixe o efd-contribuicoes-11222333000181-2025-01.txt sintético (a lista completa está em Sandbox).

3. Peça o relatório​

Com o arquivo em lido, POST /v1/relatorios com Idempotency-Key:

Corpo da requisição
{
"tipo": "icms_st",
"cnpj": "11222333000181",
"periodo": { "inicio": "2025-01", "fim": "2025-01" },
"opcoes": { "periodicidade_saida": "mensal" }
}

Os tipos disponíveis para a sua conta estão em GET /v1/relatorios/tipos; consulte-o em vez de fixar a lista no código. Se faltar o arquivo de uma competência, o relatório falha com arquivo_faltando. Neste lançamento os relatórios aceitam só empresa com CNPJ numérico (14 dígitos): um CNPJ alfanumérico responde 422 parametro_invalido. O relatório planilha_sped gera uma planilha só, do primeiro tipo de SPED com arquivo lido no período.

4. Leia o resumo​

Espere o job (ou o evento relatorio.concluido) e leia GET /v1/relatorios/{id}. O resumo em JSON vem sempre; a planilha de resultado só aparece em arquivos quando o plano da conta a inclui, e se baixa com GET /v1/relatorios/{id}/arquivos/{arquivo_id}.

200 OK (recorte, valores ilustrativos)
{
"id": "rel_01JC8F6A9V1T4B7C2D5E8FGHJK",
"tipo": "icms_st",
"estado": "concluido",
"resumo": {
"total_credito": "18234.57",
"por_competencia": [{ "competencia": "2026-01", "total_credito": "2011.40" }],
"versao_base_aliquotas": "e964db7cb0e8"
},
"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."]
}

Mostre os avisos ao usuário. Estados do relatório: na_fila, processando, concluido, concluido_sem_dados, falhou (com erro.codigo) e cancelado.