# Authentication and keys

> How to send a key, its format and how the key lifecycle works.

Source: https://diko.app/en/api-docs/authentication

## Bearer key

Every request carries an `Authorization` header with the `Bearer` scheme. Only `GET /` and `GET /openapi.json` need no key:

```http
Authorization: Bearer dk_live_...
```

Rules that protect your key:

- **Only `Authorization: Bearer` is accepted.** The `X-API-Key` header 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 400 `api_key_in_query`. Treat such a key as leaked and [rotate it](https://diko.app/en/api-docs/security.md#key-rotation-recipe).
- **A key is not used in the browser.** The API sends no `Access-Control-*` headers and an `OPTIONS` request 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`](https://studio.diko.app/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](https://diko.app/en/api-docs/scopes.md). 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](https://diko.app/en/api-docs/security.md#ip-allowlist) 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:

```json
{
  "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_at` and usage statistics in the app, which makes forgotten integrations easy to spot and revoke. `last_used_at` is 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_until` and becomes `revoked` (`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](https://diko.app/en/api-docs/security.md#key-rotation-recipe).

## 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 `queued` or `processing`:** 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 `queued` import becomes `canceled` at once. A `processing` import becomes `canceling` and ends `canceled` when the work stops. Each end sends the event `library.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.plan` is `asmeninis` (the personal plan, which does not include the API), `verslas` or `null` when the account has no plan;
- `access.allowed` says whether API requests are allowed;
- `access.reason` says why. Access is granted by `subscription`, `team` (a team plan), `owner` or `exempt` (accounts that pay nothing), or `paywall_off` (billing is switched off). Access is denied with `subscription_required` or `plan_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`](https://diko.app/en/api-docs/errors.md#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`](https://diko.app/en/api-docs/errors.md#invalid_api_key) |
| The key expired | 401 | [`api_key_expired`](https://diko.app/en/api-docs/errors.md#api_key_expired) |
| The key was revoked | 401 | [`api_key_revoked`](https://diko.app/en/api-docs/errors.md#api_key_revoked) |
| The key is disabled | 401 | [`api_key_disabled`](https://diko.app/en/api-docs/errors.md#api_key_disabled) |
| The IP is not on the key's list | 403 | [`ip_not_allowed`](https://diko.app/en/api-docs/errors.md#ip_not_allowed) |
| The account is disabled, the e-mail is unverified or API access was suspended | 403 | [`account_disabled`](https://diko.app/en/api-docs/errors.md#account_disabled), [`email_not_verified`](https://diko.app/en/api-docs/errors.md#email_not_verified), [`api_access_suspended`](https://diko.app/en/api-docs/errors.md#api_access_suspended) |
| No paid access | 402 | [`subscription_required`](https://diko.app/en/api-docs/errors.md#subscription_required) |
| The plan does not include the API | 403 | [`plan_required`](https://diko.app/en/api-docs/errors.md#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.

> **Note:**
> A malformed key, an unknown key and an unusable header get the **same** response, and the IP allowlist is checked before key-state errors. That way a leaked key used from a foreign address cannot find out whether it is still valid.

When a key is revoked, disabled or rotated, the change reaches the API within about **10 seconds**.
