Skip to main content

Tupana para Python

CI PyPI License: MIT

SDK oficial de la API v2 de Tupana para Python: facturación electrónica ante el SII, links de pago (recaudación), cesión de facturas y webhooks.

  • Cliente síncrono (Tupana) y asíncrono (AsyncTupana) sobre httpx.
  • Tipado: TypedDict derivados del OpenAPI v2, py.typed, verificado con mypy --strict.
  • Reintentos automáticos con backoff ante 429 y 5xx, respetando Retry-After.
  • Emisión idempotente: documents.create_batch envía un Idempotency-Key para que un reintento nunca emita dos veces.
  • Errores tipados (RateLimitError, UnprocessableEntityError…) con code, param y request_id.
  • Verificación de la firma de webhooks (X-Pana-Signature).
  • Python 3.9 o superior.

Instalación

pip install tupana

Mientras publicamos la primera versión en PyPI, puedes instalarlo directo desde GitHub:

pip install "git+https://github.com/tupana-ai/tupana-python"

Antes de empezar

  1. Crea tu cuenta y tu API key en developers.tupana.ai (en la plataforma: Configuración → Integraciones API). Tienes una key de sandbox y una de producción: el código es el mismo, solo cambia la key.
  2. Conecta la empresa emisora con sus credenciales del SII (Tupana Connect). Sin una conexión activa la API responde no_active_credentials.
  3. Guarda la key en una variable de entorno:
export TUPANA_API_KEY="tu-api-key"

Inicio rápido: emitir una factura

from tupana import Tupana

tupana = Tupana()  # lee TUPANA_API_KEY

batch = tupana.documents.create_batch(
    [
        {
            "dte_type": {"code": "33"},  # Factura electrónica
            "document_issuer": {"rut": "76543210-3"},  # tu empresa
            "document_receiver": {"rut": "77777777-7"},  # tu cliente
            "details": [
                {"item_name": "Asesoría contable octubre", "quantity": 1, "unit_price": 150000}
            ],
        }
    ],
    use_defaults=True,  # X-Use-Defaults: la API completa razón social, giro, dirección, fecha y forma de pago
)

print(batch["batch_id"])  # la emisión al SII sigue en segundo plano

# En un script puedes esperar el resultado. En producción, usa webhooks.
result = tupana.documents.wait_for_batch(batch["batch_id"])
for doc in result["documents"]:
    print(doc["index"], doc["status"], (doc.get("document") or {}).get("folio"))

create_batch acepta hasta 200 documentos por llamada. Para cargas más grandes, divide en lotes (mira el ejemplo de emisión masiva desde Excel o CSV).

Asíncrono

import asyncio
from tupana import AsyncTupana


async def main() -> None:
    async with AsyncTupana() as tupana:
        batch = await tupana.documents.create_batch([invoice], use_defaults=True)
        async for doc in tupana.documents.list_auto_paging("ment_4829301923847"):
            print(doc["folio"])


asyncio.run(main())

Borradores y vista previa

draft = tupana.documents.create_batch([invoice], state="draft", preview=True)

Con state="draft" no se pide folio ni se envía nada al SII; preview=True devuelve el PDF del borrador.

Idempotencia

Cada create_batch viaja con un header Idempotency-Key. Si no pasas uno, el SDK genera un UUID, así los reintentos automáticos del mismo proceso son seguros. Para que también sea seguro reintentar desde otro proceso, usa una key tuya y estable:

tupana.documents.create_batch([invoice], idempotency_key=f"pedido-{order.id}")

Repetir la llamada con la misma key devuelve el lote original sin volver a emitir. Para reencontrar un documento aunque pierdas la respuesta, guarda tu propio id en json_param y búscalo después:

page = tupana.documents.list("ment_4829301923847", json_param={"external_order_id": "PED-123"})

Documentos

page = tupana.documents.list(
    "ment_4829301923847",
    document_type="issued",  # o "received"
    dte_type__code__in=["33", "34"],
    issue_date_gte="2026-10-01",
    page_size=100,
)
print(page["meta"]["total"], len(page["data"]))

for doc in tupana.documents.list_auto_paging("ment_4829301923847"):
    print(doc["folio"], doc["amount_with_iva"])

doc = tupana.documents.retrieve("docu_9f2a7c1b8e4d0")  # con PDF, XML y trazas del SII
payment = tupana.payment_requests.create(
    {
        "recipient_id": "ment_4829301923847",  # quien recibe los fondos
        "amount": 50000,
        "target_dte_type": 39,  # boleta: se emite sola al confirmarse el pago
        "dte_recipient": {"rut": "11111111-1", "name": "Juan Soto", "email": "juan@ejemplo.com"},
        "items": [{"description": "Sesión de psicoterapia", "quantity": 1, "unit_price": 50000}],
        "external_id": "booking_abc123",  # idempotencia
        "redirect_url": "https://tuapp.cl/gracias",
    }
)
print(payment["payment_url"])  # redirige a tu cliente y espera el webhook payment_request.paid

Cesiones

res = tupana.cessions.create_batch(
    {
        "master_entity_id": "ment_4829301923847",
        "document_ids": ["docu_9f2a7c1b8e4d0"],
        "assignee_rut": "76798398",
        "assignee_dv": "0",
        "assignee_business_name": "Factoring SpA",
        "assignee_address": "Av. Apoquindo 1234, Las Condes",
        "assignee_email": "operaciones@factoring.cl",
    }
)
status = tupana.cessions.retrieve_batch(res["cession_batch_id"])

Webhooks

Configura el webhook con secret y Tupana firmará cada entrega:

tupana.webhooks.create(
    {
        "master_entity_id": "ment_4829301923847",
        "url": "https://tuapp.cl/webhooks/tupana",
        "events": ["document.issued", "document.rejected", "payment_request.paid"],
        "secret": os.environ["TUPANA_WEBHOOK_SECRET"],
    }
)

Cada notificación trae X-Pana-Signature: el HMAC SHA-256 (hex) del cuerpo crudo. Verifícalo antes de parsear el JSON. Con FastAPI:

from fastapi import FastAPI, HTTPException, Request
from tupana import WebhookVerificationError, construct_webhook_event

app = FastAPI()


@app.post("/webhooks/tupana")
async def tupana_webhook(request: Request) -> dict:
    try:
        event = construct_webhook_event(
            await request.body(), request.headers, os.environ["TUPANA_WEBHOOK_SECRET"]
        )
    except WebhookVerificationError:
        raise HTTPException(status_code=400)
    # Deduplica por X-Pana-Event-Id: una entrega puede repetirse.
    if event["event"] == "document.issued":
        ...
    return {"ok": True}

La firma no incluye el timestamp, así que para descartar repeticiones guarda el X-Pana-Event-Id que ya procesaste.

Errores

Todos los errores heredan de TupanaError; los de la API son APIError (o una subclase según el status):

from tupana import APIError, UnprocessableEntityError

try:
    tupana.documents.create_batch(documents)
except UnprocessableEntityError as err:
    print(err.code, err.param, err.message)
except APIError as err:
    print(err.status_code, err.type, err.code, err.request_id, err.doc_url)
Status Clase
400 BadRequestError
401 AuthenticationError
403 PermissionDeniedError
404 NotFoundError
409 ConflictError
413 PayloadTooLargeError
422 UnprocessableEntityError
429 RateLimitError
5xx InternalServerError
red / timeout APIConnectionError / APITimeoutError

Cuando reportes un problema a soporte, incluye err.request_id. El catálogo de códigos está en docs.tupana.ai/v2/errors.

Reintentos y timeouts

Por defecto el SDK reintenta 2 veces con backoff exponencial y jitter, respetando Retry-After:

  • GET, PUT y DELETE: ante errores de red, 408, 429 y 5xx.
  • POST y PATCH con Idempotency-Key: igual que arriba.
  • POST y PATCH sin Idempotency-Key: solo ante 429 y 503, donde el servidor garantiza que no procesó el request.
tupana = Tupana(max_retries=4, timeout=30.0)

Otros endpoints

Para endpoints v2 sin método propio (folios, libros del SII, sincronización, nómina…) usa request, con la misma autenticación, reintentos y errores:

folios = tupana.request("GET", "/folios/", query={"master_entity_id": "ment_4829301923847"})

Configuración

Parámetro Variable de entorno Por defecto
api_key TUPANA_API_KEY (obligatoria)
base_url TUPANA_BASE_URL https://api.tupana.ai/v2
timeout 60.0 s
max_retries 2
default_headers {}
http_client un httpx.Client / httpx.AsyncClient propio (proxies, transportes)

Agentes de IA y MCP

Tupana también tiene un servidor MCP remoto en https://mcp.tupana.ai/mcp para conectar Claude, ChatGPT, Cursor y otros agentes. Instrucciones en tupana-ai/tupana-mcp y un agente que factura con confirmación humana en tupana-ai/tupana-examples.

Recursos

Desarrollo

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check . && ruff format --check . && mypy && pytest

Licencia

MIT


English summary

Official Python SDK for the Tupana API v2 — Chilean electronic invoicing (SII DTEs), payment links, invoice factoring (cessions) and webhooks.

pip install tupana
from tupana import Tupana

tupana = Tupana(api_key="...")
batch = tupana.documents.create_batch([invoice], use_defaults=True)
result = tupana.documents.wait_for_batch(batch["batch_id"])

Sync and async clients on httpx, TypedDict types derived from the OpenAPI v2 spec, automatic retries with backoff on 429/5xx (non-idempotent POSTs are only retried when the server guarantees they were not processed), automatic Idempotency-Key on batch issuing, typed errors and webhook signature verification (X-Pana-Signature, hex HMAC-SHA256 of the raw body). Python 3.9+. Get an API key at developers.tupana.ai. MIT licensed.

Metadata

Release files for tupana 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 tupana 0.1.0
File Size Uploaded Provenance
tupana-0.1.0.tar.gz 24.5 kB With provenance Details

Built distribution (wheel)

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

Total release size: 50.3 kB

Release files / tupana-0.1.0.tar.gz

Download URL tupana-0.1.0.tar.gz
Size 24.5 kB
Tags Source
SHA-256 checksum
How to use checksums
46efa55405222e3927b840018d5058dec0efd29ab67584055c29e93472ec82a5
BLAKE2b-256 checksum
How to use checksums
bc6b6ebed626e8c441644fc98f47fa33a8982fe7775ffa90a980939b0edd4c09
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 20, 2026.

Transparency log

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

Download URL tupana-0.1.0-py3-none-any.whl
Size 25.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7df15453f3ad1a94738c686ed5869ce1235466d08c615a7ad4bf177dec5ae6fe
BLAKE2b-256 checksum
How to use checksums
f5a26944c164c511f32d1905dd5d4abe2ba147885666b3b18693461a8145e19a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 20, 2026.

Transparency log

Security for tupana 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

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for tupana-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 Oct 20, 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.

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