Documentación / API / Retenciones (plataformas digitales)

Timbrar un CFDI de retenciones (plataformas digitales)

POSThttps://api.cfdi.express/v1/retencionesAPI keyIdempotency-Key

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.

Autenticación
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

CampoTipoDescripción
Idempotency-KeyreqstringLlave ú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)

CampoTipoDescripción
merchantIdreqstringEmisor de la constancia: la plataforma.
foliostringFolioInt 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.
ejercicioreqintegerAño del periodo que se reporta. De 2021 a 2100.
mesreqintegerMes del periodo (1 a 12). MesIni y MesFin son ese mismo mes.
periodicidadstringClave 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.
tipoServicioreqstringClave 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.
isrTipoPagoRetstringISR retenido a personas físicas: 03 pago definitivo (default) o 04 pago provisional. En personas morales siempre es 04.
receiverreqobjectPrestador (receptor de la constancia). Ver receiver.rfc, receiver.name, receiver.zip y receiver.curp.
receiver.rfcreqstringRFC 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.namereqstringNombre 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.zipstringCódigo postal del domicilio fiscal, 5 dígitos. Va en receptores nacionales. Se omite con XEXX010101000.
receiver.curpstringCURP del prestador. Opcional. Tiene que cumplir el formato de la CURP.
serviciosreqarrayCada 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[].fechareqstringFecha del pago (FechaServ), YYYY-MM-DD. Tiene que caer en el mes reportado.
servicios[].formaPagoreqstringClave 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[].precioSinIvareqnumberPrecio de la venta o del servicio antes de IVA. Mayor que 0 y hasta 999999999999.
servicios[].ivaTasanumberTasa de IVA trasladado: 0.16 (default), 0.08 en región fronteriza o 0. Se ignora cuando formaPago es 09.
servicios[].comisionobjectComisión de la plataforma por esa venta (ComisionDelServicio). Obligatoria salvo formaPago 09.
servicios[].comision.importenumberLo 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.porcentajenumberTasa de la comisión, de 0.001 a 1.
servicios[].comision.basenumberBase de la comisión. Mayor que 0 y hasta 999999999999.
servicios[].subTipostringClave 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[].rfcTerceroAutorizadostringRFC del tercero autorizado: 4 letras, fecha y homoclave (13 caracteres).
servicios[].contribucionGubernamentalobjectContribución gubernamental de ese servicio, por ejemplo el impuesto sobre hospedaje. Si va, exige importe y entidad.
servicios[].contribucionGubernamental.importenumberImporte de la contribución. Mayor que 1.00 y hasta 999999999999.
servicios[].contribucionGubernamental.entidadstringClave 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 — constancia mensual (v1.43.0)
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

201 Created
{
  "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

StatuscodeCuándo aparece
400validation_errorDatos 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.
401unauthorizedFalta el header Authorization o la llave es inválida.
402insufficient_creditsNo hay saldo para el timbre, o se llegó al tope mensual (monthly_limit_reached).
404not_foundEl emisor no existe en tu cuenta.
409request_in_flightOtra petición con el mismo Idempotency-Key está en proceso.
422idempotency_key_reuseReusaste la llave de idempotencia con un cuerpo distinto.
422sat_rejectedEl SAT rechazó la constancia.
503pac_unavailableEl 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. montoTotOperacion es la suma de esos precios en los servicios que llevan IVA (formaPago 09 no entra). El efectivo (formaPago 01) se excluye de las bases de ISR e IVA retenido. impRetenidos[].impuesto es 001 (ISR) o 002 (IVA). El IVA usa tipoPagoRet 01 (pago definitivo IVA). El ISR usa 03 o 04.
  • Con RFC XAXX010101000 el ISR es 20% y el IVA retenido es 100%, así que difIvaEntregadoPrestServ queda 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. XEXX010101000 es el prestador extranjero, no lleva receiver.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.status es ready, files.xmlUrl, files.pdfUrl y files.zipUrl son URLs firmadas de 15 minutos. files.status también puede ser pending, processing o failed.
  • La serie siempre es RET. folioText guarda el FolioInt del request. Si lo omites queda null y el FolioInt timbrado es RET más el folio numérico.
  • El recurso trae object: "retencion" y status en stamping, stamped, stamp_failed, cancel_pending o cancelled.

Continuar