CFDI de Retenciones para plataformas digitales vía API
URL de esta página: https://cfdi.express/docs/retenciones-plataformas-digitales
Disponible a partir de la versión v1.43.0 de la API.
La constancia de retenciones plataformas tecnológicas es el CFDI de Retenciones 2.0 que la plataforma emite cada mes a cada vendedor o prestador de servicios. Lleva la clave de retención 26 (cveRetenc) y el complemento Servicios de Plataformas Tecnológicas 1.0, estándar Rev. E. Un CFDI por receptor por mes. Esta página cubre cómo crearlo, listarlo, consultarlo y cancelarlo por API.
El OpenAPI vive en https://api.cfdi.express/openapi.json y las docs interactivas en https://api.cfdi.express/docs. La referencia general está en la API.
Qué es y quién la emite
El emisor es la plataforma. En la API ese emisor es el comercio (merchantId). El receptor es el prestador:
- Persona física: RFC de 13 caracteres.
- Persona moral: RFC de 12 caracteres.
- Sin RFC:
receiver.rfc=XAXX010101000. - Prestador extranjero:
receiver.rfc=XEXX010101000. En ese caso se omitereceiver.zip.
receiver.name va tal como en la Constancia de Situación Fiscal: mayúsculas y sin acentos. receiver.zip (5 dígitos) es obligatorio en receptores nacionales. receiver.curp es opcional.
La obligación de emitir esta constancia depende del caso de la plataforma. Confírmala con tu contador antes de timbrar el primer mes.
El RFC XEXX010101000 de una factura de ingreso usa otros campos (residenciaFiscal, numRegIdTrib). Ese flujo está en Facturación al extranjero. En esta constancia el receptor extranjero lleva el RFC genérico y el nombre, y se omite el código postal.
Flujo mensual
- El comercio emisor es la plataforma y tiene CSD. Cómo subirlo está en Subir CSD.
- Por cada prestador y cada mes se timbra un CFDI.
ejercicioes el año ymeses el mes (MesIni=MesFin).periodicidadpor default es02(mensual). - El cuerpo manda los servicios de ese periodo, con la comisión de la plataforma en cada uno. La forma de pago
09no lleva comisión. - La API calcula el ISR retenido, el IVA retenido, el IVA trasladado, las exclusiones en efectivo y los totales que valida el SAT.
POST /v1/retencionesespera hasta 20 segundos.201trae el UUID. Con carga extrema responde202y el estatus queda enstamping: se consulta conGET /v1/retenciones/{id}.- Cuando
files.statusesready, el recurso traepdfUrl(PDF con QR),xmlUrlyzipUrl. - El webhook
retencion.stampedavisa que quedó timbrada.retencion.stamp_failedavisa que falló.
El header Idempotency-Key es obligatorio en el timbrado y en la cancelación. Una llave estable por operación lógica (por ejemplo periodo + id del vendedor) hace que un reintento devuelva la misma respuesta, con Idempotency-Replayed: true.
Autenticación: Authorization: Bearer sk_test_… o sk_live_…. La llave define el modo. En un workspace, el header x-account-id indica la cuenta. Ver Equipo y workspaces.
Endpoints
| Método | Ruta | Qué hace |
|---|---|---|
POST |
/v1/retenciones |
Timbra la constancia. |
GET |
/v1/retenciones |
Lista las constancias de la cuenta. |
GET |
/v1/retenciones/{id} |
Consulta una constancia. |
POST |
/v1/retenciones/{id}/cancel |
Cancela una constancia timbrada. |
GET |
/v1/catalogs/retenciones/{catalog} |
Catálogos del complemento. |
Base: https://api.cfdi.express.
Crear la constancia
POST https://api.cfdi.express/v1/retenciones
Header Idempotency-Key (1 a 255 caracteres), requerido.
Cuerpo
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
merchantId |
string | sí | Comercio emisor: la plataforma. |
ejercicio |
integer | sí | Año del periodo. De 2021 a 2100. |
mes |
integer | sí | Mes del periodo, 1 a 12. |
tipoServicio |
string | sí | c_TipoDeServ, 2 dígitos. Aplica a todos los servicios del CFDI y define las tasas. Catálogo: GET /v1/catalogs/retenciones/tipo-servicio. |
receiver |
object | sí | Prestador. Ver abajo. |
servicios |
array | sí | Servicios del periodo. De 1 a 2000. |
folio |
string | no | FolioInt impreso. 1 a 20 letras o dígitos. El esquema trae el ejemplo 202609C0042. El folio numérico interno se asigna igual. |
periodicidad |
string | no | c_Periodicidad. Default 02 mensual. También 01 semanal, 03 diario, 04 quincenal, 05 otro. |
isrTipoPagoRet |
string | no | ISR de personas físicas: 03 pago definitivo (default) o 04 pago provisional. En personas morales siempre es 04. |
tipoServicio es una sola clave por CFDI. El catálogo completo y las tasas de cada clave están en Qué calcula la API. La clave 11 (apuestas y sorteos, IEPS) responde error: todavía no está soportada.
Receptor
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
rfc |
string | sí | RFC del prestador. XAXX010101000 si no dio RFC. XEXX010101000 si es extranjero. |
name |
string | sí | Nombre o razón social, 1 a 300 caracteres, sin ` |
zip |
string | nacional | Código postal de 5 dígitos. Se omite con XEXX010101000. |
curp |
string | no | CURP del prestador. |
Cada servicio
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
fecha |
string | sí | FechaServ, YYYY-MM-DD. Tiene que caer en el mes reportado. |
formaPago |
string | sí | c_FormaPagoServ, 2 dígitos. |
precioSinIva |
number | sí | Precio de la venta o del servicio antes de IVA. Mayor que 0. |
comision |
object | salvo 09 |
Comisión de la plataforma (ComisionDelServicio). |
ivaTasa |
number | no | IVA trasladado: 0.16 (default), 0.08 región fronteriza o 0. Se ignora si formaPago es 09. |
subTipo |
string | no | c_SubTipoServ. Solo con tipoServicio 01: 01 personal, 02 compartido, 03 plus, 04 cancelación. |
rfcTerceroAutorizado |
string | no | RFC de 13 posiciones (^[A-ZÑ&]{4}\d{6}[A-Z0-9]{3}$). |
contribucionGubernamental |
object | no | importe (mayor que 1.00) y entidad (c_EntidadesFederativas 01–32; 09 es Ciudad de México). |
formaPago (c_FormaPagoServ): 01 efectivo, 02 transferencia electrónica de fondos, 03 tarjeta de crédito, 04 monedero electrónico, 05 dinero electrónico, 06 tarjeta de débito, 07 tarjeta de servicios, 08 intermediario de pagos, 09 otros ingresos por premios, bonificaciones o análogos.
Al menos un servicio tiene que ser venta o servicio (formaPago distinto de 09): NumServ solo cuenta esos. 09 no lleva comisión ni contribución gubernamental.
comision.importe es obligatorio dentro de comision y es lo que la plataforma cobró al prestador por esa venta. comision.porcentaje (de 0.001 a 1) y comision.base son opcionales.
Respuestas del timbrado
| Status | Cuándo |
|---|---|
201 |
Timbrada. El cuerpo es el recurso retencion. |
202 |
Sigue en stamping. Consulta GET /v1/retenciones/{id}. |
400 |
Validación: reglas del SAT, claves desconocidas, comercio archivado (merchant_archived) o sin CSD (csd_missing). |
402 |
Sin créditos o tope mensual alcanzado. |
404 |
El comercio no existe. |
409 |
Esa Idempotency-Key ya está en curso. |
422 |
El SAT rechazó el comprobante, o la misma llave se reutilizó con otro cuerpo. |
503 |
El servicio de timbrado no está disponible. |
La serie del CFDI es RET. El folio numérico sale de la secuencia del comercio, la misma de facturas, complementos y nómina.
Ejemplo de POST /v1/retenciones
Este es el cuerpo del e2e del PR. El test arma ejercicio y mes como el mes anterior en America/Mexico_City; con la fecha de la suite (2026-10-29) ese periodo es septiembre 2026. merchantId lo resuelve el test con el comercio emisor que ya tiene CSD. El header Idempotency-Key va aparte.
{
"merchantId": "<id del comercio emisor>",
"ejercicio": 2026,
"mes": 9,
"tipoServicio": "06",
"receiver": {
"rfc": "XOJI740919U48",
"name": "INGRID XODAR JIMENEZ",
"zip": "76028"
},
"servicios": [
{
"fecha": "2026-09-03",
"formaPago": "03",
"precioSinIva": 1000,
"comision": { "importe": 200, "porcentaje": 0.2 }
},
{
"fecha": "2026-09-12",
"formaPago": "01",
"precioSinIva": 500,
"comision": { "importe": 100 }
},
{
"fecha": "2026-09-20",
"formaPago": "06",
"precioSinIva": 333.33,
"comision": { "importe": 66.67 }
}
]
}
201, recurso retencion con status stamped y serie RET. El e2e fija montoTotOperacion 1833.33, comisiones 366.67, ISR con baseRet 1333.33 y tipoPagoRet 03, e IVA con baseRet 213.33 y montoRet 106.66. El mismo motor deja el resto de los totales de esa entrada. id, uuid y folio los asigna el timbrado.
{
"object": "retencion",
"status": "stamped",
"serie": "RET",
"ejercicio": 2026,
"mes": 9,
"tipoServicio": "06",
"montoTotOperacion": 1833.33,
"montoTotRet": 139.99,
"isrRetenido": 33.33,
"ivaRetenido": 106.66,
"ivaTrasladado": 293.3328,
"difIvaEntregadoPrestServ": 106.6728,
"comisiones": 366.67,
"currency": "MXN",
"impRetenidos": [
{
"impuesto": "001",
"baseRet": 1333.33,
"montoRet": 33.33,
"tipoPagoRet": "03",
"tasa": 0.025
},
{
"impuesto": "002",
"baseRet": 213.33,
"montoRet": 106.66,
"tipoPagoRet": "01",
"tasa": 0.5
}
]
}
Cómo salen, con tipoServicio 06 en 2026 (ISR 2.5%, IVA retenido 50%) y ivaTasa default 0.16:
- IVA trasladado:
1000 × 0.16 = 160,500 × 0.16 = 80,333.33 × 0.16 = 53.3328. Total293.3328. MontoTotOperacion:1000 + 500 + 333.33 = 1833.33. El servicio en efectivo entra aquí.- Base de ISR:
1833.33 − 500 = 1333.33. Monto:1333.33 × 0.025, truncado a centavos,33.33. - Base de IVA retenido: IVA que no es efectivo,
160 + 53.3328 = 213.3328, truncado a213.33. Monto:213.33 × 0.5, truncado,106.66. DifIVAEntregadoPrestServ:293.3328 − 80 − 106.66 = 106.6728.- Comisiones:
200 + 100 + 66.67 = 366.67.
Qué calcula la API
El request lleva periodo, receptor, servicios y comisión. La API deriva los importes que el SAT valida. La aritmética va en micro-pesos (1e-6) y trunca a centavos: piso a 2 decimales, sin redondear.
| Importe | Cálculo |
|---|---|
| IVA trasladado del servicio | precioSinIva × ivaTasa, exacto hasta 6 decimales. Default 0.16. formaPago 09 no lleva nodo de IVA. |
MontoTotOperacion |
Suma de precioSinIva de los servicios con nodo de IVA. Igual a MontoTotGrav. |
| Base de ISR | MontoTotOperacion menos los precios con formaPago 01 (efectivo), truncada a centavos. |
| ISR retenido | Base × tasa, truncado a centavos. |
| Base de IVA retenido | IVA trasladado que no es en efectivo, truncado a centavos. |
| IVA retenido | Esa base × 50% o 100%, truncado a centavos. tipoPagoRet 01. |
DifIVAEntregadoPrestServ |
IVA trasladado total − IVA en efectivo − IVA retenido. |
Exclusión en efectivo
formaPago 01 es efectivo. Esos servicios sí entran en MontoTotOperacion y su IVA sí entra en el IVA trasladado. Salen de las dos bases de retención: el precio se resta de la base de ISR y el IVA se resta de la base de IVA retenido. En DifIVAEntregadoPrestServ ese IVA en efectivo también se resta, porque se queda con el prestador.
No hay un monto de corte. La exclusión es por forma de pago 01, el precio completo del servicio.
ISR de persona física
Aplica cuando el emisor es persona moral (RFC de 12) y el receptor es persona física (RFC de 13, distinto de XAXX010101000 y de XEXX010101000). La tasa es la de c_TipoDeServ para ese ejercicio. isrTipoPagoRet default 03 (pago definitivo); 04 es pago provisional.
| Clave | Servicio | ISR 2021–2025 | ISR 2026 | IVA retenido |
|---|---|---|---|---|
01 |
Transporte terrestre de pasajeros | 2.1% | 2.1% | 50% |
02 |
Entrega de alimentos preparados | 2.1% | 2.1% | 50% |
03 |
Entrega de bienes (distintos de alimentos preparados) | 2.1% | 2.1% | 50% |
04 |
Hospedaje | 4% | 4% | 50% |
05 |
Comercio de bienes | 1% | 2.5% | 50% |
06 |
Otro tipo de servicios | 1% | 2.5% | 50% |
07 |
Descarga o acceso a contenido digital, así como otros contenidos multimedia | sin ISR | sin ISR | 100% |
08 |
Clubes en línea y páginas de citas | sin ISR | sin ISR | 100% |
09 |
Enseñanza a distancia o test o ejercicios | sin ISR | sin ISR | 100% |
ejercicio va de 2021 al año de la fecha de expedición. 2019 y 2020 usaban rangos de ingreso que este cálculo no modela.
Otros receptores, con el mismo emisor persona moral:
- Persona moral (RFC de 12): ISR del 2.5% en los tipos
01–06.tipoPagoRetsiempre04. MandarisrTipoPagoRet03es error de validación. XAXX010101000: ISR del 20% en los tipos01–06, e IVA retenido al 100%.XEXX010101000: sin ISR e IVA retenido al 100%. En el caso de prueba (precio1000, tasa0.16)difIvaEntregadoPrestServqueda en0.- Tipos
07,08y09: sin nodo de ISR para cualquier receptor. IVA retenido al 100%.
Si el emisor es persona física (RFC de 13), no hay retención de ISR. El IVA retenido sigue la columna de la tabla.
Cuándo el IVA retenido es 50% o 100%
La proporción es sobre el IVA trasladado que no es en efectivo:
- 50% en tipos
01–06, con receptor nacional distinto deXAXX010101000. - 100% en tipos
07–09, conXAXX010101000y conXEXX010101000.
tipoServicio define esas tasas. Las instrucciones del servidor MCP piden no adivinarla.
El recurso ya timbrado trae el resultado:
| Campo | Qué es |
|---|---|
impRetenidos[] |
Cada retención: impuesto (001 ISR, 002 IVA), baseRet, montoRet, tipoPagoRet y tasa. |
isrRetenido |
ISR retenido. |
ivaRetenido |
IVA retenido. |
ivaTrasladado |
IVA trasladado. |
montoTotOperacion |
MontoTotOperacion. |
montoTotRet |
Total de retenciones. |
comisiones |
Comisiones. |
difIvaEntregadoPrestServ |
DifIVAEntregadoPrestServ. Puede venir null. |
currency |
Moneda. |
La tasa de cada elemento de impRetenidos es la que la API aplicó en ese CFDI.
Listar y consultar
GET https://api.cfdi.express/v1/retenciones
La lista trae object = list, data (recursos retencion) y has_more. Paginación por cursor: limit (1 a 100, default 10) y starting_after con el id del último elemento mientras has_more sea true. Igual que el resto de la API: referencia.
Filtros:
| Query | Descripción |
|---|---|
merchantId |
Un comercio. |
status |
stamping, stamped, stamp_failed, cancel_pending, cancelled. |
uuid |
UUID del CFDI. |
receiverRfc |
Un prestador. Con XEXX010101000 lista a todos los prestadores extranjeros. |
ejercicio |
Año reportado. |
mes |
Mes reportado, 1 a 12. |
createdFrom, createdTo |
Fecha-hora de creación. |
GET https://api.cfdi.express/v1/retenciones/{id}
200 devuelve el recurso. 404 si no existe.
Campos del recurso, además de los totales de arriba: id, object (retencion), livemode, status, merchantId, uuid, serie, folio, folioText, fecha, cveRetenc, ejercicio, mes, periodicidad, tipoServicio, receiver, servicios, files, stampedAt, createdAt.
files.status: pending, processing, ready o failed. Con archivos listos vienen xmlUrl, pdfUrl y zipUrl. El PDF incluye el QR.
Cancelar
POST https://api.cfdi.express/v1/retenciones/{id}/cancel
Header Idempotency-Key, requerido. El cuerpo es el mismo de cancelar una factura:
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
motivo |
string | sí | 01 errores con relación (exige folioSustitucion) · 02 errores sin relación · 03 la operación no se llevó a cabo · 04 operación nominativa relacionada a una global. |
folioSustitucion |
string (uuid) | con 01 |
UUID del CFDI que sustituye al cancelado. |
Cancelar cuesta 1 timbre. El e2e lo cuenta aparte del timbre del timbrado. 202 cuando quedó aceptada: status cancelled y cancellation.status accepted.
Justo después de timbrar, el SAT puede responder CR1205 - UUID No existe durante cerca de un minuto. Ese intento se reembolsa y se puede reintentar después.
Otros status: 400 validación, 402 sin créditos, 404 no existe, 422 el SAT rechazó la cancelación, 503 servicio de timbrado no disponible.
Estatus posibles del recurso: stamping, stamped, stamp_failed, cancel_pending, cancelled.
Webhooks
Tres eventos nuevos. Se suscriben en events al crear o actualizar un endpoint (POST /v1/webhook_endpoints, PATCH /v1/webhook_endpoints/{id}). El catálogo está en GET /v1/webhook_event_types; estos tres traen object = retencion.
| Evento | Cuándo |
|---|---|
retencion.stamped |
La constancia quedó timbrada. |
retencion.stamp_failed |
El timbrado falló. |
retencion.cancelled |
La constancia quedó cancelada. |
Los eventos que ya existían (invoice.*, payment.*, nomina.*) siguen en el mismo catálogo.
MCP
La v1.43.0 agrega 4 herramientas. El total queda en 22.
| Herramienta | Qué hace |
|---|---|
create_retencion |
Timbra la constancia. Consume 1 timbre. Manda periodo, tipoServicio, receiver y servicios; ISR, IVA y exclusiones en efectivo se calculan en el servidor. |
get_retencion |
Consulta una constancia: estatus, UUID, receptor, periodo, servicios, retenciones y archivos. |
list_retenciones |
Lista, de la más nueva a la más vieja. Filtra por receiverRfc, ejercicio, mes y status. |
cancel_retencion |
Cancela una constancia timbrada. Cuesta 1 timbre. motivo 01 exige folioSustitucion. |
get_catalog, que ya existía, también acepta retenciones/tipo-servicio, retenciones/forma-pago-servicio, retenciones/periodicidad, retenciones/subtipo-servicio, retenciones/tipo-pago-retencion y retenciones/entidad-federativa.
- Producción: https://api.cfdi.express/mcp
- Pruebas: https://api.cfdi.express/mcp/test
Cómo conectar el servidor, OAuth y el workspace: Servidor MCP y Equipo y workspaces.
Dashboard
En el dashboard, Documentos tiene la pestaña Retenciones. Ahí se consultan las constancias de la cuenta. Las API keys y las conexiones MCP siguen en el mismo dashboard: Equipo y workspaces.
Costo
Timbrar esta constancia cuesta 1 timbre. Cancelarla cuesta otro timbre. Sin saldo, el POST responde 402. Un rechazo CR1205 reembolsa el intento de cancelación. El saldo se consulta en Consultar saldo.
sk_test_ opera en el modo de pruebas y sk_live_ en producción. Cada modo tiene su propio saldo.
Qué sigue igual
Facturas (/v1/invoices), notas de crédito, complementos de pago y nómina quedan como estaban. Los webhooks de esos documentos también. Esta versión agrega el recurso retencion, sus cuatro operaciones, el catálogo /v1/catalogs/retenciones/{catalog} y los tres eventos retencion.*.
Catálogos
GET https://api.cfdi.express/v1/catalogs/retenciones/{catalog}
Un catálogo por llamada. La respuesta es object = list y data[] con clave y nombre.
{catalog} |
Catálogo SAT |
|---|---|
tipo-servicio |
c_TipoDeServ |
subtipo-servicio |
c_SubTipoServ |
forma-pago-servicio |
c_FormaPagoServ |
periodicidad |
c_Periodicidad |
tipo-pago-retencion |
c_TipoPagoRet |
entidad-federativa |
c_EntidadesFederativas |
Preguntas frecuentes
¿Qué es la constancia de retenciones plataformas tecnológicas?
Es el CFDI de Retenciones 2.0 con complemento de Servicios de Plataformas Tecnológicas que la plataforma emite a cada vendedor o prestador, uno por mes, clave de retención 26.
¿La API calcula el ISR y el IVA?
Sí. El request lleva precios sin IVA, forma de pago y comisión. La API calcula ISR retenido, IVA retenido, IVA trasladado, la exclusión de efectivo (formaPago 01) y los totales. Las tasas están en Qué calcula la API. tipoServicio lo define el contador de la plataforma. La tasa aplicada vuelve en impRetenidos[].tasa.
¿Cuánto cuesta?
1 timbre al timbrar y 1 timbre al cancelar. Ver Consultar saldo.
¿Dónde la veo en el dashboard?
En Documentos, pestaña Retenciones: https://dash.cfdi.express
¿Qué webhooks escucho?
retencion.stamped, retencion.stamp_failed y retencion.cancelled.
¿Cambia algo de mi integración de facturas?
Las rutas y los eventos de facturas, notas de crédito, complementos de pago y nómina siguen igual.
Relacionados
- Referencia de la API
- Consultar saldo
- Subir CSD
- Cancelar una factura — mismos
motivoyfolioSustitucion - Servidor MCP
- Equipo y workspaces
- Facturación al extranjero —
XEXX010101000en una factura de ingreso
