Developers

Guide

Authentication

Every API request authenticates with an API key sent as a bearer token. A key always acts as a named member of the organisation, clipped to the scopes chosen at creation — Kelomo has no identity that belongs to nobody.

Two kinds of key

Which key you want depends on whether the integration should outlive the person who set it up.

Personal keyService key
Who creates it Any member, for themselves An organisation admin (org.admin)
Where Settings → your own API keys Settings → API access
Endpoint POST /v1/me/api-keys POST /v1/api-keys/service
Acts as Its creator An explicitly named owner member
Scopes Any subset of your own capabilities; may be empty Required, non-empty, within the owner's capabilities
Owner can change No — the key is that person Yes, POST /v1/api-keys/{id}/reassign
When the member leaves The key ends with them Hand it over; the token keeps working

A key's raw token starts with kelomo_ and is shown once at creation — only a hash is stored, so treat the token like a password and keep it in a secret manager. The key list shows a recognisable prefix (kelomo_ab12…), a purpose note and rolling 7-day usage so you can tell keys apart later.

curl
curl https://api.kelomo.fi/v1/me/entitlements \
  -H "Authorization: Bearer kelomo_YOUR_KEY"

Bearer keys are the authentication method for the API.

Personal keys

A personal key is the key you make for yourself: a script on your laptop, an MCP client, a spreadsheet that reads your own hours. Every member can mint one, whatever their role — a key carries its creator's identity, so an employee's key is employee-sized and an admin's key is admin-sized. It is meant to end when the membership does.

Service keys

A service key belongs to the organisation. It still executes with a real member's authority — every row-level scope in Kelomo resolves through a membership — but that ownership is explicit and transferable rather than an accident of who happened to click "create".

Choose a service key for anything that runs unattended: a nightly payroll sync, a BI refresh, a Zapier or Make scenario, an ERP bridge. A personal key would tie that job to one employment: the day its creator is deactivated, the integration stops — and it stops at 03:00, in a job nobody is watching. A service key turns that outage into a two-click handover.

curl
# Admin session (org.admin). Both the owner and the scopes are required.
curl -X POST https://api.kelomo.fi/v1/api-keys/service \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Nightly payroll sync",
        "ownerMembershipId": "OWNER_MEMBERSHIP_ID",
        "capabilities": ["payroll.run", "worktime.supervise"]
      }'

# → { "id": "…", "token": "kelomo_NEW…" }   (shown once)

Scopes are capabilities

A key's scopes are the same capability catalogue that drives permissions inside the product — there is no separate API scope vocabulary. Two rules follow from that:

  • A key can never hold a capability the member it acts as lacks. Requesting one fails with 422 key_scope_exceeds_creator for a personal key and 422 key_scope_exceeds_owner for a service key — including on a reassignment, so a handover can never quietly widen what the key reaches.
  • At request time the effective rights are the intersection of that member's current capabilities and the key's scopes — if the member loses a capability, their keys lose it too.

Capabilities are product-area sized, for example worktime.supervise (team worktime and approvals), scheduling.plan (shift planning), projects.manage and projects.read, crm.use, payroll.run (payroll and money documents), reports.team, members.manage and integrations.manage (webhook endpoints and the integration-event log). The key-creation screen lists exactly the capabilities available to the member the key will act as, and each operation's requirement is stated in the API reference.

Empty scope = self-only. A personal key created with no capabilities reaches the caller's own /v1/me/* routes and the tenant's read-only configuration catalogues, and nothing behind a capability gate. Broad access is always an explicit choice of scopes, never a default. A service key may not be scope-less at all — an empty list is refused with 422 service_key_requires_capabilities, because a machine credential aimed at one person's own surface is a mistake worth refusing.

Personal data is out of reach of every key. Sickness certificates and other absence attachments, case notes, employee documents and profiles, recruitment candidates, occupational-safety records and whistleblowing evidence are never readable with an API key, whatever scopes it holds and whoever created it. Attachments reachable over the API belong to invoices, quotes, deals, projects, purchases and tasks.

Read-only keys

Mark a key read-only to make it structurally safe for dashboards, reporting and AI assistants: any request other than GET is refused with 403 read_only_key, whatever the key's scopes say.

Expiry

Keys can carry an expiry instant — the creation UI defaults to one year, and "never" is an explicit choice. Past the expiry the key stops authenticating with 401 api_key_expired. Prefer expiring keys plus rotation over immortal ones.

Rotation

Rotate a key without downtime: the rotate call returns a new token (shown once), and the old token keeps working for a 24-hour grace period while you roll out the new one.

Rotation is owner-only. It lives on /v1/me/api-keys/{id}/rotate and works on your own keys, from a human session — an API key may never manage API keys. An admin cannot rotate somebody else's key, because a rotated token is handed to whoever makes the call, and that would be a quiet way to obtain a credential in another member's name. The admin lever over another member's key is revocation, which takes authority away instead of moving it: DELETE /v1/api-keys/{id} works on any key in the organisation.

curl
curl -X POST https://api.kelomo.fi/v1/me/api-keys/KEY_ID/rotate \
  -H "Authorization: Bearer kelomo_YOUR_SESSION"

# → { "token": "kelomo_NEW…" }   (shown once; the old token works for 24 h)

When the owner leaves: 401 api_key_owner_inactive

A key stops authenticating the moment the member it acts as is deactivated. That is a distinct code, not a generic invalid_token, precisely so an integration can tell "my credential is wrong" from "the person behind my credential left":

  • Service key. Reassign it to another member and the same token resumes working — the scopes, the usage history and the audit trail all survive; only the executing identity moves. The new owner's capabilities are re-checked against the key's scopes, so the handover cannot widen its reach.
  • Personal key. Nothing to fix — a personal key is meant to end with its member. Revoke it, and if the job it did must keep running, replace it with a service key.
curl
# Admin session (org.admin). The token and the scopes are unchanged.
curl -X POST https://api.kelomo.fi/v1/api-keys/KEY_ID/reassign \
  -H "Content-Type: application/json" \
  -d '{ "ownerMembershipId": "NEW_OWNER_MEMBERSHIP_ID" }'

Deactivating a member is never blocked by the keys they hold — offboarding must not be stoppable — so the admin's key list flags every key whose owner is inactive, and Settings → API access is where a handover happens.

Rate limits

Keyed traffic is limited per key — 600 requests per minute by default — with an aggregate cap per organisation, so one runaway integration cannot starve the others. Every keyed response reports where you stand:

Response headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 574
X-RateLimit-Reset: 1770300060
HeaderMeaning
X-RateLimit-Limit Requests allowed per key in the current one-minute window.
X-RateLimit-Remaining Requests left in the window.
X-RateLimit-Reset Unix timestamp (seconds) when the window resets.

Exceeding a limit returns 429 with a Retry-After header — wait that many seconds before retrying, and back off exponentially on repeated 429s rather than hammering the window boundary.

Auditing

Changes made with an API key are recorded in the organisation's audit trail with the key's identity attached, so an integration's writes are always distinguishable from the member's own actions.