Authentication and keys
How to send a key, its format and how the key lifecycle works.
Bearer key
Every request carries an Authorization header with the Bearer scheme. Only GET / and GET /openapi.json need no key:
Authorization: Bearer dk_live_...Rules that protect your key:
- Only
Authorization: Beareris accepted. TheX-API-Keyheader is not accepted because it would leak through redirects. - A key cannot be in the URL. If it is in the query string (
api_key,key,token,access_token), the API returns 400api_key_in_query. Treat such a key as leaked and rotate it. - A key is not used in the browser. The API sends no
Access-Control-*headers and anOPTIONSrequest gets 405. Always use the key from your own server.
Key format
A key looks like this: dk_live_ followed by 49 characters from [0-9A-Za-z], 57 characters in total.
| Part | Length | Purpose |
|---|---|---|
dk_live_ |
8 | prefix that people and secret scanners recognise |
| random part | 43 | 256 bits of randomness |
| checksum | 6 | Base62 CRC32 (big-endian, zero-padded) |
Regular expression: ^dk_live_[0-9A-Za-z]{49}$. The checksum lets you reject a typo and spot a key in code without a network request. The prefix dk_test_ is reserved for the future; v1 has no sandbox.
Diko stores only the SHA-256 hash of a key and a hint (dk_live_Ab12…9xYz). The key itself is never written to logs, e-mails or error responses.
Creating keys
You create keys in the Diko app, under Nustatymai → API (Settings → API), at /settings/api. The old /kurejams link still works and redirects there. The form asks for:
| Field | Requirements |
|---|---|
| Pavadinimas (Name) | 1 to 100 characters |
| Aprašas (Description) | optional, up to 500 characters |
| Leidimai (Scopes) | pick a template or individual scopes; see Scopes. They cannot be changed after creation |
| Galiojimas (Expiry) | 7, 30, 90 (default), 180 or 365 days, or a date. At most 365 days and there is no “never”. You can only shorten the expiry |
| IP apribojimas (IP allowlist) | up to 20 CIDR entries, optional; see Security guidelines for where it works |
After creation the key is shown once. The app will not let you close the dialog until you tick Išsaugojau raktą saugioje vietoje (I saved the key in a safe place). Creating, rotating, enabling and changing the IP list may ask you to confirm again (valid for 10 minutes). Revoking, disabling and renaming need no confirmation so that safe actions stay fast.
Limits: at most 25 keys (active and disabled together). Expired and revoked keys, and old keys in a rotation grace period, are not counted. You can create or rotate at most 10 keys per hour.
The key object
GET /me returns the key that made the request. The secret itself is, of course, not shown:
{
"id": "key_4Vf9771zpS8JcQdtUcR0u8",
"object": "api_key",
"name": "CRM sync",
"description": "",
"hint": "dk_live_Ab12…9xYz",
"scopes": ["meetings:read", "library:read"],
"status": "active",
"allowed_cidrs": [],
"created_at": "2026-10-11T09:00:00.000Z",
"expires_at": "2027-01-09T09:00:00.000Z",
"last_used_at": null,
"last_used_ip": null,
"first_used_at": null,
"disabled_at": null,
"revoked_at": null,
"revoked_reason": null,
"rotated_from": null,
"grace_until": null
}The rest of the GET /me response follows the same least-privilege rule, so a key handed to a third party does not reveal the account owner’s details: user.email is always masked (j***@example.com) and credits is null unless the key has the library:write scope, the only one that spends credits.
Statuses
| Status | Meaning |
|---|---|
active |
the key works |
disabled |
temporarily switched off; it can be enabled again (this needs re-authentication) |
revoked |
permanently revoked; revoked_reason says why |
expired |
derived status: expires_at has passed |
revoked_reason values: user (you revoked it), rotated (a rotation grace period ended), admin (a team administrator revoked it), owner (the team owner revoked it), user_disabled (the account was disabled), leaked (a leak was detected), dormant (unused for a long time), revoke_all (the Atšaukti visus raktus (Revoke all keys) action).
Expiry, reminders and unused keys
- Diko sends an e-mail 7 days and 1 day before the key expires (only if the key was valid for at least 14 and at least 2 days).
- The app marks a key Nenaudojamas (Unused) after 90 days without use. A key unused for 365 days is revoked automatically (
dormant). - Every key has
last_used_atand usage statistics in the app, which makes forgotten integrations easy to spot and revoke.last_used_atis updated at most every 5 minutes, so it can lag behind the real last call.
Rotation
Rotation creates a new key and lets the old one work for a while, so you can update systems without downtime.
- Grace period: immediately, 1 hour, 24 hours (default) or 7 days.
- The new key inherits the name, description, scopes and IP list. It keeps the old key’s lifetime (at most 365 days), counted from the moment of rotation.
- The old key gets
grace_untiland becomesrevoked(rotated) when the time ends. - Only one rotation of the same key can be open at a time.
A step-by-step recipe is in the Security guidelines.
Imports when a key stops working
An import created with POST /library/imports can run for minutes. When the key that created it stops working, Diko stops the import too, so a leaked key cannot keep spending credits through work it queued.
- These actions cancel the key’s imports that are still
queuedorprocessing: revoking the key (by you, by a team administrator or owner, with Atšaukti visus raktus (Revoke all keys), or automatically after a leak report from GitHub), disabling it, deleting it, rotating it with the immediately grace period, and disabling the account. Suspending the API access of an account cancels the imports of all its keys. - A
queuedimport becomescanceledat once. Aprocessingimport becomescancelingand endscanceledwhen the work stops. Each end sends the eventlibrary.import.canceled. - A rotation with a grace period (1 hour, 24 hours or 7 days) leaves the imports running, and the old key becoming
revoked(rotated) when the period ends does not cancel them. If you revoke the old key yourself during the grace period, its imports are canceled like those of any revoked key. - Work already done is charged as usual: a transcript that was already fetched or was finishing still reaches your library.
Plan
The API is available on the Verslas plan (Verslas is the product name of the business plan). In API responses this plan is the value verslas. The plan_required error lists it in required_plans.
GET /me shows the current state in access:
access.planisasmeninis(the personal plan, which does not include the API),verslasornullwhen the account has no plan;access.allowedsays whether API requests are allowed;access.reasonsays why. Access is granted bysubscription,team(a team plan),ownerorexempt(accounts that pay nothing), orpaywall_off(billing is switched off). Access is denied withsubscription_requiredorplan_required.
The plan is checked on every request, so keys are not removed when the plan changes: they work again when the plan allows. GET /me does not check the plan, so it is the right call for diagnostics.
Authentication errors
| Situation | HTTP | Code |
|---|---|---|
No Authorization header |
401 | missing_api_key |
A scheme other than Bearer, an empty key, more than one Authorization header, a bad format or checksum, or an unknown key |
401 | invalid_api_key |
| The key expired | 401 | api_key_expired |
| The key was revoked | 401 | api_key_revoked |
| The key is disabled | 401 | api_key_disabled |
| The IP is not on the key’s list | 403 | ip_not_allowed |
| The account is disabled, the e-mail is unverified or API access was suspended | 403 | account_disabled, email_not_verified, api_access_suspended |
| No paid access | 402 | subscription_required |
| The plan does not include the API | 403 | plan_required |
401 responses carry a WWW-Authenticate header: Bearer realm="diko" when there is no Authorization header, and Bearer realm="diko", error="invalid_token" otherwise.
When a key is revoked, disabled or rotated, the change reaches the API within about 10 seconds.