Saltar al contenido
Secciones de la documentación

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.

POST/invoice/create

La 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.

curl
# 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"
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.

Versionstringobligatorio
Exactamente el texto "4.0". El número 4.0 no pasa.
Seriestringcondicional
Opcional, salvo con Folio: "AUTO".
Foliostringopcional
Tu folio, o "AUTO" para tomar el siguiente de tu rango (ver campos de Factura Digital).
Fechastringobligatorio
AAAA-MM-DDThh:mm:ss, o "AUTO" para la hora actual de tu lugar de expedición. El SAT acepta hasta 72 horas atrás.
FormaPagostringcondicional
Clave de c_FormaPago. Con MetodoPago PPD es 99. En tipo P, T o N la API la quita.
CondicionesDePagostringopcional
Texto libre.
SubTotalstringobligatorio
Suma de los Importe de los conceptos.
Descuentostringopcional
Suma de los descuentos de los conceptos. La API lo escribe con 2 decimales.
Monedastringobligatorio
Clave de c_Moneda: MXN, USD… XXX en pagos y traslados.
TipoCambiostringcondicional
Obligatorio si la moneda no es MXN ni XXX. Con MXN es 1 o se omite.
Totalstringobligatorio
SubTotal − Descuento + impuestos trasladados − impuestos retenidos. La API no lo recalcula.
TipoDeComprobantestringobligatorio
I ingreso, E egreso, T traslado, N nómina o P pago.
Exportacionstringobligatorio
01 si no es exportación.
MetodoPagostringcondicional
PUE o PPD. En tipo P o T la API lo quita.
LugarExpedicionstringobligatorio
Código postal donde expides. También define la zona horaria de Fecha: "AUTO".
Confirmacionstringopcional
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

RegimenFiscalstringobligatorio
Tu régimen, de c_RegimenFiscal, el que corresponde a esta operación.
FacAtrAdquirentestringopcional
Número de operación para facturar a cuenta de un adquirente.
Rfc, Nombrestringopcional
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

Rfcstringobligatorio
12 caracteres (persona moral) o 13 (física). XAXX010101000 para público en general.
Nombrestringobligatorio
Como aparece en la constancia de tu cliente, sin el régimen de capital.
DomicilioFiscalReceptorstringobligatorio
Código postal de su domicilio fiscal.
RegimenFiscalReceptorstringobligatorio
Su régimen, de c_RegimenFiscal.
UsoCFDIstringobligatorio
Clave de c_UsoCFDI que aplique a su régimen.
ResidenciaFiscal, NumRegIdTribstringcondicional
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.

ClaveProdServstringobligatorio
Clave de ocho dígitos de c_ClaveProdServ.
NoIdentificacionstringopcional
Tu SKU o código interno.
Cantidadstringobligatorio
Mayor que cero.
ClaveUnidadstringobligatorio
Clave de c_ClaveUnidad (H87 pieza, E48 servicio…).
Unidadstringopcional
Tu nombre de la unidad.
Descripcionstringobligatorio
Hasta 1,000 caracteres. La API corta en 999 bytes: con acentos caben un poco menos.
ValorUnitariostringobligatorio
Precio unitario sin impuestos.
Importestringobligatorio
Cantidad × ValorUnitario. La API no lo recalcula.
Descuentostringopcional
Descuento del concepto.
ObjetoImpstringobligatorio
01 no objeto, 02 sí objeto (lleva Impuestos), 03 sí objeto y no obligado al desglose, y los demás de c_ObjetoImp.
extra1 … extra15stringopcional
Campos libres para tu PDF.

Conceptos[].Impuestos

Trasladosarraycondicional
Cada uno con Base, Impuesto (002 IVA, 003 IEPS), TipoFactor (Tasa, Cuota o Exento), TasaOCuota e Importe. Con Exento, sin TasaOCuota ni Importe.
Retencionesarraycondicional
Mismos campos; 001 ISR, 002 IVA.

La API escribe TasaOCuota con seis decimales: "0.16" sale 0.160000.

Otros nodos del concepto

ACuentaTercerosobjectopcional
RfcACuentaTerceros, NombreACuentaTerceros, RegimenFiscalACuentaTerceros y DomicilioFiscalACuentaTerceros.
InformacionAduaneraarrayopcional
Lista de { "NumeroPedimento": "…" }.
CuentaPredialobjectopcional
{ "Numero": "…" }. Una por concepto.
ComplementoConceptoobjectopcional
iedu (instituciones educativas) o HidroYPetro (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.

TotalImpuestosTrasladadosstringcondicional
Suma de los Importe de Traslados.
TotalImpuestosRetenidosstringcondicional
Suma de los Importe de Retenciones. En cero, la API lo omite.
Trasladosarraycondicional
Base, Impuesto, TipoFactor, TasaOCuota e Importe.
Retencionesarraycondicional
Impuesto e Importe.

CFDI relacionados

Con un solo tipo de relación (por ejemplo 04 sustitución o 01 nota de crédito):

JSON
"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:

JSON
"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

Periodicidadstringobligatorio
Clave de c_Periodicidad: 01 diario, 02 semanal, 04 mensual…
Mesesstringobligatorio
Clave de c_Meses.
Año (o Ano)stringobligatorio
Año de las operaciones. Ano escribe 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"stringopcional
Toma el siguiente folio del rango de Configuración › Series y folios para esa Serie y ese TipoDeComprobante; los dos son obligatorios. Solo el texto exacto AUTO. El folio asignado viene en la respuesta, en cfdi.Serie y cfdi.Folio.
Fecha: "AUTO"stringopcional
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.
OmitirTimbradostringopcional
"si" arma, sella y valida el XML y responde sin timbrarlo. Pide CSD y saldo, no usa folio ni guarda nada. Con Folio: "AUTO" sí toma un folio del rango.
plantilla_idintegeropcional
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_mailobjectopcional
Manda el CFDI por correo al timbrar. Ver abajo.
LeyendaFoliostringopcional
Título del documento en el PDF.
Observacionesstringopcional
Texto libre en el PDF.
NumeroOrdenCompra, FechaOrdenComprastringopcional
Orden de compra de tu cliente, para el PDF.
fechalimitepagostringopcional
Fecha límite de pago, para el PDF (en minúsculas).
EstatusCobranzaintegeropcional
0 sin pago (por omisión en ingresos), 1 pago parcial, 2 cobrado.
MontoCobrado, SaldoInsolutostringopcional
Lo cobrado y lo que falta. Por omisión, 0 y el Total.
extra1 … extra30stringopcional
Campos libres para tu PDF.
Decimales, DecimalesItemintegeropcional
Decimales de los totales y de los importes por concepto en el PDF.
Addendaobjectopcional
G500 o TERNIUM con las variables de su plantilla. Se agrega al XML guardado después del timbrado.

Enviar el CFDI por correo al timbrar

JSON
"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.

ComplementoLlaveNotas
Pagos 2.0 (REP)Pagos20Con TipoDeComprobante: "P". Guía: Complemento de pago.
Carta Porte 3.1CartaPorte31Version 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.2Nomina12Con TipoDeComprobante: "N". Las horas extra van también como percepción 019.
Comercio Exterior 2.0ComercioExterior20Con Exportacion distinta de 01.
Impuestos LocalesImpuestosLocalesTasa en porcentaje ("3.00"). Los totales los calcula la API de las filas.
DetallistadetallistaEn minúsculas.
INEINE
DonatariasDonatarias
DivisasDivisas
AerolíneasAerolineas
Notarios PúblicosNotariosPublicosLista.
Pago en EspeciePagoEnEspecie
Vehículo UsadoVehiculoUsado
Servicios Parciales de ConstrucciónParcialesConstruccion
Ingresos de HidrocarburosIngresosHidrocarburos
Obras de Arte y AntigüedadesobrasarteantiguedadesEn minúsculas.

La colección de Postman trae ejemplos completos de Pagos, Nómina, Carta Porte y Detallista.

La respuesta

200
{
  "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

UUIDstring
El folio fiscal.
XmlBase64string
El XML timbrado en Base64. Guárdalo: es tu comprobante.
CadenaOrigTFDstring
Cadena original del timbre, para tu PDF.
CadenaQRstring
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, XMLstring
Ligas de descarga del PDF y del XML.
Serie, Foliostring
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 }, con xmlBase64 en 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

codeCuándo pasaQué hacer
501La llave falta, no existe o es de otro ambiente.Llave y ambientes.
602Tu cuenta no tiene CSD activo. Se revisa antes de leer el JSON.Carga tu CSD en Configuración.
601Tu cuenta no tiene folios. También se revisa antes de leer el JSON.Compra folios.
400Falta 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_validationUna regla de Carta Porte 3.1 (CP1xx) no se cumple.errors[].code trae la clave CP.
702El XML no cumple el esquema del SAT.errors[] trae field y value; ver la matriz.
706No se pudo usar tu CSD (archivo o contraseña).Vuelve a cargar tu CSD en Configuración.
400 + clave del SATEl SAT rechazó el CFDI (CFDI40143, CRP20…, NOM…).Búscala en la matriz de errores.
400 + timbre_previoEse mismo XML ya se había timbrado. context.uuid trae su UUID; no se cobra.Recupera el CFDI por ese UUID.
5037No se pudo conectar con el proveedor de timbrado; no se envió nada.Reintenta tal cual.
5034El 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 Fecha fija y tu propio Folio. El mismo XML repetido responde timbre_previo con 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.NombreSiempre los de la cuenta de la llave.
FormaPagoSe quita en tipo P, T y N.
MetodoPagoSe quita en tipo P y T.
TasaOCuotaSeis decimales.
Descuento de la raízDos decimales.
DescripcionSe corta en 999 bytes.
TotalImpuestosRetenidos en ceroSe omite.
Fecha con espacioEl espacio se cambia por T.
RFC, nombre y código postal del receptor, ClaveProdServ, NoIdentificacionSin espacios al inicio ni al final.

Revisado contra la API v5 en producción el 2026-11-03.