Create a charge
LiveServed by the v1 API in development with the same checks as production; production opens on the same contract. The capabilities from before v1 keep running in production at the path your account team gives you.
Call it when a card payment is approved, with what was sold and its VAT. If you only know the amount, leave items out and the invoice uses the merchant's default_item. Returns a single-use link and its QR that you show on the terminal or print on the receipt. The customer opens the link, enters their tax data, we validate it with the SAT and stamp the invoice immediately, with no action from the merchant.
Needs the charges:write scope.
oauth2 charges:writeAuthorizationBearer <token>OAuth 2.0 client credentials. Exchange your client_id and client_secret at /oauth/token of the environment for a bearer token valid for one hour. The playground of every operation asks for your own client and keeps it in your browser tab only.
Scope: charges:write
Idempotency-Key*stringUnique per operation. Retrying with the same key returns the original result for 24 hours.
length <= 255application/json- body
merchant_id*stringThe merchant that made the sale, as returned by POST /merchants.
^mer_[0-7][0-9a-hjkmnp-tv-z]{25}$external_id*stringYour transaction id.
occurred_at*stringdate-timeamount*stringTotal charged to the card, VAT included. When you send items, it must equal the sum of their amount; otherwise the charge is rejected with 422.
payment_form*string04 credit card, 28 debit card. Only the card amount is invoiced; cash never goes through a charge.
"04""28"location?stringexternal_id of one of the merchant's locations. Its postal code becomes the place of issue.
items?array<>What was sold. In a charge, each item's amount is the line total paid by the customer, VAT included; heru calculates the base and the VAT for the CFDI. Without items, the invoice uses the merchant's default_item for the whole amount, with its VAT rate. Send items when a sale mixes VAT rates.
expires_in_days?integer30The charge is open. Show qr on the terminal or print it on the receipt; it holds invoice_url. Keep id to get or void the charge later.
application/json- response
id*stringThe charge in this API: use it to get or void it.
^chg_[0-7][0-9a-hjkmnp-tv-z]{25}$external_id*stringYour transaction id, as you sent it. Webhooks carry it so you can match them to your records.
status*stringopen while the customer can invoice it; invoiced once they did; expired when expires_at passed unused; void after POST /charges/{id}/void; included_in_global when an expired charge went into the merchant's global invoice.
"open""invoiced""expired""void""included_in_global"merchant_id*stringThe merchant that made the sale.
^mer_[0-7][0-9a-hjkmnp-tv-z]{25}$amount*stringTotal charged to the card, VAT included.
^[0-9]+\.[0-9]{2}$payment_form*stringSAT c_FormaPago: 04 credit card, 28 debit card.
"04""28"occurred_at*stringWhen the card payment was approved.
date-timelocation?|external_id of the merchant's location, if you sent it.
items*array<>What the invoice will say. If you sent no items, it holds the merchant's default_item for the whole amount.
invoice_token*|Code inside invoice_url. Use it only if you build the link yourself, for example on your own domain. It is not the charge id. Null once the charge is no longer open.
invoice_url*|Link the customer opens to invoice this charge: https://invoice.heru.app/{merchant slug}/c/{invoice_token}. It works once and until expires_at. Null once the charge is no longer open.
qr*|QR with invoice_url. Null once the charge is no longer open.
expires_at*stringLast moment the customer can invoice the charge.
date-timeinvoice*|The CFDI the customer issued. Null until status is invoiced. Download its files with GET /invoices/{id}/download.
created_at*stringdate-timeupdated_at*stringdate-timecurl -X POST 'https://api.dev.b2b.heru.app/v1/charges' \ -H 'Idempotency-Key: try-it-0001' \ -H 'Content-Type: application/json' \ -d '{ "merchant_id": "mer_01m4ra8wsyfh3s0tr4nrq3y96j", "external_id": "TXN-0001845122", "occurred_at": "2026-10-11T13:42:10-06:00", "amount": "250.00", "payment_form": "04", "location": "SUC-01", "items": [ { "product_code": "90101500", "description": "Chilaquiles verdes con pollo", "amount": "165.00", "quantity": 1, "unit_code": "E48", "vat_rate": "0.16" }, { "product_code": "90101500", "description": "Café americano", "amount": "85.00", "quantity": 2, "unit_code": "E48", "vat_rate": "0.16" } ]}'{ "id": "chg_01m4p7sbvpev1s5086cty36rvs", "external_id": "TXN-0001845122", "status": "open", "merchant_id": "mer_01m4ra8wsyfh3s0tr4nrq3y96j", "amount": "250.00", "payment_form": "04", "occurred_at": "2026-10-11T13:42:10-06:00", "location": "SUC-01", "items": [ { "product_code": "90101500", "description": "Chilaquiles verdes con pollo", "amount": "165.00", "quantity": 1, "unit_code": "E48", "vat_rate": "0.16" }, { "product_code": "90101500", "description": "Café americano", "amount": "85.00", "quantity": 2, "unit_code": "E48", "vat_rate": "0.16" } ], "invoice_token": "9fK2wQ7xLm4TbR8vZp3N", "invoice_url": "https://invoice.heru.app/cafeteria-la-esquina/c/9fK2wQ7xLm4TbR8vZp3N", "qr": { "format": "png_base64", "data": "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAABlBMVEX///8AAABVwtN+…" }, "expires_at": "2026-11-10T23:59:59-06:00", "invoice": null, "created_at": "2026-10-11T19:42:11.318Z", "updated_at": "2026-10-11T19:42:11.318Z"}Create a console linkPOST
Creates a link that opens the merchant's own console without a heru login: its invoices, the KPIs of the month and an accountant report with CSV. Give it to the merchant from your back office. The link can be opened as many times as needed until `expires_at` (a day by default). Each call creates a new link and revokes the merchant's previous ones, so repeating the request does not return the same link and needs no Idempotency-Key. `url` is `null` in an environment that has no console configured. Answers `409` when the merchant is closed. Needs the `merchants:console` scope.
Void a chargePOST
Use it when the card payment is reversed before the customer invoices: a wrong amount, a refund or a bank reversal. The charge becomes `void`, its link stops working and it never goes into the global invoice. If the customer already invoiced it, cancel the invoice instead; this answers `409`. A charge that expired or was already voided answers `410`. Needs the `charges:void` scope.