Public API
Every Keanan workspace exposes the same REST API the dashboard and the app use. With an API key you can read and write your workspace from scripts, payroll providers, HR systems and AI assistants, and webhooks push events out as they happen.
The API is included in Managed, Self hosted and Reseller plans, free during a pilot, and available as an add on for Starter.
Create a key
Section titled “Create a key”Open Settings, Developers (admins) or Time portal, API and assistants (everyone) and create a key.
- Personal keys act as you, narrowed to the scopes you pick. An employee can only grant scopes their role could use anyway.
- Service keys (admins only) act for the workspace, for example a nightly sync into your payroll provider.
The secret is shown once and stored hashed. Send it as a bearer token:
curl https://acme.keanan.app/api/v1/pto/balance/ \ -H "Authorization: Bearer kn_live_..."Scopes
Section titled “Scopes”| Scope | Grants |
|---|---|
people:read |
My own profile, contracts and benefits |
team:read |
Other people’s records: team directory and every admin or manager listing (managers and admins only) |
people:write |
Create, update and deactivate people (admins) |
time:read / time:write |
Time entries, clock, my time report; track time |
absences:read / absences:write / absences:approve |
Balances and requests; request time off; review requests |
expenses:read / expenses:write / expenses:approve |
Expense claims; submit; review |
payroll:read |
Payslips, invoices, wallet |
reports:read |
Team reports, presences, overtime, dashboard summaries |
webhooks:manage |
Create and manage webhooks |
A key can never do more than its owner: the owner’s role is checked as well,
so an employee’s key cannot reach manager endpoints even if the scope name
matches. Endpoints that have no place in an integration (login, licensing,
pilots, workspace export, theme, key management) reject API keys outright. The interactive
reference at /api/docs/public/ on your workspace lists exactly what a key
can call and which scope each call needs; the raw schema is at
/api/schema/public/.
Conventions
Section titled “Conventions”Responses arrive in a stable envelope:
{ "status": 200, "message": "Success", "data": { } }Errors carry a human readable message and field errors in errors. Dates
are ISO YYYY-MM-DD, timestamps ISO 8601 with a time zone, money as strings
with two decimals plus a three letter currency.
Rate limits are per key: 120 requests a minute on Starter and pilots, 600 on
Managed, 1200 on Self hosted. A 429 tells you how long to wait.
Every write made with a key is recorded in the audit trail with the key’s
prefix and source, so the log reads created employee · api:kn_live_ab12.
Webhooks
Section titled “Webhooks”Admins register webhooks under Settings, Developers. Keanan POSTs a JSON body to your URL for the events you pick:
| Event | When |
|---|---|
absence.requested, absence.approved, absence.denied, absence.cancelled |
Time off lifecycle |
expense.submitted, expense.approved, expense.denied, expense.deleted |
Expense claims |
time_entry.created, time_entry.updated, time_entry.deleted |
Manual time entries |
clock.started, clock.stopped |
Live clock |
payment.completed, invoice.issued |
Payroll |
employee.created, employee.deactivated |
People |
{ "event": "absence.approved", "occurred_at": "2026-08-21T10:12:44+00:00", "actor": { "id": "…", "email": "maria@acme.example", "name": "Maria Schmidt" }, "data": { "id": "…", "employee": { "…": "…" }, "start_date": "2026-09-07", "end_date": "2026-09-08", "status": "approved" }}Each delivery carries X-Keanan-Event, X-Keanan-Delivery and
X-Keanan-Signature: sha256=<hmac>; the HMAC is computed over the raw body
with the webhook secret shown when you created it. Verify it before you
trust the payload:
import hashlib, hmac
def verify(secret: str, body: bytes, header: str) -> bool: expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header)Answer with any 2xx. Anything else (or a timeout after ten seconds) is
retried five times with backoff from one minute to twelve hours. Redirects
are not followed. The Developers page shows every delivery with its status,
response code and a Resend button (three resends per delivery), Ping
sends a webhook.ping to test the wiring, and Rotate secret issues a new
signing secret when the old one may have leaked.
Webhook URLs must be public https addresses; private networks, loopback, link local and cloud metadata addresses are refused at creation and again at every delivery. Delivery records are kept for 30 days.
Examples
Section titled “Examples”Sync approved absences into another system:
import requests
API = "https://acme.keanan.app/api/v1"headers = {"Authorization": "Bearer kn_live_..."}absences = requests.get(f"{API}/pto/admin/requests/", params={"status": "approved"}, headers=headers).json()["data"]Push a time entry from a ticket system:
curl -X POST https://acme.keanan.app/api/v1/tracking/entries/ \ -H "Authorization: Bearer kn_live_..." -H "Content-Type: application/json" \ -d '{"started_at":"2026-08-21T09:00:00+02:00","ended_at":"2026-08-21T11:30:00+02:00","description":"TICKET-42","billable":true}'Connect an AI assistant instead of writing code: see the MCP server.