Crear un CFDI
Mandas el comprobante en JSON y la API arma el XML, lo sella con el CSD de tu cuenta, lo valida contra el esquema del SAT y lo timbra. Los nombres de los campos son los del Anexo 20, así que puedes usarlo como guía.
/invoice/createLa petición
El comprobante va como texto JSON en el parámetro json de un formulario (application/x-www-form-urlencoded). Un cuerpo con Content-Type: application/json no se lee. Manda Accept: application/json o no mandes Accept: con otro valor la respuesta podría llegar en otro formato.
# FD_API=https://sandbox-app.facturadigital.com.mx/api/v5
curl -sS -X POST "$FD_API/invoice/create" \
-H "X-Api-Key: $FD_API_KEY" \
-H "Accept: application/json" \
--data-urlencode "json@factura.json"{
"Version": "4.0",
"Serie": "A",
"Folio": "1001",
"Fecha": "AUTO",
"FormaPago": "03",
"MetodoPago": "PUE",
"Moneda": "MXN",
"TipoDeComprobante": "I",
"Exportacion": "01",
"LugarExpedicion": "64000",
"SubTotal": "2501.00",
"Total": "2901.16",
"Emisor": { "RegimenFiscal": "601" },
"Receptor": {
"Rfc": "XIA190128J61",
"Nombre": "XENON INDUSTRIAL ARTICLES",
"DomicilioFiscalReceptor": "76343",
"RegimenFiscalReceptor": "601",
"UsoCFDI": "G03"
},
"Conceptos": [
{
"ClaveProdServ": "01010101",
"NoIdentificacion": "SKU-001",
"Cantidad": "2",
"ClaveUnidad": "H87",
"Unidad": "Pieza",
"Descripcion": "Producto de prueba",
"ValorUnitario": "1250.50",
"Importe": "2501.00",
"ObjetoImp": "02",
"Impuestos": {
"Traslados": [
{ "Base": "2501.00", "Impuesto": "002", "TipoFactor": "Tasa", "TasaOCuota": "0.160000", "Importe": "400.16" }
]
}
}
],
"Impuestos": {
"TotalImpuestosTrasladados": "400.16",
"Traslados": [
{ "Base": "2501.00", "Impuesto": "002", "TipoFactor": "Tasa", "TasaOCuota": "0.160000", "Importe": "400.16" }
]
}
}Manda los valores como texto ("2501.00"), como en el ejemplo. Los números de JSON también entran, pero pierden los ceros a la derecha (2501.00 se escribe 2501). Cada CFDI timbrado usa 1 folio; ningún error antes del timbrado lo cobra. Para revisar tu JSON sin timbrar, usa el validador de JSON.
Comprobante
Los atributos de la raíz del CFDI. Un atributo vacío (null, "") no se escribe en el XML; si el SAT lo exige, el esquema lo rechaza con un 702 antes de timbrar.
Version- Exactamente el texto
"4.0". El número4.0no pasa. Serie- Opcional, salvo con
Folio: "AUTO". Folio- Tu folio, o
"AUTO"para tomar el siguiente de tu rango (ver campos de Factura Digital). FechaAAAA-MM-DDThh:mm:ss, o"AUTO"para la hora actual de tu lugar de expedición. El SAT acepta hasta 72 horas atrás.FormaPago- Clave de c_FormaPago. Con
MetodoPagoPPD es99. En tipo P, T o N la API la quita. CondicionesDePago- Texto libre.
SubTotal- Suma de los
Importede los conceptos. Descuento- Suma de los descuentos de los conceptos. La API lo escribe con 2 decimales.
Moneda- Clave de c_Moneda:
MXN,USD…XXXen pagos y traslados. TipoCambio- Obligatorio si la moneda no es MXN ni XXX. Con MXN es
1o se omite. Total- SubTotal − Descuento + impuestos trasladados − impuestos retenidos. La API no lo recalcula.
TipoDeComprobanteIingreso,Eegreso,Ttraslado,Nnómina oPpago.Exportacion01si no es exportación.MetodoPagoPUEoPPD. En tipo P o T la API lo quita.LugarExpedicion- Código postal donde expides. También define la zona horaria de
Fecha: "AUTO". Confirmacion- Clave de confirmación del proveedor de timbrado, para importes o tipos de cambio fuera de rango.
NoCertificado, Certificado y Sello los pone la API con el CSD de tu cuenta: lo que mandes en esas llaves se ignora.
Emisor
RegimenFiscal- Tu régimen, de c_RegimenFiscal, el que corresponde a esta operación.
FacAtrAdquirente- Número de operación para facturar a cuenta de un adquirente.
Rfc, Nombre- No hace falta mandarlos: la API pone el RFC y el nombre de la cuenta de la llave. No se puede emitir a nombre de otro RFC.
Receptor
Rfc- 12 caracteres (persona moral) o 13 (física).
XAXX010101000para público en general. Nombre- Como aparece en la constancia de tu cliente, sin el régimen de capital.
DomicilioFiscalReceptor- Código postal de su domicilio fiscal.
RegimenFiscalReceptor- Su régimen, de c_RegimenFiscal.
UsoCFDI- Clave de c_UsoCFDI que aplique a su régimen.
ResidenciaFiscal, NumRegIdTrib- Solo con el RFC genérico extranjero
XEXX010101000.
El SAT valida RFC, nombre, código postal y régimen contra su lista de contribuyentes. Las claves que puede responder y el respaldo con RFC genérico están en Errores del receptor.
Para tu PDF puedes mandar también Calle, NoExt, NoInt, Colonia, Localidad, Referencia, Municipio, Estado, Pais, CodigoPostal y un objeto DomicilioEntrega con esos mismos campos. No van al XML.
Conceptos
Conceptos es una lista con al menos un concepto.
ClaveProdServ- Clave de ocho dígitos de c_ClaveProdServ.
NoIdentificacion- Tu SKU o código interno.
Cantidad- Mayor que cero.
ClaveUnidad- Clave de c_ClaveUnidad (
H87pieza,E48servicio…). Unidad- Tu nombre de la unidad.
Descripcion- Hasta 1,000 caracteres. La API corta en 999 bytes: con acentos caben un poco menos.
ValorUnitario- Precio unitario sin impuestos.
Importe- Cantidad × ValorUnitario. La API no lo recalcula.
Descuento- Descuento del concepto.
ObjetoImp01no objeto,02sí objeto (llevaImpuestos),03sí objeto y no obligado al desglose, y los demás de c_ObjetoImp.extra1 … extra15- Campos libres para tu PDF.
Conceptos[].Impuestos
Traslados- Cada uno con
Base,Impuesto(002IVA,003IEPS),TipoFactor(Tasa,CuotaoExento),TasaOCuotaeImporte. ConExento, sinTasaOCuotaniImporte. Retenciones- Mismos campos;
001ISR,002IVA.
La API escribe TasaOCuota con seis decimales: "0.16" sale 0.160000.
Otros nodos del concepto
ACuentaTercerosRfcACuentaTerceros,NombreACuentaTerceros,RegimenFiscalACuentaTercerosyDomicilioFiscalACuentaTerceros.InformacionAduanera- Lista de
{ "NumeroPedimento": "…" }. CuentaPredial{ "Numero": "…" }. Una por concepto.ComplementoConceptoiedu(instituciones educativas) oHidroYPetro(hidrocarburos y petrolíferos).
Impuestos
Los totales del comprobante. Agrupa los traslados de los conceptos por impuesto, tipo de factor y tasa: un renglón por combinación, con la suma de sus bases y sus importes. Las retenciones van un renglón por impuesto. Si el CFDI no tiene impuestos, omite el nodo.
TotalImpuestosTrasladados- Suma de los
ImportedeTraslados. TotalImpuestosRetenidos- Suma de los
ImportedeRetenciones. En cero, la API lo omite. TrasladosBase,Impuesto,TipoFactor,TasaOCuotaeImporte.RetencionesImpuestoeImporte.
CFDI relacionados
Con un solo tipo de relación (por ejemplo 04 sustitución o 01 nota de crédito):
"CfdiRelacionadosTipoRelacion": "04",
"CfdiRelacionados": [
{ "UUID": "6F1A2B3C-4D5E-4F60-8172-93A4B5C6D7E8" }
]Con varios tipos de relación en el mismo CFDI, usa CfdiRelacionadosMultiple, un grupo por tipo:
"CfdiRelacionadosMultiple": [
{ "TipoRelacion": "01", "Relacionados": [ { "UUID": "3C9B7A12-4E5D-4F60-B8A1-9D2C6E0F7B34" } ] },
{ "TipoRelacion": "07", "Relacionados": [ { "UUID": "0B7C2E51-8A4D-4E0F-A1C3-7D9E6F2B5A10" } ] }
]Sin CfdiRelacionadosTipoRelacion, los UUID de CfdiRelacionados no se escriben. Un CFDI de tu cuenta que otro relaciona ya no se puede cancelar desde la plataforma.
Factura global
InformacionGlobal
Periodicidad- Clave de c_Periodicidad:
01diario,02semanal,04mensual… Meses- Clave de c_Meses.
Año (o Ano)- Año de las operaciones.
Anoescribe lo mismo y evita problemas con la ñ.
Va con el receptor XAXX010101000, nombre PUBLICO EN GENERAL, régimen 616 y uso S01. Para la factura de un solo cliente sin RFC, no uses este nodo: pon su nombre en Receptor.Nombre.
Campos de Factura Digital
No son del SAT. Van en la raíz del JSON, al nivel de Serie y Folio.
Folio: "AUTO"- Toma el siguiente folio del rango de Configuración › Series y folios para esa
Seriey eseTipoDeComprobante; los dos son obligatorios. Solo el texto exactoAUTO. El folio asignado viene en la respuesta, encfdi.Serieycfdi.Folio. Fecha: "AUTO"- La hora de este momento en la zona horaria de tu cuenta o, si no la configuraste, en la del código postal de
LugarExpedicion. OmitirTimbrado"si"arma, sella y valida el XML y responde sin timbrarlo. Pide CSD y saldo, no usa folio ni guarda nada. ConFolio: "AUTO"sí toma un folio del rango.plantilla_id- Id de tu plantilla de PDF (Configuración › Diseño de PDF). Si no existe o no está activa, responde 400 antes de timbrar.
enviar_mail- Manda el CFDI por correo al timbrar. Ver abajo.
LeyendaFolio- Título del documento en el PDF.
Observaciones- Texto libre en el PDF.
NumeroOrdenCompra, FechaOrdenCompra- Orden de compra de tu cliente, para el PDF.
fechalimitepago- Fecha límite de pago, para el PDF (en minúsculas).
EstatusCobranza0sin pago (por omisión en ingresos),1pago parcial,2cobrado.MontoCobrado, SaldoInsoluto- Lo cobrado y lo que falta. Por omisión,
0y elTotal. extra1 … extra30- Campos libres para tu PDF.
Decimales, DecimalesItem- Decimales de los totales y de los importes por concepto en el PDF.
AddendaG500oTERNIUMcon las variables de su plantilla. Se agrega al XML guardado después del timbrado.
Enviar el CFDI por correo al timbrar
"enviar_mail": {
"destinatarios": "cliente@facturadigital.com.mx",
"bcc": "cobranza@facturadigital.com.mx",
"mensaje": "Gracias por tu compra."
}destinatarios es obligatorio dentro del objeto: correos separados por , o ;, hasta 500 caracteres. bcc hasta 500 y mensaje hasta 2,000. Se valida antes de timbrar y el correo sale en menos de un minuto. En el sandbox los correos no llegan al destinatario. Para reenviar después, usa POST /invoice/send.
Complementos
Van dentro de "Complemento": { "<llave>": { … } } en la raíz. La llave distingue mayúsculas. El esquema del SAT los valida antes de timbrar.
| Complemento | Llave | Notas |
|---|---|---|
| Pagos 2.0 (REP) | Pagos20 | Con TipoDeComprobante: "P". Guía: Complemento de pago. |
| Carta Porte 3.1 | CartaPorte31 | Version exactamente "3.1": con otro valor el complemento no se escribe. Las reglas CP del SAT se revisan antes del folio. Guía: Carta Porte 3.1. |
| Nómina 1.2 | Nomina12 | Con TipoDeComprobante: "N". Las horas extra van también como percepción 019. |
| Comercio Exterior 2.0 | ComercioExterior20 | Con Exportacion distinta de 01. |
| Impuestos Locales | ImpuestosLocales | Tasa en porcentaje ("3.00"). Los totales los calcula la API de las filas. |
| Detallista | detallista | En minúsculas. |
| INE | INE | |
| Donatarias | Donatarias | |
| Divisas | Divisas | |
| Aerolíneas | Aerolineas | |
| Notarios Públicos | NotariosPublicos | Lista. |
| Pago en Especie | PagoEnEspecie | |
| Vehículo Usado | VehiculoUsado | |
| Servicios Parciales de Construcción | ParcialesConstruccion | |
| Ingresos de Hidrocarburos | IngresosHidrocarburos | |
| Obras de Arte y Antigüedades | obrasarteantiguedades | En minúsculas. |
La colección de Postman trae ejemplos completos de Pagos, Nómina, Carta Porte y Detallista.
La respuesta
{
"message": "The document was generated successfully",
"code": 200,
"cfdi": {
"NoCertificado": "30001000000500003416",
"UUID": "6F1A2B3C-4D5E-4F60-8172-93A4B5C6D7E8",
"FechaTimbrado": "2026-11-03T10:30:12",
"RfcProvCertif": "SPR190613I52",
"SelloCFD": "Xy…Base64…==",
"NoCertificadoSAT": "30001000000500003456",
"SelloSAT": "Ab…Base64…==",
"CadenaOrigTFD": "||1.1|6F1A2B3C-4D5E-4F60-8172-93A4B5C6D7E8|2026-11-03T10:30:12|SPR190613I52|Xy…==|30001000000500003456||",
"CadenaQR": "https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?id=6F1A2B3C-…&re=EKU9003173C9&rr=XIA190128J61&tt=2,901.160000&fe=AbCd1234",
"XmlBase64": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4…",
"PDF": "https://sandbox-app.facturadigital.com.mx/docs/pdf/6F1A2B3C-4D5E-4F60-8172-93A4B5C6D7E8",
"XML": "https://sandbox-app.facturadigital.com.mx/docs/xml/EKU9003173C9/6F1A2B3C-4D5E-4F60-8172-93A4B5C6D7E8"
}
}cfdi
UUID- El folio fiscal.
XmlBase64- El XML timbrado en Base64. Guárdalo: es tu comprobante.
CadenaOrigTFD- Cadena original del timbre, para tu PDF.
CadenaQR- URL de verificación del SAT para el QR. Hoy el total (
tt) sale con coma de miles; si armas tu propio QR, quítala. PDF, XML- Ligas de descarga del PDF y del XML.
Serie, Folio- Solo con
Folio: "AUTO": el folio que se asignó.
- Con
ya_existia: true, el proveedor regresó un UUID que tu cuenta ya tenía: no se guardó otro ni se cobró otro folio. - Con
OmitirTimbrado: "si", la respuesta es{ "message": "…(SIN TIMBRAR)", "xmlBase64": "…", "code": 200 }, conxmlBase64en minúsculas. - Si algo falla después del timbrado (guardar el PDF, encolar el correo), la respuesta sigue siendo este 200: el CFDI ya existe en el SAT.
Errores
| code | Cuándo pasa | Qué hacer |
|---|---|---|
| 501 | La llave falta, no existe o es de otro ambiente. | Llave y ambientes. |
| 602 | Tu cuenta no tiene CSD activo. Se revisa antes de leer el JSON. | Carga tu CSD en Configuración. |
| 601 | Tu cuenta no tiene folios. También se revisa antes de leer el JSON. | Compra folios. |
| 400 | Falta json, no es un objeto JSON, un nodo tiene otro tipo (json_type_validation), Version no es "4.0", plantilla_id no existe, enviar_mail trae un correo inválido, o Folio: "AUTO" sin serie o sin rango. | El message dice qué corregir; no se envió nada al SAT. |
400 + cartaporte_validation | Una regla de Carta Porte 3.1 (CP1xx) no se cumple. | errors[].code trae la clave CP. |
| 702 | El XML no cumple el esquema del SAT. | errors[] trae field y value; ver la matriz. |
| 706 | No se pudo usar tu CSD (archivo o contraseña). | Vuelve a cargar tu CSD en Configuración. |
| 400 + clave del SAT | El SAT rechazó el CFDI (CFDI40143, CRP20…, NOM…). | Búscala en la matriz de errores. |
400 + timbre_previo | Ese mismo XML ya se había timbrado. context.uuid trae su UUID; no se cobra. | Recupera el CFDI por ese UUID. |
| 5037 | No se pudo conectar con el proveedor de timbrado; no se envió nada. | Reintenta tal cual. |
| 5034 | El CFDI pudo haberse timbrado sin respuesta legible. Trae Serie y Folio, y context.uuid si se pudo leer. | No reenvíes a ciegas: cómo verificar. |
Reintentar sin duplicar
La API no tiene una llave de idempotencia. Si un envío se corta y lo repites con Fecha: "AUTO" o Folio: "AUTO", el segundo es otro XML y puede salir otro CFDI con otro UUID.
- Si tu sistema reintenta solo, manda
Fechafija y tu propioFolio. El mismo XML repetido respondetimbre_previocon el UUID del primero y no se cobra dos veces. - Con 5037, reintenta: no salió nada.
- Con 5034, primero revisa si el CFDI existe (en tus listados o con
GET /validator/cfdi) y solo si no existe, reintenta. - Pon un tiempo de espera de 150 a 180 segundos: la API espera hasta 120 segundos al proveedor de timbrado.
Lo que la API ajusta sola
| Qué | Cómo |
|---|---|
Emisor.Rfc y Emisor.Nombre | Siempre los de la cuenta de la llave. |
FormaPago | Se quita en tipo P, T y N. |
MetodoPago | Se quita en tipo P y T. |
TasaOCuota | Seis decimales. |
Descuento de la raíz | Dos decimales. |
Descripcion | Se corta en 999 bytes. |
TotalImpuestosRetenidos en cero | Se omite. |
Fecha con espacio | El espacio se cambia por T. |
RFC, nombre y código postal del receptor, ClaveProdServ, NoIdentificacion | Sin espacios al inicio ni al final. |
Revisado contra la API v5 en producción el 2026-11-03.