Skip to main content

keysoft

Facturación electrónica SUNAT desde Python. Sin dependencias — solo biblioteca estándar, para que entre también donde el pip está restringido.

pip install keysoft

De cero a una factura aceptada

import os
from keysoft import KeySoft

ks = KeySoft(os.environ["KEYSOFT_API_KEY"])

# El ambiente sale de la clave: ks_test_ es pruebas, ks_live_ es producción.
print(ks.environment)  # 'DEMO'

emitido = ks.issue(company_id, {
    "documentType": "FACTURA",
    "series": "F001",
    "customer": {
        "identityType": "RUC",
        "identityValue": "20601234567",
        "legalName": "DISTRIBUIDORA DEL NORTE S.A.C.",
        "email": "compras@cliente.pe",   # le llega su factura al aceptarse
    },
    "lines": [
        {"description": "Servicio de consultoría", "quantity": 2, "unitValue": 150},
    ],
}, idempotency_key=f"pedido-{pedido.id}")

resultado = ks.send(company_id, emitido["document"]["id"])
print(resultado["cdr"]["description"])
# "La Factura numero F001-00000001, ha sido aceptada"

Idempotencia

issue() manda Idempotency-Key por defecto. Si tu proceso reintenta por un timeout, sin ella emitirías dos facturas — y una factura de más es un problema fiscal, no un duplicado inocente.

Pásale la tuya: el id de tu pedido es la mejor opción, porque sobrevive a un reinicio de tu proceso.

Los errores dicen qué hacer

from keysoft import KeySoftError

try:
    ks.issue(company_id, factura)
except KeySoftError as e:
    print(e.code)      # 'certificate_missing' — estable, programa contra él
    print(e.action)    # qué hacer para arreglarlo
    if e.retryable:
        reintentar()

Descargar y verificar

download() recalcula el SHA-256 y lanza si no cuadra. No te fíes de nosotros: la comprobación es el producto.

xml = ks.download(company_id, document_id, "xml")
pdf = ks.download(company_id, document_id, "pdf")

Boletas: el reloj de los 7 días

Una boleta no se envía sola — va en el resumen diario, y caduca a los 7 días calendario sin que nadie te avise.

estado = ks.summaries(company_id)
if estado["alert"]["level"] != "ok":
    print(estado["alert"]["message"])
    # "Quedan 2 día(s) para informar las boletas más antiguas sin resumen."

resumen = ks.create_summary(company_id)
ks.send_summary(company_id, resumen["summary"]["id"])

# El bucle de consulta lo hacemos nosotros: un ticket NO es una aceptación.
final = ks.wait_for_summary(company_id, resumen["summary"]["id"])
print(final["propagatedTo"], "boletas informadas")

Webhooks

from keysoft import verify_webhook

@app.post("/hooks/keysoft")
def hook():
    if not verify_webhook(
        request.get_data(),                      # el cuerpo CRUDO
        request.headers["keysoft-signature"],
        os.environ["KEYSOFT_WEBHOOK_SECRET"],
    ):
        return "", 400

    evento = request.get_json()
    return "", 200

El cuerpo tiene que ser el crudo. Si lo parseas y lo vuelves a serializar la firma deja de cuadrar: es el fallo más común al integrarlo.

La firma caduca a los 5 minutos, que es lo que impide que quien capture una petición válida te la reenvíe mañana.

Metadata

Release files for keysoft 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 keysoft 0.1.0
File Size Uploaded
keysoft-0.1.0.tar.gz 6.8 kB Details

Built distribution (wheel)

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

Total release size: 14.7 kB

Release files / keysoft-0.1.0.tar.gz

Download URL keysoft-0.1.0.tar.gz
Size 6.8 kB
Tags Source
SHA-256 checksum
How to use checksums
f5346ae52c78a7824d74e41f3ce189cca65a83d0e7c144264dcdd79db267720e
BLAKE2b-256 checksum
How to use checksums
da3bdff8ffd86c60deb5268e41e8f56bffab50f74e38bbb39128a24ba897f4a1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

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

Download URL keysoft-0.1.0-py3-none-any.whl
Size 7.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
873672c032ecde7b68fa35f756a1a3fe288becf1a573a1de3a87caf985d7cde2
BLAKE2b-256 checksum
How to use checksums
1eba90240c3017ec32ab69b0f53bdeb4ab66c5d0a9001b7931044c99a365220d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release 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