SDKs
One class, three methods
An
Invoiceclient withissue(),consult(), andcancel()— nothing else to instantiate.Typed models
Each item is a typed
Product, with Brazil-specific fiscal fields validated client-side before the request leaves.Same contract in every language
Python, Go, and PHP follow the same API contract — methods, errors, and resources behave identically, so switching languages doesn't mean relearning stackin.
Just getting started?
Create a free account, generate an SDK key, and issue your first document in homologation — it's free and unlimited, no card required.
pip install stackin-python-sdkAuthentication
The SDK authenticates with an SDK key — pass it once when creating the client, the library handles every request after that. You never construct a URL or set a header yourself.
Getting an SDK key
- 1Sign up and log in at .
- 2Create a company — this is required before a key.
- 3Go to Settings → API key in the dashboard, and leave context set to
sdk. No certificate needed for this step — a key can be created and used right away; a certificate is only required later, when actually issuing a document. - 4Name the key and choose its environment (
homologationorproduction, defaults tohomologation) — this is fixed for the key's whole lifetime, it can't be changed later. - 5Copy the key immediately — it's shown , at creation time, and can't be retrieved again. Losing it means revoking it and creating a new one.
from stackin import Invoice
client = Invoice(api_key="$STACKIN_API_KEY")A company can hold up to 3 keys total (any mix of sdk/api context and homologation/production environment), so you can keep separate keys per surface or environment.
A missing or invalid token gets a 401 — see below.
Environments
Every SDK key is created for exactly one environment — or or — chosen when you generate it in the dashboard, and fixed for that key's lifetime. There's no environment field in SDK calls: the key you authenticate with decides where the document goes.
Free and unlimited — it's the real SEFAZ/ADN test environment, not a mock — so integrate and test fully before switching to production.
For real documents. Make sure everything works in homologation before switching.
A company can hold keys for both environments at once (up to 3 total).
Errors
The SDKs translate each failure into a distinct type: APIError for whatever the API answered (carrying status and detail), ConnectionFailedError for a network failure before any response, and the language's own validation error for what's caught client-side, before the request leaves.
from stackin import APIError, ConnectionFailedError
try:
result = client.issue(...)
except ValueError as error:
# Caught before the request left — empty items, missing ncm/cfop,
# incomplete recipient_address on an NFE.
print(f"Invalid request: {error}")
except APIError as error:
# 4xx/5xx from the API. 502 carries the authorizer's own message.
print(f"[{error.status_code}] {error.detail}")
except ConnectionFailedError as error:
print(f"Could not reach the API: {error}")| HTTP | Error | When |
|---|---|---|
| 400 | Invalid configuration | The issuer's configuration (certificate, address, fiscal fields) is missing or invalid. |
| 401 | Unauthorized | The Bearer token is missing, expired, invalid, or wasn't created for the host you're calling (api.stackin.io vs sdk.stackin.io). |
| 402 | Quota reached | The company's plan quota for production invoices was reached and the plan has no overage price (trial). |
| 409 | Operation conflict | The requested operation doesn't apply to that document_type (e.g. an nfse-only operation on an nfe). |
| 422 | Validation error | The request body failed validation — a required field is missing or malformed. |
| 501 | Not implemented | Signing for that document type/country isn't implemented on the server yet. |
| 502 | Authorizer rejectedAuthorizer rejection | The authorizer (SEFAZ/state or ADN) rejected the request. detail.message carries the authorizer's own error, and detail.invoice_id (issuance only) lets you look the rejected attempt up. |
Invoices
Issues a new document. items[].product.br (ncm, cfop) is required for , ignored for , ignored for — a service has no tax classification code, so nfse only reads description and amount off each item.
The same call works for nfse — just change document_type.
from stackin import Address, DocumentType, Invoice
from stackin.br import Product
client = Invoice(api_key="$STACKIN_API_KEY")
result = client.issue(
document_type=DocumentType.NFE,
client_name="Buyer Company Ltd",
tax_id="11222333000181",
items=[
Product(
description="Rosa Holambra Vermelha",
amount=112.44,
ncm="06031100",
cfop="5102",
)
],
recipient_address=Address(
street="Rua das Flores",
number="1200",
neighborhood="Centro",
city="Joinville",
state="SC",
zip_code="89201100",
city_code="4209102",
),
)Looks up a document's current status directly at the authorizer by its access key. Requires as a query param.
result = client.consult(
document_type=DocumentType.NFE,
access_key="42260831112223330001815500000012341123456789",
)Cancels a previously authorized document. must be at least 15 characters for nfe (the authorizer's xJust field requires it). Cancelling doesn't refund plan quota — the authorizer capacity was already spent on issuance.
result = client.cancel(
document_type=DocumentType.NFE,
access_key="42260831112223330001815500000012341123456789",
reason="Duplicate order, cancelled by the buyer within 24h",
)Retries a failed submission by the document's local `id` (not its access key — a rejected invoice doesn't have one). Useful for retrying after fixing a configuration problem or an authorizer outage. Consumes credit like a fresh issuance, since it's a new transmission.
result = client.reissue(
invoice_id="9f2c1e3a-4b5d-6e7f-8a9b-0c1d2e3f4a5b",
)