MX
Referencia de la API

API de Autofactura

Integra tu sistema POS, ERP o e-commerce con FacturaMX para registrar tickets de venta y permitir que tus clientes generen su CFDI 4.0 al instante.

CFDI 4.0 REST / JSON Bearer Token 🧪 Sandbox 60 req/min

Inicio rápido

En menos de 5 minutos puedes enviar tu primer ticket desde cualquier sistema.

1

Obtén tu API Key

En tu dashboard → Autofactura → tab API Externa → Generar API Key.

2

Envía el ticket

Haz un POST a /api/autofactura_webhook.php con los datos de la venta.

3

El cliente factura

Tu cliente recibe el enlace del portal y genera su CFDI 4.0 con sus datos fiscales.

Base URL

https://facturamx.bradown.com/api/autofactura_webhook.php Solo acepta POST

Autenticación

Todas las peticiones deben incluir el header Authorization con tu API Key. Existen dos tipos de key según el entorno.

LIVE

API Key de producción

afk_xxxxxxxxxxxxxxxxxxxx
  • ✅ Timbra CFDIs reales ante el SAT
  • ✅ Descuenta timbres de tu saldo
  • ✅ Aparece en historial
  • ⚠️ Requiere mínimo 199 timbres
🧪 SANDBOX

API Key de pruebas

afk_test_xxxxxxxxxxxxxxxxxxxx
  • ✅ Timbra en ambiente de pruebas (SAT dev)
  • ✅ No descuenta timbres
  • ✅ No requiere saldo mínimo
  • ⚠️ Rate limit: 20 req/min
// Producción — mismos endpoints, key live
Authorization: Bearer afk_xxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type:  application/json

// Pruebas — mismos endpoints, key sandbox
Authorization: Bearer afk_test_xxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type:  application/json

Seguridad

  • Nunca expongas tu API Key en código frontend (JS del navegador).
  • Las llamadas deben hacerse desde tu servidor backend.
  • Genera y gestiona tus keys en Dashboard → Autofactura → pestaña API.
  • La clave completa se muestra una sola vez al generarla — guárdala en tu .env.

Modo Sandbox

🧪 Pruebas

Genera una API Key sandbox desde el dashboard para integrar y probar sin gastar timbres reales. Usa exactamente los mismos endpoints — solo cambia el prefijo de la key.

Diferencias Live vs Sandbox

Regla / Característica Live (afk_...) Sandbox (afk_test_...)
Ambiente PACProducción (SAT real)Desarrollo (SAT pruebas)
Credenciales PACCuenta liveCuenta de pruebas
Descuenta timbres✅ Sí❌ No
Saldo mínimo requerido199 timbresSin límite
Rate limit60 req/min20 req/min
UUID retornadoReal (válido SAT)Prueba (SAT dev)
Aparece en historial✅ Sí❌ No (oculto)
Log en af_api_logis_sandbox=0is_sandbox=1
Webhooks disparados✅ Sí✅ Sí (para probar)
Campo "sandbox": true en respuesta✅ Siempre

Respuesta en modo sandbox

// POST /api/facturar_directo.php con key afk_test_...
{
  "success":    true,
  "uuid":       "AABB1122-3344-5566-7788-CCDDEEFF0011",
  "message":    "CFDI timbrado en modo sandbox (no consume timbres reales).",
  "sandbox":    true,   // siempre presente en modo sandbox
  "request_id": "X9Z2K1L4"
}

// POST /api/complemento_pago_ext.php con key afk_test_...
{
  "success":    true,
  "uuid":       "CCDD3344-5566-7788-AABB-EEFF00112233",
  "serie":      "CP",
  "folio":      1,
  "message":    "Complemento de pago timbrado en modo sandbox (no consume timbres reales).",
  "sandbox":    true,
  "request_id": "Y7W3K2M9"
}

RFC receptor de prueba (sandbox)

🧪 Datos de prueba Usa estos datos como receptor en tus peticiones sandbox
Campo Valor
RFC MASO451221PM4
Nombre MARIA OLIVIA MARTINEZ SAGAZ
Código Postal 80290
Tipo Persona Física
Regímenes válidos
605 606 607 608 610 611 612 614 615 616 621 625 626

Ejemplo de payload sandbox con este receptor

// Registra este cliente primero vía POST /api/cliente.php o usa su cliente_id
{
  "cliente_id": 123,          // ID del cliente con RFC MASO451221PM4
  "uso_cfdi":   "G03",
  "conceptos": [{
    "descripcion":     "Servicio de prueba",
    "clave_prod_serv": "84111506",
    "clave_unidad":    "E48",
    "cantidad":        1,
    "precio_unitario": 1000.00
  }]
}

Cómo generar tu Sandbox Key

  1. Ve a Dashboard → Autofactura → pestaña API.
  2. En la sección API Key Sandbox presiona Generar Sandbox Key.
  3. Copia la clave completa — se muestra una sola vez.
  4. Úsala en tu entorno de desarrollo con el header Authorization: Bearer afk_test_...
  5. Cuando estés listo para producción, cambia la key por la live en tu .env.

Rate limiting

La API aplica dos niveles de control de tráfico. El límite por key varía según el tipo (live vs sandbox).

Live — por API Key

60 req/min

Se reinicia cada minuto calendario.

🧪

Sandbox — por API Key

20 req/min

Límite reducido para ambiente de pruebas.

Por IP (fallos de auth)

20 fallos / 10 min

La IP queda bloqueada temporalmente.

Headers de respuesta (en todas las peticiones)

// Live: Limit = 60 | Sandbox: Limit = 20
X-RateLimit-Limit:     60
X-RateLimit-Remaining: 45
X-RateLimit-Reset:     1711234560  // Unix timestamp — cuándo se reinicia

// Cuando se excede el límite — HTTP 429
Retry-After: 23  // segundos restantes

Autofactura (Portal)

Webhook POS / ERP

Registra tickets de venta desde tu POS o ERP para que el cliente pueda generar su factura en el portal de autofactura sin intervención manual.

Registrar ticket

POST

Registra una venta en el sistema. El cliente podrá entrar al portal de autofactura, buscar su ticket por código y generar su CFDI 4.0 con sus datos fiscales.

Endpoint

POST /api/autofactura_webhook.php
El ticket registrado no genera ni timbra el CFDI. Solo almacena la venta. El timbrado ocurre cuando el cliente llena sus datos fiscales en el portal de autofactura.
Si envias el mismo ticket otra vez y todavia no ha sido facturado, la API lo toma como actualizacion del ticket. Esto sirve para cambios por devoluciones parciales, cambios de cantidad o correcciones del POS.

Vigencia y depuracion automatica de tickets

Los tickets sin facturar se eliminan automaticamente al cumplir 45 dias desde la fecha de compra. El dashboard muestra alerta visual cuando faltan 7 dias o menos para el vencimiento.

Actualizar ticket

No existe un endpoint distinto para editar. Para actualizar un ticket pendiente, vuelve a hacer POST /api/autofactura_webhook.php con el mismo valor de ticket y manda el estado final correcto de la venta.

Ejemplo POS: se venden 10 articulos y luego regresan 2.

El POS debe reenviar el mismo ticket con las cantidades ya ajustadas a 8. La respuesta incluira "mode": "updated" o "mode": "reactivated" si el ticket estaba cancelado.

Cancelar ticket

POST

Endpoint

POST /api/autofactura_ticket_cancel.php
{
  "ticket": "TX-0001",
  "motivo": "Devolucion total o cancelacion en POS"
}

Consultar ticket

GET / POST

Endpoint

GET /api/autofactura_ticket_get.php?ticket=TX-0001

Devuelve el estado actual del ticket: pending, cancelled o invoiced.

Request body

Campo Tipo Requerido Descripción
ticket string Código único de la venta. Máx. 100 chars. Ej: TX-0001
fecha string Fecha de la venta en formato YYYY-MM-DD. Debe estar dentro del periodo permitido para el negocio.
conceptos array Array de ítems de la venta. Mínimo 1, máximo 100.

Estructura Interna del Array "conceptos" (Campos por Producto/Servicio)

Cada objeto dentro del array conceptos representa un producto o servicio de la venta.

Estos son los campos reales que recibe /api/autofactura_webhook.php. Los alias indicados tambien son aceptados por la API.

Campo Tipo Req. Default Descripción
descripcion string Descripción del producto o servicio.
clave_sat string Clave SAT de producto o servicio (8 dígitos). Ej: 81111508. Acepta también clave_prod_serv.
clave_unit string Clave de unidad SAT. Ej: E48. Acepta también clave_unidad.
unidad_desc string No "Pieza" Descripción visual de la unidad de medida. Ej: Servicio.
cantidad number Cantidad a facturar (permite decimales).
precio_unitario number Precio unitario antes de impuestos.
objeto_imp string No "02" Objeto de impuesto CFDI. Valores aceptados: "01", "02", "03".
iva_tasa number No 0.16 Tasa de IVA. Acepta 0.16, 0.08, 0 o "exento".
ieps_val number No 0.0 Valor del IEPS (tasa o cuota fija).
ieps_type string No "tasa" Tipo de IEPS. Valores: "tasa" o "cuota".
ret_iva_rate number No 0.0 Tasa de retención de IVA en decimales. Ej: 0.1067.
ret_isr_rate number No 0.0 Tasa de retención de ISR en decimales. Ej: 0.1000.

Respuesta exitosa 200

{
  "success": true,
  "ticket_id": 1042,
  "mode": "created",
  "portal_url": "https://tudominio.com/facturas/autofactura/?t=TOKEN&ticket=TX-0001",
  "request_id": "A3F9B12C"
}
Campo Descripción
successtrue siempre en respuesta 200.
ticket_idID interno del ticket en FacturaMX. Guárdalo para referencias futuras.
modeIndica si el ticket fue created, updated o reactivated.
portal_urlURL directa al portal de autofactura con el ticket precargado. Puedes enviarsela al cliente por WhatsApp, email o SMS. Recuerda facturarlo antes de 45 dias para evitar depuracion automatica.
request_idID único de la petición. Incluirlo al reportar un problema para diagnóstico rápido.

Respuesta de error

{
  "success":    false,
  "error":      "API Key inválida o inexistente.",
  "code":       "AUTH_INVALID",
  "field":      "",           // campo inválido, si aplica
  "request_id": "B7D2A91F"
}
Guarda siempre el request_id en tus logs. Con él podemos rastrear cualquier petición en segundos.

Códigos de error

HTTP code Descripción
200 Ticket registrado correctamente.
400 JSON_INVALID El JSON no es valido.
422 MISSING_FIELD Falta un campo requerido. Ver field en la respuesta.
422 INVALID_FORMAT La fecha, clave SAT u otro campo no tiene el formato esperado.
422 DATE_OUT_OF_RANGE La fecha de venta esta fuera del periodo permitido para el negocio.
422 TOO_MANY_ITEMS El array conceptos supera el maximo de 100 items.
422 INVALID_VALUE Cantidad o precio_unitario fuera de rango.
422 INVALID_SAT_CLAVE La clave SAT no existe en el catalogo SAT.
422 STAMP_FAILED El SAT rechazó el timbrado. Ver campo finkok_code para el código específico del PAC.
404 TICKET_NOT_FOUND El ticket no existe para esa API Key / negocio.
409 DUPLICATE_TICKET El ticket ya fue facturado y ya no puede modificarse.
409 TICKET_ALREADY_INVOICED El ticket ya fue facturado y no puede cancelarse.
401 AUTH_MISSING No se envió el header Authorization.
401 AUTH_INVALID La API Key no existe o no pertenece a ningún negocio activo.
402 INSUFFICIENT_STAMPS Saldo insuficiente para usar la API. El campo error indica el mínimo requerido y tu saldo actual. Ej: "Minimo requerido: 500. Saldo actual: 120."
429 RATE_LIMIT_EXCEEDED Se superaron las 60 peticiones por minuto.
429 IP_BLOCKED IP bloqueada temporalmente por demasiados fallos de auth.
500 INTERNAL_ERROR Error interno del servidor. Reintenta en unos segundos.

Códigos de error Finkok (finkok_code)

Cuando code: "STAMP_FAILED", la respuesta incluye el campo finkok_code con el código exacto que devolvió el PAC. Úsalo para distinguir el tipo de rechazo sin parsear el texto de error.

{
  "success":     false,
  "error":       "[301] El RFC del receptor no está registrado en el SAT.",
  "code":        "STAMP_FAILED",
  "finkok_code": "301",
  "request_id": "X920A1BD"
}
finkok_code Causa Accion sugerida
301 RFC del emisor o receptor invalido / no registrado en el SAT. Verifica que el RFC sea correcto y este activo en el SAT.
302 Fecha de emision fuera del rango permitido por el SAT. Ajusta la fecha al dia actual o al periodo vigente.
303 El XML tiene errores de estructura o namespace. Revisa que los campos del CFDI coincidan con el catalogo SAT.
304 Certificado (CSD) no valido o vencido. Renueva el CSD de la empresa en el SAT y actualiza las credenciales.
305 El sello digital no corresponde al certificado. Verifica que la llave .key y el .cer sean del mismo par.
307 El folio fiscal (UUID) ya fue utilizado. Genera un nuevo CFDI; no reutilizes el mismo UUID.
401 El CFDI contiene errores de calculo o impuestos inconsistentes. Revisa tasas de IVA, IEPS y retenciones en los conceptos.
709 Error interno del PAC (Finkok). Reintento recomendado. Reintenta la solicitud en unos segundos.

* Esta tabla cubre los códigos más frecuentes. Cuando finkok_code no aparezca en esta lista, el campo error contiene el mensaje completo del PAC.

API Directa

Nuevo

Timbra CFDIs al instante, descarga XML/PDF, cancela comprobantes y mantén tus clientes sincronizados directamente desde tu backend con Bearer Token.

Facturacion Directa

POST

Genera y timbra al instante un CFDI 4.0 utilizando la informacion del cliente y la empresa. Este endpoint consume timbres inmediatamente al emitirse con exito.

Tipos de CFDI soportados — tipo_comprobante

"I" — Ingreso (default)

  • IVA 16%, 8%, 0%, exento o sin impuesto
  • IEPS tasa o cuota
  • Retenciones ISR / IVA
  • Público en General (XAXX010101000) con InformacionGlobal
  • Extranjeros (XEXX010101000)
  • Moneda extranjera con tipo_cambio
  • Descuentos por concepto
  • Borradores ("es_borrador": true)
  • Método de pago PPD (forma_pago: "99")

"E" — Egreso / Nota de crédito

  • Requiere uuid_relacionado con el UUID original
  • tipo_relacion: "01" nota de crédito, "04" sustitución
  • Mismo soporte de impuestos que tipo I

"P" — Complemento de Pago (REP 2.0)

  • Endpoint propio: POST /api/complemento_pago_ext.php
  • Liquida CFDIs con metodo_pago = PPD
  • Soporta múltiples facturas por pago
  • Moneda extranjera con equivalencia DR
  • Idempotency-Key soportado

"T" — Traslado / Carta Porte

  • Requiere Complemento Carta Porte 3.1
  • Envía carta_porte_version o transpInternac
  • Sin estos campos el SAT rechaza el CFDI

"N" — Nómina

Requiere el módulo de Nómina del dashboard. No disponible en este endpoint — usa el portal.

Retenciones e Información de Pagos

Estructura XML independiente del CFDI 4.0. Usa el módulo de Retenciones del dashboard.

Validaciones automáticas del sistema

  • PPD + forma_pago: Si metodo_pago = "PPD" la forma de pago se fuerza a "99" (por definir).
  • Egreso sin relacionado: Tipo "E" sin uuid_relacionado retorna HTTP 422.
  • Traslado sin Carta Porte: Tipo "T" sin datos de Carta Porte retorna HTTP 422.
  • Moneda extranjera: Cualquier moneda distinta de MXN/XXX requiere tipo_cambio > 0.
  • Moneda XXX: SubTotal y Total deben ser 0.

Endpoint

POST /api/facturar_directo.php

Payload (JSON)

Campo Tipo Req. Descripcion
cliente_id int No ID del cliente registrado en el sistema. Requerido si no se usa es_publico_general.
es_publico_general bool No Establece true para emitir a Publico en General (RFC: XAXX010101000).
es_borrador bool No Default false. Si se envia true, se guarda como borrador.
serie / folio string No Serie (ej: "A") y Folio (ej: "102").
moneda string No Default "MXN". Codigo de moneda ISO de 3 caracteres.
tipo_cambio number Cond. Obligatorio si moneda no es MXN (debe ser > 0). Lo exige el SAT para el atributo TipoCambio. Ej: 17.50.
uso_cfdi string Recom. Uso CFDI del receptor. Default "S01" (Sin efectos fiscales). Para clientes registrados normalmente usa "G01", "G03", etc. Debe ser compatible con el Régimen Fiscal del receptor o el SAT rechaza el CFDI. En Público en General se fuerza a S01 automáticamente.
emisor_regimen_fiscal string No Clave SAT del Régimen Fiscal del Emisor (ej. "606", "612", "626"). Si se omite, se usa el régimen predeterminado del perfil del negocio.
tipo_comprobante string No Default "I". Para nota de credito usa "E" y envia uuid_relacionado.
metodo_pago string No Default "PUE" (Pago en una sola exhibicion) o "PPD".
forma_pago string No Default "03" (Transferencia). Si metodo de pago es PPD, debe ser "99".
exportacion string No Default "01". Clave de exportacion CFDI.
uuid_relacionado array No Lista de UUID relacionados. Requerido cuando tipo_comprobante es "E".
tipo_relacion string No Clave de relación del nodo CfdiRelacionados. Default "04" (Sustitución). Para nota de crédito usa "01".
ticket string No Codigo del ticket registrado en autofactura (ej: "TX-0001"). Si se envia y el timbrado es exitoso, el sistema marca automaticamente ese ticket como facturado para que el cliente ya no pueda volver a facturarlo desde el portal de autofactura. Recomendado cuando el POS tambien usa el modulo de autofactura.
periodicidad string No* Solo Publico en General. "01"=Diario, "02"=Semanal, "03"=Quincenal, "04"=Mensual (default), "05"=Bimestral.
meses string No* Solo Publico en General. Mes del CFDI global en 2 digitos. Ej: "06"=Junio. Default: mes actual.
anio_global string No* Solo Publico en General. Ano del CFDI global (4 digitos). Ej: "2025". Default: ano actual.
conceptos array Si Lista de productos/servicios (mín. 1, máx. 100). Ver la tabla de campos del concepto abajo.

Campos de cada concepto (array conceptos)

Mismos campos y alias que acepta el webhook de autofactura. Los marcados son obligatorios por el SAT.

Campo Tipo Req. Default Descripción
descripcion string Descripción del producto o servicio. Si va vacía, el concepto se ignora.
clave_sat string Clave SAT ProdServ (8 díg.). Se valida contra el catálogo. Alias: clave_prod_serv.
clave_unit string Clave SAT de unidad. Se valida contra el catálogo. Alias: clave_unidad.
precio number Precio unitario antes de impuestos. Alias: precio_unitario.
cantidad number No 1 Cantidad a facturar (permite decimales).
unidad_desc string No "Pieza" Descripción visual de la unidad.
descuento number No 0 Importe de descuento del renglón (no porcentaje). Alias: descuento_row.
objeto_imp string No "02" Objeto de impuesto CFDI. "01" No objeto, "02" Sí objeto, "03" Sí no obligado. Con "02" el concepto debe llevar al menos un impuesto (IVA, IEPS o retención), incluido IVA exento.
iva_tipo string No "tasa" "tasa" o "exento". Alias: iva_type.
iva_tasa number No 0.16 Tasa de IVA: 0.16, 0.08 o 0. Ignorado si iva_tipo="exento". Alias: iva_rate.
ieps_val number No 0 Valor de IEPS (tasa o cuota fija). Solo se aplica si > 0.
ieps_type string No "tasa" "tasa" o "cuota". Alias: ieps_tipo.
ret_iva_rate number No 0 Tasa de retención de IVA en decimales. Ej: 0.1067.
ret_isr_rate number No 0 Tasa de retención de ISR en decimales. Ej: 0.1000.
ret_ieps number No 0 Tasa de retención de IEPS en decimales.

Reglas SAT que validan el timbrado

  • uso_cfdi debe ser compatible con el Régimen Fiscal del receptor. Ej: G03 exige regímenes como 601, 603, 606… Si no concuerdan, el CFDI se rechaza.
  • Con objeto_imp="02" (default) cada concepto debe llevar al menos un impuesto. Si el producto no causa IVA, usa iva_tipo="exento" o cambia a objeto_imp="01".
  • Moneda distinta de MXN requiere tipo_cambio > 0.
  • Método de pago PPD obliga forma de pago "99".

Ejemplo — Cliente registrado (IVA 16%)

{
  "cliente_id": 42,
  "emisor_regimen_fiscal": "606",
  "serie": "A",
  "folio": "1024",
  "moneda": "MXN",
  "tipo_comprobante": "I",
  "metodo_pago": "PUE",
  "forma_pago": "03",
  "uso_cfdi": "G03",
  "exportacion": "01",
  "conceptos": [
    {
      "descripcion": "Desarrollo de Software Integrado",
      "clave_sat": "81111508",
      "clave_unit": "E48",
      "cantidad": 1,
      "precio": 15000.00,
      "objeto_imp": "02",
      "iva_tipo": "tasa",
      "iva_tasa": 0.16
    }
  ]
}

Ejemplo — Publico en General (CFDI Global)

Reglas SAT — Publico en General

  • RFC receptor: XAXX010101000 — el sistema lo asigna automaticamente, no lo envies.
  • Uso CFDI S01 y Regimen receptor 616 — asignados automaticamente.
  • Usa "forma_pago": "99" (Por definir) cuando consolidas ventas de un periodo.
  • Los campos periodicidad, meses y anio_global son requeridos por el SAT para el nodo cfdi:InformacionGlobal.
{
  "es_publico_general": true,
  "serie": "PG",
  "folio": "50",
  "metodo_pago": "PUE",
  "forma_pago": "99",
  "periodicidad": "04",
  "meses": "06",
  "anio_global": "2025",
  "conceptos": [
    {
      "descripcion": "Ventas del periodo",
      "clave_sat": "01010101",
      "clave_unit": "ACT",
      "cantidad": 1,
      "precio": 500.00,
      "iva_tipo": "tasa",
      "iva_tasa": 0.16
    }
  ]
}

Respuesta Exitosa (200)

{
  "success":       true,
  "uuid":          "CE890A12-C84F-45F2-81D2-90A091FB7D42",
  "message":       "Factura timbrada con exito",
  "ticket_marked": true,  // solo presente si se envio "ticket" en el payload
  "request_id":   "F39D0A82"
}

¿Como obtener el PDF?

Guarda el uuid de la respuesta y usalo en el endpoint Descargar XML/PDF para obtener el PDF o XML cuando el cliente lo solicite. El PDF se genera con la plantilla configurada por el negocio.

Vista Previa PDF

POST

Genera la representacion en PDF de un comprobante como borrador/vista previa sin consumir timbres ni persistir datos en el servidor.

Endpoint

POST /api/factura_preview.php?format=json

El payload JSON de entrada y parametros de conceptos es exactamente el mismo que el del endpoint de Facturacion Directa.

Parametros Query

Parametro Req. Valores Descripcion
format No json | pdf Default "json". Si se especifica pdf, descarga/abre directamente el PDF binario en el navegador.

Respuesta Exitosa (JSON por defecto)

{
  "success": true,
  "pdf": "JVBERi0xLjQKJVR5cGVzZXR0aW5nIEJ5...", // Base64 string del PDF
  "request_id": "P830D98F"
}

Descargar XML/PDF

GET

Recupera el XML timbrado y genera el PDF de una factura usando el UUID obtenido al timbrar. El PDF se genera con la plantilla configurada por el negocio (la misma que aparece en el dashboard). Requiere el mismo Bearer Token de la API.

PDF binario

?uuid={UUID}&format=pdf

Descarga el PDF directo. Adjuntalo a un correo o muéstralo en un iframe.

XML timbrado

?uuid={UUID}&format=xml

Descarga el XML con el sello SAT. Requerido para declaraciones fiscales.

JSON (ambos en Base64)

?uuid={UUID}&format=json

Default. Devuelve PDF y XML en Base64 dentro de un JSON. Util para procesar en tu servidor.

Endpoint

GET /api/descargar_comprobante.php?uuid={UUID}&format=pdf|xml|json

Parametros Query

Parametro Req. Valores Descripcion
uuid Si* string UUID SAT del comprobante. Lo recibes en la respuesta de facturar_directo. Recomendado sobre id.
id Si* int ID numerico interno (alternativa al UUID). Usa uno u otro.
format No json | xml | pdf Default "json" — devuelve PDF + XML en Base64. pdf descarga el PDF; xml descarga el XML timbrado.

Respuesta cuando format=json (default)

{
  "success": true,
  "uuid":    "CE890A12-C84F-45F2-81D2-90A091FB7D42",
  "xml":    "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4...", // Base64
  "pdf":    "JVBERi0xLjQKJVR5cGVzZXR0aW5nIEJ5IERvbXBkZg...",         // Base64
  "request_id": "E83A9201"
}

Cancelar CFDI (SAT)

POST

Solicita la cancelación oficial de un CFDI timbrado directamente ante el SAT.

Endpoint

POST /api/cancelar_cfdi.php

Payload (JSON)

Campo Tipo Req. Descripcion
uuid / id string | int Si El SAT UUID o ID numerico del CFDI que deseas cancelar.
motivo string No Default "02". Codigos del SAT: "01" (Con errores, con relacion), "02" (Con errores, sin relacion), "03" (No se llevo a cabo), "04" (Operacion global nominativa).
uuid_sustitucion string No Requerido unicamente si el motivo es "01". Indica el UUID del nuevo CFDI sustituto.

Respuesta Exitosa (200)

{
  "success": true,
  "message": "Factura cancelada exitosamente ante el SAT.",
  "status_code": "201",
  "uuid": "CE890A12-C84F-45F2-81D2-90A091FB7D42",
  "acuse_url": "storage/uploads/acuses/acuse_e8a93...xml",
  "sat_estado": "vigente",
  "request_id": "K28D9A83"
}

Saldo de Timbres

GET

Consulta el saldo de timbres disponibles en tu negocio. Se requieren mínimo 199 timbres para usar la API.

Endpoint

GET /api/saldo_timbres.php

Respuesta Exitosa (200)

{
  "success":          true,
  "saldo":            1250,
  "min_required_api": 500,
  "negocio":          "Mi Negocio SA de CV",
  "request_id":      "L930F829"
}
Campo Descripción
saldo Timbres disponibles actualmente en tu negocio.
min_required_api Mínimo requerido para usar la API. Actualmente: 199 timbres.
negocio Nombre comercial del negocio asociado a la API Key.

Saldo mínimo requerido: 199 timbres

Si tu saldo cae por debajo de ese número, la API devuelve 402 en todas las peticiones hasta que recargues timbres.

Clientes

GET POST

Dos operaciones en un solo endpoint. GET busca un cliente por RFC (útil antes de facturar para obtener su cliente_id). POST crea o actualiza el cliente usando el RFC como clave única — si ya existe, actualiza sus datos; si no, lo crea.

GET — Buscar por RFC

GET /api/cliente.php?rfc={RFC}
// Respuesta 200 — encontrado
{
  "success":  true,
  "cliente": {
    "id":                  128,
    "rfc":                 "KOF0112111A1",
    "nombre_razon_social": "COCA COLA FEMSA SAB DE CV",
    "email":               "facturacion@femsa.com.mx",
    "codigo_postal":       "06700",
    "regimen_fiscal":      "601",
    "uso_cfdi":            "G03",
    "calle": "", "colonia": "", "municipio": "", "estado": ""
  },
  "request_id": "A1B2C3D4"
}

// Respuesta 404 — no encontrado
{ "success": false, "error": "Cliente con RFC \"KOF0112111A1\" no encontrado en este negocio.", "code": "NOT_FOUND" }

POST — Crear / Actualizar (Upsert)

POST /api/cliente.php

Payload (JSON)

Campo Tipo Req. Descripción
rfc string RFC del receptor (12–13 chars). Acepta genéricos: XAXX010101000, XEXX010101000.
nombre_razon_social string Nombre o Razón Social exacta como consta en la CSF del SAT.
codigo_postal string No CP del domicilio fiscal (5 dígitos).
regimen_fiscal string No Clave SAT. Ej: "601", "626", "605".
uso_cfdi string No Uso CFDI preferido del cliente. Ej: "G03", "S01".
email string No Correo para envío de comprobantes.
telefono / calle / colonia / municipio / estado string No Datos de domicilio opcionales.
{
  "rfc":                 "KOF0112111A1",
  "nombre_razon_social": "COCA COLA FEMSA SAB DE CV",
  "codigo_postal":       "06700",
  "regimen_fiscal":      "601",
  "uso_cfdi":            "G03",
  "email":               "facturacion@femsa.com.mx"
}

Respuesta Exitosa (200)

{
  "success":    true,
  "cliente_id": 128,
  "action":     "created",  // "created" | "updated"
  "rfc":        "KOF0112111A1",
  "message":    "Cliente creado exitosamente.",
  "request_id": "M830A91D"
}

💡 Flujo recomendado para POS/ERP

  1. Validar RFC → GET /api/validar_rfc.php?rfc=XXX
  2. Si cliente_previo existe → usar su cliente_id directo
  3. Si no existe → POST /api/cliente.php para registrarlo y obtener el ID
  4. Timbrar → POST /api/facturar_directo.php con el cliente_id

Listado / Historial de Facturas

GET

Consulta e investiga el historial de facturas emitidas por tu negocio. Permite filtrar por RFC de cliente, rango de fechas y estatus, con paginación integrada.

Endpoint

GET /api/facturas_list.php

Parámetros Query

Parámetro Tipo Req. Descripción
rfc_cliente string No Filtra facturas emitidas a un RFC específico.
fecha_inicio string No Desde esta fecha, inclusive (formato YYYY-MM-DD). Aplica sobre la fecha del CFDI.
fecha_fin string No Hasta esta fecha, inclusive (formato YYYY-MM-DD).
estatus string No "timbrada" o "cancelada". Sin filtro devuelve ambas.
serie NUEVO string No Filtra por serie del CFDI. Ej: "A", "CP", "NC". Se normaliza a mayúsculas.
folio NUEVO string No Filtra por folio exacto. Ej: "1024". Combinar con serie para búsqueda precisa.
tipo NUEVO string No Tipo de comprobante SAT: I Ingreso · E Egreso · T Traslado · N Nómina · P Pago (REP).
limit int No Resultados por página (1–100, default: 50).
offset int No Registros a omitir para paginación (default: 0).

💡 Paginación con total y has_more

La respuesta ahora incluye total (conteo global) y has_more (boolean). Úsalos para construir paginación sin hacer un request extra de conteo.

// Ejemplo: iterar todas las páginas
let offset = 0, hasMore = true;
while (hasMore) {
  const res = await fetch(`/api/facturas_list.php?limite=100&offset=${offset}`, headers);
  const data = await res.json();
  processar(data.invoices);
  hasMore = data.has_more;
  offset += 100;
}

Respuesta Exitosa (200)

{
  "success":  true,
  "total":    342,
  "limit":    50,
  "offset":   0,
  "has_more": true,
  "invoices": [
    {
      "id":               1024,
      "uuid":             "CE890A12-C84F-45F2-81D2-90A091FB7D42",
      "folio":            "154",
      "serie":            "A",
      "tipo_comprobante": "I",
      "fecha":            "2026-03-21",
      "subtotal":         100.00,
      "iva":              16.00,
      "ret_isr":          0.00,
      "ret_iva":          0.00,
      "total":            116.00,
      "estatus":          "timbrada",
      "cliente_rfc":      "XAXX010101000",
      "cliente_nombre":   "PUBLICO EN GENERAL"
    }
  ],
  "request_id": "R920A18D"
}

Estado SAT (UUID)

GET

Consulta el estado de un CFDI directamente en el SAT. Útil para confirmar si una cancelación fue aceptada por el receptor, verificar que un UUID sigue vigente antes de emitir una nota de crédito, o detectar facturas canceladas en el SAT que el sistema aún marca como timbradas.

Endpoint

GET /api/sat_status.php?uuid={UUID}

Parámetros Query

Parámetro Tipo Req. Descripción
uuid string UUID del CFDI a consultar. Debe pertenecer a tu negocio y estar timbrado.

Campos de la Respuesta

Campo Tipo Descripción
sat_estado string Estado que reporta el SAT: "vigente" o "cancelado". Cadena vacía si el SAT no respondió.
es_cancelable string "Cancelable sin aceptacion", "Cancelable con aceptacion", "No cancelable", o cadena vacía.
estatus_local string Estado en tu sistema: "timbrada" o "cancelada".
sincronizado bool | null true si el SAT y tu sistema coinciden. false si están desincronizados. null si el SAT no respondió.
nota string? Presente solo cuando sincronizado = false. Mensaje orientativo sobre la discrepancia.

Respuesta Exitosa (200)

{
  "success": true,
  "uuid": "CE890A12-C84F-45F2-81D2-90A091FB7D42",
  "sat_estado": "vigente",
  "es_cancelable": "Cancelable sin aceptacion",
  "estatus_local": "timbrada",
  "sincronizado": true,
  "request_id": "R920A18D"
}

⚠ Nota sobre sincronizado = false

Si sat_estado = "cancelado" pero estatus_local = "timbrada", el SAT ya procesó la cancelación pero tu sistema no fue actualizado. Esto sucede cuando la cancelación fue aceptada en el portal del SAT directamente sin pasar por la API. En ese caso, usa el endpoint Cancelar CFDI para sincronizar el estatus.

Complemento de Pago (REP)

POST

Genera y timbra un Recibo Electrónico de Pago 2.0 para liquidar uno o más CFDIs emitidos con metodo_pago=PPD. Obligatorio ante el SAT cuando se recibe el pago de una factura PPD.

¿Cuándo emitir el REP?

Cuando emites una factura con metodo_pago=PPD (pago en parcialidades o diferido), el SAT te obliga a emitir este complemento cada vez que recibes un pago. El complemento acredita el saldo pagado y actualiza el saldo insoluto de cada CFDI relacionado.

Endpoint

POST /api/complemento_pago_ext.php

Payload (JSON)

Campo Tipo Req. Descripción
fecha_pagostringFecha y hora del pago. Formato YYYY-MM-DDTHH:ii:ss. Ej: "2025-06-04T14:30:00".
forma_pagostringClave SAT de la forma de pago. Ej: "03" transferencia, "01" efectivo, "04" tarjeta crédito.
invoicesarrayLista de CFDIs que se están pagando (mín. 1). Ver tabla de campos de invoice abajo.
cliente_idintNo*ID interno del receptor. Si se omite, se resuelve automáticamente desde invoices[0].uuid.
monedastringNoMoneda del pago. Default "MXN".
tipo_cambionumberCond.Obligatorio si moneda ≠ MXN.
num_operacionstringNoNúmero de referencia o folio bancario.
rfc_banco_ordstringNoRFC del banco ordenante (del que sale el dinero).
cta_ordstringNoCuenta ordenante. Obligatoria si rfc_banco_ord = XEXX010101000 (banco extranjero).
rfc_banco_benstringNoRFC del banco beneficiario (al que llega el dinero).
cta_benstringNoCuenta beneficiaria. Obligatoria si rfc_banco_ben = XEXX010101000.

Campos de cada invoice

Campo Tipo Req. Descripción
uuidstringUUID del CFDI PPD original que se está pagando.
imp_saldo_antnumberSaldo insoluto anterior (total del CFDI si es la primera parcialidad, o el saldo restante).
imp_pagadonumberImporte que se paga en esta ocasión (≤ imp_saldo_ant).
seriestringNoSerie del CFDI original.
foliostringNoFolio del CFDI original.

Ejemplo — Pago único de una factura

{
  "fecha_pago":    "2025-06-04T14:30:00",
  "forma_pago":    "03",
  "num_operacion": "OP-789456",
  "invoices": [
    {
      "uuid":         "CE890A12-C84F-45F2-81D2-90A091FB7D42",
      "imp_saldo_ant": 11600.00,
      "imp_pagado":    11600.00
    }
  ]
}

Respuesta Exitosa (200)

{
  "success":     true,
  "uuid":        "AA11BB22-CC33-DD44-EE55-FF6677889900",
  "serie":       "CP",
  "folio":       "42",
  "message":     "Complemento de pago timbrado exitosamente.",
  "request_id":  "F39D0A82"
}

Confiabilidad & DX

Nuevo

Catálogos SAT para validar antes de timbrar, Idempotency-Key para prevenir doble timbrado y Webhooks para que tu sistema reciba notificaciones en tiempo real.

Validar RFC

GET

Valida el formato de un RFC mexicano antes de usarlo en una factura. Evita errores de timbrado por datos incorrectos del receptor y detecta si el RFC ya está registrado como cliente en tu negocio.

Endpoint

GET /api/validar_rfc.php?rfc={RFC}

Checks que realiza

Formato SAT (regex oficial 12/13 chars)
Tipo de persona (Física o Moral)
Fecha de nacimiento válida en el RFC
Homoclave válida (dígitos y letras)
Palabras inconvenientes SAT
RFC ya registrado como cliente en tu negocio

Ejemplo — RFC válido con cliente previo

// GET /api/validar_rfc.php?rfc=XAXX010101000
{
  "success":      true,
  "valido":       true,
  "rfc":          "XAXX010101000",
  "tipo":         "generico",
  "es_generico":  true,
  "descripcion":  "Público en General (Ventas al consumidor final)",
  "advertencias": ["RFC genérico: no emitir CFDI deducible con este receptor."],
  "request_id":   "A1B2C3"
}

// GET /api/validar_rfc.php?rfc=KOF0112111A1
{
  "success":      true,
  "valido":       true,
  "rfc":          "KOF0112111A1",
  "tipo":         "moral",
  "es_generico":  false,
  "errores":      [],
  "advertencias": [],
  "cliente_previo": {
    "cliente_id":          42,
    "nombre_razon_social": "COCA COLA FEMSA SAB DE CV",
    "email":               "facturacion@femsa.com.mx",
    "codigo_postal":       "06700",
    "regimen_fiscal":      "601"
  },
  "request_id":   "B4C5D6"
}

// GET /api/validar_rfc.php?rfc=INVALIDO
{
  "success":      true,
  "valido":       false,
  "rfc":          "INVALIDO",
  "tipo":         "desconocido",
  "errores": ["RFC demasiado corto (8 caracteres). Mínimo 12 (PM) o 13 (PF)."],
  "request_id":   "C7D8E9"
}

Tip — Flujo recomendado en el POS

  1. El cliente escribe su RFC en la pantalla del POS.
  2. POS llama a GET /api/validar_rfc.php?rfc=... — respuesta instantánea.
  3. Si valido=true y cliente_previo existe → usa el cliente_id directamente en la factura.
  4. Si no existe → llama a POST /api/cliente.php para registrarlo y obtener el cliente_id.
  5. Timbra con POST /api/facturar_directo.php.

Idempotency-Key — Prevención de Doble Timbrado

Si la red falla después de que el CFDI fue timbrado pero antes de que tu POS reciba la respuesta, un reintento sin protección genera dos facturas y gasta dos timbres. Con el header Idempotency-Key el sistema detecta el reintento y devuelve la respuesta original sin volver a timbrar.

Cómo funciona

  1. Tu POS genera un UUID único por operación y lo envía en el header Idempotency-Key.
  2. Si es la primera vez que se ve esa key → se procesa normalmente y se cachea la respuesta por 24 horas.
  3. Si el POS reintenta con la misma key → se devuelve la respuesta cacheada con el header Idempotency-Replay: true, sin timbrar de nuevo.
  4. Si la primera llamada falló (4xx/5xx) → no se cachea, el reintento se procesa normalmente.

Aplica en

POST /api/facturar_directo.php

Ejemplo

# Primera llamada
curl -X POST https://tudominio.com/facturas/api/facturar_directo.php \
  -H "Authorization: Bearer {API_KEY}" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{...}'

# Reintento con misma key → misma respuesta, sin timbrar de nuevo
# Response header incluirá: Idempotency-Replay: true

Webhooks Salientes

En lugar de hacer polling a sat_status, configura una URL en tu sistema y FacturaMX te notificará automáticamente cuando ocurran eventos. Configurable desde el dashboard en Autofactura → Webhooks.

cfdi.timbrado

CFDI timbrado exitosamente vía API

cfdi.cancelado

CFDI cancelado ante el SAT

ticket.registrado

Ticket registrado por POS/ERP

ticket.facturado

Ticket marcado como facturado

Payload que recibe tu endpoint

{
  "evento":      "cfdi.timbrado",
  "negocio_id":  5,
  "timestamp":   "2025-06-04T14:30:00+00:00",
  "data": {
    "uuid":        "CE890A12-C84F-45F2-81D2-90A091FB7D42",
    "request_id": "F39D0A82",
    "ticket":      "TX-0001"   // solo si se envió en la llamada original
  }
}

Verificar la firma (recomendado)

// PHP — verifica que el request viene de FacturaMX
$body     = file_get_contents('php://input');
$recibida = $_SERVER['HTTP_X_FACTURAMX_SIGNATURE'] ?? '';
$esperada = 'sha256=' . hash_hmac('sha256', $body, $tu_secreto);
if (!hash_equals($esperada, $recibida)) {
    http_response_code(401); exit;
}
$evento = json_decode($body, true);

Headers enviados en cada request

Content-Typeapplication/json
X-FacturaMX-Signaturesha256={hmac_sha256_del_body} — usa tu secreto para verificar
X-FacturaMX-TimestampUnix timestamp del envío
X-FacturaMX-EventNombre del evento (ej: cfdi.timbrado)
User-AgentFacturaMX-Webhooks/1.0
Tu endpoint debe responder HTTP 2xx en menos de 4 segundos. Si tarda más o responde con error, el sistema reintenta hasta 5 veces con backoff (5 min, 15 min, 1h, 6h, 24h).

Ejemplo — cURL

curl -X POST https://tudominio.com/facturas/api/autofactura_webhook.php \
  -H "Authorization: Bearer afk_tu_api_key_aqui" \
  -H "Content-Type: application/json" \
  -d '{
    "ticket": "TX-0001",
    "fecha": "2026-03-21",
    "conceptos": [{
      "descripcion": "Cafe Americano",
      "clave_sat": "90111800",
      "clave_unit": "H87",
      "cantidad": 2,
      "precio_unitario": 45.00,
      "iva_tasa": 0.16
    }]
  }'

Ejemplo — PHP

<?php
$url     = 'https://tudominio.com/facturas/api/autofactura_webhook.php';
$api_key = 'afk_tu_api_key_aqui';

$payload = [
    'ticket'    => 'TX-0001',
    'fecha'     => '2026-03-21',
    'conceptos' => [[
        'descripcion'     => 'Café Americano',
        'clave_sat'       => '90111800',
        'clave_unit'      => 'H87',
        'cantidad'        => 2,
        'precio_unitario' => 45.00,
        'iva_tasa'        => 0.16,
    ]],
];

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $api_key,
        'Content-Type: application/json',
    ],
]);

$res  = curl_exec($ch);
$data = json_decode($res, true);
curl_close($ch);

if ($data['success']) {
    echo 'Portal: ' . $data['portal_url'];
} else {
    error_log('[facturamx] ' . $data['code'] . ' — ' . $data['error']);
}

Ejemplo — JavaScript (Node.js)

Llama a la API siempre desde tu servidor, nunca desde el navegador del cliente.

// Node.js — fetch nativo (v18+)
const API_KEY = 'afk_tu_api_key_aqui';
const URL     = 'https://tudominio.com/facturas/api/autofactura_webhook.php';

const res  = await fetch(URL, {
  method:  'POST',
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type':  'application/json',
  },
  body: JSON.stringify({
    ticket: 'TX-0001',
    fecha:  '2026-03-21',
    conceptos: [{
      descripcion:     'Café Americano',
      clave_sat:       '90111800',
      clave_unit:      'H87',
      cantidad:        2,
      precio_unitario: 45.00,
      iva_tasa:        0.16,
    }],
  }),
});

const data = await res.json();

if (data.success) {
  console.log('Portal:', data.portal_url);
} else {
  console.error(`[${data.code}]`, data.error);
}

Ejemplo — Python

import requests

API_KEY = "afk_tu_api_key_aqui"
URL     = "https://tudominio.com/facturas/api/autofactura_webhook.php"

payload = {
    "ticket": "TX-0001",
    "fecha":  "2026-03-21",
    "conceptos": [{
        "descripcion":     "Café Americano",
        "clave_sat":       "90111800",
        "clave_unit":      "H87",
        "cantidad":        2,
        "precio_unitario": 45.00,
        "iva_tasa":        0.16,
    }],
}

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type":  "application/json",
}

r    = requests.post(URL, json=payload, headers=headers, timeout=10)
data = r.json()

if data["success"]:
    # Enviar data["portal_url"] al cliente
    print("Portal:", data["portal_url"])
else:
    print(f"[{data['code']}]", data["error"])

¿Listo para integrar?

Crea tu cuenta, activa el módulo de autofactura y obtén tu API Key en minutos.