Timbrado desde JSON
Construye el CFDI en JSON y deja que Timbra genere el XML, lo selle y lo timbre ante el PAC.
Timbra un CFDI 4.0 a partir de un objeto JSON.
Request
Envía un objeto con los datos del CFDI 4.0. Los importes y cantidades se envían como texto, igual que en el XML del SAT.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
Version | string | Sí | Versión del CFDI. Usa 4.0. |
Serie | string | No | Serie del comprobante. |
Folio | string | No | Folio del comprobante. |
Fecha | string | Sí | Fecha y hora de emisión, sin zona horaria. |
FormaPago | string | Sí | Clave de forma de pago del SAT. |
MetodoPago | string | Sí | PUE o PPD. |
LugarExpedicion | string | Sí | Código postal de expedición. |
Moneda | string | Sí | Moneda del comprobante, por ejemplo MXN. |
TipoDeComprobante | string | Sí | I ingreso, E egreso, T traslado, etc. |
Exportacion | string | Sí | Clave de exportación del SAT. Usa 01 si no es una operación de comercio exterior. |
SubTotal | string | Sí | Suma de conceptos antes de impuestos. |
Total | string | Sí | Total del comprobante. |
Emisor | object | Sí | RFC, nombre y régimen fiscal del emisor. |
Receptor | object | Sí | RFC, nombre, domicilio, régimen y uso del CFDI. |
Conceptos | array | Sí | Al menos un concepto con clave, cantidad, unidad, descripción e importe. |
Impuestos | object | No | Impuestos trasladados o retenidos del comprobante. |
Ejemplo
json
{
"Version": "4.0",
"Serie": "F",
"Folio": "101",
"Fecha": "2026-10-20T12:00:00",
"FormaPago": "01",
"MetodoPago": "PUE",
"LugarExpedicion": "26015",
"Moneda": "MXN",
"TipoDeComprobante": "I",
"Exportacion": "01",
"SubTotal": "1000.00",
"Total": "1160.00",
"Emisor": {
"Rfc": "EKU9003173C9",
"Nombre": "ESCUELA KEMPER URGATE",
"RegimenFiscal": "601"
},
"Receptor": {
"Rfc": "URE180429TM6",
"Nombre": "UNIVERSIDAD ROBOTICA ESPAÑOLA",
"DomicilioFiscalReceptor": "86991",
"RegimenFiscalReceptor": "601",
"UsoCFDI": "G01"
},
"Conceptos": [
{
"ClaveProdServ": "84111506",
"Cantidad": "1",
"ClaveUnidad": "E48",
"Descripcion": "Servicio de consultoría",
"ValorUnitario": "1000.00",
"Importe": "1000.00",
"ObjetoImp": "02",
"Impuestos": {
"Traslados": [
{
"Base": "1000.00",
"Impuesto": "002",
"TipoFactor": "Tasa",
"TasaOCuota": "0.160000",
"Importe": "160.00"
}
]
}
}
],
"Impuestos": {
"TotalImpuestosTrasladados": "160.00",
"Traslados": [
{
"Base": "1000.00",
"Impuesto": "002",
"TipoFactor": "Tasa",
"TasaOCuota": "0.160000",
"Importe": "160.00"
}
]
}
}Ejemplo listo para Sandbox
El ejemplo usa el RFC de pruebas EKU9003173C9. Para probarlo, envía una llave tmb_test_... y conserva ese RFC en Emisor.Rfc. En Live, cambia el emisor por los datos reales de tu cuenta.
Ejemplos
bash
# Opción 1: Enviando un archivo cfdi.json
curl -X POST "https://dashboard.timbra.com.mx/api/masivo/v3/cfdi33/issue/json/v4" \
-H "Authorization: Bearer tmb_test_tu_api_key_aqui" \
-H "Content-Type: application/json" \
-d @cfdi.json
# Opción 2: Enviando JSON inline con fecha dinámica en bash
curl -X POST "https://dashboard.timbra.com.mx/api/masivo/v3/cfdi33/issue/json/v4" \
-H "Authorization: Bearer tmb_test_tu_api_key_aqui" \
-H "Content-Type: application/json" \
-d '{
"Version": "4.0",
"Serie": "F",
"Folio": "101",
"Fecha": "'$(date +"%Y-%m-%dT%H:%M:%S")'",
"FormaPago": "01",
"MetodoPago": "PUE",
"LugarExpedicion": "26015",
"Moneda": "MXN",
"TipoDeComprobante": "I",
"Exportacion": "01",
"SubTotal": "1000.00",
"Total": "1160.00",
"Emisor": {
"Rfc": "EKU9003173C9",
"Nombre": "ESCUELA KEMPER URGATE",
"RegimenFiscal": "601"
},
"Receptor": {
"Rfc": "URE180429TM6",
"Nombre": "UNIVERSIDAD ROBOTICA ESPAÑOLA",
"DomicilioFiscalReceptor": "86991",
"RegimenFiscalReceptor": "601",
"UsoCFDI": "G01"
},
"Conceptos": [
{
"ClaveProdServ": "84111506",
"Cantidad": "1",
"ClaveUnidad": "E48",
"Descripcion": "Servicio de consultoría",
"ValorUnitario": "1000.00",
"Importe": "1000.00",
"ObjetoImp": "02",
"Impuestos": {
"Traslados": [
{
"Base": "1000.00",
"Impuesto": "002",
"TipoFactor": "Tasa",
"TasaOCuota": "0.160000",
"Importe": "160.00"
}
]
}
}
],
"Impuestos": {
"TotalImpuestosTrasladados": "160.00",
"Traslados": [
{
"Base": "1000.00",
"Impuesto": "002",
"TipoFactor": "Tasa",
"TasaOCuota": "0.160000",
"Importe": "160.00"
}
]
}
}'js
// Fecha actual en hora local (formato YYYY-MM-DDTHH:mm:ss requerido por SAT)
const now = new Date();
const fechaLocal = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString().slice(0, 19);
const cfdi = {
Version: '4.0',
Serie: 'F',
Folio: '101',
Fecha: fechaLocal,
FormaPago: '01',
MetodoPago: 'PUE',
LugarExpedicion: '26015',
Moneda: 'MXN',
TipoDeComprobante: 'I',
Exportacion: '01',
SubTotal: '1000.00',
Total: '1160.00',
Emisor: {
Rfc: 'EKU9003173C9',
Nombre: 'ESCUELA KEMPER URGATE',
RegimenFiscal: '601'
},
Receptor: {
Rfc: 'URE180429TM6',
Nombre: 'UNIVERSIDAD ROBOTICA ESPAÑOLA',
DomicilioFiscalReceptor: '86991',
RegimenFiscalReceptor: '601',
UsoCFDI: 'G01'
},
Conceptos: [
{
ClaveProdServ: '84111506',
Cantidad: '1',
ClaveUnidad: 'E48',
Descripcion: 'Servicio de consultoría',
ValorUnitario: '1000.00',
Importe: '1000.00',
ObjetoImp: '02',
Impuestos: {
Traslados: [
{
Base: '1000.00',
Impuesto: '002',
TipoFactor: 'Tasa',
TasaOCuota: '0.160000',
Importe: '160.00'
}
]
}
}
],
Impuestos: {
TotalImpuestosTrasladados: '160.00',
Traslados: [
{
Base: '1000.00',
Impuesto: '002',
TipoFactor: 'Tasa',
TasaOCuota: '0.160000',
Importe: '160.00'
}
]
}
};
const response = await fetch(
'https://dashboard.timbra.com.mx/api/masivo/v3/cfdi33/issue/json/v4',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TIMBRA_API_KEY || 'tmb_test_tu_api_key_aqui'}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(cfdi)
}
);
const result = await response.json();
if (!response.ok) {
throw new Error(result.message || 'Error al timbrar');
}
console.log('UUID:', result.data.uuid);
console.log('XML:', result.data.cfdi);python
import os
import requests
from datetime import datetime
cfdi = {
"Version": "4.0",
"Serie": "F",
"Folio": "101",
"Fecha": datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
"FormaPago": "01",
"MetodoPago": "PUE",
"LugarExpedicion": "26015",
"Moneda": "MXN",
"TipoDeComprobante": "I",
"Exportacion": "01",
"SubTotal": "1000.00",
"Total": "1160.00",
"Emisor": {
"Rfc": "EKU9003173C9",
"Nombre": "ESCUELA KEMPER URGATE",
"RegimenFiscal": "601"
},
"Receptor": {
"Rfc": "URE180429TM6",
"Nombre": "UNIVERSIDAD ROBOTICA ESPAÑOLA",
"DomicilioFiscalReceptor": "86991",
"RegimenFiscalReceptor": "601",
"UsoCFDI": "G01"
},
"Conceptos": [
{
"ClaveProdServ": "84111506",
"Cantidad": "1",
"ClaveUnidad": "E48",
"Descripcion": "Servicio de consultoría",
"ValorUnitario": "1000.00",
"Importe": "1000.00",
"ObjetoImp": "02",
"Impuestos": {
"Traslados": [
{
"Base": "1000.00",
"Impuesto": "002",
"TipoFactor": "Tasa",
"TasaOCuota": "0.160000",
"Importe": "160.00"
}
]
}
}
],
"Impuestos": {
"TotalImpuestosTrasladados": "160.00",
"Traslados": [
{
"Base": "1000.00",
"Impuesto": "002",
"TipoFactor": "Tasa",
"TasaOCuota": "0.160000",
"Importe": "160.00"
}
]
}
}
api_key = os.environ.get("TIMBRA_API_KEY", "tmb_test_tu_api_key_aqui")
response = requests.post(
"https://dashboard.timbra.com.mx/api/masivo/v3/cfdi33/issue/json/v4",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json=cfdi,
)
result = response.json()
if response.status_code != 200:
raise Exception(result.get("message", "Error al timbrar"))
print("UUID:", result["data"]["uuid"])
print("XML:", result["data"]["cfdi"])Response
Si el CFDI se timbra correctamente, la respuesta es HTTP 200:
json
{
"status": "success",
"data": {
"msg": "Se ha timbrado correctamente",
"uuid": "A1B2C3D4-5E6F-7890-ABCD-EF1234567890",
"cfdi": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><cfdi:Comprobante ...>",
"noCertificado": "30001000000400002345",
"fechaTimbrado": "2026-10-20T12:00:05"
}
}| Campo | Descripción |
|---|---|
status | success cuando el timbrado fue exitoso. |
data.uuid | UUID del CFDI timbrado. |
data.cfdi | XML timbrado completo. |
data.msg | Mensaje de confirmación. |
data.noCertificado | Número de certificado del PAC, si está disponible. |
data.fechaTimbrado | Fecha y hora del timbre, si está disponible. |
El objeto data puede incluir campos adicionales devueltos por el PAC.
Errores
| HTTP | errorCode | Causa |
|---|---|---|
400 | TIMBRES_INSUFICIENTES | No hay saldo ni suscripción activa en Live. |
400 | — | El RFC del emisor no es un RFC de prueba válido en Sandbox. |
400 | — | El PAC rechazó el CFDI. Revisa message. |
401 | — | La API Key falta, es inválida o fue revocada. |
500 | — | Error interno al procesar el timbrado. |
Ejemplo de error por saldo:
json
{
"status": "error",
"message": "Timbres insuficientes. Disponibles: 0, Requeridos: 1",
"errorCode": "TIMBRES_INSUFICIENTES"
}Consulta Errores y códigos de respuesta para ver el detalle de cada caso.
Notas
- En Live, cada CFDI timbrado descuenta un timbre. En Test no se descuenta nada.
- Si el XML ya fue timbrado previamente, la API puede devolver el UUID existente con el mensaje
El XML ya ha sido timbrado previamente. - El CFDI timbrado se guarda en tu cuenta solo en Live.
- Para cancelar el CFDI recién timbrado, usa Cancelación por UUID o Cancelación por CSD.