Access control
Goal: have your access control system (turnstiles, card readers, kiosks) record check-ins in Kinmu in real time.
Required scopes: org:fichajes:write (record) and org:fichajes:read (query).
Record a check-in
POST /v1/check-ins records an event on behalf of an employee. It is persisted with source=api, is audited and respects workday validations (no rule bypass).
curl -s -X POST "$KINMU_BASE_URL/check-ins" \
-H "Authorization: Bearer $KINMU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"employee_id": "a1b2c3d4-…",
"type": "in",
"timestamp": "2026-07-08T08:00:00Z",
"location_id": "loc-uuid",
"note": "Main door access"
}'Body fields:
| Field | Required | Notes |
|---|---|---|
employee_id | Yes | Public UUID of the employee. |
type | Yes | in, out, break_start or break_end. |
timestamp | Yes | ISO 8601 in UTC. Must fall within the [now − 24 h, now + 5 min] window; outside it → 422 validation_failed. |
location_id | No | Site where the check-in occurs. |
note | No | Free text (e.g. the reader’s identifier). |
201 response:
{
"id": "ci-uuid",
"employee_id": "a1b2c3d4-…",
"type": "in",
"timestamp": "2026-07-08T08:00:00Z",
"location": { "id": "loc-uuid", "name": "Madrid Office" },
"source": "api",
"validated": true,
"created_at": "2026-07-08T08:00:01Z",
"updated_at": "2026-07-08T08:00:01Z"
}The timestamp window is narrow: 24 h into the past and 5 minutes into the future. A reader that buffers events offline for more than 24 h will have the whole batch rejected when it reconnects, event by event, with 422. Design the buffer to flush as soon as the network is back, and treat that 422 as something to reconcile by hand (or from the dashboard), not as retryable: the retry won’t get in either.
Use an Idempotency-Key on every check-in. Turnstiles retry on network outages; with an idempotent key you avoid recording the same access twice. Resending the same key returns the original check-in; reusing it with a different body gives 409.
Map your employees
Your access system identifies people by card or PIN; Kinmu by employee_id (UUID). Build the mapping once and refresh it periodically:
Download the employees and their structure
curl -s "$KINMU_BASE_URL/employees?status=active&limit=100" \
-H "Authorization: Bearer $KINMU_API_KEY"
curl -s "$KINMU_BASE_URL/locations" -H "Authorization: Bearer $KINMU_API_KEY"Store the card → employee_id and zone → location_id mapping
Use email or a field from your master record to match people; store the Kinmu UUIDs. The employee email is unique per company, not globally: the same person can exist in another Kinmu company without colliding with yours.
If your integration creates employees (POST /v1/employees), be aware the response may be 402 billing_required: the subscription is alive but the plan has no free seats. It is not retryable; the plan has to be upgraded in the dashboard.
Refresh with updated_since
Re-read only what changed: GET /v1/employees?updated_since=<last sync>.
Query check-ins
To reconcile, read the events with a required range of ≤ 92 days:
curl -s "$KINMU_BASE_URL/check-ins?employee_id=a1b2…&from=2026-07-01&to=2026-07-08" \
-H "Authorization: Bearer $KINMU_API_KEY"Want to react in real time to every check-in (for example, for a presence board)? Subscribe to the checkin.created event with webhooks instead of polling.
Common errors
| Situation | Response |
|---|---|
Missing org:fichajes:write | 403 invalid_scope |
timestamp outside the window (> 24 h in the past or > 5 min in the future) or invalid type | 422 validation_failed (with errors) |
Retry with same Idempotency-Key and different body | 409 idempotency_conflict |
| Employee from another company | 404 not_found |
| Creating an employee with no free seat in the plan | 402 billing_required |