Skip to main content

Integrate once. Issue everywhere.

Python PyPI License

API Reference · Python SDK guide


stackin

Python SDK for fiscal document issuance — a handful of business fields, nothing about certificates, XML, XSD, signing or SOAP. The API resolves all of that from the issuer's own configuration, identified by api_key.

One class, Invoiceissue()/consult()/cancel()/reissue(), nothing else to instantiate. Each line item is a Product (stackin.br) — description/amount are universal, everything else (ncm/cfop/cest/tax groups...) is Brazil-specific and only required for NFE; NFSE ignores it.

Install

pip install stackin-python-sdk

Usage

Get an api_key from the stackin dashboard — select the issuing company, then Settings → API key (context sdk). One key per issuing company, shown once at creation. The API resolves the issuer (CNPJ, state, address, certificate, environment) entirely from it; nothing about the issuer is ever passed on a call.

from stackin import Invoice, DocumentType, Address
from stackin.br import Product  # Brazil-specific line item — NCM/CFOP

client = Invoice(api_key="COMPANY_API_KEY")  # defaults to https://sdk.stackin.io

invoice = client.issue(
    document_type=DocumentType.NFSE,
    client_name="John Doe",
    tax_id="00000000000",
    items=[Product(description="Software development", amount=5000.00)],
)

status = client.consult("ACCESS_KEY...", document_type=DocumentType.NFSE)
client.cancel(
    "ACCESS_KEY...",
    document_type=DocumentType.NFSE,
    reason="Typo",
)
client.reissue(invoice["id"])  # retries a rejected/failed submission

# NFE requires ncm/cfop on every item, plus the buyer's full recipient_address:
client.issue(
    document_type=DocumentType.NFE,
    client_name="Buyer Company Ltd",
    tax_id="11111111111111",
    items=[Product(description="Test product", amount=100.00, ncm="84713012", cfop="5102")],
    recipient_address=Address(
        street="Avenida Atlantica",
        number="500",
        neighborhood="Copacabana",
        city="Rio de Janeiro",
        state="RJ",
        zip_code="22010000",
        city_code="3304557",
    ),
)

recipient_address is an Address — the buyer's address, required for NFE and ignored for NFSE. Every field is required, city_code (the 7-digit IBGE municipality code) included: it becomes enderDest on the wire and the SEFAZ rejects a partial one. state is also what resolves idDest — a buyer in another state is emitted as an interstate operation automatically. A missing or incomplete address raises a ValueError locally, before the request goes out.

items is a list of Product (stackin.br) — description/amount apply to any document type; ncm/cfop (plus everything else on Product: cest, tax groups, presumed credits...) are Brazil-specific and required per item for NFE, ignored for NFSE (a service isn't a physical good).

Retrying safely

Issuing is the one call you must not repeat blindly. If the response is lost — a timeout, a dropped connection — the document may well have been authorized, and a second attempt issues a second fiscal document: another credit, another number burned, and undoing it means cancelling, which has a deadline.

Pass idempotency_key to make the retry safe:

key = str(uuid.uuid4())

result = invoice.issue(
    document_type=DocumentType.NFSE,
    client_name="Maria Silva",
    tax_id="12345678909",
    items=[Product(description="Consultoria", amount=1500.00)],
    idempotency_key=key,
)

Retry with the same key and the same body and you get the first response back, replayed — no second document, no credit consumed. reissue() takes the same argument.

Situation What the API does
New key issues normally, records the response
Same key, same body replays the recorded response
Same key, different body APIError 422
Same key, first call still running APIError 409
Previous attempt failed key is released — the retry issues
Key older than 24 hours treated as new

Generate the key yourself and keep it for as long as you might retry — a uuid4 per business event, not per HTTP call. The SDK never generates one, because a key minted per call would protect nothing, and because two genuinely separate invoices for the same customer and amount on the same day are a normal thing to issue.

Correcting a document

Some mistakes don't need a cancellation. A wrong product name, wrong transport details, a typo in the extra information — a CC-e (carta de correção) fixes those, and it is free: no new credit, no burned series number, no reissue.

result = invoice.correct(
    "35240912345678000199550010000000011000000017",
    document_type=DocumentType.NFE,
    correction="Transportadora corrigida para Rapido Ltda",
)

The correction text is 15 to 1000 characters, checked locally before the call.

What a CC-e cannot fix: anything that changes the tax owed (base, rate, price, quantity, totals), the buyer or the seller, or the issue date. Those still mean cancelling and reissuing. The API sends the legally fixed wording that says exactly this, attached to every correction.

The original document does not change — the CC-e is an event attached to it, and the authorized XML stays as it was. A document accepts at most 20 of them, and they are numbered for you.

NF-e only. NFS-e has no correction letter, and asking for one returns a 409.

Invalidating unused numbers

NF-e numbering is sequential and the SEFAZ expects it to have no gaps. A number gets reserved the moment issuing starts, so a submission that fails afterwards — a rejection, a timeout — leaves a hole in the series. Reporting that range is how you close it.

result = invoice.invalidate(
    series="1",
    number_start=10,
    number_end=12,
    reason="Numeracao reservada e nao utilizada por falha no ERP",
)

The reason is 15 to 255 characters and the range is inclusive; both are checked locally, as is number_end not being below number_start.

A number that already reached the authorizer can't be invalidated. The API checks its own records first and answers 409 naming the offending numbers, without a round trip — and the authorizer checks again for what we can't see from here.

NF-e only, and it takes no access key: there is no document to point at.

Errors

  • stackin.APIError — the API responded with a non-2xx status (status_code, detail) — a 401 here means api_key is missing, wrong, or was rotated.
  • stackin.ConnectionFailedError — the API didn't respond (network/DNS/timeout).
  • ValueErrorissue()'s items is empty, missing ncm/cfop on an item for NFE, or a missing/incomplete recipient_address on NFE.

Building the full fiscal document (issuer data, service code, tax groups, schema-accurate XML) is the API's job — configured once per company, not passed on every call.

Examples

Runnable end-to-end scripts in examples/nfe/ and examples/nfse/ — one file per field/variant, from the bare minimum to every field filled. examples/consult_invoice.py, examples/cancel_invoice.py, and examples/reissue_invoice.py cover the operations that act on an already-issued document.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stackin_python_sdk-0.2.0.tar.gz (13.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stackin_python_sdk-0.2.0-py3-none-any.whl (13.4 kB view details)

Uploaded Python 3

File details

Details for the file stackin_python_sdk-0.2.0.tar.gz.

File metadata

  • Download URL: stackin_python_sdk-0.2.0.tar.gz
  • Upload date:
  • Size: 13.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for stackin_python_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4d5ac958c450b701ffa0b3f4e17da525fdc40356d60f098876011b46dd7d7da1
MD5 4b2418cecae85f4f26a23aabf6342e90
BLAKE2b-256 30addb213a7fe60490723fd98aea102ab7df5791cf2dc208768f26ab46b17d55

See more details on using hashes here.

Provenance

The following attestation bundles were made for stackin_python_sdk-0.2.0.tar.gz:

Publisher: python-publish-pypi.yml on stackin-io/stackin-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stackin_python_sdk-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for stackin_python_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e8e48420ed4c007f1095d46c1100669c300d118354a6134638484697f9d247eb
MD5 cceff84075d5c4117169aaf486d5f3e0
BLAKE2b-256 26856946470ef3d562e5363ef552ea5e0098b57774cc60a9b10a22164cd8affa

See more details on using hashes here.

Provenance

The following attestation bundles were made for stackin_python_sdk-0.2.0-py3-none-any.whl:

Publisher: python-publish-pypi.yml on stackin-io/stackin-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.4.0

2 files

0.3.1

2 files

0.3.0

2 files

This release

0.2.0 This release

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

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