> For the complete documentation index, see [llms.txt](https://docs.khotan.com/llms.txt). A markdown version of any page is available at its `index.md` URL.

# Authentication

Every operation except `GET /api/v1/health` and the OpenAPI document requires an organization-scoped API key.

## Obtaining a key

Two paths, and they produce the same kind of key:

- **Workspace settings** — mint one in the dashboard when a human is setting up an integration.
- **`POST /api/v1/api-keys`** — exchange email, password, and an organization id for a key when a script needs to bootstrap itself. See the [quickstart](/api/quickstart/) for the request.

Credential exchange is as powerful as a login, so it is rate limited to 5 requests per minute keyed on the calling IP and the email, and it fails closed when limits cannot be enforced. It verifies your credentials without creating a session — it does not log you in.

## Sending a key

Either header works. `Authorization: Bearer` is the primary convention; `x-api-key` is accepted for clients that reserve the `Authorization` header:

```bash
curl https://app.khotan.com/api/v1/me \
  -H "Authorization: Bearer $KHOTAN_API_KEY"
```

```bash
curl https://app.khotan.com/api/v1/me \
  -H "x-api-key: $KHOTAN_API_KEY"
```

Session cookies are not accepted on `/api/v1`. An unauthenticated request returns a JSON `401`, never a redirect to a sign-in page, so a script never has to distinguish an auth failure from an HTML response.

## What a key carries

A key is bound to one organization and one role. That has three consequences worth internalising:

- **No request names an organization.** There is no account, workspace, or tenant parameter anywhere in the API, and no request can widen its own scope.
- **The host is irrelevant.** Since the key resolves the organization, `https://app.khotan.com` serves every workspace.
- **A key cannot exceed its minter.** You cannot mint a key carrying more access than your own role in that organization.

Confirm the binding before trusting any automated run:

```bash
curl -s https://app.khotan.com/api/v1/me \
  -H "Authorization: Bearer $KHOTAN_API_KEY"
```

```json
{ "organizationId": "org_...", "role": "member", "keyId": "..." }
```

## Roles

A key carries the role it was minted with. Most resource operations need only a valid key scoped to the organization — the role is not checked. Key management is the exception: listing an organization's keys, or acting on a key other than your own, requires a role holding the `manageApiKeys` permission.

## Keeping a key safe

- **Never commit one.** Keys belong in environment variables or a secret manager, not in a repository, a container image, or a CI log.
- **Never ship one to a browser.** The API sends no cross-origin headers precisely because an organization-scoped key must not reach client-side code. See [conventions](/api/conventions/).
- **Mint one per integration.** Separate keys make it possible to revoke one consumer without disrupting the rest, and make audit logs attributable.
- **Treat the response as the only copy.** The plaintext key is returned exactly once and is never retrievable afterwards. Losing it means minting a replacement.

## Managing keys

Existing keys are listed and revoked from workspace settings in the dashboard. Revocation takes effect immediately: the next request carrying that key is rejected `401`.
