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 omite receiver.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

  1. El comercio emisor es la plataforma y tiene CSD. Cómo subirlo está en Subir CSD.
  2. Por cada prestador y cada mes se timbra un CFDI. ejercicio es el año y mes es el mes (MesIni = MesFin). periodicidad por default es 02 (mensual).
  3. El cuerpo manda los servicios de ese periodo, con la comisión de la plataforma en cada uno. La forma de pago 09 no lleva comisión.
  4. La API calcula el ISR retenido, el IVA retenido, el IVA trasladado, las exclusiones en efectivo y los totales que valida el SAT.
  5. POST /v1/retenciones espera hasta 20 segundos. 201 trae el UUID. Con carga extrema responde 202 y el estatus queda en stamping: se consulta con GET /v1/retenciones/{id}.
  6. Cuando files.status es ready, el recurso trae pdfUrl (PDF con QR), xmlUrl y zipUrl.
  7. El webhook retencion.stamped avisa que quedó timbrada. retencion.stamp_failed avisa 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. Total 293.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 a 213.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. tipoPagoRet siempre 04. Mandar isrTipoPagoRet 03 es error de validación.
  • XAXX010101000: ISR del 20% en los tipos 01–06, e IVA retenido al 100%.
  • XEXX010101000: sin ISR e IVA retenido al 100%. En el caso de prueba (precio 1000, tasa 0.16) difIvaEntregadoPrestServ queda en 0.
  • Tipos 07, 08 y 09: 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 de XAXX010101000.
  • 100% en tipos 07–09, con XAXX010101000 y con XEXX010101000.

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.

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