EntrarComeçar
Referência da API

Referência da API

  • Emita, consulte, cancele e corrija

    Emissão, consulta, cancelamento, reemissão, carta de correção e inutilização de faixa — NF-e e NFS-e, com o que cada uma suporta.

  • JSON sobre HTTPS

    Requisições e respostas são JSON, autenticadas com uma API key Bearer.

  • Sempre no contexto de uma empresa

    Toda requisição pertence a uma empresa — a UF, o endereço e o certificado digital do emitente são resolvidos no servidor a partir da API key, então a requisição só leva os campos de negócio (comprador, itens, valores).

Começando agora?

Crie uma conta gratuita, gere uma API key e emita seu primeiro documento em homologation — é grátis e ilimitado, sem cartão.

Base URL
https://api.stackin.io/api/v1Copiar

Todos os endpoints da API são relativos a essa base URL.

Baixar collection do Postman

Gerada do mesmo OpenAPI desta página. Importe e preencha a variável api_key.

Autenticação

A API autentica com uma API key — mesmo mecanismo do SDK, só que com o contexto em . Sem login, sem JWT — um header Bearer em cada requisição.

Conseguindo uma API key

  1. 1
    Cadastre-se e faça login em .
  2. 2
    Crie uma empresa — obrigatório antes da key.
  3. 3
    No dashboard, abra a empresa e vá em Empresa → API keys. O contexto vem em sdk por padrão — troque para api.
  4. 4
    Nomeie a key e escolha o ambiente — homologation ou production. Os dois são obrigatórios na criação, e o ambiente é fixo pelo resto da vida da key.
  5. 5
    Copie a key na hora — ela aparece , na criação, e não pode ser recuperada depois. Perdeu, revoga e cria outra.
cURL
Copy
curl https://api.stackin.io/api/v1/invoices \
  -H "Authorization: Bearer $STACKIN_API_KEY"

Token ausente ou inválido retorna 401 — veja abaixo.

Ambientes

Cada API key é criada para exatamente um ambiente — ou or — escolhido ao gerá-la, e fixo pelo resto da vida da key.

Não existe campo de ambiente nas requisições da API: a key decide pra onde vai o documento.

HomologationAmbiente de teste

Grátis e ilimitado — é o ambiente de teste real do SEFAZ/ADN, não é mock — então integre e teste tudo antes de trocar pra production.

ProductionAmbiente real

Para documentos reais. Garanta que tudo funciona em homologation antes de trocar.

Uma empresa pode ter keys dos dois ambientes ao mesmo tempo (até 3 no total).

Idempotência

Emitir é a única chamada que você não deve repetir às cegas. Se a resposta se perde — timeout, conexão caída — o documento pode muito bem ter sido autorizado, e uma segunda tentativa emite uma segunda nota fiscal: outro crédito, outro número da série queimado, e desfazer exige cancelamento, que tem prazo.

Mande o header Idempotency-Key para tornar a repetição segura. Repetindo com a mesma chave e o mesmo corpo, você recebe de volta a primeira resposta — sem segunda nota, sem consumir crédito. Vale em POST /invoices e em POST /invoices/{id}/reissue.

Gere a chave você mesmo — um UUID por evento de negócio, não por requisição HTTP — e guarde enquanto ainda puder tentar de novo. As chaves valem 24 horas; depois disso a mesma chave é tratada como nova.

cURL
Copy
curl -X POST https://api.stackin.io/api/v1/invoices \
  -H "Authorization: Bearer $STACKIN_API_KEY" \
  -H "Idempotency-Key: 3f7c1e9a-2b4d-4f6a-8c1e-9d0b5a7f2c34" \
  -H "Content-Type: application/json" \
  -d '{
  "document_type": "nfse",
  "client_name": "John Doe",
  "tax_id": "00000000000",
  "items": [
    { "description": "Technical consulting - 10 hours", "unit_price": "1500.00" }
  ]
}'
Idempotency-Key
header | optionalsua chave, no máximo 255 caracteres. O escopo é a sua empresa: a chave de uma conta nunca colide com a de outra.
201
replaymesma chave, mesmo corpo: devolve a resposta gravada, sem reprocessar.
422
mismatchmesma chave, corpo diferente. Reusar uma chave em outra requisição é erro do cliente, não repetição.
409
in progressa primeira chamada ainda está rodando. Consulte antes de tentar de novo.

Limites de uso

Os limites são por chave de API. Cada empresa tem a sua própria contagem — o volume de um cliente nunca gasta a cota de outro.

Emitir tem limite mais apertado que consultar, e consultar mais apertado que ler o histórico. Quanto mais uma operação depende da SEFAZ, menos chamadas por minuto ela aceita.

Toda resposta traz o limite, quanto sobrou e o horário em que a janela reabre. Ao estourar, a resposta é 429 com Retry-After em segundos — espere esse tempo antes de tentar de novo.

Headers da resposta
Copy
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1788556800
429
Copy
{
  "detail": "Rate limit reached: 60 requests per 60s for this operation. Retry after the window resets."
}
POST /invoices
60 / mingera documento e consome crédito.
POST /invoices/{id}/reissue
60 / mingera documento e consome crédito.
GET /invoices/{access_key}
120 / minfala com a SEFAZ, mas não gera documento.
POST /invoices/{access_key}/cancel
120 / minfala com a SEFAZ, mas não gera documento.
GET /invoices
600 / minnão sai da stackin.

Webhooks

Em vez de consultar em loop para saber o desfecho de uma emissão, cadastre uma URL https e a stackin avisa. Endpoint com events: [] recebe todos; nome de evento desconhecido é recusado no cadastro.

Cada entrega vai assinada em Stackin-Signature, no formato t=<epoch>,v1=<hmac>. O HMAC é SHA-256 sobre {timestamp}.{corpo} com o seu segredo. Confira antes de processar: a assinatura é o que distingue a nossa entrega de quem descobriu a sua URL.

A entrega é ao menos uma vez — em falha tentamos 6 vezes, com espera crescente de 30s até 6h. Use o Stackin-Delivery-Id para ignorar o que já processou. Consulte o histórico em GET /webhooks/deliveries, incluindo as que desistimos de entregar.

cURL
Copy
curl -X POST https://api.stackin.io/api/v1/webhooks \
  -H "Authorization: Bearer $STACKIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://seu-erp.com.br/stackin", "secret": "um-segredo-longo-e-aleatorio", "events": []}'
O que chega no seu endpoint
Copy
{
  "event": "invoice.authorized",
  "data": {
    "invoice_id": "9f2c1e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b",
    "document_type": "nfe",
    "access_key": "35240912345678000199550010000000011000000017",
    "status": "issued",
    "amount": 1500.0
  }
}
Conferindo a assinatura
Copy
import hashlib, hmac, time

def verify(secret: str, body: str, header: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    if abs(int(time.time()) - int(parts["t"])) > tolerance:
        return False
    expected = hmac.new(
        secret.encode(),
        f'{parts["t"]}.{body}'.encode(),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
invoice.authorized
eventa SEFAZ autorizou o documento.
invoice.rejected
eventa SEFAZ recusou. O motivo está no histórico da nota.
invoice.cancelled
evento cancelamento foi aceito.
invoice.corrected
eventuma carta de correção foi registrada.
Stackin-Signature
headero timestamp entra no que é assinado, então uma entrega capturada não pode ser reenviada depois.
Stackin-Delivery-Id
headeridentifica a entrega. Duas com o mesmo id são a mesma coisa, reenviada.

Versionamento

O caminho é /api/v1. Dentro dele podemos acrescentar — campo novo na resposta, valor novo num enum, endpoint novo, parâmetro opcional novo — sem trocar de versão.

A contrapartida é sua: trate campo desconhecido como dado, não como erro. Os quatro SDKs oficiais já fazem isso, cada um com teste travando o comportamento. Se você chama a API direto, não valide a resposta contra um schema fechado.

Remover, mudar tipo, apertar validação ou mudar o significado de um valor exige versão nova. Quando houver uma /v2, a /v1 continua respondendo por no mínimo 12 meses — e avisa em toda resposta, com os headers ao lado.

Headers quando a versão for depreciada
Copy
Deprecation: Fri, 01 Jan 2027 00:00:00 GMT
Sunset: Sat, 01 Jan 2028 00:00:00 GMT
Link: <https://docs.stackin.io/api#versioning>; rel="deprecation"
aditivo
sem versão novacampo novo, valor novo num enum, endpoint novo, parâmetro opcional novo.
breaking
exige /v2remover algo, mudar tipo, apertar validação, mudar o significado de um valor, tornar obrigatório o que era opcional.
Deprecation
HTTP-datequando a versão foi marcada para sair. É HTTP-date, não ISO.
Sunset
HTTP-datequando ela para de responder. Ao menos 12 meses depois do anúncio.

Paginação

GET /invoices é paginado com os query params e and (padrão query params (default , máx , max ).

O envelope da resposta sempre inclui:

total
integertotal de linhas que batem com os filtros.
total_pages
integer
next_page / prev_page
integer | null
data
arrayas linhas da página.

Erros

A API utiliza códigos HTTP padrão. Na maioria dos casos, a resposta contém um objeto detail. Rejeições da SEFAZ ou ADN retornam informações adicionais sobre o autorizador.

Emitir responde 201 Created, não 200. Inutilizar numeração e cadastrar webhook também. Excluir webhook responde 204, sem corpo.

Exemplo de resposta
{
  "detail": "This company has no state set — required for NFe. Set it via PATCH /companies/{id}."
}
HTTPErroQuandoSaiba mais
400Configuração inválidaA configuração do emissor (certificado, endereço, campos fiscais) está ausente ou inválida.
401Não autorizadoO Bearer token está ausente, expirado, inválido ou não foi criado para o host que você está chamando (api.stackin.io vs sdk.stackin.io).Ver detalhes
402Limite atingidoA cota do plano da empresa para notas de production foi atingida e o plano não tem preço de excedente (trial).
404Não encontradoNenhum documento com essa chave de acesso ou invoice_id pertence à empresa da sua key.
409Conflito de operaçãoA operação pedida não se aplica a esse document_type (ex.: operação exclusiva de nfse numa nfe).
422Erro de validaçãoO corpo da requisição falhou na validação — campo obrigatório faltando ou malformado.
429Limite de requisiçõesChamadas demais para essa key na janela atual. O header Retry-After diz quantos segundos esperar.Ver detalhes
501Não implementadoAssinatura para esse tipo de documento/país ainda não implementada no servidor.
502Autorizador rejeitouRejeição do autorizadorO autorizador (SEFAZ/UF ou ADN) rejeitou a requisição. detail.message traz o erro do próprio autorizador, e detail.invoice_id (só na emissão) permite consultar a tentativa rejeitada.Ver detalhes

Rejeições do autorizador

Quando a SEFAZ ou ADN rejeita uma operação, a resposta inclui informações adicionais.

Use detail.invoice_id para consultar o status da tentativa em /invoices/{{id}}.

Copy
{
  "detail": {
    "message": "IE do emitente inválida",
    "invoice_id": "inv_8f3c9b2e6a7d"
  }
}
detail.message
Mensagem retornada pelo autorizador.
detail.invoice_id
Identifica a tentativa de emissão.

Valores do item

unit_price é o preço de uma unidade. amount é o valor bruto total dos produtos da linha, antes de desconto, frete, seguro e outras despesas.

Envie um dos dois. Enviar os dois é afirmar que fecham: quantity x unit_price é comparado com amount e a nota é recusada antes da transmissão se divergirem. Valores decimais podem ir como string para não perder precisão — unit_price aceita até 10 casas.

Copy
# unit_price: what one unit costs
{
  "description": "Teclado",
  "product": { "quantity": 2, "unit": "UN", "br": { "ncm": "84716052", "cfop": "5102" } },
  "unit_price": "120.00"
}
# -> qCom 2.0000  vUnCom 120.0000000000  vProd 240.00

# amount: the line's gross total (the original contract)
{
  "description": "Teclado",
  "product": { "quantity": 3 },
  "amount": "50.00"
}
# -> qCom 3.0000  vUnCom 16.6666666667  vProd 50.00

# both, checked against each other before transmission
{
  "description": "Teclado",
  "product": { "quantity": 2 },
  "unit_price": "120.00",
  "amount": "240.00"
}

Quando não fecham, a resposta é 422 e diz qual linha e quais números:

Copy
{
  "detail": {
    "code": "ITEM_TOTAL_MISMATCH",
    "field": "items[0].amount",
    "message": "amount is 50.00, but quantity x unit_price is 150.00.",
    "expected": "150.00",
    "received": "50.00"
  }
}

Também são recusados: nenhum dos dois campos (ITEM_AMOUNT_MISSING), valores menores ou iguais a zero, desconto maior que o valor bruto (ITEM_DISCOUNT_EXCEEDS_GROSS) e precisão que o XML não carrega (ITEM_PRECISION_UNSUPPORTED). Nada precisa migrar: amount mantém o significado que sempre teve.

IBS/CBS — Reforma Tributária

O grupo da Reforma Tributária, por item. Obrigatório a partir de 2026 para o regime normal. cst e classification (cClassTrib) são decisão fiscal sua; a API deriva os valores das alíquotas.

As três alíquotas são percentuais: rate_state (pIBSUF), rate_city (pIBSMun) e rate_federal (pCBS). base é opcional — sem ela, a base é o valor do item. Item sem o grupo não emite nada, que é como a NF-e era antes da reforma.

Copy
{
  "description": "Teclado",
  "unit_price": "120.00",
  "product": {
    "quantity": 2,
    "br": {
      "ncm": "84716052",
      "cfop": "5102",
      "ibs_cbs": {
        "cst": "000",
        "classification": "000001",
        "rate_state": 0.1,
        "rate_city": 0.0,
        "rate_federal": 0.9
      }
    }
  }
}
Endpoints

Notas fiscais

Emitir nota

Emite um novo documento. items[].product.br (ncm, cfop) é obrigatório para , ignorado em , ignored for — serviço não tem código de classificação fiscal, então nfse só lê description e amount de cada item.

Listar notas

Lista o histórico de emissões da empresa, paginado (veja Paginação acima). Filtre com document_type e and (issued, rejected, cancelled).

Consultar nota

Consulta o status atual de um documento direto no autorizador pela access key. Requer como query param.

Cancelar nota

Cancela um documento previamente autorizado. precisa ter pelo menos 15 caracteres para nfe (o campo xJust do autorizador exige). Cancelar não devolve cota do plano — a capacidade do autorizador já foi gasta na emissão.

Reemitir nota

Reenvia uma emissão que falhou, pelo `id` local do documento (não pela chave de acesso — uma nota rejeitada não tem uma). Útil pra retentar depois de corrigir um problema de configuração ou uma indisponibilidade do autorizador. Consome crédito como uma emissão nova, já que é uma nova transmissão.

Corrigir nota

Corrige uma nota já autorizada com uma carta de correção, sem cancelar nem reemitir — não consome crédito nem queima número da série. Serve para erro que não muda o imposto: nome de produto, dados de transporte, informações complementares.

O texto vai de 15 a 1000 caracteres. Uma CC-e não corrige nada que mude o valor do imposto, o emitente, o destinatário ou a data de emissão — nesses casos ainda é cancelar e reemitir. A nota original não muda: a correção fica anexada a ela. São no máximo 20 por nota, numeradas automaticamente. Só NF-e; para NFS-e a resposta é 409.

Inutilizar faixa

A numeração da NF-e é sequencial e a SEFAZ cobra que não tenha buraco. O número é reservado quando a emissão começa, então uma emissão que falha depois disso — rejeição, timeout — deixa um vão na série. Inutilizar a faixa é como se fecha esse vão.

O motivo vai de 15 a 255 caracteres e a faixa é inclusiva. Número que já chegou na SEFAZ não pode ser inutilizado: conferimos primeiro nos nossos registros e respondemos 409 dizendo quais são, sem ida à SEFAZ — e a SEFAZ confere de novo o que não dá para ver daqui. Só NF-e, e sem chave de acesso: não há documento para apontar. Use GET /invoices/invalidations para listar as faixas já inutilizadas.

PDF do documento

A representação impressa que a autorizadora gera — DANFE para NF-e, DANFSe para NFS-e. Retorna bytes crus, o único método que não devolve objeto: grave em arquivo ou sirva direto.

O XML continua sendo o documento com valor jurídico; o PDF é conveniência. O endpoint da autorizadora para isso é instável, então um erro 502 aqui significa autorizadora indisponível, não que a nota está errada.

Tentativas

Toda tentativa feita para uma nota e o que voltou da autorizadora. consult dá o status; este dá o motivo — o código da rejeição e o XML que foi enviado.

Recebe o invoice_id, não a chave de acesso: um documento rejeitado nunca teve chave. É por aqui que se descobre, por exemplo, que a rejeição foi 972.

Notas recebidas

Documentos que outras empresas emitiram contra o seu CNPJ. Lê o que a API já coletou — não chama a autorizadora na hora.

A coleta roda em agenda do lado da API porque a SEFAZ limita quantas vezes um CNPJ pode consultar por dia. Consultar aqui não gasta essa cota.

Manifestação

A resposta formal de quem recebeu: confirmação, ciência, desconhecimento ou operação não realizada.

Só operação não realizada aceita justificativa — e exige uma. O SDK verifica isso antes de sair da sua máquina, em vez de gastar uma ida à API para ser informado.

Confiável

Mais de 99,9% de uptime

Seguro

Dados criptografados e LGPD ready

Rápido

Infraestrutura escalável e de baixa latência

IA em primeiro lugar

Servidor MCP oficial — emita por agente

© 2026 stackin. Todos os direitos reservados.Feito com código, propósito e café.Política de PrivacidadeTermos de UsoContato