Referencia de la API
La API de CFDI Express timbra CFDI 4.0 ante el SAT desde cualquier sistema que hable HTTPS: facturas PUE, PPD y globales, complementos de pago REP, notas de crédito y cancelaciones con acuse. Es independiente de Shopify y sigue convenciones estilo Stripe, así que si ya integraste Stripe alguna vez, esto te va a resultar familiar.
Empezar en 4 pasos
- Crea tu cuenta en dash.cfdi.express y copia tu llave
sk_test_. Viene con saldo de pruebas. - Registra tu emisor con POST /v1/merchants y sube su CSD con POST /v1/merchants/{id}/csd.
- Timbra tu primer CFDI con POST /v1/invoices.
- Entrega el XML y el PDF con las URLs firmadas que devuelve GET /v1/invoices/{id}.
curl https://api.cfdi.express/v1/balance \
-H "Authorization: Bearer sk_test_..."URL base
Todos los endpoints viven bajo https://api.cfdi.express y los de la versión 1 llevan el prefijo /v1. Sólo se aceptan conexiones HTTPS.
Autenticación
Cada request lleva tu llave secreta como bearer token:
Authorization: Bearer sk_live_...La llave define el modo: sk_test_ timbra contra el sandbox del PAC sin costo y sk_live_ emite CFDIs reales con validez fiscal. Cada modo tiene su propio saldo, sus propios emisores y sus propios documentos; ningún recurso cruza de un modo al otro. Todos los recursos traen el campo livemode para que sepas en cuál estás.
- Las llaves son secretas: úsalas sólo desde tu servidor, nunca desde el navegador o una app móvil.
- El sandbox es gratis e ilimitado. Prueba ahí todo el flujo antes de recargar saldo.
Idempotencia
Los endpoints que timbran o cancelan requieren el header Idempotency-Key. Manda un valor estable por operación lógica —el id de tu pedido, de tu devolución o de tu transacción— y los reintentos son seguros:
| Situación | Respuesta |
|---|---|
| Misma llave, mismo cuerpo, petición ya resuelta | La respuesta original con el header Idempotency-Replayed: true. No gasta otro timbre. |
| Misma llave, otra petición todavía en proceso | 409 request_in_flight |
| Misma llave, cuerpo distinto | 422 idempotency_key_reuse |
Paginación
Las listas usan paginación por cursor: pide hasta limit=100 elementos y, mientras has_more sea true, manda el id del último elemento en starting_after.
curl -G https://api.cfdi.express/v1/invoices \
-H "Authorization: Bearer sk_live_..." \
--data-urlencode "limit=100" \
--data-urlencode "starting_after=inv7k3q9x2m4v1t8p6d0n5cb"Errores
Los errores siguen application/problem+json (RFC 7807) con un code estable pensado para que tu integración —o tu agente de IA— reaccione sin parsear textos:
{
"type": "https://api.cfdi.express/docs/errors/validation_error",
"title": "Validation error",
"status": 400,
"code": "validation_error",
"detail": "Request validation failed",
"errors": [
{ "path": "receiver.zip", "message": "String must match pattern ^\\d{5}$" }
]
}| Status | code | Cuándo aparece |
|---|---|---|
| 400 | validation_error | Algún campo no pasó validación. Incluye errors[] con el path de cada problema, o subcode al validar un CSD. |
| 401 | unauthorized | Falta el header Authorization o la llave no es válida. |
| 403 | forbidden | La llave no tiene acceso a ese recurso. |
| 404 | not_found | El recurso no existe en tu cuenta y modo. |
| 402 | insufficient_credits | No hay saldo suficiente para el timbre. Recarga y reintenta. |
| 402 | monthly_limit_reached | Alcanzaste el límite mensual configurado en tu cuenta. |
| 409 | request_in_flight | Otra petición con el mismo Idempotency-Key sigue en proceso. |
| 422 | idempotency_key_reuse | Reusaste una llave de idempotencia con un cuerpo distinto. |
| 422 | sat_rejected | El SAT rechazó el comprobante. El detalle trae el código del SAT. |
| 429 | rate_limited | Excediste el límite de requests por minuto. |
| 503 | pac_unavailable | El PAC está temporalmente fuera de servicio. Reintenta con la misma llave. |
| 500 | internal_error | Error inesperado de nuestro lado. |
Límites
- 300 requests por minuto por cuenta (configurable). Al excederlo recibes
429conRetry-After; las respuestas traenX-RateLimit-LimityX-RateLimit-Remaining. - El cuerpo de la petición no puede exceder 1 MB.
- Hasta 500 conceptos por factura.
- Las URLs de XML, PDF, ZIP, acuses y logos son firmadas y expiran a los 15 minutos: vuelve a pedir el recurso cuando necesites unas nuevas.
Precio
$1 MXN por timbre con descuentos por volumen, saldo prepagado, sin mensualidad. Cada timbre reserva saldo y lo confirma al recibir el UUID; si el SAT rechaza o el PAC falla, el reembolso es automático y queda registrado en el ledger de transacciones. Las cancelaciones también consumen un timbre. El modo de pruebas es gratis.
Para agentes de IA
Además de REST, la API expone un servidor MCP en https://api.cfdi.express/mcp (y https://api.cfdi.express/mcp/test para pruebas) que le da a claude.ai, ChatGPT, Claude Code, Cursor o a tu propio agente 14 herramientas para timbrar, cancelar y consultar. Ver la guía del servidor MCP.
Todos los endpoints
Meta
Endpoints públicos de salud del servicio. No requieren llave y sirven para monitoreo.
Saldo y facturación
Consulta tu saldo prepagado, recarga con tarjeta vía Stripe y audita cada centavo con el ledger de transacciones.
Emisores (merchants)
Cada emisor es un RFC con su propio CSD, serie y logo. Una cuenta puede tener varios emisores activos al mismo tiempo.
- POST/v1/merchantsCrear emisor
- GET/v1/merchantsListar emisores
- GET/v1/merchants/{id}Obtener emisor
- PATCH/v1/merchants/{id}Actualizar emisor
- DELETE/v1/merchants/{id}Eliminar emisor
- POST/v1/merchants/{id}/csdSubir CSD
- PUT/v1/merchants/{id}/csdReemplazar CSD
- GET/v1/merchants/{id}/csdConsultar CSD
- PUT/v1/merchants/{id}/logoSubir logo
- DELETE/v1/merchants/{id}/logoEliminar logo
Facturas
Timbrado de CFDI 4.0 de ingreso: nominativas PUE y PPD, y facturas globales al público en general.
Notas de crédito
CFDI de egreso para devoluciones, reembolsos y descuentos posteriores a la factura.
Complementos de pago (REP)
Pagos 2.0 contra facturas PPD, con parcialidades y saldos calculados en el servidor.
Catálogos SAT
Claves de producto/servicio, unidades, usos de CFDI, regímenes fiscales y formas de pago servidos desde nuestra base local.
