Guide
Authentication
Every API request carries a bearer credential: an API key, or an OAuth 2.1 access token an app received with the user's consent. Either one acts as a named member of the organisation, clipped to the scopes chosen when it was issued — Kelomo has no identity that belongs to nobody. This guide covers API keys; the OAuth guide covers tokens.
Two kinds of key
Which key you want depends on whether the integration should outlive the person who set it up.
| Personal key | Service key | |
|---|---|---|
| Who creates it | Any member, for themselves | An organisation admin (org.admin) |
| Where | My settings → My API keys | Settings → Integrations → API keys and apps |
| 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 https://api.kelomo.fi/v1/me/entitlements \
-H "Authorization: Bearer kelomo_YOUR_KEY" 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.
# 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) In the OpenAPI document
The published contract declares both credentials as security schemes, and every operation accepts either one:
| Scheme | Credential |
|---|---|
bearer |
An API key, sent as Authorization: Bearer kelomo_…. HTTP bearer with no
bearerFormat: the token is opaque.
|
oauth2 |
An OAuth 2.1 access token from the authorization-code flow with PKCE (S256), sent in
the same header. Scopes are capability names; the authorization, token and refresh
URLs match /.well-known/oauth-authorization-server.
|
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_creatorfor a personal key and422 key_scope_exceeds_ownerfor 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 exports, period locks and pay codes),
invoicing.manage (invoices, billing material and the receivable ledger),
reports.team (the report catalogue and a member's own details),
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.
Area names overlap where the data does. A reporting integration that reads other people's
working time — team time, overtime, absence or balance reports — needs
worktime.supervise alongside reports.team; a read-only key holding
both reads those reports and still writes nothing.
Empty scope = the key's own member, nothing behind a capability. A
personal key created with no capabilities reaches the caller's own /v1/me/*
routes and the operations below, which need no capability because each acts as the key's
member on rows that member owns or is related to (their own absence, a project they are
assigned to, a request anyone may file) and on the tenant's read-only catalogues. 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.
POST /v1/absence-periods/{id}/cancel— Cancel an approved absence periodPOST /v1/attachments— Initiate an attachment uploadGET /v1/attachments— List the attachments of an owner objectDELETE /v1/attachments/{id}— Delete an attachmentPOST /v1/attachments/{id}/finalize— Finalize an uploaded attachmentGET /v1/attachments/{id}/url— Get a short-lived download URL for an attachmentGET /v1/balance-account-types— List balance account typesGET /v1/break-types— List break typesGET /v1/day-types— List day typesGET /v1/dimensions— List tracking dimensionsGET /v1/dimensions/{id}/values— List or look up tracking targets of a levelGET /v1/entry-types— List work entry typesGET /v1/expense-types— List the expense types a claim can useGET /v1/geo/geocode— Search places by free textGET /v1/geo/reverse-geocode— Name the place nearest to a coordinatePOST /v1/geo/route— Route between points and get the driving distanceGET /v1/geo/status— Whether geocoding and routing are availablePOST /v1/intake— Create an intake itemGET /v1/shift-types— List shift typesGET /v1/webhook-events— List the webhook event catalogue
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 -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.
# 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 → Integrations → API keys and apps 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:
HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 574
X-RateLimit-Reset: 1770300060 | Header | Meaning |
|---|---|
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.