heruAPI

Retention certificates

The monthly CFDI de retenciones a platform gives to its merchants and couriers, with the complement for digital platforms.

Every month a platform gives each merchant and courier it withheld taxes from a CFDI de retenciones e información de pagos, with the complemento de servicios de plataformas tecnológicas (retention code 26). This guide covers the whole cycle: who issues it, what you send, how heru computes the withholdings, how to issue a month of certificates in one batch, how to reconcile, and how to correct one.

Where each part runs today

Every operation on this page is Live on the v1 API: run it in the development environment with your own credentials, where the SAT test environment stamps it with the same checks production applies. Production opens on the same contract; until then, heru issues certificates from the monthly file agreed with your account team, who will also agree the switch date with you.

Who issues the certificate

The CFDI de retenciones is issued by whoever withheld: your company. heru seals it with your company's CSD, which it keeps for you (see Companies), and stamps it with its PAC. heru never issues it in its own name, and it cannot issue it as a third party.

The receiver is the merchant or courier, identified in one of two ways:

  • by id, if you registered them with heru: receiver.merchant_id or receiver.worker_id, and heru fills their RFC, name, regime and postal code;
  • by their SAT data: receiver.rfc, name, tax_regime and postal_code, exactly as the SAT has them. Validate them first with a fiscal validation: a name or postal code that differs from the SAT is the most common reason a certificate is rejected.

What you send

One certificate per receiver, per month and per service type:

  • period: one month (start_month equals end_month), from 2019 and never later than the current month.
  • retention_code: 26.
  • platform.service_type: what the receiver did through your platform. It sets the ISR rate.
  • platform.periodicity: how often you pay them, e.g. weekly.
  • platform.services: the services of the month. Send one entry per service, or add up the services of one day and payment form into one entry; the SAT counts each entry as one service. Each entry has the date, the payment_form the customer used, the price_without_vat, the vat_rate and your platform's commission.

You do not send amounts or withheld_taxes: heru computes them the way the SAT checks them. If you want heru to check your own numbers, send them too; any difference answers 422 with the value the SAT expects.

The rates heru applies

service_type (SAT c_TipoDeServ)What it coversISR withheld
01Passenger transport (rides)2.1 %
02Delivering prepared food (couriers)2.1 %
03Delivering other goods2.1 %
04Lodging4 %
05Selling goods, e.g. a restaurant's food (merchants)2.5 % (1 % before 2026)
06Any other service2.5 % (1 % before 2026)
07, 08, 09Digital content, online clubs and dating, distance learning2.5 % (1 % before 2026)
  • Receivers with a 12-character RFC (personas morales, such as most restaurants): 2.5 % of ISR from 2026, whatever the service type.
  • Receivers without an RFC (XAXX010101000, PUBLICO EN GENERAL): 20 % of ISR and all the VAT.
  • VAT: half of the VAT transferred on the services. The base of the VAT withholding is the VAT itself, not the price: on a service of $2,850.00 at 16 %, the VAT is $456.00 and you withhold $228.00.
  • Cash: services paid in cash (payment_form 01) count in the total but not in the ISR and VAT bases, because the platform did not collect that money.
  • Bonuses and prizes (payment_form 09) carry no VAT and no commission.
  • ISR payment type: 04 (provisional) unless the courier opted to treat the withholding as their definitive payment; then set platform.isr_payment_type to 03 (definitive).

A merchant and a courier

A restaurant that sold $61,500.00 through your platform in September, one week in cash. heru withholds 2.5 % of ISR on $59,350.00 and half of the $9,496.00 of VAT on the non-cash sales: $6,231.75 in total.

POST /retention-certificates
{
  "external_id": "RET-2026-000121",
  "receiver": { "rfc": "TAD190521KX1", "name": "TACOS EL ALIADO DEMO", "tax_regime": "601", "postal_code": "03100" },
  "period": { "start_month": "09", "end_month": "09", "year": "2026" },
  "retention_code": "26",
  "platform": {
    "service_type": "05",
    "periodicity": "02",
    "services": [
      { "date": "2026-09-07", "payment_form": "03", "price_without_vat": "18450.00", "vat_rate": "0.16", "commission": { "rate": "0.25", "amount": "4612.50" } },
      { "date": "2026-09-14", "payment_form": "03", "price_without_vat": "21300.00", "vat_rate": "0.16", "commission": { "rate": "0.25", "amount": "5325.00" } },
      { "date": "2026-09-21", "payment_form": "01", "price_without_vat": "2150.00", "vat_rate": "0.16", "commission": { "rate": "0.25", "amount": "537.50" } },
      { "date": "2026-09-28", "payment_form": "03", "price_without_vat": "19600.00", "vat_rate": "0.16", "commission": { "rate": "0.25", "amount": "4900.00" } }
    ]
  }
}
Merchant (aliado): a month of sales, ISR 2.5 % receiver RFC TAD190521KX1

A courier who earned $8,950.00 in delivery fees and a $500.00 bonus. heru withholds 2.1 % of ISR on $9,450.00 and half of the $1,432.00 of VAT: $914.45.

Courier (repartidor): a month of deliveries, ISR 2.1 % receiver RFC HEGL900101K44

The answer is 201 with the stamped certificate: its folio fiscal (uuid), the withholdings, the complement with its totals and the SAT stamp. retention_certificate.stamped follows by webhook.

A month of certificates: use a batch

For thousands of receivers, send one CSV a month instead of one request per certificate. A batch of type retention_certificate takes one row per service; rows with the same external_id are one certificate.

  1. POST /batches with {"type": "retention_certificate"} and PUT the CSV to upload_url.
  2. heru checks every certificate against the same rules as POST /retention-certificates before anything is stamped, except that it does not verify the receiver against the SAT: check your receivers with fiscal validations first. A certificate the stamping authority refuses ends failed in the report, with its reason. GET /batches/{id} lists in errors the row and column to fix.
  3. Fix the file and upload it to a new batch, or POST /batches/{id}/process to stamp the valid certificates now and the rest later.
  4. When batch.done fires, report_url has the folio fiscal of every stamped certificate.
Monthly batch: four certificates, one with an error CSV 6 rows

Reconcile the month

GET /retention-certificates?period=2026-09&status=stamped lists every certificate of the month, newest first, 100 per page (up to 500 with limit). Follow next_cursor until it is null. Filter by receiver_rfc, merchant_id, worker_id, company_id or external_id to find one.

Your external_id names one certificate per issuer. Sending it again, with a new Idempotency-Key, answers 409 EXTERNAL_ID_ALREADY_USED with the id of the certificate you already have, so a retry of your monthly job never stamps a second CFDI.

Files

GET /retention-certificates/{id}/download?format=xml (or pdf) returns a signed link valid for 15 minutes. The XML is the document with fiscal validity; the PDF shows the SAT seal and a QR to verify it. The files are never at a public address: request a new link each time a merchant or courier needs theirs.

Correct or cancel a certificate

Retention certificates need no acceptance from the receiver.

  • Wrong data, with a replacement. Issue the corrected certificate with the old one in related_certificates (relationship 04, same receiver), then DELETE /retention-certificates/{old id}?reason=01&replacement_uuid={new uuid}.
  • Wrong data, no replacement. DELETE …?reason=02.
  • The operation did not happen. DELETE …?reason=03.

DELETE answers 202 with status cancellation_requested. The SAT confirms within seconds: the status becomes cancelled and retention_certificate.cancelled fires.

Replaced: issue the corrected certificate, then cancel the old one (reason 01) receiver RFC TAD190521KX1

When something fails

AnswerWhat happenedWhat to do
422 VALIDATION_FAILEDA rule of the CFDI or the complement failed. fields names the field, the value the SAT expects and the SAT rule (e.g. SPT116).Fix the field and send it again with the same external_id.
422 SAT_REJECTEDThe SAT does not recognize the receiver: RFC, name or postal code.Validate the receiver with a fiscal validation and use its values.
422 COMPANY_CSD_NOT_ACTIVEThe CSD heru keeps for your company expired or was revoked.Send the new CSD to your account team.
409 EXTERNAL_ID_ALREADY_USEDYou already have a certificate with this external_id.Read it with the id in the message.
503 STAMP_UNAVAILABLEThe PAC or the SAT did not answer in time. Nothing was stamped.Retry after Retry-After with the same Idempotency-Key. Batches retry for you.

Several of these failures are runnable cases on the development page.

© 2026 Kuarto Inc. (heru)heru.appQuestions about your integration? Contact your heru account team.

On this page