# Authentication

> How API keys work, how to send one with a request, how to keep the secret on your server, and what happens when a key is deleted or stops working.

- Source: https://docs.kotova.io/x/api/authentication
- API base URL: https://api.kotova.io/x/v1
- OpenAPI document: https://api.kotova.io/x/v1/openapi.json

Requests that quote, create or read an order are made with your account's API key.
The catalogue endpoints and the XML rates feed are public and need no key.

## Your key

An API key has two parts:

| Part | Looks like | What it is for |
| --- | --- | --- |
| Key ID | `ktv_pk_` followed by 24 letters and digits | Identifies the key. It is not a secret: the account API page shows it, `GET /me` returns it as `key.public_key`, and you can quote it to support. |
| Secret key | `ktv_sk_` followed by 46 letters and digits | Authenticates your requests. It is shown once, when the key is created, and never again. |

Kotova keeps only a fingerprint of the secret key, so nobody can show it to you a
second time, support included. If you lose it, regenerate the key.

## Creating a key

You create the key on the [account API page](https://app.kotova.io/account/api) at
app.kotova.io. Creating one needs:

- an account with a confirmed e-mail address that is not a disposable one;
- a referral code, which makes you an affiliate (the page offers to generate one);
- two-factor authentication with an authenticator app, turned on under **Personal
  data** in your account;
- your acceptance of the [API Terms of Use](https://kotova.io/terms/api).

Choose **Create API key** and enter the 6-digit code from your authenticator app.
Backup codes cannot be used here. The page then shows the secret key once, and stays
open until you confirm that you have stored it. Creating and regenerating keys is
limited to 5 per hour per account.

Each time a key is created, regenerated or deleted, Kotova sends a notice to your
account's e-mail address. If you did not make the change, secure your account and
delete the key.

## Sending the key

Send the secret key with every request to a keyed endpoint, in the `Authorization`
header:

```http
Authorization: Bearer ktv_sk_…
```

Never put the key in a URL. `GET /me` is a quick way to check that a key works: it
returns the key ID, your referral code, your rate limit and today's usage.

**curl**

```sh
curl -sS https://api.kotova.io/x/v1/me \
  -H "Authorization: Bearer $KOTOVA_API_KEY"
```

**JavaScript**

```js
const res = await fetch('https://api.kotova.io/x/v1/me', {
  headers: { Authorization: `Bearer ${process.env.KOTOVA_API_KEY}` },
});
const me = await res.json();
console.log(me.key.public_key, me.rate_limit.limit);
```

**Python**

```python
import os
import requests

API_KEY = os.environ["KOTOVA_API_KEY"]
res = requests.get(
    "https://api.kotova.io/x/v1/me",
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=10,
)
me = res.json()
print(me["key"]["public_key"], me["rate_limit"]["limit"])
```

An excerpt of the response:

```json
{
  "key": {
    "public_key": "ktv_pk_3f9a7c2kd8q1m4x6b2n5r7t9",
    "created_at": "2026-11-03T09:12:44Z",
    "last_used_at": "2026-11-10T10:12:01Z"
  },
  "affiliate": { "ref_code": "KX7M2Q", "status": "active" },
  "rate_limit": {
    "limit": 250,
    "remaining": 243,
    "window_seconds": 60,
    "resets_at": "2026-11-10T10:16:00Z",
    "weights": { "create_order": 50, "default": 1 }
  },
  "usage_today": { "quotes": 12, "orders": 1, "reads": 40, "validations": 0, "limited": 0 }
}
```

`GET /me` is documented in [Utilities](https://docs.kotova.io/x/api/utilities).

Send no key to the keyless endpoints (`/health`, `/openapi.json`, `/currencies`,
`/networks`, `/sources`, `/pairs` and the rates feed). They ignore it, and a request
without one can be answered from the CDN cache.

## Keep the secret on your server

Anyone who has your secret key can quote and create orders in your account's name and
use up its rate limit. Keep it on your server, in an environment variable or a secret
manager, and never put it in a website, a mobile app or a public code repository.

Browsers enforce this too. The keyed endpoints send CORS headers only to an allowlist
that holds the **Try it** consoles on `https://docs.kotova.io`, so a script on any
other web page cannot read their responses. That does not protect a key you ship in
an app: call the API from your server, and let your website or app talk to your
server.

The keyless endpoints answer `Access-Control-Allow-Origin: *`, so your web pages may
call them directly, for example to show the currency list.

> [!NOTE]
> **Using your key in the Try it consoles**
>
> The consoles on these pages send requests from your browser straight to the API. Your
> key stays in the page and is sent only to the base URL the console shows. It is kept
> in memory, or in this browser tab's session storage if you tick **Remember in this
> tab**, and never in local storage. Creating an order from a console creates a real
> order, so the console asks you to confirm every time.

## One key per account

An account has one active key at a time. Your rate limit, your commission and your
orders belong to the account rather than to the key:

- a new key starts with the same rate limit and the same commission, and the budget
  of the current minute is not reset;
- orders created with an earlier key stay readable with the new one, through
  `GET /orders/{id}` and `GET /orders`.

## Regenerating or deleting a key

Both actions are on the [account API page](https://app.kotova.io/account/api).

- **Regenerate** replaces the key: you get a new key ID and a new secret key, after
  confirming with a code from your authenticator app. The old key stops working.
- **Delete** revokes the key without a replacement. Orders already created keep
  running and still count for your affiliate profile. You can create a new key
  afterwards.

In both cases, order creation with the old key stops immediately. Its other requests
are refused within 30 seconds. Put the new secret key on your servers straight away:
requests with the old one answer `401` [`key_revoked`](https://docs.kotova.io/x/api/errors#key_revoked).

## When a key stops working

| Status | Code | Why |
| --- | --- | --- |
| 401 | [`unauthorized`](https://docs.kotova.io/x/api/errors#unauthorized) | No `Authorization` header, a value that is not a well-formed `ktv_sk_` key, or a key that does not exist. |
| 401 | [`key_revoked`](https://docs.kotova.io/x/api/errors#key_revoked) | The key was regenerated or deleted on the account API page, or revoked by Kotova. |
| 403 | [`affiliate_inactive`](https://docs.kotova.io/x/api/errors#affiliate_inactive) | The key exists, but its affiliate profile is inactive or has expired, or the account that created the key no longer exists or no longer owns that profile. |

Both `401` answers carry `WWW-Authenticate: Bearer realm="kotova-x"`. While your
affiliate profile is inactive, the account API page says that API access is paused;
write to [api@kotova.io](mailto:api@kotova.io) to find out why.

A request refused at authentication costs nothing from your
[rate limit](https://docs.kotova.io/x/api/rate-limits).

## Failed authentication attempts

Failed authentications are limited per IP address (per /64 for IPv6) to 30 a minute.
Once an address has sent 30 keys the API does not know within a minute, its requests
with keys the API cannot yet confirm may be refused with `429`
[`rate_limited`](https://docs.kotova.io/x/api/errors#rate_limited) and `details.reason: "auth_failures"`
until the minute ends; `Retry-After` gives the seconds to wait. A key that has
authenticated successfully within the last hour is not held up by this limit.

## How API orders are attributed

The key belongs to your affiliate profile. Every order created with it is attributed
to that profile and earns commission like an order through your referral link. See
[How you earn](https://docs.kotova.io/x/api#how-you-earn).
