Timbra CFDI desde tu aplicación
API de facturación electrónica para México. Facturas, nómina, pagos REP y retenciones. Integra en minutos, paga solo por timbre exitoso. Ideal para revendedores.
Planes y Precios
Elige el plan que se adapte a tu volumen. Sin contratos, cancela cuando quieras.
Dos cargos independientes
Sin suscripción aplica el precio por volumen sobre la cantidad completa. Con plan activo aplica la tarifa del plan a cualquier cantidad. Los folios se compran siempre por separado de la renta. Los folios se compran por separado, siempre por prepago. Ningún plan incluye folios y no existe postpago.
Tarifa por volumen
El total de tu compra define el precio: todos los timbres se cobran a esa tarifa, sin importar el tramo por el que pasaste.
Precios netos + IVA (16%). La mejor tarifa por volumen es $0.80 por timbre.
Con suscripción activa
Sin suscripción aplica el precio por volumen sobre la cantidad completa. Con plan activo aplica la tarifa del plan a cualquier cantidad. Los folios se compran siempre por separado de la renta.
- Starter$0.75/timbrela suscripción se recupera desde ~1,980 timbres/mes
- Pro$0.65/timbrela suscripción se recupera desde ~1,327 timbres/mes
- Enterprise$0.60/timbrela suscripción se recupera desde ~1,995 timbres/mes
Autenticación
Autenticación por OTP (código por email). Recibes un access_token de 30 minutos y un refresh_token de 7 días para renovar sin volver a pedir OTP.
Flujo de autenticación
El OTP solo se usa una vez. Después, tu backend renueva con el refresh_token automáticamente.
curl -X POST https://lite.senhub.mx/api/v1/auth/otp/request \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]"}'
# → {"sent": true, "message": "Código enviado"}curl -X POST https://lite.senhub.mx/api/v1/auth/otp/verify \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "code": "123456"}'
# → {
# "access_token": "eyJhbGciOiJIUzI1NiIs...",
# "refresh_token": "rt_a1b2c3d4e5f6...",
# "expires_in": 1800
# }curl -X POST https://lite.senhub.mx/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token": "rt_a1b2c3d4e5f6..."}'
# → {"access_token": "eyJ...(nuevo)...", "expires_in": 1800}curl https://lite.senhub.mx/api/v1/creditos/balance \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Para tu backend: Guarda el refresh_token en tu base de datos. Programa un cron o middleware que renueve el access_token antes de que expire (cada 25 min). Si el refresh_token expira (7 días sin uso), necesitarás un nuevo OTP.
Certificados (CSD)
Sube los CSD de tus clientes. Sin límite de RFCs por cuenta.
/csdSubir CSD (archivos .cer + .key + password)
/csdListar CSD activos
/csd/{rfc}Eliminar CSD
curl -X POST https://lite.senhub.mx/api/v1/csd \
-H "Authorization: Bearer $TOKEN" \
-F "[email protected]" \
-F "[email protected]" \
-F "password=MiPassword123"Timbrado
Timbra cualquier tipo de CFDI. Cada timbrado consume 1 folio y, si falla, se reembolsa automáticamente. Cancelar no consume folios.
/timbrado/timbrarFactura (I/E)
/timbrado/nominaNómina 1.2 (N)
/timbrado/pagoPago REP 2.0 (P)
/timbrado/retencionRetención 2.0 (R)
/timbrado/cancelarCancelar CFDI
/timbrado/batchTimbrado masivo (hasta 500)
No envíes el campo fecha. Lite la genera al timbrar en la zona fiscal correspondiente al lugar_expedicion. Envía siempre el código postal real del lugar de emisión registrado para el emisor.
curl -X POST https://lite.senhub.mx/api/v1/timbrado/timbrar \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: <uuid-unico-por-factura>" \
-d '{
"emisor_rfc": "TU_RFC_AQUI",
"emisor_nombre": "Tu Empresa SA de CV",
"emisor_regimen": "601",
"lugar_expedicion": "06600",
"tipo_de_comprobante": "I",
"moneda": "MXN",
"tipo_cambio": "1",
"exportacion": "01",
"forma_pago": "03",
"metodo_pago": "PUE",
"receptor": {
"rfc": "EKU9003173C9",
"nombre": "ESCUELA KEMPER URGATE",
"domicilio_fiscal_receptor": "06600",
"regimen_fiscal_receptor": "601",
"uso_cfdi": "G03"
},
"conceptos": [{
"clave_prod_serv": "84111506",
"cantidad": 1,
"clave_unidad": "E48",
"descripcion": "Servicio de consultoría",
"valor_unitario": 1000.00,
"objeto_imp": "02",
"traslados": [{
"impuesto": "002",
"tipo_factor": "Tasa",
"tasa_o_cuota": "0.160000"
}]
}]
}'Idempotencia y campos que suelen causar rechazo
Genera un UUID como Idempotency-Key para cada factura y reutiliza exactamente la misma llave al reintentar esa operación. Así evitas un doble timbrado por timeouts o errores de red.
lugar_expedicion— CP del emisor. Obligatorio; define la zona horaria del comprobante.exportacion—"01"para operaciones nacionales. Obligatorio en CFDI 4.0.- El receptor usa
domicilio_fiscal_receptoryregimen_fiscal_receptor, nodomicilio_fiscalniregimen_fiscal. - En
trasladosno envíesbase: el servidor la calcula desdecantidad × valor_unitario, restando eldescuentodel concepto si lo hay. objeto_imp—"02"si el concepto causa impuestos,"01"si no es objeto de impuesto. Con"01"el concepto va sin nodo de impuestos.tipo_cambio—"1"cuando la moneda es MXN; el tipo de cambio real si es otra divisa.
Factura global (público en general). Si usas el RFC genérico XAXX010101000, el SAT exige además el nodo informacion_global con periodicidad, meses y ano (regla 2.7.1.22 RMF). Sin él el comprobante se rechaza con CFDI40130.
Descuentos por concepto
El descuento se declara por concepto, con el campo opcional descuento dentro de cada elemento de conceptos. Es un importe decimal en la moneda del comprobante, no un porcentaje.
"conceptos": [
{
"clave_prod_serv": "01010101",
"cantidad": 1,
"clave_unidad": "H87",
"descripcion": "Producto con descuento",
"valor_unitario": 3313.80,
"descuento": 926.66,
"objeto_imp": "02",
"traslados": [
{ "impuesto": "002", "tipo_factor": "Tasa", "tasa_o_cuota": "0.000000" }
]
}
]El resto se calcula solo. No hay que enviarlo:
| Atributo del CFDI | Cómo se obtiene |
|---|---|
| Concepto@Importe | cantidad × valor_unitario |
| Traslado@Base | Importe − Descuento (la base se calcula después del descuento) |
| Comprobante@SubTotal | Suma de Importe de los conceptos, antes del descuento |
| Comprobante@Descuento | Suma de los descuento de los conceptos |
| Comprobante@Total | SubTotal − Descuento + traslados − retenciones |
No existe un campo descuento a nivel comprobante. Si el descuento es global, repártelo entre los conceptos: la suma se refleja automáticamente en Comprobante@Descuento.
La misma base para todos los impuestos
traslados y retenciones comparten la misma base del concepto, siempre calculada después del descuento:
Base = (cantidad × valor_unitario) − descuento| Caso | tipo_factor | Resultado |
|---|---|---|
| IVA 16 %, 8 %, 0 % | Tasa | Importe = Base × TasaOCuota |
| IEPS y varios traslados | Tasa | Todos sobre la misma Base |
| Retención de ISR e IVA | Tasa | Sobre la misma Base; suman en TotalImpuestosRetenidos |
| Exento | Exento | Lleva Base, sin Importe, no suma al total |
| No objeto de impuesto | — | objeto_imp "01" y sin nodo de impuestos |
| Cuota fija | Cuota | Requiere enviar importe (ver aviso) |
Las retenciones van en el arreglo retenciones del concepto, con la misma estructura que traslados. Se reflejan solas en Impuestos@TotalImpuestosRetenidos y se restan del total.
"retenciones": [
{ "impuesto": "001", "tipo_factor": "Tasa", "tasa_o_cuota": "0.012500" },
{ "impuesto": "002", "tipo_factor": "Tasa", "tasa_o_cuota": "0.106667" }
]
// 001 = ISR · 002 = IVA · 003 = IEPSCon tipo_factor: "Tasa" no hay que calcular nada. Para los casos que requieren control manual, traslados y retenciones aceptan dos campos opcionales:
importe— fija el importe del impuesto en lugar de calcularlo.base— fija la base en lugar de usarImporte − Descuento.
Cuotas fijas (tipo_factor: "Cuota"). En el IEPS de cuota fija —combustibles, tabaco, bebidas saborizadas— el importe depende de la unidad gravada, no de la base, así que no puede derivarse automáticamente. Si no envías importe, se usa tasa_o_cuota redondeado a 2 decimales, que solo es correcto cuando la cuota aplica una vez. Para cuotas por unidad, envía importe explícitamente.
{
"impuesto": "003",
"tipo_factor": "Cuota",
"tasa_o_cuota": "1.500000",
"importe": 4.50
}Cifras verificadas, para cuadrar tu implementación
Todos los casos parten del mismo concepto: valor_unitario 3313.80 con descuento 926.66, es decir Base 2387.14. Compara tus números con estos.
| Caso | Base | Impuesto | Total |
|---|---|---|---|
| IVA 16% | 2387.14 | 381.94 | 2769.08 |
| IVA 0% | 2387.14 | 0.00 | 2387.14 |
| IEPS 8% + IVA 16% | 2387.14 | 190.97 + 381.94 | 2960.05 |
| IVA 16% + retención ISR 1.25% + retención IVA 10.6667% | 2387.14 | trasladados 381.94 · retenidos 284.47 | 2484.61 |
| Exento | 2387.14 | sin importe | 2387.14 |
| No objeto de impuesto | — | sin importe | 2387.14 |
| Cuota fija | — | — | — |
Contrato 2026-08-01.1 · Un concepto con descuento e IVA 0%. La base del impuesto se calcula después del descuento.
{
"id": "uuid-interno",
"uuid": "B765E38F-1234-5678-ABCD-EF1234567890",
"status": "stamped",
"rfc_emisor": "ABC123456XY7",
"rfc_receptor": "EKU9003173C9",
"serie": "A",
"folio": "1002",
"total": 1160.0,
"credits_charged": 1,
"xml_path": "...",
"error_code": null,
"error_message": null,
"sandbox": false,
"pac_environment": "production"
}Timbrado Masivo (Batch)
Envía hasta 500 comprobantes en un solo request. Procesamiento asíncrono con polling.
curl -X POST https://lite.senhub.mx/api/v1/timbrado/batch \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: lote-junio-001" \
-d '{
"items": [
{"kind": "factura", "emisor_rfc": "...", "emisor_nombre": "...", "emisor_regimen": "601", "payload": {...}},
{"kind": "nomina", "emisor_rfc": "...", "emisor_nombre": "...", "emisor_regimen": "601", "payload": {...}},
{"kind": "pago", "emisor_rfc": "...", "emisor_nombre": "...", "emisor_regimen": "601", "payload": {...}}
]
}'
# → 202 Accepted
# Polling: GET /timbrado/batch/{job_id}Nota: Máximo 500 items por lote. Cada item consume 1 crédito. Si uno falla, su crédito se reembolsa automáticamente.
Nómina 1.2
Complemento de Nómina para recibos de sueldo, aguinaldo, liquidaciones y más.
{
"emisor_rfc": "EKU9003173C9",
"emisor_nombre": "MI EMPRESA SA DE CV",
"emisor_regimen": "601",
"lugar_expedicion": "06600",
"serie": "NOM",
"folio": "001",
"receptor": {
"rfc": "CACX7605101P8",
"nombre": "CARLOS CASTRO XALAPA",
"domicilio_fiscal_receptor": "62000",
"regimen_fiscal_receptor": "605",
"curp": "CACX760510HMSRRL09",
"tipo_contrato": "01",
"tipo_regimen": "02",
"num_empleado": "EMP-001",
"periodicidad_pago": "04",
"clave_ent_fed": "MOR",
"num_seguridad_social": "12345678901",
"fecha_inicio_rel_laboral": "2020-01-15",
"antiguedad": "P280W",
"salario_diario_integrado": 850.00
},
"nomina": {
"tipo_nomina": "O",
"fecha_pago": "2026-06-15",
"fecha_inicial_pago": "2026-06-01",
"fecha_final_pago": "2026-06-15",
"num_dias_pagados": 15,
"registro_patronal": "A1234567890",
"percepciones": [
{"tipo_percepcion": "001", "clave": "P001", "concepto": "Sueldo quincenal", "importe_gravado": 12000.00, "importe_exento": 0.00},
{"tipo_percepcion": "005", "clave": "P005", "concepto": "Prima vacacional", "importe_gravado": 800.00, "importe_exento": 200.00}
],
"deducciones": [
{"tipo_deduccion": "002", "clave": "D002", "concepto": "ISR", "importe": 2100.00},
{"tipo_deduccion": "001", "clave": "D001", "concepto": "Seguridad Social", "importe": 450.00}
],
"otros_pagos": [
{"tipo_otro_pago": "002", "clave": "OP002", "concepto": "Subsidio para el empleo", "importe": 407.02, "subsidio_al_empleo": 407.02}
]
}
}Complemento de Pagos 2.0 (REP)
Recibos Electrónicos de Pago para parcialidades o pagos diferidos.
{
"emisor_rfc": "EKU9003173C9",
"emisor_nombre": "MI EMPRESA SA DE CV",
"emisor_regimen": "601",
"lugar_expedicion": "06600",
"serie": "PAG",
"folio": "001",
"receptor": {
"rfc": "XAXX010101000",
"nombre": "PUBLICO EN GENERAL",
"domicilio_fiscal": "06600",
"regimen_fiscal": "616"
},
"pagos": [{
"fecha_pago": "2026-06-20T10:30:00",
"forma_de_pago_p": "03",
"moneda_p": "MXN",
"num_operacion": "REF-SPEI-123456",
"docto_relacionado": [{
"id_documento": "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
"moneda_dr": "MXN",
"num_parcialidad": 1,
"imp_saldo_ant": 11600.00,
"imp_pagado": 11600.00,
"objeto_imp_dr": "02",
"impuestos_dr": {
"traslados_dr": [{
"base_dr": 10000.00,
"impuesto_dr": "002",
"tipo_factor_dr": "Tasa",
"tasa_o_cuota_dr": "0.160000",
"importe_dr": 1600.00
}]
}
}]
}]
}Validaciones importantes
- •
forma_de_pago_p≠ "99" (prohibido "Por definir" en REP) - •
moneda_p≠ "XXX" (solo el comprobante padre usa XXX) - •
tipo_cambio_prequerido simoneda_p≠ "MXN" - •
imp_pagado≤imp_saldo_ant - •
id_documentodebe ser UUID válido del CFDI original
Retenciones 2.0
Constancias de retención de ISR, IVA, dividendos y más.
{
"emisor_rfc": "EKU9003173C9",
"emisor_nombre": "MI EMPRESA SA DE CV",
"emisor_regimen": "601",
"lugar_expedicion": "06600",
"cve_retenc": "01",
"folio_int": "RET-2026-001",
"receptor": {
"nacionalidad_r": "Nacional",
"nacional": {
"rfc_r": "CACX7605101P8",
"nom_den_raz_soc_r": "CARLOS CASTRO XALAPA",
"domicilio_fiscal_r": "62000",
"curp_r": "CACX760510HMSRRL09"
}
},
"periodo": {
"mes_ini": "01",
"mes_fin": "06",
"ejercicio": "2026"
},
"totales": {
"monto_tot_operacion": 100000.00,
"monto_tot_grav": 100000.00,
"monto_tot_exent": 0.00,
"monto_tot_ret": 10000.00,
"imp_retenidos": [{
"base_ret": 100000.00,
"impuesto_ret": "01",
"monto_ret": 10000.00,
"tipo_pago_ret": "03"
}]
}
}Cancelación de CFDI
Cancela un CFDI timbrado. Sin costo: cobramos por emitir, no por el ciclo de vida de un comprobante ya pagado. El motivo de cancelación es el catálogo oficial del SAT.
curl -X POST https://lite.senhub.mx/api/v1/timbrado/cancelar \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"emisor_rfc": "EKU9003173C9",
"uuid": "B765E38F-1234-5678-ABCD-EF1234567890",
"motivo": "02",
"folio_sustitucion": null
}'| Campo | Descripción |
|---|---|
emisor_rfc | RFC del emisor que timbró el comprobante. |
uuid | UUID fiscal del CFDI a cancelar. |
motivo | "01" Comprobante con errores con relación · "02" Errores sin relación · "03" No se llevó a cabo la operación · "04" Operación nominativa en factura global. |
folio_sustitucion | UUID del CFDI sustituto. Obligatorio cuando el motivo es "01"; en los demás motivos envía null u omítelo. |
Consulta y Descarga
Accede al historial de timbres y descarga XML/PDF en cualquier momento.
/timbrado/timbresListar timbres (paginado)
/timbrado/cfdi/{uuid}Detalle de un CFDI
/timbrado/cfdi/{uuid}/xmlDescargar XML timbrado
/timbrado/cfdi/{uuid}/pdfDescargar PDF generado
/timbrado/cfdi/{uuid}/htmlVista HTML del CFDI
Créditos
Cada operación de timbrado consume 1 crédito. Consulta tu saldo programáticamente para implementar alertas de bajo saldo en tu sistema.
/creditos/balanceSaldo actual y plan activo
GET /api/v1/creditos/balance
Authorization: Bearer $TOKEN
{
"balance": 847,
"plan": "pro"
}Gestión de créditos
La compra de créditos, historial de pagos y configuración de auto-recarga se gestionan desde tu Developer Dashboard.
- Compra paquetes o cantidades custom
- Los créditos no expiran
- Si un timbre falla, el crédito se reembolsa automáticamente
- Si tu saldo llega a 0, la API responde
402 Saldo insuficiente
Recomendación para tu backend
// Verificar saldo antes de timbrar (opcional pero recomendado)
const { balance } = await fetch("/api/v1/creditos/balance", { headers }).then(r => r.json());
if (balance < 10) {
// Alerta interna: "Saldo bajo, recargar desde dashboard"
notifyAdmin("SenHub credits low: " + balance);
}Webhooks (Plan Pro+)
Próximamente. Los webhooks estarán disponibles para el plan Pro+. Recibirás notificaciones en tu servidor cuando ocurran eventos como timbre.created, timbre.cancelled y batch.completed, con verificación HMAC-SHA256.
Mientras tanto, puedes consultar el estado de tus timbres con GET /timbrado/cfdi/{uuid} o listar con GET /timbrado/timbres.
Catálogos SAT
60+ catálogos SAT actualizados. Úsalos para autocompletados y validaciones en tu frontend.
/catalogos/{nombre}Consultar catálogo por nombre
forma-pagometodo-pagomonedaregimen-fiscaluso-cfdiproducto-serviciounidadtipo-comprobantetipo-relacionimpuestoestado+50 máscurl https://lite.senhub.mx/api/v1/catalogos/regimen-fiscal \
-H "Authorization: Bearer $TOKEN"Códigos de Error
Respuestas JSON consistentes con detalle accionable. Esta tabla se genera desde el contrato de integración que sirve la propia API, versión 2026-08-01.1.
Toda respuesta incluye la cabecera X-Request-Id. citar este id al reportar un problema a soporte. Muéstrala en tus propios mensajes de error: es la referencia con la que podemos rastrear una operación concreta.
| HTTP | code | ¿Se timbró? | Folios | ¿Reintentar? |
|---|---|---|---|---|
| 400 | PAC_ERROR | No | 0 | Sí, con la misma llave |
| 400 | BUILD_OR_SIGN | No | 0 | Sí, con la misma llave |
| 402 | INSUFFICIENT_CREDITS | No | 0 | No |
| 401 | UNAUTHORIZED | No | 0 | No |
| 403 | SUBSCRIPTION_REQUIRED | No | 0 | No |
| 422 | VALIDATION_ERROR | No | 0 | Sí, con la misma llave |
| 429 | RATE_LIMITED | No | 0 | Sí, con la misma llave |
| 500 | INTERNAL_ERROR | Indeterminado | Indeterminado | Sí, con la misma llave |
PAC_ERROREl PAC rechazó el comprobanteEl comprobante no se timbró. El PAC devolvió una incidencia, por ejemplo CFDI40144 cuando el nombre fiscal no coincide con la constancia del SAT.
Qué hacer: Corregir los datos y reintentar. El cargo de folios se reembolsa automáticamente.
BUILD_OR_SIGNError al construir o sellar el CFDIEl payload no pudo convertirse en un CFDI válido, o el sellado con el CSD falló. No se llegó al PAC.
Qué hacer: Revisar catálogos, importes y vigencia del CSD.
INSUFFICIENT_CREDITSSin folios disponiblesNo hay saldo para timbrar. Nunca ocurre en sandbox.
Qué hacer: Comprar folios y reintentar.
UNAUTHORIZEDCredencial ausente o inválidaEl token o la API Key no son válidos.
Qué hacer: Reautenticar.
SUBSCRIPTION_REQUIREDSuscripción inactivaLa API Key requiere una suscripción activa para uso servidor a servidor.
Qué hacer: Activar o renovar la suscripción.
VALIDATION_ERRORPayload inválidoFalta un campo obligatorio o un tipo es incorrecto. Lo valida el esquema antes de construir el CFDI.
Qué hacer: Corregir el payload según el detalle por campo.
RATE_LIMITEDLímite de peticiones excedidoSe superó el límite por minuto.
Qué hacer: Esperar los segundos de la cabecera Retry-After y reintentar con la misma Idempotency-Key.
INTERNAL_ERRORError internoFalla inesperada de nuestro lado. IMPORTANTE: el comprobante PUEDE haberse timbrado. No asumas que no existe.
Qué hacer: Reintentar con la MISMA Idempotency-Key. Si el CFDI ya existía recibirás 200 con el comprobante original y sin cobro nuevo. Nunca reintentes con una llave nueva: eso sí duplicaría el comprobante. Reporta el request_id a soporte.
Idempotencia: la regla de oro
Una factura, una llave. Ante cualquier error reintenta con la MISMA llave; nunca generes una nueva para el mismo comprobante.
Cabecera Idempotency-Key · UUID v4, uno por comprobante lógico · alcance por tenant.
| Situación del primer intento | Respuesta al reintentar con la misma llave |
|---|---|
| Ya se timbró | 200 · El comprobante original, con el mismo uuid, sin cobrar de nuevo |
| Había fallado | 200 · Se intenta timbrar de nuevo |
No devuelve 409. Verificado: 2026-07-31, tres intentos con la misma llave devolvieron el mismo uuid.
El 500 es el único caso ambiguo
Ante un 500 el comprobante puede haberse timbrado. No asumas que no existe: reintenta con la misma Idempotency-Key y recibirás el original si ya se emitió. Generar una llave nueva sí duplicaría el comprobante.
{
"detail": "Internal Server Error",
"code": "INTERNAL_ERROR",
"request_id": "9f2c1a7b4e8d4c1fa0b3c5d6e7f80912",
"retriable": true,
"retry_with_same_idempotency_key": true
}No existe un 502 en esta API. Cuando el PAC rechaza o no responde, la respuesta es 400 con code: "PAC_ERROR".
{
"detail": {
"code": "PAC_ERROR",
"message": "El PAC rechazó el timbrado: CFDI40144 - El nombre del emisor no coincide"
}
}{
"detail": [
{"loc": ["body", "receptor", "rfc"], "msg": "String should have at least 12 characters", "type": "string_too_short"},
{"loc": ["body", "conceptos", 0, "valor_unitario"], "msg": "Input should be greater than 0", "type": "greater_than"}
]
}{
"detail": {
"code": "INSUFFICIENT_CREDITS",
"message": "Saldo de créditos insuficiente"
}
}Seguridad y Límites
| Parámetro | Valor |
|---|---|
| Límite general | 120 peticiones/min |
| Límite de timbrado | 30 peticiones/min |
| Límite de OTP | 5 peticiones/min |
| Conexiones simultáneas | 50 por IP |
| Token de acceso (JWT) | 30 minutos |
| Refresh token | 7 días (renueva el JWT sin OTP) |
| Cifrado | HTTPS obligatorio (TLS 1.2+) |
| CSD por cuenta | Sin límite |
Sandbox
CFDI válido pero no reportado al SAT. 0 créditos (gratis ilimitado). Ideal para pruebas de integración.
Producción
Timbre fiscal real ante el SAT. 1 crédito por operación exitosa. 5 créditos gratis al registrarte.
Cómo activar el sandbox
Agrega "sandbox": true al payload de cualquier endpoint de timbrado. El servidor decide el ambiente final: una llave sk_test fuerza sandbox siempre, y un CSD de prueba del SAT (p. ej. EKU9003173C9) lo activa automáticamente. Enviar "sandbox": false nunca convierte una prueba en un timbrado real.
Para verificar el ambiente antes de timbrar: GET /timbrado/entorno?emisor_rfc=. El parámetro es opcional, pero envíalo siempre: sin él la respuesta no puede decirte qué ocurrirá con un emisor concreto.
{
"environment": "production",
"effective_environment": "sandbox",
"emisor_rfc": "EKU9003173C9",
"emisor_is_test_csd": true,
"sandbox_forced": true,
"sandbox_forced_reason": "sat_test_csd",
"sandbox_on_request": true,
"credits_per_stamp": 0
}environment— ambiente por defecto de la credencial. No describe un comprobante concreto.effective_environment— este es el campo a mostrar y registrar. Ambiente que se aplicará realmente al timbrar con eseemisor_rfc, sin enviar el flagsandbox.emisor_is_test_csd—truesi ese RFC es un CSD de prueba del SAT.sandbox_forced—truecuando no es posible timbrar en real con esa combinación de credencial y emisor.sandbox_forced_reason—api_key_test,sat_test_csdonull.sandbox_on_request— el sandbox también puede pedirse por comprobante con"sandbox": true.sandbox_mode— cómo se simula el timbrado, p. ej.local_simulation.credits_per_stamp— folios que costará ese timbrado.0confirma sandbox.
Importante: leer solo environment lleva a la conclusión equivocada de que la cuenta está en producción cuando el emisor consultado es un CSD de prueba. Que diga production no significa que el próximo timbrado será real.
Ya en la respuesta del timbrado, toda operación sandbox llega con "sandbox": true, "pac_environment": "sandbox" y "credits_charged": 0. Ojo con la diferencia: credits_per_stamp es la previsión antes de timbrar y credits_charged es lo que se cobró de hecho.
También puedes probarlo sin escribir código: la herramienta web tiene modo pruebas en senhub.mx/herramientas/timbrado?sandbox=1. Activa el checkbox "Modo pruebas" antes de timbrar.
Quickstart
Del registro a tu primer timbre en 5 minutos.
Registrarse (5 créditos gratis)
curl -X POST https://lite.senhub.mx/api/v1/auth/otp/request \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'Verificar OTP
curl -X POST https://lite.senhub.mx/api/v1/auth/otp/verify \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","code":"XXXXXX"}'
# → {"access_token":"eyJ...", "refresh_token":"xxx"}Verificar saldo
curl https://lite.senhub.mx/api/v1/creditos/balance \
-H "Authorization: Bearer eyJ..."
# → {"balance": 5, "plan": "free"}Subir CSD
curl -X POST https://lite.senhub.mx/api/v1/csd \
-H "Authorization: Bearer eyJ..." \
-F "[email protected]" -F "[email protected]" -F "password=xxx"Timbrar tu primer CFDI
curl -X POST https://lite.senhub.mx/api/v1/timbrado/timbrar \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{ ... payload CFDI ... }'
# → {"uuid":"B765E38F-...", "xml_url":"...", "pdf_url":"..."}Preguntas Frecuentes
¿Cuántos RFCs puedo timbrar con una cuenta?
Ilimitados. Sube N certificados CSD y timbra para cualquiera.
¿Cómo pruebo la integración?
Al crear tu cuenta recibes 5 timbres gratis para validar el flujo completo contra el SAT. Si un timbre falla, el crédito se reembolsa automáticamente.
¿Cómo obtengo API Keys?
Desde el Dashboard, con una suscripción activa Starter o superior. La llave se muestra una sola vez y es exclusivamente server-to-server: nunca la uses desde el navegador.
¿Qué pasa si un timbre falla?
El crédito se reembolsa automáticamente. Solo pagas por timbres exitosos.
¿Puedo cancelar CFDIs?
Sí. POST /timbrado/cancelar con el UUID. Cancelar no consume folios: cobramos por emitir, no por el ciclo de vida de un comprobante ya pagado.
¿Soportan complementos?
Sí: Nómina 1.2, Pagos 2.0, Retenciones 2.0, IEDU 1.0. Todos con generación de PDF profesional.
¿El PDF tiene mi marca?
En plan Pro+ puedes personalizar branding. En Free/Starter usa template estándar.
¿Hay postpago?
No. Todos los planes son prepago: compras folios y se descuentan al timbrar. La suscripción cubre el acceso y las capacidades del plan, nunca los folios.
¿Idempotencia?
Sí. Genera un UUID único por factura en el header Idempotency-Key y reutiliza la misma llave al reintentar esa operación. Así evitas doble timbrado por retries o timeouts.
¿Puedo revender el servicio?
¡Sí! SenHub es infraestructura white-label. Tú pones el precio a tus clientes.
¿Listo para integrar?
Crea tu cuenta, recibe 5 timbres gratis y emite tu primer CFDI en menos de 5 minutos. Sin compromisos.
[email protected] · senhub.mx/developers