Timbrado desde XML
Envía un XML de CFDI 4.0 previamente sellado con tus CSD y Timbra se encarga de timbrarlo ante el PAC y el SAT para obtener el Timbre Fiscal Digital.
Timbra un XML de CFDI 4.0 previamente sellado.
Request
El cuerpo es un objeto JSON con un solo campo:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
data | string | Sí | Contenido completo del XML prefirmado/sellado como texto. |
{
"data": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><cfdi:Comprobante ...>...</cfdi:Comprobante>"
}¿Prefieres no generar ni sellar el XML manualmente?
Este endpoint está diseñado para sistemas que ya generan el XML y lo firman localmente con su Certificado de Sello Digital (CSD).
- Si generas el XML pero no quieres sellarlo localmente, utiliza el endpoint Emisión de XML sin sellar (
/cfdi33/issue/v4) para que Timbra lo selle con tu CSD automáticamente. - Si prefieres enviar un simple objeto con los datos y que Timbra genere el XML, calcule los impuestos, lo selle con tu CSD y lo timbre automáticamente, utiliza Timbrado desde JSON.
XML de ejemplo (Pre-sellado)
El XML enviado debe estar previamente sellado y contener los atributos Sello, NoCertificado y Certificado:
<?xml version="1.0" encoding="UTF-8"?>
<cfdi:Comprobante
xmlns:cfdi="http://www.sat.gob.mx/cfd/4"
Version="4.0"
Serie="F"
Folio="101"
Fecha="2026-10-21T12:00:00"
Sello="iW2Y..."
NoCertificado="30001000000400002434"
Certificado="MIIF..."
FormaPago="01"
MetodoPago="PUE"
LugarExpedicion="26015"
Moneda="MXN"
TipoDeComprobante="I"
Exportacion="01"
SubTotal="1000.00"
Total="1160.00">
<cfdi:Emisor Rfc="EKU9003173C9" Nombre="ESCUELA KEMPER URGATE" RegimenFiscal="601"/>
<cfdi:Receptor
Rfc="URE180429TM6"
Nombre="UNIVERSIDAD ROBOTICA ESPAÑOLA"
DomicilioFiscalReceptor="86991"
RegimenFiscalReceptor="601"
UsoCFDI="G01"/>
<cfdi:Conceptos>
<cfdi:Concepto
ClaveProdServ="84111506"
Cantidad="1"
ClaveUnidad="E48"
Descripcion="Servicio de consultoría"
ValorUnitario="1000.00"
Importe="1000.00"
ObjetoImp="02">
<cfdi:Impuestos>
<cfdi:Traslados>
<cfdi:Traslado Base="1000.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="160.00"/>
</cfdi:Traslados>
</cfdi:Impuestos>
</cfdi:Concepto>
</cfdi:Conceptos>
<cfdi:Impuestos TotalImpuestosTrasladados="160.00">
<cfdi:Traslados>
<cfdi:Traslado Base="1000.00" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="160.00"/>
</cfdi:Traslados>
</cfdi:Impuestos>
</cfdi:Comprobante>Ejemplos
# Convierte el XML a un string JSON y envía la petición
jq -Rs '{data: .}' factura.xml > payload.json
curl -X POST "https://dashboard.timbra.com.mx/api/masivo/cfdi33/stamp/json/v4" \
-H "Authorization: Bearer tmb_test_tu_api_key_aqui" \
-H "Content-Type: application/json" \
-d @payload.jsonimport fs from 'node:fs'
const xml = fs.readFileSync('factura.xml', 'utf8')
const response = await fetch(
'https://dashboard.timbra.com.mx/api/masivo/cfdi33/stamp/json/v4',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TIMBRA_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: xml })
}
)
const result = await response.json()
if (!response.ok) {
throw new Error(result.message)
}
console.log('UUID:', result.data.uuid)
console.log('XML timbrado:', result.data.cfdi)import os
import requests
with open("factura.xml", "r", encoding="utf-8") as archivo:
xml = archivo.read()
response = requests.post(
"https://dashboard.timbra.com.mx/api/masivo/cfdi33/stamp/json/v4",
headers={
"Authorization": f"Bearer {os.environ['TIMBRA_API_KEY']}",
"Content-Type": "application/json",
},
json={"data": xml},
)
result = response.json()
if response.status_code != 200:
raise Exception(result["message"])
print("UUID:", result["data"]["uuid"])
print("XML timbrado:", result["data"]["cfdi"])Response
{
"status": "success",
"data": {
"msg": "Se ha timbrado correctamente",
"uuid": "A1B2C3D4-5E6F-7890-ABCD-EF1234567890",
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><cfdi:Comprobante ...>",
"cfdi": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><cfdi:Comprobante ...>"
}
}data.cfdi contiene el XML timbrado. Por compatibilidad, la respuesta también incluye data.xml con el mismo contenido.
Emisión con XML sin sellar (/cfdi33/issue/v4)
Si tu sistema ya construye la estructura del XML de CFDI 4.0 pero no deseas encargarte del sellado criptográfico con los certificados .cer y .key, envía el XML directamente a /cfdi33/issue/v4. Timbra aplicará el CSD y timbrará ante el SAT en una sola operación.
Emite y timbra un XML sin sellar (sellado automático con CSD)
Formatos aceptados
Puedes enviar el XML sin sellar de dos formas equivalentes:
multipart/form-data: Como archivo adjunto o campo con nombrexml.application/json: Con el objeto{ "data": "<cfdi:Comprobante ...>" }.
curl -X POST "https://dashboard.timbra.com.mx/api/masivo/cfdi33/issue/v4" \
-H "Authorization: Bearer tmb_test_tu_api_key_aqui" \
-F "xml=@factura_sin_sellar.xml"jq -Rs '{data: .}' factura_sin_sellar.xml > payload.json
curl -X POST "https://dashboard.timbra.com.mx/api/masivo/cfdi33/issue/v4" \
-H "Authorization: Bearer tmb_test_tu_api_key_aqui" \
-H "Content-Type: application/json" \
-d @payload.jsonimport fs from 'node:fs'
const xml = fs.readFileSync('factura_sin_sellar.xml', 'utf8')
const response = await fetch(
'https://dashboard.timbra.com.mx/api/masivo/cfdi33/issue/v4',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TIMBRA_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: xml })
}
)
const result = await response.json()
console.log('UUID:', result.data.uuid)import os
import requests
with open("factura_sin_sellar.xml", "rb") as f:
files = {"xml": ("factura.xml", f, "application/xml")}
response = requests.post(
"https://dashboard.timbra.com.mx/api/masivo/cfdi33/issue/v4",
headers={"Authorization": f"Bearer {os.environ['TIMBRA_API_KEY']}"},
files=files,
)
result = response.json()
print("UUID:", result["data"]["uuid"])Errores
| HTTP | Causa |
|---|---|
400 | Falta el campo data o el XML no es válido. |
400 | El RFC del emisor no es un RFC de prueba válido en Sandbox. |
400 | El PAC rechazó el XML. El detalle viene en message. |
400 | No hay saldo ni suscripción activa en Live. |
401 | La API Key falta, es inválida o fue revocada. |
500 | Error interno al procesar el timbrado. |
Ejemplo cuando falta el XML:
{
"status": "error",
"message": "El cuerpo de la petición debe contener el campo \"data\" con el XML a timbrar."
}Notas
- En Live, cada CFDI timbrado descuenta un timbre. En Test no se descuenta nada.
- Si el XML ya fue timbrado, 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.
- El XML puede ir sin
Sello,CertificadoyNoCertificado; Timbra los genera automáticamente antes de timbrar.