Skip to Content
Use-case guidesAccess control

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:

FieldRequiredNotes
employee_idYesPublic UUID of the employee.
typeYesin, out, break_start or break_end.
timestampYesISO 8601 in UTC. Must fall within the [now − 24 h, now + 5 min] window; outside it → 422 validation_failed.
location_idNoSite where the check-in occurs.
noteNoFree 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

SituationResponse
Missing org:fichajes:write403 invalid_scope
timestamp outside the window (> 24 h in the past or > 5 min in the future) or invalid type422 validation_failed (with errors)
Retry with same Idempotency-Key and different body409 idempotency_conflict
Employee from another company404 not_found
Creating an employee with no free seat in the plan402 billing_required
Last updated on