Skip to main content

emissaoja: API de NFS-e nacional para Python

PyPI CI Licença MIT

Cliente oficial da API do emissaoja para emitir NFS-e (Nota Fiscal de Serviço eletrônica) no Emissor Nacional (nfse.gov.br) a partir do seu sistema em Python, sem certificado digital: o emissaoja entra no portal com o CNPJ e a senha da própria empresa. Feito para MEI e empresas do Simples Nacional. Cadastre tomadores pelo CNPJ (nome e endereço vêm da Receita Federal), emita, acompanhe a autorização, baixe o PDF (DANFSe) e o XML e cancele notas.

  • Só biblioteca padrão (urllib), Python 3.9+, com py.typed.
  • Idempotência embutida: toda emissão leva uma referencia sua, que vira o cabeçalho Idempotency-Key. Repetir a mesma referência nunca emite duas notas.
  • notas.aguardar() acompanha o pedido até autorizada, recusada ou incerta, no ritmo que a API indica (proxima_consulta_ms).
  • Erros com code estável (nota_bloqueada, limite_notas, possivel_duplicada…) e o que falta resolver em extras["bloqueios"].
  • Sandbox grátis com Emissor Nacional simulado: teste o fluxo inteiro sem efeito fiscal.

Instalação

pip install emissaoja

Crie a conta com o Google em emissaoja.com.br e gere a chave em Conta → API (ela aparece uma vez; prefixo ej_live_). Guarde a chave no servidor:

export EMISSAOJA_KEY=ej_live_...

Teste no sandbox em 2 minutos

O sandbox (https://sandbox.emissaoja.com.br) é o mesmo emissaoja com um Emissor Nacional simulado: nada ali existe no fisco e nada é cobrado. Entre com o Google, crie uma chave ej_test_ em Conta → API e rode:

from emissaoja import BASE_SANDBOX, Emissaoja

ej = Emissaoja(base_url=BASE_SANDBOX)  # lê a chave de EMISSAOJA_KEY; ou Emissaoja("ej_test_...", base_url=...)

# 1. Conectar a empresa (uma vez). 11111111000191 é um MEI de exemplo pronto para emitir; qualquer senha serve.
ej.empresa.conectar("11111111000191", "senha-de-teste")
ej.empresa.aguardar_credencial()

# 2. Tomador pelo CNPJ: nome e endereço vêm da Receita Federal
tomador = ej.tomadores.criar("12345678000195")["tomador"]

# 3. Emitir (prévia + emissão num passo). A referência é o seu id da nota.
pedido = ej.notas.emitir(
    "pedido-2026-11-0001",
    tomador["tipo"],
    tomador["id"],
    1500,
    descricao="Consultoria em marketing digital - novembro/2026",
)["pedido"]

# 4. Acompanhar até autorizada | recusada | incerta
final = ej.notas.aguardar(pedido["id"])["pedido"]
print(final["estado"], final.get("numero"), final.get("chave_acesso"))

# 5. PDF (DANFSe) e XML
if final["estado"] == "autorizada":
    pdf = ej.notas.pdf(final["nota"]["origem"], final["nota"]["id"])  # bytes
    xml = ej.notas.xml(final["nota"]["origem"], final["nota"]["id"])  # str

Valores com significado no sandbox: 13.13 gera uma nota incerta que ej.emissoes.conferir(id) resolve como autorizada, 77.77 uma incerta que não saiu, 66.66 uma recusada pelo fisco e 99.99 um bloqueio já na prévia (422 nota_bloqueada). Outras empresas de exemplo: 22222222000191 (ME que pede a alíquota do Simples antes de emitir), 33333333000191 (MEI sem notas anteriores) e 44444444000191 (bloqueada). A senha errada simula senha recusada pelo portal. Exemplo completo em exemplos/emitir_nfse_sandbox.py.

Em produção

Emissaoja() usa https://emissaoja.com.br e lê EMISSAOJA_KEY. A chave ej_live_ vale para a empresa e o plano da conta que a criou; chaves ej_test_ só valem no sandbox.

A chave é secreta. Use o cliente só no servidor (Django, Flask, FastAPI, filas, scripts). Quem tiver a chave emite notas em nome da empresa.

Com Django ou FastAPI, crie um cliente por processo e reutilize (ele não guarda estado entre chamadas):

# settings.py / config.py
from emissaoja import Emissaoja

EMISSAOJA = Emissaoja()  # chave de EMISSAOJA_KEY, lida na primeira chamada

Empresa: conectar ao Emissor Nacional

ej.empresa.conectar("11222333000181", "senha-do-emissor-nacional")  # 202, verificação em segundo plano
tentativa = ej.empresa.aguardar_credencial()["tentativa"]
# tentativa["proximo_passo"]: configurando | pronto | corrigir_senha | tentar_mais_tarde | redefinir_no_portal | suporte

empresa = ej.empresa.obter()  # empresa["onboarding"]["estado"]: configurando → perguntas → pronto
if empresa["onboarding"]["estado"] == "perguntas":
    print(ej.empresa.onboarding())  # o que o cadastro fiscal não respondeu sozinho
    ej.empresa.fiscal(aliquota_simples=6)  # só com o valor que a empresa informou

A senha é conferida no portal e nunca volta em resposta. O portal bloqueia a conta da empresa depois de algumas senhas erradas: a API limita a 3 tentativas erradas por CNPJ em 24 h.

Tomadores e serviços

ej.tomadores.criar("52998224725", nome="Maria da Silva")  # CPF precisa do nome
ej.tomadores.listar(busca="alfa", ordem="recentes")
ej.tomadores.buscar("12.345.678")

itens = ej.servicos.catalogo("consultoria")["itens"]  # código nacional (cTribNac) por texto
ej.servicos.criar(descricao="Consultoria", c_trib_nac=itens[0]["c_trib_nac"], trib_issqn="tributavel", retem_iss=False)
ej.servicos.listar()  # emitir sem servico_id usa o principal

Notas

notas = ej.notas.listar(periodo="mes_atual", situacao="emitidas")["notas"]
operacao = ej.notas.cancelar("local", nota_id, "1", justificativa="Valor digitado errado.")["operacao"]
ej.operacoes.aguardar(operacao["id"])  # processando → concluida | falhou

Nota não se altera: para corrigir, cancele e emita outra. Nota autorizada e depois cancelada conta no plano; recusada não conta.

Idempotência e nota em dobro

  • Use uma referencia por nota que você quer emitir (o id do seu pedido serve; 8 a 64 caracteres: letras, números, _ e -). Repetir a mesma devolve o mesmo pedido com repetido: True.
  • 503 fila_indisponivel: repita com a mesma referência.
  • 409 possivel_duplicada: já existe nota igual (tomador, valor e competência) no mesmo dia. Se for mesmo outra nota, repita com permitir_repetida_de=e.extras["nota_igual_recente"]["pedido_id"].
  • 409 emissao_repetida_recente: mesma nota há menos de 2 minutos. Se for intencional, repita com confirmo_repeticao=True.
  • A biblioteca não repete chamadas sozinha: emitir tem efeito fiscal, e quem decide repetir é você.

Erros

from emissaoja import EmissaojaErro

try:
    ej.notas.emitir("pedido-0002", "pj", tomador_id, 100)
except EmissaojaErro as e:
    if e.code == "nota_bloqueada":
        print(e.extras["bloqueios"])  # [{"code", "mensagem", "acao"}]
    elif e.code == "limite_notas":
        print("limite do plano no mês")
    else:
        print(e.status, e.code, e, e.request_id)
HTTP code O que fazer
400 validation_error, valor_invalido, cnpj_invalido, chave_idempotencia_invalida Corrigir o campo
401 chave_invalida Conferir a chave (ou o ambiente: ej_test_ só no sandbox)
402 limite_notas Limite do plano no mês
404 tomador_nao_encontrado, servico_nao_encontrado, nota_nao_encontrada, pedido_nao_encontrado Conferir o id
409 sem_empresa Conectar a empresa (empresa.conectar)
409 aceite_pendente Termos ou Política mudaram: entre no app e aceite a versão nova; a chave volta a funcionar
409 possivel_duplicada, emissao_repetida_recente, incerta_pendente Ver "Idempotência e nota em dobro"
422 nota_bloqueada Resolver extras["bloqueios"]
429 rate_limited Esperar Retry-After
503 motor_indisponivel, fila_indisponivel, receita_indisponivel Tentar de novo (com a mesma referência)
0 rede, tempo_esgotado Falha de rede ou tempo esgotado no seu lado

Limites: 60 chamadas por minuto por chave, 10 emissões por minuto e 120 por minuto em buscas e catálogo.

Referência

Recurso Métodos
empresa obter, conectar, credencial, aguardar_credencial, onboarding, fiscal, reler_fisco
tomadores listar, buscar, criar, obter, atualizar
servicos listar, criar, atualizar, desativar, catalogo
notas emitir, aguardar, listar, pdf, xml, cancelar
emissoes obter, listar, conferir
operacoes obter, aguardar
cnpj consultar (dados da Receita Federal)
— status() (sem chave)

Emissaoja(chave=None, base_url=BASE_PRODUCAO, timeout=30.0). Referência completa da API: emissaoja.com.br/docs e OpenAPI 3.1.

Perguntas frequentes

Precisa de certificado digital? Não. O emissaoja entra no Emissor Nacional com o CNPJ e a senha do portal da própria empresa, o acesso que o portal oferece a MEI e empresas do Simples Nacional.

Funciona para MEI? Sim. O público é MEI, ME e EPP optantes pelo Simples Nacional, em um município que emite pelo Emissor Nacional.

Tem ambiente de testes? Sim, o sandbox em https://sandbox.emissaoja.com.br, com o Emissor Nacional simulado e empresas de exemplo.

Quanto custa? O plano Grátis emite 1 nota por mês; os planos pagos estão em emissaoja.com.br/#precos. O sandbox é grátis. Nota recusada não conta.

Tem webhook? Ainda não. Acompanhe o pedido com notas.aguardar() ou emissoes.obter(), que trazem proxima_consulta_ms.

Outras formas de integrar

In English

Official Python client for the emissaoja API: issue Brazilian NFS-e (electronic service invoices) through the national Emissor Nacional without a digital certificate, for MEI and Simples Nacional companies. Standard library only, typed, idempotent issuing, PDF and XML download, and a free sandbox with a simulated tax authority. The API and the docs are in Portuguese.


Suporte: suporte@emissaoja.com.br. Licença MIT, © 2026 BINOBO LTDA.

O emissaoja é um serviço privado e não tem vínculo com a Receita Federal nem com o Emissor Nacional.

Metadata

Release files for emissaoja 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for emissaoja 0.1.0
File Size Uploaded Provenance Actions
emissaoja-0.1.0.tar.gz 12.9 kB With provenance Details for emissaoja-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for emissaoja 0.1.0
File Interpreter ABI Platform Actions
emissaoja-0.1.0-py3-none-any.whl Python 3 none any Details for emissaoja-0.1.0-py3-none-any.whl

Total release size: 24.1 kB

Release files / emissaoja-0.1.0.tar.gz

Download URL emissaoja-0.1.0.tar.gz
Size 12.9 kB
Tags Source
SHA-256 checksum
How to use checksums
8bbcd17f7747d1424d0fbfa7d88aec15835b63807d3a2ea4c59267f0665b4b74
BLAKE2b-256 checksum
How to use checksums
70f0ecf23097c3f366a058890ef9823485369aee3830c67b941a98d176f68f47
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Release files / emissaoja-0.1.0-py3-none-any.whl

Download URL emissaoja-0.1.0-py3-none-any.whl
Size 11.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4ef1498b8c1d678de882cee1e6a4a1424fc1f1becff67b200ffde29742f7f37e
BLAKE2b-256 checksum
How to use checksums
f3c04ec03aa58a753d0db43fb09a389c92f3b1b5206118add0a570fdf336cdbc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Security for emissaoja 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for emissaoja-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page