heruAPI

Getting started

Environments, authentication and your first request.

Environments

EnvironmentBase URLWhat happens
Developmenthttps://api.dev.b2b.heru.app/v1The same services and checks as production, on test data. CFDI are stamped by the SAT test environment and have no fiscal validity. Every operation labeled Live runs here today; see the development environment.
Productionhttps://api.b2b.heru.app/v1Real queries to the SAT, RENAPO and the IMSS, and real CFDI. The v1 routes open here with the gateway; your account team tells you when to switch.

Your heru account team issues a client (client_id and client_secret) for each environment, with the scopes of your contract. The Try it of every operation in the API reference takes them: pick the environment, paste the client and press Get token. They stay in your browser tab only.

Real identities in development

With your development credentials, a real CURP or RFC runs a real validation against the SAT, RENAPO or the IMSS. Use real identities in development only with the person's consent, as you would in production.

v1 paths

Customers already in production reach the live capabilities at earlier paths. The /v1 routes open in production with the new gateway; your account team will tell you when to switch.

Authentication

The API uses OAuth 2.0 client credentials. Exchange your client_id and client_secret at /oauth/token of the environment for an access token, then send it on every request as Authorization: Bearer <token>.

  • A token is valid for one hour. Request a new one when it expires; there is no refresh token.
  • A token belongs to the environment that issued it: a development token is rejected in production and the other way round.
  • Each credential carries scopes for the services in your contract, such as workers:write or invoices:read. A call outside your scopes answers 403. Every scope and the operations it allows are in Permissions.
  • You can keep two client secrets active at the same time, so you can rotate without downtime.
  • Keep the secret on your servers. Never ship it in a mobile app or a web page.

Your first request

Three calls against development: get a token, register a worker, and read them back.

Get a token

curl -X POST https://api.dev.b2b.heru.app/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=$HERU_CLIENT_ID \
  -d client_secret=$HERU_CLIENT_SECRET
Response
{
  "access_token": "eyJhbGciOiJFUzI1NiIs…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "workers:read workers:write …"
}

A wrong client answers 401 with "error": "invalid_client"; a scope outside your contract answers 400 with "error": "invalid_scope".

Register a worker

POST /workers needs an Idempotency-Key, like almost every POST; see Idempotency and retries. The CURP below is synthetic: it has the shape of a real one and belongs to nobody.

curl -X POST https://api.dev.b2b.heru.app/v1/workers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: DRV-000088213" \
  -H "Content-Type: application/json" \
  -d '{"external_id": "DRV-000088213", "curp": "HEGL900101HDFRRS06", "services": ["fiscal_validation", "invoicing"]}'

The answer is 201 Created with the worker pending and the enrollment link heru sends them. Keep the id: it is how you read the worker, and what you send as receiver.worker_id on their retention certificates and third_party.worker_id on their invoices.

201 Created
{
  "id": "wrk_01m4ra8w0he5esnnsew0v9ffkv",
  "external_id": "DRV-000088213",
  "status": "pending",
  "curp": "HEGL900101HDFRRS06",
  "services": ["fiscal_validation", "invoicing"],
  "open_actions": ["give_consent", "upload_efirma"],
  "enrollment": {
    "status": "pending",
    "url": "https://enroll.heru.app/w/Rk7pX2mQ9vLc4TzN8bWy?expires=1792160571&signature=9f2c7a41e8",
    "expires_at": "2026-10-16T14:22:51.604Z",
    "completed_at": null
  },
  "created_at": "2026-10-09T14:22:51.604Z"
}

Read the worker

Read one worker by id, or list them with GET /workers, filtering by external_id or status. In production, a webhook tells you when their consent, e.firma or status changes.

curl https://api.dev.b2b.heru.app/v1/workers/wrk_01m4ra8w0he5esnnsew0v9ffkv \
  -H "Authorization: Bearer $TOKEN"

status stays pending until the worker completes the enrollment; open_actions lists what is still missing. From here, the Platforms with workers guide takes you to their invoices and retention certificates.

Or send the same calls from the Try it of POST /workers and GET /workers/{id}, with your development credentials.

Conventions

Asynchronous workValidations with the SAT, RENAPO or the IMSS answer 202 as soon as heru accepts the request. The result arrives by webhook; you can also read it with GET. Invoices and retention certificates are the exception: they are stamped while you wait and answer 201.
IdempotencyAlmost every POST requires an Idempotency-Key; the reference says which take none. A retry with the same key returns the original result for 24 hours, so a network error never creates a second invoice. More.
Your idsSend external_id with your own id. It comes back when you read the resource and in every webhook, and you can filter lists by it.
MoneyMXN as strings with two decimals: "1250.00".
DatesISO 8601. Timestamps are in UTC unless you send an offset.
PaginationLists return data and next_cursor; pass it back as cursor. More.
ErrorsJSON with a stable code, a message, a link to its docs page and, for validation errors, the fields that failed. Branch on code. More.
Rate limitsPer credential, as agreed in your contract. Current usage comes in the RateLimit headers; a 429 carries Retry-After.
VersioningThe version is in the path. We add fields without notice, so ignore the ones you do not know. More.
Personal dataWe never ask for card numbers. Signed file links expire after 24 hours.
© 2026 Kuarto Inc. (heru)heru.appQuestions about your integration? Contact your heru account team.

On this page