TimbraleFacturación electrónica
Referencia de la API

Emitir facturas electrónicas

Una clave, un POST con JSON y el documento entra en el SIFEN con numeración fiscal y CDC. El sandbox está abierto en todos los planes y no consume cuota: se integra entero antes de cargar el certificado de producción.

https://api.timbrale.com.py

Toda esta página en un archivo de texto, lista para pegar en un agente: /llms.txt.

Inicio rápido

Para probar sin crear cuenta, usá la clave demo pública sk_test_PTBJ4kRP.bI1MOdIBMdFJ2lUVhPbHbPHKpfMkr2kj5uM-lKb9WTs: ya va en el ejemplo de abajo y en la colección Postman. Emite en el Ambiente de Pruebas de la DNIT, sin validez fiscal. Es compartida — todos ven los documentos de todos —, así que no uses datos reales de clientes. Webhooks y datos propios piden la clave de tu cuenta.

Con la clave de tu cuenta, antes de la primera emisión hacen falta tres cosas en el panel: el timbrado con su establecimiento y punto de expedición, el certificado .p12 y el CSC de la DNIT. Sin cualquiera de ellos la API responde 422 antes de gastar numeración — nunca queda un número asignado a un documento que no puede transmitirse.

Request
curl -X POST https://api.timbrale.com.py/v1/documentos \
  -H "Authorization: Bearer sk_test_PTBJ4kRP.bI1MOdIBMdFJ2lUVhPbHbPHKpfMkr2kj5uM-lKb9WTs" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "tipoDocumento": "factura",
    "establecimiento": "001",
    "puntoExpedicion": "001",
    "moneda": "PYG",
    "tipoTransaccion": 2,
    "condicion": {
      "tipo": 1,
      "entregas": [
        {
          "tipo": 1,
          "monto": "450000",
          "moneda": "PYG"
        }
      ]
    },
    "factura": {
      "presencia": 1
    },
    "cliente": {
      "contribuyente": true,
      "ruc": "80069590-9",
      "razonSocial": "EMPRESA DE PRUEBA S.A.",
      "tipoOperacion": 1
    },
    "items": [
      {
        "codigo": "SRV-01",
        "descripcion": "Servicio mensual",
        "cantidad": 1,
        "precioUnitario": "450000",
        "iva": 10
      }
    ]
  }'
Response
202 Accepted

{
  "id": "0f3a1c2e-...",
  "cdc": "01800123450001001000000112026...",
  "tipoDocumento": "factura",
  "numeroCompleto": "001-001-0000001",
  "status": "queued",
  "fechaEmision": "2026-09-10T14:03:11.000Z",
  "moneda": "PYG",
  "total": "450000",
  "ambiente": "test",
  "qrLink": null,
  "ultimoError": null,
  "ultimoIntentoAt": null,
  "intentos": 0
}

total es la suma de cantidad × precioUnitario menos los descuentos: el precio unitario ya incluye el IVA y el total no le suma nada encima.

202 significa que el documento existe, tiene número y CDC, y quedó en cola para firma y transmisión. El veredicto del SIFEN llega después: se consulta con GET /v1/documentos/:id hasta que el status sea approved o rejected, o llega solo por webhook.

Autenticación

Todas las rutas exigen el header Authorization: Bearer sk_.... Las claves se crean en Panel → Claves de API y el secreto se muestra una sola vez.

PrefijoAmbienteQué hace
sk_test_sandboxemite contra el ambiente de prueba del SIFEN; no consume cuota, disponible en todos los planes
sk_prod_producciónemite y consulta documentos con validez fiscal; exige un plan con API

El ambiente es el de la clave, no un parámetro: una clave de sandbox nunca ve ni emite documentos de producción, ni siquiera pasando ?ambiente=prod. Sin un plan con API, toda llamada con clave de producción —emitir, listar, descargar XML o KuDE, contexto— responde 402; lo emitido se consulta en el panel.

Emitir un documento

POST/v1/documentos

El header Idempotency-Key es obligatorio: sin él la respuesta es 400. Reenviar la misma clave devuelve 200 con el documento anterior en lugar de emitir otro — un timeout de red no duplica una factura. Emisión nueva devuelve 202.

HeaderObligatorioQué hace
AuthorizationsíBearer sk_test_… o sk_prod_…
Idempotency-Keysícualquier string único por documento — un UUID sirve
Content-Typesíapplication/json
Accept-Languagenoes, pt o en; define el idioma de los mensajes de error
Prefernowait=N (hasta 30): espera hasta N segundos el veredicto del SIFEN

Con Prefer: wait=10 la respuesta espera el veredicto: si el SIFEN responde dentro del plazo, devuelve 201 con el documento completo (approved o rejected, igual que el GET); si no, el mismo 202 de siempre y el veredicto llega después. La integración tiene que tratar los dos. Sin el header nada cambia; modo: "lote" no espera.

modo: "lote" deja el documento en queued hasta que se cierre el lote, y el cierre hoy sale del panel, no de la clave de API. Si se emite por lote y nadie lo cierra, el número queda asignado y el documento nunca llega al SIFEN. Con sincrono (el default) el documento sale solo, apenas se emite, y no hay nada que cerrar. En los dos casos la transmisión es por el WS asíncrono del SIFEN: el veredicto no viene en la respuesta, llega en segundos o minutos, por webhook o consultando el documento.

Campos del documento

El cuerpo se valida entero antes de tocar la numeración. En la raíz, en cliente y en items[] los campos desconocidos se rechazan: si un nombre está mal escrito, la respuesta lo dice en vez de ignorarlo. Dentro de condicion se descartan en silencio.

Raíz

CampoTipoObligatorioNota
tipoDocumentoenumsífactura, autofactura, notaCredito, notaDebito, notaRemision
establecimientostring(3)sícódigo del establecimiento, 3 dígitos: "001"
puntoExpedicionstring(3)sípunto de expedición, 3 dígitos
fechadatenoomitida, vale el instante de la emisión. Sin zona horaria (`2026-09-30` o `2026-09-30T23:59:59`) es la hora de Asunción del emisor. Hasta 720 h (30 días) atrás y 120 h (5 días) adelante: fuera de eso el SIFEN rechaza (1150/1151) y la API responde 422 sin gastar numeración
tipoEmisionint 1-2no1 normal (default), 2 contingencia
tipoTransaccionint 1-13noomitido, cae en el configurado en la empresa; si tampoco está, 422
tipoImpuestoint 1-5no1 IVA (default), 2 ISC, 3 renta, 4 ninguno, 5 IVA-renta
monedaenumnoPYG (default), USD, BRL, ARS, EUR
cambiostring | numbercondicionalobligatorio cuando moneda ≠ PYG
condicionobjetocondicionalobligatorio salvo en notaRemision
clienteobjetosíver abajo
itemsarraysíal menos uno
factura{ presencia }condicionalobligatorio en factura: presencia 1-9 — 1 presencial, 2 electrónica, 3 telemarketing…
autoFacturaobjetocondicionalobligatorio en autofactura: datos del vendedor no contribuyente
remisionobjetocondicionalobligatorio en notaRemision: motivo, tipoResponsable, kms
detalleTransporteobjetocondicionalobligatorio en notaRemision, opcional en factura
documentoAsociadoobjetocondicionalobligatorio en notaCredito, notaDebito y autofactura
notaCreditoDebitoint 1-5condicionalmotivo: 1 devolución, 2 descuento, 3 bonificación, 4 crédito incobrable…
descripcionstring(1000)no—
observacionstring(1000)no—
modoenumnosincrono (default) o lote — ver la nota más abajo

cliente

CampoTipoObligatorioNota
contribuyentebooleansítrue exige ruc con DV válido
rucstringcondicionalformato 80012345-0 (3 a 8 dígitos, guion, DV); el dígito verificador se valida antes de emitir
razonSocialstring(255)sí—
nombreFantasiastring(255)no—
documentoTipoint 1-9no1 CI, 2 pasaporte, 3 cédula extranjera, 9 otro (exige documentoTipoDescripcion)
documentoNumerostring(20)condicionalobligatorio cuando contribuyente = false
documentoTipoDescripcionstring(50)condicionalobligatorio cuando documentoTipo = 9
tipoOperacionint 1-4sí1 B2B, 2 B2C, 3 B2G, 4 B2F (exterior, exige pais ≠ PRY)
paisstring(3)noPRY por default
paisDescripcionstring(50)nonombre del país en el B2F
direccionstring(255)condicionalobligatoria en notaRemision y cuando tipoOperacion = 4 (B2F): sin ella el SIFEN rechaza con 1318
numeroCasastring(6)nonúmero de puerta; acompaña a direccion
ciudad / distrito / departamentointnocódigos del catálogo del SIFEN; ignorados cuando tipoOperacion = 4 (la localidad es extranjera)
telefono / celularstring(20)no—
emailstringnodestino del KuDE por correo

items[]

CampoTipoObligatorioNota
codigostring(50)sícódigo interno del producto o servicio
descripcionstring(500)sí—
cantidadnumber > 0sí—
precioUnitariostring | numbersíIVA incluido; se devuelve siempre como string, sin pasar por float
descuentostring | numbernopor unidad
unidadMedidaintno77 = unidad (default)
ivaTipoint 1-5no1 gravado (default), 2 exonerado, 3 exento, 4 gravado parcial
iva0 | 5 | 10noalícuota; 10 por default
ivaBase0-100nobase gravada en porcentaje; 100 en el caso normal
agricolabooleannoproducto agrícola en estado natural; separa las casillas del Formulario 120
fleteExportacionbooleannoflete internacional para exportación de bienes; exige ivaTipo 2 o 3 y va a la casilla 153 del Formulario 120
observacionstring(500)no—
productoIdstring(40)noid de un producto del catálogo; no va al XML. Con el stock en ventas activado, la factura descuenta stock y la nota de crédito lo devuelve

condicion

tipo: 1 contado, 2 crédito.

Al contado, entregas[] es obligatorio: una entrada por forma de pago, con tipo y monto obligatorios y moneda (PYG por default) y cambio opcionales — la suma de los montos es el total del documento. Un contado sin entregas devuelve 422.

Formas de pago en entregas[].tipo: 1 efectivo, 2 cheque, 3 tarjeta de crédito, 4 tarjeta de débito, 5 transferencia, 6 giro, 7 billetera electrónica, 8 tarjeta empresarial, 9 vale, 10 retención, 11 anticipo, 16 pago bancario, 17 pago móvil, 21 pago electrónico, 99 otro (exige tipoDescripcion).

A crédito se agrega credito con tipo (1 plazo, 2 cuotas): el tipo 1 exige plazo (de 2 a 15 caracteres, «30 días»), el tipo 2 exige cuotas. montoEntrega es opcional.

Estados

El documento avanza solo; la integración consulta hasta el estado final. El qrLink apunta a la consulta pública de la DNIT y solo resuelve después de que el SIFEN tiene el documento. ultimoError, ultimoIntentoAt e intentos vienen en cada respuesta y distinguen un queued que está en la cola de uno que quedó parado con error.

statusQué significa
queuednumeración asignada y CDC generado; esperando firma y envío
signedXML firmado con el certificado de la empresa
senttransmitido al SIFEN, sin veredicto todavía
approvedaprobado por el SIFEN — el documento existe legalmente
approved_with_observationsaprobado con observaciones; válido igual
rejectedrechazado; el motivo viene en mensajesSifen
cancelledcancelado por un evento posterior
draftborrador — no lo produce la API

Consultar

GET/v1/documentos

Filtros: q (busca por CDC o por documento del receptor), status, tipoDocumento, take (1-100, default 25) y skip. El ambiente sale de la clave.

Request / Response
GET https://api.timbrale.com.py/v1/documentos?status=approved&take=25&skip=0
Authorization: Bearer sk_prod_...

{
  "items": [
    {
      "id": "0f3a1c2e-...",
      "cdc": "01800123450001001000000112026...",
      "tipoDocumento": "factura",
      "numeroCompleto": "001-001-0000001",
      "status": "approved",
      "fechaEmision": "2026-09-10T14:03:11.000Z",
      "moneda": "PYG",
      "total": "450000",
      "ambiente": "prod",
      "qrLink": "https://ekuatia.set.gov.py/consultas/...",
      "ultimoError": null,
      "ultimoIntentoAt": "2026-09-10T14:03:14.000Z",
      "intentos": 1,
      "receptorDoc": "80069590-9",
      "receptorNombre": "EMPRESA DE PRUEBA S.A.",
      "protocolo": "1234567890",
      "mensajesSifen": [{ "codigo": "0260", "mensaje": "Autorizado el e-Kuatia" }]
    }
  ],
  "total": 1,
  "take": 25,
  "skip": 0
}
GET/v1/documentos/:id

Un documento, con los mismos campos de la respuesta de emisión.

GET/v1/documentos/:id/xml

El XML del DE en application/xml, firmado cuando hay certificado configurado en el ambiente.

GET/v1/documentos/:id/kude.pdf

El KuDE en PDF — el documento que recibe el receptor, el que se imprime, se adjunta o se manda. Existe recién después de que el SIFEN aprueba y el worker lo genera: hasta ahí responde 404 diciendo cuál de las dos cosas falta. El polling es el mismo que ya se hace por status.

GET/v1/documentos/:id/kude

El mismo KuDE en HTML, disponible desde el primer segundo: antes de la aprobación sale con la franja «DOCUMENTO EN PROCESO DE APROBACIÓN — SIN VALIDEZ FISCAL». Las dos rutas respetan el ambiente de la clave: una sk_test_ no baja el KuDE de un documento de producción.

GET/v1/documentos/opciones

Establecimientos y puntos de expedición habilitados de la empresa — los valores válidos para establecimiento y puntoExpedicion.

GET/v1/contexto

El ambiente de la clave, la empresa emisora, sus establecimientos y los timbrados vigentes, en una sola respuesta — lo que un programa necesita saber antes de armar el primer documento.

La inutilización de numeración, los eventos del receptor y el cierre de lote se hacen hoy desde el panel, con sesión. La clave de API cubre emisión, cancelación, consulta y las dos representaciones del documento: XML y KuDE.

Webhooks

En vez de consultar cada documento, tu sistema recibe un POST cuando el SIFEN decide. Se configuran en Panel → Webhooks: URL https://, ambiente (un webhook de sandbox nunca recibe documentos de producción), eventos y, si hace falta, establecimientos. Sin establecimientos marcados llegan los de todos; una empresa con un sistema por local crea un webhook por establecimiento. El secreto de firma (whsec_…) se muestra una sola vez. Hasta 10 webhooks por ambiente, contando los pausados; para cambiar uno, se elimina y se crea otro.

EventoCuándo
documento.approvedel SIFEN aprobó el documento
documento.approved_with_observationsaprobado con observaciones; válido igual
documento.rejectedrechazado; el motivo viene en datos.mensajesSifen
documento.cancelledla DNIT confirmó la cancelación
pingbotón «Enviar prueba» del panel; datos trae solo webhookId

datos trae los mismos campos que GET /v1/documentos/:id.

Request
POST https://tu-sistema.com/timbrale
Content-Type: application/json
Timbrale-Evento: documento.approved
Timbrale-Entrega: cm1x8k2p40001...
Timbrale-Signature: t=1757772194,v1=5f2c9a...

{
  "id": "documento.approved:0f3a1c2e-...",
  "evento": "documento.approved",
  "creadoEn": "2026-09-10T14:03:14.000Z",
  "ambiente": "prod",
  "datos": {
    "id": "0f3a1c2e-...",
    "cdc": "01800123450001001000000112026...",
    "tipoDocumento": "factura",
    "establecimiento": "001",
    "puntoExpedicion": "001",
    "numeroCompleto": "001-001-0000001",
    "status": "approved",
    "fechaEmision": "2026-09-10T14:03:11.000Z",
    "moneda": "PYG",
    "total": "450000",
    "qrLink": "https://ekuatia.set.gov.py/consultas/...",
    "protocolo": "1234567890",
    "mensajesSifen": [{ "codigo": "0260", "mensaje": "Autorizado el e-Kuatia" }]
  }
}

Timbrale-Signature es t=<unix>,v1=<hex>: HMAC-SHA256 con el secreto sobre <t>.<cuerpo crudo>. Rechazá un t de más de 5 minutos.

Verificar la firma
import { createHmac, timingSafeEqual } from 'node:crypto';

// body: el cuerpo crudo, tal como llegó — antes de JSON.parse.
function firmaValida(body, header, secreto) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  // Más de 5 minutos: una entrega vieja reenviada por un tercero.
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const esperada = createHmac('sha256', secreto).update(`${t}.${body}`).digest('hex');
  return v1?.length === esperada.length && timingSafeEqual(Buffer.from(v1), Buffer.from(esperada));
}

Respondé 2xx en menos de 10 segundos; otro código, un timeout o una redirección cuentan como falla. Reintentamos después de 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h y 24 h — 9 intentos, unos 3 días. Si todos fallan, el webhook queda detenido y avisamos por correo; al reactivarlo se entregan los eventos acumulados.

El id del evento se repite en cada reintento: deduplicá por él. El orden no está garantizado — un documento.cancelled puede llegar antes que un documento.approved reintentado; usá datos.status, o GET /v1/documentos/:id ante la duda.

Otros tipos de documento

Los cinco tipos comparten el mismo cuerpo; cambia lo que cada uno exige de más.

tipoDocumentoExige además
facturafactura.presencia y condicion
notaCreditodocumentoAsociado (formato 1 con el CDC, o formato 2 con timbrado, establecimiento, punto, número y fecha) y notaCreditoDebito
notaDebitolo mismo que la nota de crédito
autofacturaautoFactura con los datos del vendedor no contribuyente, cliente.tipoOperacion = 2 (B2C) y documentoAsociado formato 3 con constanciaTipo, constanciaControl y constanciaNumero
notaRemisionremision (motivo, tipoResponsable, kms), detalleTransporte completo y cliente.direccion; no lleva condicion

Errores

Los errores de validación devuelven la lista de campos, nunca un código opaco. El idioma del mensaje sigue el Accept-Language.

CódigoCuándo
400falta el header Idempotency-Key en POST /v1/documentos
401clave ausente, mal formada, revocada, o cuenta suspendida
402el plan no incluye el recurso pedido (API en producción, dossier de fiscalización, asistente de rectificativa: recurso dice cuál y upgrade el plan que lo incluye), o el plan gratis agotó la cuota del mes (el consumo va en uso)
404el documento no existe, o es de otro ambiente que el de la clave
409carrera de idempotencia que no se pudo resolver; el caso normal de reenvío devuelve 200 con el documento anterior
429más de 600 pedidos por minuto con la misma clave; Retry-After dice en cuántos segundos vuelve. Toda respuesta trae X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset
422validación del cuerpo (lista en fields) o regla de negocio: CSC no configurado, certificado vencido o ausente, timbrado vencido, numeración agotada o no configurada para el punto, establecimiento o punto no registrado, tipoTransaccion no configurado, fecha fuera del plazo del SIFEN (más de 720 h atrás o 120 h adelante)
503GET /v1/documentos/:id/xml cuando la firma o el QR fallan del lado de Timbrale (certificado, contraseña, CSC) — no es culpa del payload
Ejemplo
422 Unprocessable Entity

{
  "statusCode": 422,
  "error": "Unprocessable Entity",
  "fields": [
    { "path": "cliente.ruc", "message": "dígito verificador incorrecto: para 80012345 el DV es 0" },
    { "path": "items", "message": "documento precisa de pelo menos um item" }
  ]
}

Conservación de los archivos

Timbrale conserva por 10 años, contados desde el cierre del ejercicio fiscal de cada documento, el XML firmado tal como fue transmitido al SIFEN, la respuesta del SIFEN con su protocolo, los eventos asociados (cancelación, inutilización, eventos del receptor) con sus XML firmados, y el KuDE generado. El plazo legal es la prescripción de la Ley 125/91 art. 164 — unos 6 años, suspendible—; los 10 son margen.

Nada se borra automáticamente antes de eso. De cada XML se guarda además su SHA-256 en el momento de la emisión: es lo que permite demostrar, en una fiscalización, que el archivo entregado es el original y no una copia rehecha. Un XML regenerado desde la base de datos pierde la firma digital y con ella el valor probatorio.

La exportación está siempre disponible, pero se pide desde el panel, con sesión, no con la clave de API: GET /v1/tenant/fiscal/periods/:periodo/export?format=zip devuelve los XML firmados del período, los KuDE y un manifiesto que compara cada hash con el guardado al emitir. En un período grande la respuesta es 202 con un jobId, y el archivo se retira después en GET /v1/tenant/fiscal/exports/:id/download.

Para una fiscalización que abarca varios meses está el dossier: POST /v1/tenant/fiscal/dossier con { "desde": "AAAA-MM-DD", "hasta": "AAAA-MM-DD" } responde siempre 202 con un jobId y arma los XML firmados del intervalo —también los de documentos anulados, sus eventos y las inutilizaciones—, con el control de integridad de cada uno y el estado del cierre de cada mes. Está incluido en Alto volumen y Enterprise; en los demás planes la respuesta es 402.

La obligación legal de conservación (RG 05/18, art. 18) es del contribuyente. Timbrale la facilita y entrega los archivos cuando se los pida; no actúa como depositario legal por plazo indeterminado.

¿Falta algo acá?

Se crea la cuenta, se genera una clave sk_test_ y se emite contra el sandbox sin costo ni certificado.