HOURSQUARE · EST 2026 HR you run yourself.
Developer guide Guide 4 of 5

Rate limits, idempotency and errors

The parts of the API that make an integration behave well when things go wrong: what to do on 429, how to retry a write safely, how to test without writing, how errors look, how lists page, and which changes we promise never to make inside v1.

Rate limits

Each key may make 300 requests a minute on resource endpoints and 60 a minute on the token endpoint. Limits are counted per server instance, so treat them as approximate rather than exact. Every response carries the current state:

Response headers
RateLimit-Policy: apikey;q=300;w=60
RateLimit: limit=300, remaining=298, reset=60
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 298
X-RateLimit-Reset: 60

When you exceed the quota you get 429 with Retry-After: 60. Back off for that long, then continue; do not retry in a tight loop. A time clock that sends punches as they happen or a nightly sync should never come near these numbers, and the essentials panel in the reference shows the remaining quota of your last call.

When the quota is spent
HTTP/1.1 429 Too Many Requests
Retry-After: 60

Idempotency

Networks drop responses. To make a write safe to retry, send an Idempotency-Key header, any unique string up to 128 characters (a UUID is ideal), on POST /time-clock/events and POST /time-entries.

  • The first request with a key runs normally and its result is stored for 24 hours, including a 4xx error.
  • A retry with the same key and the same body returns the stored status and body verbatim, with Idempotent-Replayed: true. Nothing runs twice.
  • The same key with a different body is refused with 422 idempotency_key_reused.
  • A retry that arrives while the first request is still running gets 409 idempotency_in_flight; wait a moment and retry.
  • Keys are scoped to your company and your API key, so two integrations cannot collide.
A replayed response
HTTP/1.1 201 Created
Idempotent-Replayed: true

Without the header there is still a safety net: a punch for the same employee, from the same key, within 60 seconds of an identical one is treated as a duplicate and returns the existing entry with 200 instead of creating a second one. That covers a double tap on a device. For anything you retry programmatically, send the header.

Dry run

Send X-HourSquare-Dry-Run: true on any write and the server runs every check and resolution, writes nothing, and answers 200 with "dryRun": true. Use it to validate a badge mapping, to test your error handling, or to see where a punch would land before going live. An absent header is a real write. The interactive reference sends the header by default, so a click there never changes data until you switch it off.

sh
curl -s -X POST https://api.hoursquare.com/api/public/v1/time-clock/events \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'X-HourSquare-Dry-Run: true' \
  -d '{"employee":{"externalId":"B-1042"},"type":"clock_in","occurredAt":"2026-09-14T08:59:12+02:00"}'

Errors

Errors are RFC 9457 problem details, served as application/problem+json:

A conflict
{
  "type": "https://hoursquare.com/problems/already-clocked-in",
  "title": "Already clocked in",
  "status": 409,
  "detail": "Employee B-1042 is already clocked in since 2026-09-14 08:59. Send clock_out first.",
  "instance": "/api/public/v1/time-clock/events",
  "code": 2904,
  "error": "already_clocked_in",
  "correlationId": "0HN7PQ4K2M3VL:00000007"
}
A validation error
{
  "type": "https://hoursquare.com/problems/validation-failed",
  "title": "Validation failed",
  "status": 422,
  "detail": "One or more fields are invalid.",
  "instance": "/api/public/v1/time-clock/events",
  "code": 1800,
  "error": "validation_failed",
  "correlationId": "0HN7PQ4K2M3VL:00000009",
  "errors": [
    { "field": "occurredAt", "message": "Must be an RFC 3339 timestamp with a UTC offset." }
  ]
}
FieldMeaning
typeA URL that identifies the class of problem
titleShort and human-readable; English
statusThe HTTP status, repeated
detailWhat went wrong in this instance; the wording may change
instanceThe request path
codeA numeric code; stable
errorA stable snake_case string: the thing to match on
correlationIdQuote it when you contact support
errors[]On 422, field and message per invalid field

Authentication failures use the same shape: 401 invalid_token (with a WWW-Authenticate: Bearer header) when the token is missing, expired or invalid, and 403 insufficient_scope when it lacks a scope. As a rule: 404 not found, 409 conflicts with the current state, 422 invalid input, 403 not allowed, 429 rate limited, 500 our fault.

Pagination

Lists are cursor-based. Ask for up to limit items (1 to 100, default 25); the response has items, hasMore and nextCursor. Pass nextCursor back as cursor to get the next page. Cursors are opaque strings: do not parse or build them, and do not expect offsets.

sh
curl -s "https://api.hoursquare.com/api/public/v1/employees?limit=100" \
  -H "Authorization: Bearer $TOKEN"
# → { "items": [ … 100 … ], "nextCursor": "MDFKN1Q4…", "hasMore": true }

curl -s "https://api.hoursquare.com/api/public/v1/employees?limit=100&cursor=MDFKN1Q4…" \
  -H "Authorization: Bearer $TOKEN"
# → { "items": [ … 37 … ], "nextCursor": null, "hasMore": false }

Versioning and deprecation

The major version is in the path: /api/public/v1. Inside v1 we only make additive changes: new optional fields, new endpoints, new enum values (documented as "may grow"). Write your client to ignore fields it does not know and to tolerate new enum values.

A breaking change means a /v2. When that happens, v1 keeps working and answers with Deprecation and Sunset headers for at least six months before it is switched off, and the change is announced on this page.

What a deprecated version would send
Deprecation: true
Sunset: Sat, 01 May 2027 00:00:00 GMT
Link: <https://hoursquare.com/developers/reliability>; rel="deprecation"
Next guideCompany sign-in (SSO) with Okta, Microsoft Entra ID or Google Workspace

Ready when you are

Try HourSquare for your team.

Sign up in under a minute. No card. Free for teams up to 10.

Free up to 10 employees · GDPR-native · EU-hosted