Inicio rápido
En menos de 5 minutos puedes enviar tu primer ticket desde cualquier sistema.
Obtén tu API Key
En tu dashboard → Autofactura → tab API Externa → Generar API Key.
Envía el ticket
Haz un POST a /api/autofactura_webhook.php con los datos de la venta.
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.
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
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
🧪 PruebasGenera 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 PAC | Producción (SAT real) | Desarrollo (SAT pruebas) |
| Credenciales PAC | Cuenta live | Cuenta de pruebas |
| Descuenta timbres | ✅ Sí | ❌ No |
| Saldo mínimo requerido | 199 timbres | Sin límite |
| Rate limit | 60 req/min | 20 req/min |
| UUID retornado | Real (válido SAT) | Prueba (SAT dev) |
| Aparece en historial | ✅ Sí | ❌ No (oculto) |
Log en af_api_log | ✅ is_sandbox=0 | ✅ is_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)
| 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
- Ve a Dashboard → Autofactura → pestaña API.
- En la sección API Key Sandbox presiona Generar Sandbox Key.
- Copia la clave completa — se muestra una sola vez.
- Úsala en tu entorno de desarrollo con el header
Authorization: Bearer afk_test_... - 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)
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
POSTRegistra 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
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
POSTEndpoint
POST /api/autofactura_ticket_cancel.php
{
"ticket": "TX-0001",
"motivo": "Devolucion total o cancelacion en POS"
}
Consultar ticket
GET / POSTEndpoint
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 | Sí | Código único de la venta. Máx. 100 chars. Ej: TX-0001 |
| fecha | string | Sí | Fecha de la venta en formato YYYY-MM-DD. Debe estar dentro del periodo permitido para el negocio. |
| conceptos | array | Sí | 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 | Sí | — | Descripción del producto o servicio. |
| clave_sat | string | Sí | — | Clave SAT de producto o servicio (8 dígitos). Ej: 81111508. Acepta también clave_prod_serv. |
| clave_unit | string | Sí | — | 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 | Sí | — | Cantidad a facturar (permite decimales). |
| precio_unitario | number | Sí | — | 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 |
|---|---|
| success | true siempre en respuesta 200. |
| ticket_id | ID interno del ticket en FacturaMX. Guárdalo para referencias futuras. |
| mode | Indica si el ticket fue created, updated o reactivated. |
| portal_url | URL 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_id | ID ú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"
}
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
Timbra CFDIs al instante, descarga XML/PDF, cancela comprobantes y mantén tus clientes sincronizados directamente desde tu backend con Bearer Token.
Facturacion Directa
POSTGenera 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_relacionadocon 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_versionotranspInternac - 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"sinuuid_relacionadoretorna 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 Sí son obligatorios por el SAT.
| Campo | Tipo | Req. | Default | Descripción |
|---|---|---|---|---|
| descripcion | string | Sí | — | Descripción del producto o servicio. Si va vacía, el concepto se ignora. |
| clave_sat | string | Sí | — | Clave SAT ProdServ (8 díg.). Se valida contra el catálogo. Alias: clave_prod_serv. |
| clave_unit | string | Sí | — | Clave SAT de unidad. Se valida contra el catálogo. Alias: clave_unidad. |
| precio | number | Sí | — | 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_cfdidebe ser compatible con el Régimen Fiscal del receptor. Ej:G03exige 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, usaiva_tipo="exento"o cambia aobjeto_imp="01". - Moneda distinta de
MXNrequieretipo_cambio> 0. - Método de pago
PPDobliga 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
S01y Regimen receptor616— asignados automaticamente. - Usa
"forma_pago": "99"(Por definir) cuando consolidas ventas de un periodo. - Los campos
periodicidad,mesesyanio_globalson requeridos por el SAT para el nodocfdi: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
POSTGenera 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
GETRecupera 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)
POSTSolicita 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
GETConsulta 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 | Sí | RFC del receptor (12–13 chars). Acepta genéricos: XAXX010101000, XEXX010101000. |
| nombre_razon_social | string | Sí | 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". |
| 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
- Validar RFC →
GET /api/validar_rfc.php?rfc=XXX - Si
cliente_previoexiste → usar sucliente_iddirecto - Si no existe →
POST /api/cliente.phppara registrarlo y obtener el ID - Timbrar →
POST /api/facturar_directo.phpcon elcliente_id
Listado / Historial de Facturas
GETConsulta 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)
GETConsulta 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 | Sí | 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)
POSTGenera 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_pago | string | Sí | Fecha y hora del pago. Formato YYYY-MM-DDTHH:ii:ss. Ej: "2025-06-04T14:30:00". |
| forma_pago | string | Sí | Clave SAT de la forma de pago. Ej: "03" transferencia, "01" efectivo, "04" tarjeta crédito. |
| invoices | array | Sí | Lista de CFDIs que se están pagando (mín. 1). Ver tabla de campos de invoice abajo. |
| cliente_id | int | No* | ID interno del receptor. Si se omite, se resuelve automáticamente desde invoices[0].uuid. |
| moneda | string | No | Moneda del pago. Default "MXN". |
| tipo_cambio | number | Cond. | Obligatorio si moneda ≠ MXN. |
| num_operacion | string | No | Número de referencia o folio bancario. |
| rfc_banco_ord | string | No | RFC del banco ordenante (del que sale el dinero). |
| cta_ord | string | No | Cuenta ordenante. Obligatoria si rfc_banco_ord = XEXX010101000 (banco extranjero). |
| rfc_banco_ben | string | No | RFC del banco beneficiario (al que llega el dinero). |
| cta_ben | string | No | Cuenta beneficiaria. Obligatoria si rfc_banco_ben = XEXX010101000. |
Campos de cada invoice
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
| uuid | string | Sí | UUID del CFDI PPD original que se está pagando. |
| imp_saldo_ant | number | Sí | Saldo insoluto anterior (total del CFDI si es la primera parcialidad, o el saldo restante). |
| imp_pagado | number | Sí | Importe que se paga en esta ocasión (≤ imp_saldo_ant). |
| serie | string | No | Serie del CFDI original. |
| folio | string | No | Folio 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
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
GETValida 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
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
- El cliente escribe su RFC en la pantalla del POS.
- POS llama a
GET /api/validar_rfc.php?rfc=...— respuesta instantánea. - Si
valido=trueycliente_previoexiste → usa elcliente_iddirectamente en la factura. - Si no existe → llama a
POST /api/cliente.phppara registrarlo y obtener elcliente_id. - Timbra con
POST /api/facturar_directo.php.
Catálogos SAT
GETConsulta los catálogos del SAT desde tu POS/ERP para validar claves antes de timbrar, eliminando errores 422 por claves inválidas.
Endpoint
GET /api/sat_catalogo.php?tipo={tipo}&q={query}
| Parámetro | Valores | Descripción |
|---|---|---|
| tipo | prod_serv | Busca en catálogo de productos/servicios SAT (tabla sat_prod_serv). Requiere q ≥ 2 chars. |
| tipo | unidad | Busca en catálogo de unidades SAT. Requiere q ≥ 2 chars. |
| tipo | uso_cfdi | Devuelve catálogo completo de Usos CFDI. Filtrable con q. |
| tipo | regimen | Catálogo de regímenes fiscales SAT. |
| tipo | forma_pago | Catálogo de formas de pago SAT. |
| tipo | metodo_pago | PUE y PPD. |
| tipo | tipo_relacion | Tipos de relación CFDI (nota crédito, sustitución, etc.). |
| q | string | Término de búsqueda. Mínimo 2 chars para prod_serv y unidad. Opcional para catálogos estáticos. |
Respuesta Exitosa (200)
{
"success": true,
"tipo": "prod_serv",
"q": "software",
"total": 3,
"data": [
{ "clave": "81111508", "descripcion": "Servicios de programacion de software" },
{ "clave": "43231513", "descripcion": "Software de contabilidad" }
],
"request_id": "A1B2C3D4"
}
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
- Tu POS genera un UUID único por operación y lo envía en el header
Idempotency-Key. - Si es la primera vez que se ve esa key → se procesa normalmente y se cachea la respuesta por 24 horas.
- Si el POS reintenta con la misma key → se devuelve la respuesta cacheada con el header
Idempotency-Replay: true, sin timbrar de nuevo. - 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-Type | application/json |
| X-FacturaMX-Signature | sha256={hmac_sha256_del_body} — usa tu secreto para verificar |
| X-FacturaMX-Timestamp | Unix timestamp del envío |
| X-FacturaMX-Event | Nombre del evento (ej: cfdi.timbrado) |
| User-Agent | FacturaMX-Webhooks/1.0 |
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.