Add a merchant
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.
Registers a merchant that will invoice its card sales. The response carries onboarding.url, a page hosted by heru with your brand where the merchant uploads their CSD; send it to them. If you already hold the CSD, upload it with PUT /merchants/{id}/csd instead. merchant.activated tells you when the merchant can invoice.
Needs the merchants:write scope.
oauth2 merchants: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: merchants:write
Idempotency-Key*stringUnique per operation. Retrying with the same key returns the original result for 24 hours.
length <= 255application/json- body
external_id*stringYour merchant id, e.g. the card-acceptance affiliation.
length <= 128rfc*stringRFC, 12 characters (company) or 13 (individual). The merchant issues the invoices with it.
^[A-ZÑ&]{3,4}[0-9]{6}[A-Z0-9]{3}$legal_name*stringName exactly as registered with the SAT. For an individual it is the person's name, not the business name.
tax_regime*stringSAT c_RegimenFiscal of the merchant.
postal_code*stringPostal code of the merchant's tax address.
trade_name?stringBusiness name the customer sees on the self-invoicing page.
slug?stringShort name in the merchant's invoicing link: https://invoice.heru.app/{slug}. Unique across heru. If you leave it out, heru builds it from trade_name (or legal_name). A taken slug answers 409.
^[a-z0-9]+(-[a-z0-9]+)*$3 <= length <= 40email?stringWhere heru sends the CSD expiry reminders.
emaillocations?array<>default_item?What the invoice says when a charge comes without items: one line for the whole amount. Leave it out if you always send items; then a charge without them answers 422. A merchant that sells at different VAT rates, like a supermarket, should send items in every charge.
Created
application/json- response
id*string^mer_[0-7][0-9a-hjkmnp-tv-z]{25}$external_id*stringstatus*stringpending_csd until the merchant uploads their CSD; active when they can invoice; active_pending_sat while the SAT confirms a new CSD; csd_expiring 30 days before it expires; csd_expired, suspended or closed when they cannot invoice.
"pending_csd""active""active_pending_sat""csd_expiring""csd_expired""suspended""closed"rfc*stringRFC, 12 characters (company) or 13 (individual). The merchant issues the invoices with it.
^[A-ZÑ&]{3,4}[0-9]{6}[A-Z0-9]{3}$legal_name*stringtrade_name?string|nullslug*stringShort name in the merchant's invoicing link.
^[a-z0-9]+(-[a-z0-9]+)*$3 <= length <= 40tax_regime*stringpostal_code*stringemail?|nulllocations?array<>default_item*|What the invoice says when a charge comes without items. Null if the merchant has none.
csd*onboarding*created_at*stringdate-timeupdated_at*stringdate-timecurl -X POST 'https://api.dev.b2b.heru.app/v1/merchants' \ -H 'Idempotency-Key: try-it-0001' \ -H 'Content-Type: application/json' \ -d '{ "external_id": "AFIL-000125", "rfc": "XAMA620210DQ5", "legal_name": "ALBA XKARAJAM MENDEZ", "tax_regime": "612", "postal_code": "03100", "trade_name": "Clínica Dental Roma", "slug": "clinica-dental-roma", "email": "contacto@clinica-dental-roma.example", "locations": [ { "external_id": "SUC-01", "postal_code": "03100", "name": "Centro" } ], "default_item": { "product_code": "85122000", "unit_code": "E48", "description": "Consulta y limpieza dental", "vat_rate": "exempt" }}'{ "id": "mer_01m4xnvf23ecb963n48h2j9vxz", "external_id": "AFIL-000125", "status": "pending_csd", "rfc": "XAMA620210DQ5", "legal_name": "ALBA XKARAJAM MENDEZ", "trade_name": "Clínica Dental Roma", "slug": "clinica-dental-roma", "tax_regime": "612", "postal_code": "03100", "email": "contacto@clinica-dental-roma.example", "locations": [ { "external_id": "SUC-01", "name": "Centro", "postal_code": "03100" } ], "default_item": { "product_code": "85122000", "unit_code": "E48", "description": "Consulta y limpieza dental", "vat_rate": "exempt" }, "csd": { "active": false, "valid_from": null, "valid_to": null }, "onboarding": { "status": "pending", "url": "https://onboarding.heru.app/m/7KQ2M9XT4PWB", "expires_at": "2026-10-21T17:02:41.219Z", "completed_at": null }, "created_at": "2026-10-14T17:02:41.219Z", "updated_at": "2026-10-14T17:02:41.219Z"}Get a batchGET
Validation and processing progress, counted in rows and in documents, with the first 100 problems. The links in `errors_url` and `report_url` are signed for 15 minutes, so read them from a fresh `GET`. `upload_url` is always `null` here: the URL is a write credential that only `POST /batches` returns. If you lost it, repeat `POST /batches` with the same `Idempotency-Key` to get it again. Needs the `batches:read` scope.
Create an onboardingPOST
Creates a new onboarding link for the merchant, valid for 7 days. Any previous link stops working. Use it when the first link expired, or when the CSD is about to expire (`csd_expiring`) and the merchant must upload the new one. The current onboarding always comes in `onboarding` when you get the merchant. Answers `409` when the merchant is active and its CSD is not expiring. Needs the `merchants:write` scope.