Essentials
Get a key
In HourSquare: Settings › API & integrations › New key. Pick the scopes it needs. The secret is shown once; the Client ID (hsq_…) stays visible.
Get a token
Exchange the key for an access token (OAuth 2.0 client credentials) and send it as a Bearer:
curl -s -X POST https://api.hoursquare.com/api/public/v1/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d grant_type=client_credentials -d client_id=hsq_k7Qm3xZp \
--data-urlencode "client_secret=$HSQ_KEY"
# → {"access_token":"…","token_type":"Bearer","expires_in":900}
curl -s https://api.hoursquare.com/api/public/v1/me -H "Authorization: Bearer $TOKEN"
Tokens live 15 minutes. Cache one and request a new one when it expires or when a call answers 401. The token endpoint also accepts the key as the HTTP Basic password.
Rate limits
- 300 requests per minute per key on the resources.
- 60 per minute per key on the token endpoint.
- Sliding windows, counted per server instance — so approximately.
- Every response carries
RateLimit, RateLimit-Policy and X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset; a 429 rate_limited carries Retry-After.
Revocation
Revoking a key stops new tokens immediately; tokens already issued keep working until they expire. Within 15 minutes of Revoke every request with that key fails.
Errors
Every error is application/problem+json (RFC 9457): type, title, status, detail, instance, the numeric code, a stable snake_case error to branch on, and a correlationId to quote to support. validation_failed (422) lists the offending fields in errors[]. Never match on detail; it is written for people.
| error | status | code |
invalid_credentials | 401 | 1703 |
invalid_token | 401 | 1704 |
feature_disabled | 403 | 2506 |
forbidden | 403 | 1403 |
insufficient_scope | 403 | 2909 |
time_tracking_disabled | 403 | 2908 |
company_not_found | 404 | 2201 |
employee_not_found | 404 | 2202 |
not_found | 404 | 1400 |
time_entry_not_found | 404 | 2501 |
work_week_not_found | 404 | 2502 |
already_clocked_in | 409 | 2904 |
already_exists | 409 | 1501 |
business_rule_violation | 409 | 2503 |
clock_out_before_clock_in | 409 | 2911 |
day_locked | 409 | 2907 |
has_dependencies | 409 | 2505 |
idempotency_in_flight | 409 | 2903 |
no_open_session | 409 | 2905 |
overlaps_existing_entry | 409 | 2906 |
session_too_long | 409 | 2901 |
stale_data | 409 | 1409 |
idempotency_key_reused | 422 | 2902 |
validation_failed | 422 | 1800 |
rate_limited | 429 | 2910 |
internal_error | 500 | 1500 |
Idempotency
Send an Idempotency-Key header (unique per request, at most 128 characters) on writes to make retries safe: the same key replays the first response, errors included, for 24 hours. Reusing a key with a different body answers 422 idempotency_key_reused; a retry while the first request is still running answers 409 idempotency_in_flight. Without the header, the same employee, event type and caller within 60 seconds of an existing entry returns that entry with 200 instead of a duplicate.
Dry run
X-HourSquare-Dry-Run: true on any write validates and resolves everything, writes nothing, and returns the would-be result with dryRun: true and status 200. The server treats an absent header as a real write.
On this page the Dry run switch in the top bar is the source of truth: while it is on, every write sent from here carries the header as true whatever the parameter says; switch it off and requests run against live data.
Time zones
occurredAt, clockIn and clockOut are RFC 3339 timestamps with an explicit offset (2026-09-14T08:59:12+04:00; a naive timestamp is a 422). They are recorded in the employee's workplace time zone, else the company's; when neither is configured the wall-clock you sent is kept. The response echoes timeZone (an IANA id or offset) and the server-resolved instant. Timestamps may be at most 5 minutes in the future and 30 days in the past.
Versioning
The major version is in the path (/api/public/v1). Within v1 only additive changes happen: new optional fields, new endpoints, new enum values (documented as “may grow”). Breaking changes get a new major version; the old one then carries Deprecation and Sunset headers for at least six months.
Scopes
A key carries the scopes it was created with; each operation names the one it needs. Calling without it answers 403 insufficient_scope. A person's own HourSquare token may call the operations their permissions cover.
attendance.record (86)- Record clock-in / clock-out events and completed shifts for any employee of the company, and set the external (badge / HRIS) id an employee is matched by. Machine-only: a person never holds this permission.
attendance.read (47)- List an employee's time entries (work date, times, hours, status, source) so a device or bridge can reconcile what it sent.
attendance.reports (49)- Read aggregated attendance and overtime reports. Reserved: no public endpoint uses it yet.
employees.read (52)- List the company's employees with their ids, external ids, names, e-mail addresses and employment status — what an integrator needs to build its badge map.
leave.read (72)- Read leave balances and requests. Reserved: no public endpoint uses it yet.
Keep it safe
- A key or a token never belongs in a URL, a query string or a log line.
- Never use a key from a browser page or a mobile app; keys are for servers and devices you control.
- When in doubt, rotate: create a new key, move the integration, revoke the old one.
- Public routes send no CORS headers on purpose; a browser call fails, and that is the intended outcome. This page works because it is served from the same origin.
The full document: openapi.json. Public hostname: https://api.hoursquare.com.