Recetas¶
Migrar desde la API cruda¶
Si ya te integraste a mano contra /api/v2, esto es lo que reemplaza el SDK.
import uuid, requests
headers = {
"Authorization": f"Bearer {API_KEY}",
"Idempotency-Key": str(uuid.uuid4()),
"Content-Type": "application/json",
}
contribuyente = requests.get(f"{BASE}/taxpayer", headers=headers).json()["data"]
timbrados = requests.get(f"{BASE}/stamps?enabled=true", headers=headers).json()["data"]
timbrado = timbrados[0]
total = 1100000
iva = round(total / 11)
cuerpo = {
"taxPayerRuc": contribuyente["ruc"],
"taxPayerSocialName": contribuyente["socialName"],
"taxPayerFantasyName": contribuyente["fantasyName"],
"stampIdentifier": timbrado["identifier"],
"stampBeginDate": timbrado["beginDate"][:10],
"establishmentCode": "001",
"dispatchPointIdentifier": "001",
"invoiceDate": "2026-06-15T10:00:00",
"saleType": "cash",
"transactionTypeCode": 1,
"currency": "PYG",
"clientType": "entity",
"clientRuc": "80012345-1",
"clientSocialName": "Cliente S.A.",
"clientIsTaxPayer": True,
"clientCountryCode": "PRY",
"clientHasAddress": False,
"amount": total,
"amount10Percent": total,
"taxed10Percent": total - iva,
"iva10Percent": iva,
"items": [{
"description": "Consultoría",
"code": "1",
"measureUnit": 77,
"quantity": 1,
"unitAmount": total,
"totalAmount": total,
"ivaType": "10_percent",
"amount10Percent": total,
"taxed10Percent": total - iva,
"iva10Percent": iva,
}],
}
r = requests.post(f"{BASE}/invoices", json=cuerpo, headers=headers)
r.raise_for_status()
factura = r.json()["data"]
import smartdoc
sd = smartdoc.Client(establishment="001", dispatch_point="001")
factura = sd.invoices.create(
recipient=smartdoc.Recipient.entity(
ruc="80012345-1", social_name="Cliente S.A."
),
items=[smartdoc.Item("Consultoría", quantity=1, unit_amount=1100000)],
transaction_type=smartdoc.TransactionType.MERCHANDISE_SALE,
invoice_date="2026-06-15T10:00:00",
)
Mandar campos que el SDK no modela¶
Para un campo de la API que no tiene parámetro propio, pasalo como argumento con su nombre tal cual: va al cuerpo sin tocar.
sd.invoices.create(
recipient=...,
items=[...],
dncpContractCode="ABC-123",
dncpContractYear="2026",
)
Leer un campo de la respuesta cruda¶
Para un campo que el modelo no expone como propiedad:
Emitir por lotes¶
Para emitir muchos documentos seguidos. El SDK regula el ritmo solo; hay que decidir qué hacer con los que fallan.
def emitir_lote(sd, ventas):
emitidas, fallidas = [], []
for venta in ventas:
try:
factura = sd.invoices.create(
recipient=recipient_for(venta.cliente),
items=items_de(venta),
)
emitidas.append((venta.id, factura.id))
except smartdoc.ValidationError as e:
fallidas.append((venta.id, str(e)))
except (smartdoc.RateLimitError, smartdoc.ServerError) as e:
cola.reintentar(venta.id, motivo=str(e))
return emitidas, fallidas
Con el cliente asíncrono, en paralelo:
import asyncio
async def emitir_lote_async(ventas):
async with smartdoc.AsyncClient(
establishment="001", dispatch_point="001"
) as sd:
tareas = [
sd.invoices.create(
recipient=recipient_for(v.cliente), items=items_de(v)
)
for v in ventas
]
return await asyncio.gather(*tareas, return_exceptions=True)
Exportar un mes de facturación¶
Para bajar todas las facturas de un período a un archivo.
import csv
from datetime import date
facturas = sd.invoices.list(
status="approved_by_set",
date_from=date(2026, 7, 1),
date_to=date(2026, 7, 31),
).auto_paging_iter()
with open("julio.csv", "w", newline="") as f:
escritor = csv.writer(f)
escritor.writerow(["numero", "fecha", "cliente", "monto", "cdc"])
for factura in facturas:
escritor.writerow([
factura["invoiceNumber"],
factura["invoiceDate"][:10],
factura["clientSocialName"],
factura.amount,
factura.cdc,
])
Emitir desde varias sucursales¶
Si el contribuyente tiene más de un establecimiento, no lo fijes en el cliente: pasalo en cada llamada.
sd = smartdoc.Client() # sin establishment ni dispatch_point
sd.invoices.create(
recipient=..., items=[...],
establishment=sucursal.codigo_establecimiento,
dispatch_point=caja.codigo_punto,
)
Sin indicarlo y con más de uno, la llamada levanta ConfigurationError con las
opciones disponibles.
Verificar una factura de proveedor¶
Para consultar en la DNIT un documento que recibiste, antes de cargarlo. No lo registra.
documento = sd.received_documents.lookup_by_cdc("01800695631001...")
print(documento["status"], documento["taxPayerRuc"])
Requiere el permiso write_received_documents. Si el documento no existe en la
DNIT, la llamada levanta ValidationError con código SIFEN_REJECTED.
Usar tu propia clave de idempotencia¶
Cada create() ya va con una clave nueva que genera el SDK, así que esto es
opcional. Sirve si preferís que la clave venga de tu sistema.
factura = sd.invoices.create(
recipient=recipient_for(venta.cliente),
items=items_de(venta),
idempotency_key=f"venta-{venta.id}",
)
Repetir la llamada con la misma clave devuelve el documento ya creado.
Conciliar contra tu base¶
Guardá el id de SmartDoc, que es lo que llega en el webhook:
Para el camino inverso, poné tu identificador en code, que queda impreso en el
documento. Tiene que medir entre 3 y 15 caracteres:
smartdoc.Recipient.entity(
ruc="80012345-1",
social_name="Cliente S.A.",
code=f"cli-{cliente_local.id}",
)
Ver qué manda el SDK¶
Para inspeccionar las solicitudes HTTP:
import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("httpx").setLevel(logging.DEBUG)
Para ver el cuerpo sin mandarlo:
import json
from smartdoc.resources.bodies import invoice_body
cuerpo = invoice_body(
{"taxPayerRuc": "80012345-1"}, # base del emisor, a mano
recipient=smartdoc.Recipient.entity(ruc="8-1", social_name="C"),
items=[smartdoc.Item("X", quantity=1, unit_amount=1100000)],
)
print(json.dumps(cuerpo, indent=2, ensure_ascii=False))