Public API conventions
These rules apply to every route. The API reference gives the details for each one.
Requests and responses
- HTTPS only, TLS 1.2 or later.
- JSON bodies in UTF-8, with
Content-Type: application/json. The token URL is the exception: it takes a form. - Field names are camelCase. IDs are UUIDs.
- Dates are
YYYY-MM-DD. Times are ISO 8601 with an offset, for example2026-11-15T09:30:00Z. - A field is only returned if the reference lists it. Still, ignore fields you don't recognise, so your integration keeps working if one is added in a later version.
Paging
List routes return one page at a time:
{ "items": [ ... ], "nextCursor": "eyJpZCI6..." }
| Parameter | Meaning |
|---|---|
limit | Items per page, 1 to 200. Default 50 |
cursor | The nextCursor value from the previous page. Leave it out for the first page |
Keep requesting pages while nextCursor isn't null. Treat the cursor as an opaque value: don't build or change one.
To keep a copy of employees up to date, do one full read, then pass updatedSince with the time of your previous run to GET /v1/employees to get only employees changed since.
Rate limits
| Limit | Applies to |
|---|---|
| 120 requests a minute | Each client, across all API routes |
| 30 requests a minute | The token URL, per source address |
API responses include X-RateLimit-Remaining, the calls left in the current minute. Over a limit, the API returns 429 with Retry-After, the number of seconds to wait. Wait at least that long before retrying, and slow down rather than retrying straight away. A first full sync of a large tenant is the usual cause: spread it out over a few minutes.
Retries and idempotency
Reads can be retried safely.
POST /v1/employees and POST /v1/attendance/punches need an Idempotency-Key header: a unique value of 16 to 128 characters, such as a UUID, for each operation.
- If a call times out or fails with a network error, send it again with the same key and the same body. You get the first result, and nothing is created twice.
- Use a new key for each new operation.
- Keys are kept for 24 hours.
- The same key with a different body, or while the first request is still running, returns
409.
If an employee create gets no answer at all, look the employee up before trying again with a new key, because it may have been created.
Errors
API errors use problem details (RFC 9457), with Content-Type: application/problem+json. detail, when present, explains what was wrong:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.4",
"title": "Forbidden",
"status": 403,
"traceId": "00-bcf0593eaf9aa9f5a16985260520ba34-a4a8d507048989ac-00"
}
| Status | Meaning |
|---|---|
| 400 | The request is malformed or a value is invalid. detail says which |
| 401 | The access token is missing, expired or invalid |
| 403 | The token doesn't hold the scope this route needs |
| 404 | The record doesn't exist or belongs to another tenant, or the route isn't part of the API |
| 409 | An Idempotency-Key conflict |
| 429 | A rate limit. See Retry-After |
| 5xx | A problem on our side. Retry later, with the same Idempotency-Key for writes |
Include traceId whenever you contact HCM Cafe about an error. Some 403, 404 and 429 responses come from the network edge before the API, and those aren't problem details.
Versioning
The version is in the path, currently /v1. Within a version, changes are additive only: new routes, new optional fields and new optional parameters. A change that could break an integration goes into a new version, announced in advance, with the old version kept running during a transition period.