Timbrar un CFDI de retenciones (plataformas digitales)
Timbra la constancia mensual de Retenciones 2.0 con el complemento de Servicios de Plataformas Tecnológicas (CveRetenc 26).
Desde la v1.43.0. Es la constancia que una plataforma digital emite a cada prestador de servicios o vendedor, una por receptor y por mes. Mandas los servicios del periodo con la comisión de la plataforma en cada uno. La API deriva los montos que el SAT valida: MontoTotOperacion, el IVA trasladado de cada servicio, el ISR retenido, el IVA retenido, las exclusiones de efectivo y DifIVAEntregadoPrestServ.
La serie del comprobante es RET. folio en el recurso es el folio numérico interno, de la misma secuencia del emisor. folioText es el FolioInt que mandaste, o null. Si no mandas folio, el FolioInt timbrado es RET seguido de ese número. La moneda del recurso es MXN.
tipoServicio (c_TipoDeServ) decide las tasas. No lo adivines: está en GET /v1/catalogs/retenciones/tipo-servicio y, por MCP, en get_catalog con retenciones/tipo-servicio. Para una persona física prestadora la tasa de ISR depende del tipo y del Ejercicio: 06 (Otro tipo de servicios) es 2.5% en 2026 y retiene 50% del IVA; 09 (Enseñanza a distancia o test o ejercicios) no retiene ISR y retiene 100% del IVA. Los tipos 07 y 08 tampoco retienen ISR y retienen 100% del IVA. Una persona moral prestadora paga 2.5% de ISR en los tipos que sí retienen ISR, y isrTipoPagoRet queda en 04. XAXX010101000 es 20% de ISR y 100% de IVA. Un prestador XEXX010101000 no lleva ISR y retiene 100% del IVA. Si el RFC del emisor tiene 13 caracteres, no hay retención de ISR.
El timbrado es el mismo que el de las facturas: espera hasta unos 20 segundos por el UUID (201). Bajo carga extrema responde 202 y hay que consultar GET /v1/retenciones/{id}. Exige Idempotency-Key. Un replay trae Idempotency-Replayed: true.
Al timbrar, fallar o cancelarse se pueden avisar con los webhooks retencion.stamped, retencion.stamp_failed y retencion.cancelled, suscritos en POST /v1/webhook_endpoints.
Por MCP, create_retencion acepta el mismo cuerpo. La guía de producto está en retenciones de plataformas digitales.
Authorization: Bearer sk_test_... | sk_live_...El header x-account-id indica la cuenta. Si no eres miembro, la respuesta es 403.
Parámetros
Headers
| Campo | Tipo | Descripción |
|---|---|---|
| Idempotency-Keyreq | string | Llave única de la operación lógica (por ejemplo periodo + id del prestador), de 1 a 255 caracteres. Los replays devuelven la respuesta original con Idempotency-Replayed: true. |
Cuerpo (JSON)
| Campo | Tipo | Descripción |
|---|---|---|
| merchantIdreq | string | Emisor de la constancia: la plataforma. |
| folio | string | FolioInt que se timbra en el CFDI. Opcional. De 1 a 20 letras o dígitos (^[0-9a-zA-Z]{1,20}$). Ejemplo: 202609C0042. El folio numérico interno se sigue asignando. |
| ejercicioreq | integer | Año del periodo que se reporta. De 2021 a 2100. |
| mesreq | integer | Mes del periodo (1 a 12). MesIni y MesFin son ese mismo mes. |
| periodicidad | string | Clave de c_Periodicidad, dos dígitos. Default 02 (mensual). 01 semanal, 03 diario, 04 quincenal, 05 otro. El catálogo está en GET /v1/catalogs/retenciones/periodicidad. |
| tipoServicioreq | string | Clave de c_TipoDeServ, dos dígitos, la misma para todos los servicios de este CFDI. Decide las tasas de ISR e IVA retenido. Ejemplos del catálogo: 05 comercio de bienes, 06 otro tipo de servicios, 07 contenido digital, 08 clubes en línea, 09 enseñanza a distancia. Lista completa: GET /v1/catalogs/retenciones/tipo-servicio. |
| isrTipoPagoRet | string | ISR retenido a personas físicas: 03 pago definitivo (default) o 04 pago provisional. En personas morales siempre es 04. |
| receiverreq | object | Prestador (receptor de la constancia). Ver receiver.rfc, receiver.name, receiver.zip y receiver.curp. |
| receiver.rfcreq | string | RFC del prestador. 13 caracteres si es persona física y 12 si es persona moral. XAXX010101000 cuando no dio RFC (ISR 20%, IVA retenido 100%). XEXX010101000 si el prestador es extranjero. |
| receiver.namereq | string | Nombre tal como está en la Constancia de Situación Fiscal del prestador: mayúsculas, sin acentos, de 1 a 300 caracteres, sin el carácter |. |
| receiver.zip | string | Código postal del domicilio fiscal, 5 dígitos. Va en receptores nacionales. Se omite con XEXX010101000. |
| receiver.curp | string | CURP del prestador. Opcional. Tiene que cumplir el formato de la CURP. |
| serviciosreq | array | Cada venta o servicio del prestador en el periodo (DetallesDelServicio). De 1 a 2,000 elementos. La fecha de cada uno tiene que caer en el mes reportado. |
| servicios[].fechareq | string | Fecha del pago (FechaServ), YYYY-MM-DD. Tiene que caer en el mes reportado. |
| servicios[].formaPagoreq | string | Clave de c_FormaPagoServ, dos dígitos. 01 efectivo, 02 transferencia, 03 tarjeta de crédito, 06 tarjeta de débito, 08 intermediario de pagos, 09 premios o bonificaciones. Lista: GET /v1/catalogs/retenciones/forma-pago-servicio. Con 09 se ignora ivaTasa y la comisión no es obligatoria. |
| servicios[].precioSinIvareq | number | Precio de la venta o del servicio antes de IVA. Mayor que 0 y hasta 999999999999. |
| servicios[].ivaTasa | number | Tasa de IVA trasladado: 0.16 (default), 0.08 en región fronteriza o 0. Se ignora cuando formaPago es 09. |
| servicios[].comision | object | Comisión de la plataforma por esa venta (ComisionDelServicio). Obligatoria salvo formaPago 09. |
| servicios[].comision.importe | number | Lo que la plataforma cobró al prestador por esa venta. Mayor que 0 y hasta 999999999999. Es el único campo obligatorio dentro de comision. La comisión misma es obligatoria salvo formaPago 09. |
| servicios[].comision.porcentaje | number | Tasa de la comisión, de 0.001 a 1. |
| servicios[].comision.base | number | Base de la comisión. Mayor que 0 y hasta 999999999999. |
| servicios[].subTipo | string | Clave de c_SubTipoServ, dos dígitos. Sólo con tipoServicio 01: 01 Personal, 02 Compartido, 03 Plus, 04 Cancelación. Catálogo: GET /v1/catalogs/retenciones/subtipo-servicio. |
| servicios[].rfcTerceroAutorizado | string | RFC del tercero autorizado: 4 letras, fecha y homoclave (13 caracteres). |
| servicios[].contribucionGubernamental | object | Contribución gubernamental de ese servicio, por ejemplo el impuesto sobre hospedaje. Si va, exige importe y entidad. |
| servicios[].contribucionGubernamental.importe | number | Importe de la contribución. Mayor que 1.00 y hasta 999999999999. |
| servicios[].contribucionGubernamental.entidad | string | Clave de c_EntidadesFederativas (01 a 32) de la entidad donde se pagó. 09 es Ciudad de México. Catálogo: GET /v1/catalogs/retenciones/entidad-federativa. |
Ejemplos
curl -X POST https://api.cfdi.express/v1/retenciones \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: retencion-2026-09-prestador-42" \
-H "Content-Type: application/json" \
-d '{
"merchantId": "mkq2f8v3x1t7p9d4n6c0b5rz",
"folio": "202609C0042",
"ejercicio": 2026,
"mes": 9,
"periodicidad": "02",
"tipoServicio": "05",
"isrTipoPagoRet": "03",
"receiver": {
"rfc": "XAXX010101000",
"name": "NOMBRE DEL PRESTADOR",
"zip": "42501"
},
"servicios": [
{
"fecha": "2026-09-15",
"formaPago": "02",
"precioSinIva": 1000,
"ivaTasa": 0.16,
"comision": { "importe": 150, "porcentaje": 0.15 }
}
]
}'Respuesta
{
"id": "ret7k3q9x2m4v1t8p6d0n5cb",
"object": "retencion",
"livemode": false,
"status": "stamped",
"merchantId": "mkq2f8v3x1t7p9d4n6c0b5rz",
"uuid": "9f1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
"serie": "RET",
"folio": 42,
"folioText": "202609C0042",
"fecha": "2026-10-01T10:00:00",
"cveRetenc": "26",
"ejercicio": 2026,
"mes": 9,
"periodicidad": "02",
"tipoServicio": "05",
"receiver": {
"rfc": "XAXX010101000",
"name": "NOMBRE DEL PRESTADOR",
"zip": "42501"
},
"servicios": [
{
"fecha": "2026-09-15",
"formaPago": "02",
"precioSinIva": 1000,
"ivaTasa": 0.16,
"comision": { "importe": 150, "porcentaje": 0.15 }
}
],
"impRetenidos": [
{ "impuesto": "001", "baseRet": 1000, "montoRet": 200, "tipoPagoRet": "03", "tasa": 0.2 },
{ "impuesto": "002", "baseRet": 160, "montoRet": 160, "tipoPagoRet": "01", "tasa": 1 }
],
"montoTotOperacion": 1000,
"montoTotRet": 360,
"isrRetenido": 200,
"ivaRetenido": 160,
"ivaTrasladado": 160,
"difIvaEntregadoPrestServ": 0,
"comisiones": 150,
"currency": "MXN",
"files": { "status": "pending" },
"stampedAt": "2026-10-01T16:00:10.512Z",
"createdAt": "2026-10-01T16:00:08.907Z"
}Errores
| Status | code | Cuándo aparece |
|---|---|---|
| 400 | validation_error | Datos inválidos: reglas cruzadas del SAT, claves de catálogo desconocidas u otro campo del cuerpo. El arreglo errors trae el path. subcode merchant_archived si el emisor está archivado (hay que restaurarlo con POST /v1/merchants/{id}/unarchive). subcode csd_missing si el emisor no tiene CSD. |
| 401 | unauthorized | Falta el header Authorization o la llave es inválida. |
| 402 | insufficient_credits | No hay saldo para el timbre, o se llegó al tope mensual (monthly_limit_reached). |
| 404 | not_found | El emisor no existe en tu cuenta. |
| 409 | request_in_flight | Otra petición con el mismo Idempotency-Key está en proceso. |
| 422 | idempotency_key_reuse | Reusaste la llave de idempotencia con un cuerpo distinto. |
| 422 | sat_rejected | El SAT rechazó la constancia. |
| 503 | pac_unavailable | El PAC no está disponible. Reintenta con la misma llave de idempotencia. |
Notas
- Los montos del ejemplo 201 los calcula la API a partir de
precioSinIva.montoTotOperaciones la suma de esos precios en los servicios que llevan IVA (formaPago09no entra). El efectivo (formaPago01) se excluye de las bases de ISR e IVA retenido.impRetenidos[].impuestoes001(ISR) o002(IVA). El IVA usatipoPagoRet01(pago definitivo IVA). El ISR usa03o04. - Con RFC
XAXX010101000el ISR es 20% y el IVA retenido es 100%, así quedifIvaEntregadoPrestServqueda en 0. El ejemplo asume un emisor con RFC de 12 caracteres: si el emisor es persona física (13 caracteres) no hay partida de ISR.XEXX010101000es el prestador extranjero, no llevareceiver.zip, no retiene ISR y retiene 100% del IVA. - Un 202 significa que el timbrado sigue en curso (
status: "stamping"). Consulta GET /v1/retenciones/{id}. - Cuando
files.statusesready,files.xmlUrl,files.pdfUrlyfiles.zipUrlson URLs firmadas de 15 minutos.files.statustambién puede serpending,processingofailed. - La serie siempre es
RET.folioTextguarda el FolioInt del request. Si lo omites queda null y el FolioInt timbrado esRETmás el folio numérico. - El recurso trae
object: "retencion"ystatusenstamping,stamped,stamp_failed,cancel_pendingocancelled.
