Timbrado por Lotes — REST
Timbrado por Lotes es un servicio BATCH que realiza el timbrado masivo de comprobantes previamente sellados CFDI 4.0 en formato XML, comprimidos dentro de un archivo .zip.
El procesamiento es asíncrono, aceptar el lote responde en milisegundos y devuelve un batchId, el timbrado corre después.
En ambiente de pruebas se pueden usar certificados reales, pero recomendamos hacer uso de los CSD de pruebas.
✅ Antes de empezar
409 SETTING_MISSING.
🌐 Ambientes
Marca tu ambiente activo — los botones de copiar ruta de cada endpoint arman la URL completa con la base que elijas aquí.
https://batchstamp-smarter-rest.test.swsapien.com/v2/batchstamp
📄
https://batchstamp-smarter-rest.swsapien.com/v2/batchstamp
📄
🔄 Flujo timbrado por Lotes
-
POST /batches→ 201Crea el lote. DevuelvebatchId+uploadUrlpresignado. -
PUT <uploadUrl>(cuerpo = ZIP) → 200Sube el ZIP en binario directo a S3 con la URL presignada. -
POST /batches/{batchId}/finalize→ 200Valida el ZIP, pasa el lote a Recibido y dispara el procesamiento. -
GET /batches/{batchId}→ pollConsulta el estatus del lote hasta llegar a Completado. -
GET /batches/{batchId}/downloads→ 200URLs de reportes, XML timbrados y PDFs (si aplica).Si tienes configurado un webhoook, recibirás esta informacición en tu endpoint.
1️⃣ Crear lote
/batches
📄
| Header | Valor |
|---|---|
Authorization | Bearer Token |
Content-Type | application/json |
Ejemplo request
curl --location --request POST 'https://batchstamp-smarter-rest.test.swsapien.com/v2/batchstamp/batches' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer Token' \
--data '{
"reportConfig": {
"variant": "both",
"format": "XLSX"
},
"pdf": {
"generate": true,
"templateId": "cfdi40"
}
}'
Respuesta
{
"batchId": "86d096d2-c5f8-4e42-90b4-fa617bdcd2b1",
"uploadUrl": "https://smarter-batch-stamp-test.s3.us-east-1.amazonaws.com/batches/2fb21d29-1716-4d6b-b01c-3d6c242b8eaf/86d096d2-c5f8-4e42-90b4-fa617bdcd2b1/input.zip?...hm=CRC32&x-amz-tagging=lifecycle%3Dpending-upload&x-id=PutObject",
"uploadMethod": "PUT",
"uploadHeaders": {
"Content-Type": "application/zip"
},
"expiresAt": "2026-07-14T16:15:53.358Z"
}
{
"success": false,
"error": {
"issues": [
{
"received": "XLSXX",
"code": "invalid_enum_value",
"options": [
"CSV",
"XLSX"
],
"path": [
"reportConfig",
"format"
],
"message": "Invalid enum value. Expected 'CSV' | 'XLSX', received 'XLSXX'"
}
],
"name": "ZodError"
}
}
| Atributo | Descripción |
|---|---|
batchId | Identificador del lote. Se usa en los pasos 3, 4 y 5. |
uploadUrl | URL presignada (S3, PUT). Vigente 15 minutos. |
uploadMethod | Método HTTP a utilizar al subir el ZIP. |
uploadHeaders | Headers que deben utilizarse al subir el ZIP (van firmados en la URL). |
expiresAt | Momento en que expira uploadUrl (ISO 8601). |
⚙️ Opciones avanzadas del cuerpo (opcionales)
Ambas van dentro del mismo POST /batches.
📄 reportConfig
| Propiedad | Tipo | Valores | Default |
|---|---|---|---|
variant | string | errors · complete · both | both |
format | string | CSV · XLSX | XLSX |
| Propiedad | Tipo | Descripción |
|---|---|---|
generate | bool | Default false. Genera PDF de cada documento del lote. |
templateId | string | Opcional. Si se omite, el sistema detecta la plantilla por tipo de documento (nómina, pagos, carta porte, etc.). Si se envía, esa plantilla se aplica a todos los documentos del lote. |
templateId salvo que todos los documentos del lote sean del mismo tipo.2️⃣ Subir el ZIP
<uploadUrl>
uploadHeaders devueltos al crear el lote.uploadUrl expira a los 15 minutos de creado el lote. Si expira, hay que crear el lote de nuevo (paso 1).Ejemplo request
curl --location --request PUT '' \ --header 'Content-Type: application/zip' \ --data-binary '@batch.zip'
Respuesta: 200 (directo de S3, sin cuerpo relevante).
Respuesta
Estatus 200 (directo de S3, sin cuerpo relevante).
3️⃣ Finalizar lote
/batches/{batchId}/finalize
📄
| Header | Valor |
|---|---|
Authorization | Bearer Token |
Valida el ZIP (solo .xml, planos en la raíz, sin subcarpetas), pasa el lote a Recibido y dispara el procesamiento.
Ejemplo request
curl --location --request POST 'https://batchstamp-smarter-rest.test.swsapien.com/v2/batchstamp/batches/86d096d2-c5f8-4e42-90b4-fa617bdcd2b1/finalize' \ --header 'Authorization: Bearer Token'
Respuesta
{
"batchId": "86d096d2-c5f8-4e42-90b4-fa617bdcd2b1",
"status": "Recibido",
"totalDocuments": 10,
"metrics": {
"processed": 0,
"failed": 0,
"successful": 0,
"warnings": 0
},
"reportConfig": {
"variant": "both",
"format": "XLSX"
},
"createdAt": "2026-07-14T16:16:17.613Z",
"updatedAt": "2026-07-14T16:20:36.932Z",
"pdf": {
"generate": true,
"templateId": "cfdi40"
}
}
{
"success": false,
"error": {
"issues": [
{
"validation": "uuid",
"code": "invalid_string",
"message": "Invalid uuid",
"path": [
"batchId"
]
}
],
"name": "ZodError"
}
}
| Atributo | Descripción |
|---|---|
batchId | Identificador único del lote. |
status | Estado del lote: Recibido. |
totalDocuments | Cantidad de XML encontrados en el ZIP. |
metrics | Objeto con métricas del lote. |
reportConfig | Objeto con la configuración de reportes del lote. |
createdAt | Fecha de creación del lote (ISO 8601). |
updatedAt | Fecha de última actualización del lote (ISO 8601). |
pdf | Objeto con la configuración de generación de PDFs del lote (si aplica). |
4️⃣ Consultar estado
/batches/{batchId}
📄
| Header | Valor |
|---|---|
Authorization | Bearer Token |
Devuelve el estado, las métricas del lote y, cuando llega a Completado, información sobre el envío de notificaciones (correo y/o webhook).
Estados posibles
| Estado | Significado |
|---|---|
| Recibido | Lote finalizado, en cola para procesarse. |
| EnProceso | Timbrando documentos. Puede incluir el arranque en frío (ver nota al inicio del artículo). |
| Completado | El lote siempre termina aquí, incluso con documentos fallidos. Ver . |
| FalloCritico | Falla de plataforma, no de los datos del cliente. Debe escalarse a soporte. |
Ejemplo request
curl --location --request GET 'https://batchstamp-smarter-rest.test.swsapien.com/v2/batchstamp/batches/86d096d2-c5f8-4e42-90b4-fa617bdcd2b1' \ --header 'Authorization: Bearer Token'
Respuesta
{
"batchId": "86d096d2-c5f8-4e42-90b4-fa617bdcd2b1",
"status": "Completado",
"totalDocuments": 10,
"metrics": {
"failed": 10,
"processed": 10,
"successful": 0,
"warnings": 0
},
"reportConfig": {
"variant": "both",
"format": "XLSX"
},
"createdAt": "2026-07-14T16:16:17.613Z",
"updatedAt": "2026-07-14T16:21:11.385Z",
"pdf": {
"generate": true,
"templateId": "cfdi40"
},
"notifications": {
"email": {
"lastAttemptedAt": "2026-07-14T16:21:10.132Z",
"state": "Enviado"
},
"webhook": {
"attemptCount": 1,
"lastAttemptedAt": "2026-07-14T16:21:11.385Z",
"state": "Pendiente"
}
}
}
Cuando status es Completado, la respuesta también incluye información del envío de notificaciones (correo y, si la cuenta tiene webhook, del intento de entrega).
{
"success": false,
"error": {
"issues": [
{
"validation": "uuid",
"code": "invalid_string",
"message": "Invalid uuid",
"path": [
"batchId"
]
}
],
"name": "ZodError"
}
}
| Atributo | Descripción |
|---|---|
batchId | Identificador único del lote. |
status | Estado del lote: Completado. |
totalDocuments | Cantidad de XML procesados en el ZIP. |
metrics | Objeto con métricas del lote. |
reportConfig | Objeto con la configuración de reportes del lote. |
createdAt | Fecha de creación del lote (ISO 8601). |
updatedAt | Fecha de última actualización del lote (ISO 8601). |
pdf | Objeto con la configuración de generación de PDFs del lote (si aplica). |
notifications | Objeto con información de los intentos de envío de notificaciones (correo y/o webhook). |
5️⃣ Descargar resultados
/batches/{batchId}/downloads
📄
| Header | Valor |
|---|---|
Authorization | Bearer Token |
Ejemplo request
curl --location --request GET 'https://batchstamp-smarter-rest.test.swsapien.com/v2/batchstamp/batches/86d096d2-c5f8-4e42-90b4-fa617bdcd2b1/downloads' \ --header 'Authorization: Bearer Token'
Respuesta
{
"batchId": "86d096d2-c5f8-4e42-90b4-fa617bdcd2b1",
"status": "Completado",
"artifacts": {
"reports": [
{
"kind": "errors",
"format": "XLSX",
"url": "https://smarter-batch-stamp-test.s3.us-east-1.amazonaws.com/batches/e8750a58-90cc-4f47-a75f-b6dc01cbadae/86d096d2-c5f8-4e42-90b4-fa617bdcd2b1/artifacts/report-errors.xlsx?X...amz-checksum-mode=ENABLED&x-id=GetObject",
"expiresAt": "2026-07-21T16:27:42.099Z"
},
{
"kind": "complete",
"format": "XLSX",
"url": "https://smarter-batch-stamp-test.s3.us-east-1.amazonaws.com/batches/e8750a58-90cc-4f47-a75f-b6dc01cbadae/86d096d2-c5f8-4e42-90b4-fa617bdcd2b1/artifacts/report-complete.xlsx?X...amz-checksum-mode=ENABLED&x-id=GetObject",
"expiresAt": "2026-07-21T16:27:42.100Z"
}
],
"stampedXmls": {
"url": "https://smarter-batch-stamp-test.s3.us-east-1.amazonaws.com/batches/e8750a58-90cc-4f47-a75f-b6dc01cbadae/86d096d2-c5f8-4e42-90b4-fa617bdcd2b1/artifacts/stamped-xmls.zip?X...amz-checksum-mode=ENABLED&x-id=GetObject",
"expiresAt": "2026-07-21T16:27:42.101Z"
},
"pdfs": {
"url": "https://smarter-batch-stamp-test.s3.us-east-1.amazonaws.com/batches/e8750a58-90cc-4f47-a75f-b6dc01cbadae/86d096d2-c5f8-4e42-90b4-fa617bdcd2b1/artifacts/pdfs.zip?X...amz-checksum-mode=ENABLED&x-id=GetObject",
"expiresAt": "2026-07-21T16:27:42.111Z"
}
}
}
{
"success": false,
"error": {
"issues": [
{
"validation": "uuid",
"code": "invalid_string",
"message": "Invalid uuid",
"path": [
"batchId"
]
}
],
"name": "ZodError"
}
}
| Atributo | Descripción |
|---|---|
batchId | Identificador único del lote. |
status | Estado del lote: Completado. |
artifacts | Objeto con URLs de reportes, reportes, XML timbrados y PDFs (si aplica). |
🧭 Catálogo de errores
Errores de API (HTTP)
| HTTP | code | Significado |
|---|---|---|
| 401 | UNAUTHENTICATED | Falta el header Authorization: Bearer. |
| 401 | INVALID_TOKEN | SW rechazó el token de la petición. |
| 402 | NO_BALANCE | La cuenta no tiene saldo de timbres disponible. |
| 404 | BATCH_NOT_FOUND | El lote no existe o es de otra cuenta. |
| 409 | SETTING_MISSING | Se intentó crear un lote sin que la cuenta tenga alta previa. |
| 409 | BATCH_NOT_READY | Se pidieron las descargas antes de llegar a Completado. |
| 400 | INVALID_BATCH | ZIP inválido al finalizar (subcarpetas o archivos no .xml). El lote se borra. |
| 422 | UPLOAD_NOT_COMPLETED | finalize llamado sin haber subido el ZIP. |
| 422 | INVALID_SW_TOKEN | El token guardado en la configuración de la cuenta ya no valida. |
| 503 | POST_PROCESS_FAILED | El lote quedó en FalloCritico. |
Errores por documento (columna MensajeError del reporte)
| Código | Tipo | Significado |
|---|---|---|
ACCOUNT_TOKEN_EXPIRED | Error | El token venció a medio lote. |
PARSE_ERROR | Error | El XML no se pudo interpretar. |
CFDIXXXXX y otros | Error | Código devuelto directamente por el PAC. |
UNKNOWN | Error | Sin clasificar. |
TIMBRE_PREVIO | Advertencia | El CFDI ya estaba timbrado. Cuenta como exitoso con advertencia, no como error — el sistema devuelve el timbre anterior y no descuenta doble ese documento. |
PDF — columna EstatusPdf = Fallido:
| Código | Significado |
|---|---|
PDF_TIMEOUT | La generación del PDF tardó más de lo permitido. |
PDF_METADATA_MISSING | Faltan datos necesarios para generar el PDF. |
PDF_ENQUEUE_FAILED | No se pudo encolar la generación del PDF. |
PDF_XML_UNAVAILABLE | El XML timbrado no estaba disponible al generar el PDF. |
PDF_COLLECT_ERROR | Falló la recolección del PDF ya generado. |
📏 Reglas y límites
El lote siempre termina en Completado
Un documento fallido no tumba el lote. Llega a Completado con sus métricas (processed = successful + failed) y el detalle por documento en los reportes. FalloCritico se reserva para fallas de plataforma, no de los datos del cliente.
GET /batches/{batchId}; si ya está Completado, los archivos están disponibles y no hace falta reenviar. Un CFDI ya timbrado que se reenvía sale como TIMBRE_PREVIO (advertencia, no se descuenta doble) — el riesgo real está en los documentos que aún no se habían timbrado.
Límites
| Límite | Valor |
|---|---|
| Documentos por lote (certificado) | 10,000 |
| Tamaño del ZIP | 500 MB |
| Vigencia de las URLs de descarga | 7 días |
Vigencia de uploadUrl | 15 minutos |
| Vida del token | 2 h (expira) |
| Arranque en frío | ~1–3 min tras inactividad |
Tiempos de referencia: 1,000 documentos ≈ 6–8 min · 10,000 documentos ≈ 60–70 min.
Otras limitaciones
| Restricción | Detalle |
|---|---|
| Re-solicitud de PDF | No existe individual — solo reenviando el lote completo (con la advertencia de arriba). |
| Estructura del ZIP | Solo archivos .xml planos en la raíz, sin subcarpetas. |
| Token vencido a media corrida | Marca los documentos afectados como Fallido / ACCOUNT_TOKEN_EXPIRED; el lote igual termina, se recomienda utilizar token infinito. |
En SW® somos mejores para TI, es por ello que tu opinión es muy importante, por favor ayúdanos calificando este articulo y dejando tus comentarios.