Store keys in a secrets manager
Doppler, 1Password, AWS Secrets Manager, Vault — never in .env
files committed to git, never in client-side bundles.
The Kirim Public API uses bearer-token authentication. Every request
to /v1/* (except /v1/health and /v1/openapi.json) must carry an
Authorization: Bearer <token> header.
GET /v1/me HTTP/1.1Host: api.kirim.chatAuthorization: Bearer kdv_live_TavbPKwIuqOr69ALEKLNennZkdv_live_<24 url-safe base64 chars>kdv_ prefix identifies the key as issued by Kirim.live indicates the production environment.A future kdv_test_* prefix is reserved for the sandbox environment
(not yet shipped).
API keys are issued through the dashboard under Developers → API Keys. Each key belongs to one organization and grants full org-wide access — there is no scoped permission system today (deferred to a later phase, non-breaking when added).
The plaintext is shown once in the create-key modal and never again. Kirim stores only an argon2id hash plus the first 12 and last 4 characters for display.
Pass the plaintext as a bearer token on every request:
import { Kirim } from '@kirimdev/sdk'
const kirim = new Kirim({ apiKey: process.env.KIRIM_KEY! })const ctx = await kirim.me()curl https://api.kirim.chat/v1/me \ -H "Authorization: Bearer $KIRIM_KEY"import os, httpxctx = httpx.get( "https://api.kirim.chat/v1/me", headers={"Authorization": f"Bearer {os.environ['KIRIM_KEY']}"},).json()The API does not accept the key as a query parameter, basic auth, or cookie. Bearer header only.
Keys may be issued with an optional expires_at timestamp. Once the
clock passes that moment, requests return:
HTTP/1.1 401 Unauthorized{ "error": { "type": "authentication_error", "code": "expired_api_key", "message": "This API key has expired.", "request_id": "req_…" }}Expiration is enforced server-side at the auth-middleware layer; there is no client-visible warning before it triggers, so wire your own heads-up alert if needed.
Click Revoke next to a key in the dashboard. Effective immediately — the verification cache is invalidated synchronously. The next request with that key returns:
HTTP/1.1 401 Unauthorized{ "error": { "type": "authentication_error", "code": "revoked_api_key", "message": "This API key has been revoked.", "request_id": "req_…" }}Revocation is irreversible. To rotate, issue a new key first, update your application, then revoke the old key.
For each request Kirim:
Authorization: Bearer <token> header.key_prefix index.api_keys (single-row index hit).revoked_at IS NULL and expires_at IS NULL OR expires_at > now().A 60-second Redis cache amortises the argon2id cost for high-RPS keys. Revoke / rotate / expiration changes invalidate the cache synchronously.
| HTTP | type | code | Trigger |
|---|---|---|---|
| 401 | authentication_error | missing_api_key | No Authorization header |
| 401 | authentication_error | invalid_api_key | Malformed token, unknown prefix, or hash mismatch |
| 401 | authentication_error | revoked_api_key | Key has been revoked |
| 401 | authentication_error | expired_api_key | expires_at has passed |
All four return the standard error envelope with a
request_id you can quote in support tickets.
Store keys in a secrets manager
Doppler, 1Password, AWS Secrets Manager, Vault — never in .env
files committed to git, never in client-side bundles.
One key per environment + consumer
Dev / staging / prod, and per integration (Zapier, n8n, your backend). Granular tracking makes leak response painless.
Rotate proactively
No enforced cadence, but every 90 days is a reasonable default. Issue new → update apps → revoke old.
Set expirations on temporary keys
For one-off scripts, contractor access, or trial integrations.
Audit the API log
Every /v1/* call appears under Developers → API Logs with
method, path, status, latency, IP, request id. 30-day retention.
Subscribe to key-lifecycle webhooks
Once shipped, webhook.subscription.* events give real-time
alerts when a key is created or revoked.