SDKs y CLI de YoFacturo para Python, Node y PHP

Los SDKs de YoFacturo envuelven la API REST pública para que tu ERP, tu tienda online o tus scripts emitan facturas electrónicas de ARCA, notas de crédito y débito, y administren catálogo, clientes y ventas sin armar a mano las llamadas HTTP. Hay tres librerías con la misma superficie de recursos (Python, Node/TypeScript y PHP) y un CLI en Python con salida JSON pensado para scripts y agentes de IA. Todas hablan con https://api.yo-facturo.com/api/v1 y se autentican con tu token de API.

Actualizado:

Las tres librerías

LenguajePaqueteInstalaciónNotas
Pythonyofacturopip install yofacturoSin dependencias en tiempo de ejecución. Incluye el CLI yofacturo y verificación de webhooks.
Node / TypeScriptyofacturonpm install yofacturoCon tipos. Incluye verificación de webhooks.
PHPyofacturo/yofacturo-phpcomposer require yofacturo/yofacturo-phpMismos recursos que los otros SDKs. Sin verificador de webhooks por ahora.

Qué resuelven por vos

  • RecursosClientes, facturas, productos, ventas, partner (facturación en nombre de varios CUIT) y SIFEN para Paraguay, con los mismos nombres en los tres lenguajes.
  • Reintentos automáticosAnte errores 429 y 5xx el cliente reintenta con espera creciente, hasta 3 veces por defecto, configurable.
  • IdempotenciaAl crear una factura podés enviar una clave de idempotencia para que un reintento de tu lado no emita dos comprobantes.
  • SandboxCon un token de prueba (prefijo yf_test_) el SDK detecta el modo sandbox y las facturas llevan un CAE sintético: nada llega a ARCA. Probá el flujo completo antes de pasar a producción.
  • Webhooks firmadosEn Python y Node podés validar la firma X-YF-Signature (HMAC-SHA256 sobre el cuerpo crudo, con tolerancia de 300 segundos) antes de procesar un evento.
  • CLI para scripts y agentesEl comando yofacturo permite iniciar sesión, crear, consultar y obtener el link público de una factura desde un archivo JSON, y verificar firmas de webhooks. Toda la salida es JSON y los códigos de salida distinguen error, uso incorrecto y falta de token.

Emitir una factura: ejemplo completo por lenguaje

Cada ejemplo emite una Factura a Consumidor Final con un token de prueba (yf_test_...: CAE sintético, nada llega a ARCA), usa una clave de idempotencia para que un reintento no duplique el comprobante, y maneja los errores de la API. El precio de los ítems es el precio final con IVA incluido: el backend calcula neto, IVA y total. Para producción cambiá el token por uno yf_live_....

Python

import os
import uuid

from yofacturo import YoFacturo, ApiError, RateLimitError

# Token de prueba (yf_test_...): CAE sintético, nada llega a ARCA.
client = YoFacturo(api_key=os.environ["YOFACTURO_API_KEY"], environment="testing")

try:
    factura = client.invoices.create(
        punto_venta=1,                      # habilitado en ARCA para web services
        concepto=2,                         # 1 productos, 2 servicios, 3 ambos
        tipo_documento=99,                  # 99 = Consumidor Final
        numero_documento="0",
        razon_social="CONSUMIDOR FINAL",
        condicion_iva_receptor_id=5,        # 5 = Consumidor Final
        items=[
            # precio FINAL (IVA incluido): el backend calcula neto, IVA y total
            {"descripcion": "Servicio de consultoría", "cantidad": 1, "precio_unitario": 15000.00},
        ],
        autorizar=True,                     # autoriza ante ARCA en segundo plano
        idempotency_key="pedido-1042",      # un reintento no emite dos comprobantes
    )
    comprobante_id = factura["data"]["id"]
    # La autorización corre en segundo plano: releé hasta que venga el CAE.
    comprobante = client.invoices.get(comprobante_id)["data"]
    print(comprobante_id, comprobante.get("cae"))
except RateLimitError as e:
    print("Demasiados pedidos, esperá", e.retry_after)
except ApiError as e:
    print(e.status_code, e.code, e.errors)

Node / TypeScript

const { YoFacturo, ApiError, RateLimitError } = require("yofacturo");

// Token de prueba (yf_test_...): CAE sintético, nada llega a ARCA.
const client = new YoFacturo(process.env.YOFACTURO_API_KEY);

async function main() {
  try {
    const factura = await client.invoices.create(
      {
        punto_venta: 1, // habilitado en ARCA para web services
        concepto: 2, // 1 productos, 2 servicios, 3 ambos
        tipo_documento: 99, // 99 = Consumidor Final
        numero_documento: "0",
        razon_social: "CONSUMIDOR FINAL",
        condicion_iva_receptor_id: 5, // 5 = Consumidor Final
        // precio FINAL (IVA incluido): el backend calcula neto, IVA y total
        items: [{ descripcion: "Servicio de consultoría", cantidad: 1, precio_unitario: 15000.0 }],
        autorizar: true, // autoriza ante ARCA en segundo plano
      },
      "pedido-1042" // idempotency key: un reintento no emite dos comprobantes
    );
    // La autorización corre en segundo plano: releé hasta que venga el CAE.
    const comprobante = await client.invoices.get(factura.data.id);
    console.log(factura.data.id, comprobante.data.cae);
  } catch (err) {
    if (err instanceof RateLimitError) console.error("Demasiados pedidos, esperá", err.retryAfter);
    else if (err instanceof ApiError) console.error(err.statusCode, err.code, err.errors);
    else throw err;
  }
}

main();

PHP

<?php
require __DIR__ . '/vendor/autoload.php';

use YoFacturo\Client;
use YoFacturo\Exceptions\ApiException;

// Token de prueba (yf_test_...): CAE sintético, nada llega a ARCA.
$client = new Client(getenv('YOFACTURO_API_KEY'));

try {
    $factura = $client->invoices->create([
        'punto_venta' => 1,                  // habilitado en ARCA para web services
        'concepto' => 2,                     // 1 productos, 2 servicios, 3 ambos
        'tipo_documento' => 99,              // 99 = Consumidor Final
        'numero_documento' => '0',
        'razon_social' => 'CONSUMIDOR FINAL',
        'condicion_iva_receptor_id' => 5,    // 5 = Consumidor Final
        'items' => [
            // precio FINAL (IVA incluido): el backend calcula neto, IVA y total
            ['descripcion' => 'Servicio de consultoría', 'cantidad' => 1, 'precio_unitario' => 15000.00],
        ],
        'autorizar' => true,                 // autoriza ante ARCA en segundo plano
    ], 'pedido-1042');                       // idempotency key: un reintento no emite dos comprobantes

    // La autorización corre en segundo plano: releé hasta que venga el CAE.
    $comprobante = $client->invoices->get($factura['data']['id'])['data'];
    echo $factura['data']['id'], ' ', $comprobante['cae'] ?? 'sin CAE todavía', "\n";
} catch (ApiException $e) {
    echo $e->getStatusCode(), ' ', $e->getApiCode(), "\n";
}

Sin SDK: la misma llamada por HTTP

Si tu lenguaje no está en la lista, o preferís no sumar una dependencia, llamá a la API directo. La autenticación es el header X-API-Key y la idempotencia el header Idempotency-Key. No existe un SDK oficial de otros lenguajes: no instales paquetes que no figuren en la tabla de arriba.

curl

curl -X POST "https://api.yo-facturo.com/api/v1/bill/receipts/invoice/" \
  -H "X-API-Key: $YOFACTURO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-1042" \
  -d '{
    "punto_venta": 1,
    "concepto": 2,
    "tipo_documento": 99,
    "numero_documento": "0",
    "razon_social": "CONSUMIDOR FINAL",
    "condicion_iva_receptor_id": 5,
    "items": [{"descripcion": "Servicio de consultoría", "cantidad": 1, "precio_unitario": 15000.00}],
    "autorizar": true
  }'

Node (fetch)

const BASE = "https://api.yo-facturo.com/api/v1";

const res = await fetch(BASE + "/bill/receipts/invoice/", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.YOFACTURO_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "pedido-1042",
  },
  body: JSON.stringify({
    punto_venta: 1,
    concepto: 2,
    tipo_documento: 99,
    numero_documento: "0",
    razon_social: "CONSUMIDOR FINAL",
    condicion_iva_receptor_id: 5,
    items: [{ descripcion: "Servicio de consultoría", cantidad: 1, precio_unitario: 15000.0 }],
    autorizar: true,
  }),
});
if (!res.ok) throw new Error(res.status + " " + (await res.text()));
console.log((await res.json()).data.id);

Verificar la firma de un webhook

Python y Node incluyen un verificador de la firma X-YF-Signature (HMAC-SHA256 con tolerancia de 300 segundos). Tiene que correr sobre el cuerpo crudo de la request. El SDK de PHP todavía no trae verificador de webhooks.

Python (Flask)

import os

from flask import Flask, request
from yofacturo import webhooks

app = Flask(__name__)

@app.post("/yofacturo/webhook")
def recibir():
    try:
        # Firmá sobre el cuerpo CRUDO, nunca sobre el JSON re-serializado.
        event = webhooks.construct_event(
            os.environ["YOFACTURO_WEBHOOK_SECRET"],
            request.get_data(),
            request.headers.get("X-YF-Signature"),
        )
    except ValueError:
        return "firma inválida", 400
    print(event)
    return "", 204

Node (Express)

const express = require("express");
const { webhooks } = require("yofacturo");

const app = express();

// express.raw: necesitamos el cuerpo CRUDO para validar la firma.
app.post("/yofacturo/webhook", express.raw({ type: "application/json" }), (req, res) => {
  try {
    const event = webhooks.constructEvent(
      process.env.YOFACTURO_WEBHOOK_SECRET,
      req.body,
      req.get("X-YF-Signature")
    );
    console.log(event);
    res.sendStatus(204);
  } catch (err) {
    res.status(400).send("firma inválida");
  }
});

Seguí leyendo

Preguntas frecuentes

¿Qué plan necesito para usar la API con el SDK?

Los tokens de producción requieren el plan Premium o período de prueba. Los tokens de prueba (sandbox) funcionan en todos los planes, así que podés desarrollar la integración antes de contratar. Los planes están en la página de precios.

¿El SDK cubre toda la API?

No, es un subconjunto curado de las operaciones más usadas. La especificación OpenAPI completa tiene muchas más operaciones; si te falta algo en el SDK, podés llamar el endpoint directo con la misma autenticación.

¿Dónde descargo las librerías?

Los nombres de paquete son los de la tabla. El estado de publicación en cada registro (PyPI, npm y Packagist) puede cambiar; el README del repositorio del SDK es la referencia vigente para instalar.

Empezá en el sandbox

Creá tu cuenta, generá un token de prueba y probá la integración completa sin emitir comprobantes reales.