heruAPI

Changelog

Every change to the v1 contract, newest first.

VersionDateChanges
1.0.0-draft.1030 Oct 2026Batches are built for files of up to 100 million rows and 32 GiB. Scope: type is only invoice or retention_certificate; validations go through their JSON endpoints. Upload: POST /batches takes defaults for the columns you would repeat on every row; upload_url is a resumable session valid for 24 hours (upload_expires_at, upload_max_bytes) that takes one PUT or several parts with Content-Range, and the file can be gzip. Errors: errors holds the first 100 and each one gains code; error_count has the total and errors_url all of them. Progress: new counters validated_rows, invalid_rows, total_documents, valid_documents, invalid_documents, duplicates, processed_documents, in_flight_documents, cancelled, pending_review and receiver_overrides; succeeded and failed now count documents, not rows. Columns: each party is identified by its RFC or its heru id, and with neither the row is invalid (new issuer_rfc, third_party_rfc and *_kind). New *_worker_external_id is optional, is not enough by itself and, sent with the other, must be the same party. New discount, issue_date, series, folio, metadata_<key>, receiver_curp and receiver_country; notify_email replaces receiver_email; third_party_merchant_id is gone, because a third party is always a worker. Rows of one document must be together (EXTERNAL_ID_NOT_CONTIGUOUS), and negative amounts are rejected (NEGATIVE_AMOUNT). Receiver: batches check format and catalogs and do not verify the receiver against the SAT, because files are expected to carry data already checked with fiscal validations; a document the stamping authority refuses is failed in the report with its reason. A generic RFC written in a row is refused, and amounts are never altered. A receiver registered with different data is stamped with the registered data, with the note receiver_data_overridden in the report; foreign receivers are not available in v1 (FOREIGN_RECEIVER_NOT_AVAILABLE). Report: it adds notes, the new statuses duplicate, cancelled and pending_review, and a specific error_code instead of VALIDATION_FAILED. New operations POST /batches/{id}/pause, /resume and /cancel, new statuses paused, cancelling and cancelled, and new error 409 BATCH_INVALID_STATE. Cancelling stops what is pending and never cancels a CFDI; batch.done also fires with status cancelled. A batch lives 20 days at most: one still unfinished at that point is cancelled the same way. heru sets the processing pace: a file of tens of millions of documents takes weeks, and a document can leave the 72-hour window of issue_date before it is stamped. Estimate: a ready batch carries estimated_completion_at and fits_lifetime, computed from the pace available to your company; it is an estimate, not a promise, and created_at is new, since the 20 days count from it. POST /batches/{id}/process answers 409 BATCH_EXCEEDS_CAPACITY when fits_lifetime is false, and takes an optional body {"allow_partial": true} to start anyway: it stamps what fits and cancels what is pending at 20 days. A ready batch shows the valid and invalid counts, errors_url and the estimate before anything is stamped. Clarifications: a batch whose documents are all duplicates, with no invalid document, ends done without stamping and fires batch.done with status done and its report_url, even though it never went through process. A document already stamped with the same external_id wins over its own validation errors: it is reported duplicate, not invalid, so uploading a file again does not fail on rows already stamped (EXTERNAL_ID_NOT_CONTIGUOUS still applies). upload_url is a write credential that only POST /batches returns: GET /batches/{id} always answers it as null, and a client that lost it repeats POST /batches with the same Idempotency-Key. GET /batches/{id} and POST /batches/{id}/process answer 422 COMPANY_REQUIRED when the credential covers several companies and the request does not name one, like POST /batches. While emission is paused for maintenance, a ready batch may report fits_lifetime false and process answer 409 BATCH_EXCEEDS_CAPACITY until it resumes; allow_partial still starts it. The report_url and errors_url carried in the batch.done event are signed links valid for 15 minutes from when the event was written: a client that receives the event later, or on a late retry, calls GET /batches/{id} for a fresh link. Building: not in production yet.
1.0.0-draft.930 Oct 2026Two lists in the point of sale. GET /charges returns the charges of your company, most recent first, and filters by merchant_id, external_id, status, location, occurred_from/occurred_to, created_from/created_to (days in Mexico City time, at most 366 days when you send both ends) and global_invoice_id. In a list qr is always null and invoice is a summary (id, uuid, status, stamped_at); GET /charges/{id} still returns the QR and the whole invoice. GET /merchants returns the merchants of your company, most recent first, and filters by external_id, rfc, status and created_from/created_to. Both take cursor and limit (1 to 500, 100 by default). They need the existing scopes charges:read and merchants:read, and a credential that covers several companies answers 422 COMPANY_REQUIRED.
1.0.0-draft.827 Oct 2026New resource Authentication: the token endpoint and the caller check are now operations of the reference. POST /oauth/token (at the root of each environment, not under /v1) exchanges client_id and client_secret for a bearer token valid for one hour, with RFC 6749 errors (invalid_request, invalid_client, unsupported_grant_type, invalid_scope), and GET /me answers the client, organization, company and scopes of the token you send. Both are live in both environments; nothing changes in how they behave. New operation POST /merchants/{id}/console-link with the new scope merchants:console: it creates a link that opens the merchant's own console without a heru login, for you to give to the merchant. It is reusable until it expires (a day by default) and a new one revokes the previous ones; url is null in an environment without a console, as production today.
1.0.0-draft.727 Oct 2026New resource Branding: a merchant shows its own name, colour, help mailbox, footer and logo on its invoicing page and emails. GET /branding/merchants/{id} returns the effective brand, each field the merchant's own or, when it set none, its company's or its organization's, with sources saying where each comes from; PUT /branding/merchants/{id} sets display_name, primary_color, support_email and footer_text (a field you leave out keeps its value, null clears it and the merchant inherits again; an empty string is not a valid value); PUT /branding/merchants/{id}/logo uploads a PNG or an SVG as multipart/form-data in the file field, and DELETE /branding/merchants/{id}/logo removes it. The logo file is image/png or image/svg+xml. A credential that covers several companies answers 422 COMPANY_REQUIRED to every Branding operation. New scopes branding:read and branding:write. Building: not in production yet.
1.0.0-draft.626 Oct 2026New resource Webhooks: you register the URLs that receive events yourself, instead of asking your account team. POST /webhooks creates a subscription with its own signing secret (shown once), GET /webhooks, GET /webhooks/{id}, PATCH /webhooks/{id} (also status active or paused) and DELETE /webhooks/{id} manage it; POST /webhooks/{id}/secret rotates the secret with an overlap during which every delivery carries two signatures; POST /webhooks/{id}/test sends a signed webhook.test event right away and answers what your endpoint replied; GET /webhooks/{id}/deliveries lists every delivery with its attempts, and POST /deliveries/{id}/replay sends one again. New scopes webhooks:read and webhooks:write, and new errors WEBHOOK_URL_NOT_ALLOWED, WEBHOOK_EVENT_TYPE_UNKNOWN, WEBHOOK_LIMIT_REACHED and DELIVERY_NOT_REPLAYABLE. Building in production.
1.0.0-draft.420 Oct 2026The SAT catalogs of the platforms complement use their SAT keys, like every other catalog in the contract: platform.service_type is a c_TipoDeServ key ("02" instead of "prepared_food_delivery"), platform.periodicity a c_Periodicidad key ("02" instead of "weekly") and platform.isr_payment_type "03" or "04" instead of "definitive" or "provisional". The same values go in the batch columns platform_service_type, platform_periodicity and platform_isr_payment_type. The production base URL is https://api.b2b.heru.app/v1. Invoices and retention certificates accept a customer as receiver (receiver.customer_id, also receiver_customer_id in batches), and both lists filter by customer_id. New event invoice.stamped: one per stamped invoice, including every invoice a batch stamps, before batch.done. In the sandbox, every scripted step and its webhook happens within 3 seconds.
1.0.0-draft.316 Oct 2026Retention certificates for platforms carry the full complemento de servicios de plataformas tecnológicas in platform.services (date, payment form, price, VAT, commission and government contribution per service, the nine SAT service types and the periodicity), and heru computes amounts and withheld_taxes with the SAT rules: VAT withheld on the VAT transferred, ISR 2.5 % for sales of goods and other services from 2026, cash left out of the bases. Every SAT rule runs before stamping. external_id is unique per issuer (409 EXTERNAL_ID_ALREADY_USED). New GET /retention-certificates to list and reconcile. Cancelling answers cancellation_requested and the SAT confirms it; reason 01 checks the replacement. New event retention_certificate.stamped. Retention batches take one row per service and are checked before stamping. Batches are labeled Building: the v1 routes are not in production yet. New error COMPANY_CSD_NOT_ACTIVE.
1.0.0-draft.215 Oct 2026Invoices and retention certificates are stamped in the same call, with 503 STAMP_UNAVAILABLE when the SAT does not answer. Error codes follow one convention, with SAT rejections as SAT_REJECTED and the rule in sat_code, and every operation lists its errors. Validations complete with a code when the authority answers there is no profile or NSS. Fiscal validations return validation_url, and IMSS documents come as download links. Batches cover validations and are processed with POST /batches/{id}/process. Cancellations accept replacement_uuid only with reason 01. Errors are plain JSON with code, status, message and link. A merchant's default_item carries its VAT rate.
1.0.0-draft.111 Oct 2026First public draft of v1: fiscal validation, social security, workers, e.firma and CSD, invoices, retention certificates, batches, point of sale, reports and webhooks. Each operation is labeled Live, Building or Planned.

Additions within v1 do not break existing integrations; see Versioning.

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