Requests
The API accepts and returns JSON. List endpoints use cursor pagination where a large result set is possible.
Pagination
List responses that can be paginated return a pagination object:
Code
Pass next_cursor back as the cursor query parameter to request the next
page. Do not parse cursor values; treat them as opaque strings.
For endpoint-specific loops, filters, and retry behavior, use the
pagination examples.
Incremental sync
People, time-off, and document metadata support updated_since for polling
jobs:
Code
Use ISO-8601 UTC timestamps and follow pagination until has_more is false.
Idempotency
Write requests require an Idempotency-Key header so retries cannot create or
apply the same HR change twice:
Code
Reusing the same key with the same method, route, path resource, version
precondition, and request body replays the original response. Reusing it with a
different body, person id, or If-Match value returns
idempotency_conflict.
Conditional updates
Mutable updates use ETags to avoid lost updates. First read the resource you
are going to update and keep the exact strong ETag response header:
Code
Then send that exact value back as If-Match when patching safe setup fields:
Code
The same If-Match rule applies to lifecycle actions such as ending or
reactivating a person. Focused employment updates use the employment resource's
own ETag:
Code
Code
Code
Employment-history creation is append-style and requires Idempotency-Key.
Unsupported free-text reason/notes and compensation fields are rejected:
Code
Missing If-Match returns precondition_required. A stale or non-exact ETag
returns precondition_failed; read the person again, reconcile the current
data, then retry with a new idempotency key.
Webhooks
Webhook management is available through /api/v1/webhooks
for API keys with webhooks:manage. Webhook mutations require
Idempotency-Key, and create/rotate responses reveal the plaintext signing
secret once.
For setup, signatures, retries, duplicate handling, and testing, use the Webhooks guide. For exact request and response schemas, use the Webhooks section in the API reference.
Errors
Errors use a stable JSON envelope. HollyHR is deliberately keeping this custom v1 envelope instead of switching to RFC 7807 because every public API error can carry the same compact shape, stable machine code, and request id:
Code
Validation errors may include details, an array of field-level issues. The
message is human-readable and may become clearer over time; clients should
branch on error.code, the HTTP status, and field identifiers in details.
Common codes:
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Invalid query, path, cursor, or body shape. |
| 401 | authentication_required | Missing, malformed, unknown, or revoked key. |
| 403 | permission_denied | Plan entitlement or scope is missing. |
| 404 | not_found | Resource not found or not visible to org. |
| 409 | idempotency_conflict | Idempotency key reused for a different write. |
| 412 | precondition_failed | Conditional update used a stale ETag. |
| 428 | precondition_required | Conditional update omitted If-Match. |
| 429 | rate_limited | Too many requests. |
Keep the request_id when asking HollyHR support to investigate a failed API
request.
Rate limits
Rate limits are tenant/key aware. Public API requests are limited before authentication and again after authentication:
| Bucket | Default |
|---|---|
| Pre-authentication abuse protection | 60 requests per minute per IP and credential fingerprint. |
| Authenticated route bucket | 600 requests per minute per organisation, API key, and route. |
Individual endpoints may use lower limits when a request is more expensive or triggers delivery side effects. Follow the API reference for endpoint-specific 429 responses.
Successful authenticated responses include:
| Header | Meaning |
|---|---|
RateLimit-Limit | The current authenticated route bucket limit. |
RateLimit-Remaining | Requests remaining in the current route bucket window. |
RateLimit-Reset | Seconds until the current route bucket resets. |
A 429 response includes Retry-After when a retry window is available. When
rate-limit metadata is available, the response also includes RateLimit-Limit,
RateLimit-Remaining, and RateLimit-Reset. Back off until the reset window
instead of retrying immediately.