# Authentication and API keys

> Send your API key as a bearer token, check which key you use, and validate the key format and checksum offline.

Every public route takes an API key in the `authorization` header. Keys come from the console at https://thinqit.ai/console/: sign in with your email address and create a test or live key. Test keys are free. This page covers the header, the two kinds of key, scopes, how to check which key you are using, and the key format with its built-in checksum.

## Send the key

```bash
curl https://api.thinqit.ai/v1/models \
  -H "authorization: Bearer $DEX_API_KEY"
```

The SDKs and the CLI read the key from the `DEX_API_KEY` environment variable, or take it as an argument. Never put a key in source code, a URL or a log line.

A missing key gives `401 missing_api_key`. A key that does not exist gives `401 invalid_api_key`. A revoked or expired key gives `401 revoked_api_key` or `401 expired_api_key`. See [Errors](/docs/reference/errors/#status-codes).

## Live keys and test keys

| Kind | Starts with | Billed | Serving path | Limits |
| --- | --- | --- | --- | --- |
| Live | `dex_live_` | Yes, from your prepaid balance | GPU, with the fallback when allowed | 300 requests and 300,000 tokens per minute by default |
| Test | `dex_test_` | No | GPU only | 30 requests and 40,000 tokens per minute, 250,000 tokens a day per account |

See [Rate limits](/docs/reference/rate-limits/) and [Tokens and billing](/docs/reference/billing/).

## Scopes

A key carries one or more scopes. A new key gets all three unless you narrow it in the console.

| Scope | Allows |
| --- | --- |
| `decide` | `POST /v1/decide` |
| `usage:read` | `GET /v1/usage` |
| `balance:read` | `GET /v1/balance` |

`GET /v1/models` and `GET /v1/key` work with any valid key, whatever its scopes. A key without the scope a route needs gets `403 insufficient_scope`.

## Check which key you are using

`GET /v1/key` returns the key that made the call: its id, display prefix, mode, scopes, account and creation time. It never returns the secret. Use it to confirm a deployment has the right key.

```bash tab="curl"
curl https://api.thinqit.ai/v1/key \
  -H "authorization: Bearer $DEX_API_KEY"
```

```python tab="Python"
from thinqit_dex import Client

key = Client().get_key()
print(key.prefix, key.mode, key.scopes, key.account_id)
```

```ts tab="TypeScript"
import { Client } from "@thinqit/dex";

const key = await new Client().getKey();
console.log(key.prefix, key.mode, key.scopes, key.account_id);
```

```bash tab="CLI"
dex whoami
```

```json
{
  "object": "key",
  "id": "key_01M54VQCG06CQ643DZVMXXQKFB",
  "prefix": "dex_live_Q7mK",
  "mode": "live",
  "scopes": ["decide", "usage:read", "balance:read"],
  "account_id": "acc_01M4ZPXYG0F5KZNWJ47TAN9ZT2",
  "created": 1792225800
}
```

The values above are illustrative. See [GET /v1/key](/docs/api/getCurrentKey/) in the API reference.

## Key format and checksum

A key is a mode prefix followed by 40 base62 characters: 34 random characters (about 202 bits) and a 6-character checksum. The checksum lets secret scanners and your own tools reject a mistyped key without calling the API.

- **Alphabet.** `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`, with digit values 0 to 61 in that order.
- **Shape.** The key matches `^dex_(live|test)_([0-9A-Za-z]{34})([0-9A-Za-z]{6})$`.
- **Checksum input.** The UTF-8 bytes of the mode prefix plus the 34 random characters (43 bytes).
- **Checksum.** CRC-32 as in IEEE 802.3 and zlib, as an unsigned 32-bit number, written in base62 with the most significant digit first and left-padded with `0` to exactly 6 characters.
- **Test vectors.** `dex_live_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0JnKxX` and `dex_test_0123456789abcdefghijABCDEFGHIJxyzX1wdXRa` are well formed. Changing any one character of either breaks the checksum.

A matching checksum proves only that the key was not mistyped. It does not prove that the key exists or is active. Only the API can tell you that, for example with `GET /v1/key`.

```python tab="Python"
import re
import zlib

ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
KEY = re.compile(r"^(dex_(?:live|test)_)([0-9A-Za-z]{34})([0-9A-Za-z]{6})$")


def key_is_well_formed(key: str) -> bool:
    m = KEY.fullmatch(key)
    if not m:
        return False
    crc = zlib.crc32((m.group(1) + m.group(2)).encode("utf-8"))
    digits = ""
    for _ in range(6):
        crc, r = divmod(crc, 62)
        digits = ALPHABET[r] + digits
    return digits == m.group(3)
```

```ts tab="TypeScript"
const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const KEY = /^(dex_(?:live|test)_)([0-9A-Za-z]{34})([0-9A-Za-z]{6})$/;

function crc32(bytes: Uint8Array): number {
  let crc = 0xffffffff;
  for (const b of bytes) {
    crc ^= b;
    for (let k = 0; k < 8; k++) crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
  }
  return (crc ^ 0xffffffff) >>> 0;
}

export function keyIsWellFormed(key: string): boolean {
  const m = KEY.exec(key);
  if (!m) return false;
  let n = crc32(new TextEncoder().encode(m[1] + m[2]));
  let digits = "";
  for (let i = 0; i < 6; i++) {
    digits = ALPHABET[n % 62] + digits;
    n = Math.floor(n / 62);
  }
  return digits === m[3];
}
```

The CLI runs this check before it stores or sends a key: `dex login` refuses a mistyped key with the error code `invalid_key_checksum`.

## Keeping keys safe

- **Shown once.** The console shows a key's secret only when you create it. Dex stores only a SHA-256 hash of the key and a display prefix, such as `dex_live_Q7mK`.
- **Revocation is fast.** A key you revoke in the console stops working on every gateway replica within about a second.
- **Expiry and limits.** A key can have an expiry date and its own limits, at or below the account caps.
- **Leaked keys.** We are registering the key format with GitHub secret scanning. Once that is in place, a key found in a public repository is revoked automatically and the account owner gets an email.
- **Test keys for development.** Use a test key on development machines and in CI. It is never billed.
