# Authentication

Use an organization-bound API key or OAuth client credentials for backend integrations. Interactive browser sessions support the app's reviewed, role-controlled workflows. Service credentials are limited to the preparatory and read operations listed in [Scopes and permissions](/developers/scopes/).

The API base path is `/api/liquidity-studio/v2` on your configured KlineO API origin. Set `KLINEO_ORIGIN` to that origin, without the API path. The examples below use environment variables so secrets stay out of source files.

## Choose an authentication method

| Method | Intended use | How requests authenticate |
| --- | --- | --- |
| API key | A server or scheduled integration with a fixed scope grant | `Authorization: Bearer <issued API key>` |
| OAuth client credentials | A server obtaining a short-lived access token | HTTP Basic at `/oauth/token`, then `Authorization: Bearer <access_token>` |
| Interactive session | App use, credential administration, approvals, and authority changes | The session cookie set by the email sign-in flow |

API keys use the `klo_key_` prefix. OAuth client secrets use `klo_client_`; access tokens use `klo_at_`. Treat every secret and access token as opaque. An OAuth client secret is exchanged at the token endpoint; it cannot be used directly as a bearer token.

## Organization isolation

Send `x-klineo-organization-id` with authenticated v2 requests, including the OAuth token exchange. Credentials belong to the organization that issued them. An OAuth access token is also signed with an organization binding, and the header must match it.

An API key is looked up inside the requested organization. Supplying another organization's ID does not grant access to its resources. After service authentication succeeds, KlineO checks the credential creator's current membership in that organization and applies their workspace role. A scope grants an allowed API surface; it does not bypass membership or role checks.

Public projections and public discovery routes have their own access boundaries. Consult the individual API operation before adding authentication or organization headers to a public request.

## Obtain a credential

Ask an organization owner or a KlineO operator with access to your organization to issue a credential from the app's Developer workspace. Credential creation and rotation require an interactive authentication ceremony within the preceding ten minutes.

The API accepts the following creation body at `POST /developer-credentials`. This operation requires the interactive session, organization header, and an `Idempotency-Key` of 8–160 characters.

```json
{
  "label": "Market quality reporting",
  "kind": "API_KEY",
  "scopes": ["scores:read"],
  "reason": "Read market quality scores for the reporting service."
}
```

Use `"kind": "OAUTH_CLIENT"` to issue a client instead. `expiresAt` is optional and accepts a timestamp with a timezone offset. If omitted, the service credential has no configured expiry; choose a future expiry appropriate for your integration. The creation response has a `data` object containing `credential`, `secret`, and `secretReturnedOnce: true`. The OAuth client ID is `credential.id`.

Save the secret to your server's secret manager immediately. KlineO stores a secret hash and selector, and returns the plaintext secret only in the first successful response. An identical retry returns `409` with `LIQUIDITY_OPERATING_SYSTEM_SECRET_ALREADY_RETURNED` and non-secret resource metadata; it cannot recover the secret. If the response was lost after creation, retrieve the credential record and rotate or revoke it.

## Make an API-key request

Set `KLINEO_TOKEN` to the issued API key and `KLINEO_ORGANIZATION_ID` to its organization ID. This request requires `scores:read`.

```bash
curl --fail-with-body \
  "$KLINEO_ORIGIN/api/liquidity-studio/v2/scores?limit=10" \
  -H "Authorization: Bearer $KLINEO_TOKEN" \
  -H "x-klineo-organization-id: $KLINEO_ORGANIZATION_ID" \
  -H "Accept: application/json"
```

Use service credentials only from a trusted server. Do not embed keys or client secrets in browser bundles, mobile applications, public examples, or committed `.env` files.

## Exchange an OAuth client secret

The implemented grant is `client_credentials`. Send the client ID and secret as HTTP Basic credentials and the grant as form-encoded fields. Set `KLINEO_CLIENT_ID` and `KLINEO_CLIENT_SECRET` from the issued client.

```bash
curl --fail-with-body \
  "$KLINEO_ORIGIN/api/liquidity-studio/v2/oauth/token" \
  --user "$KLINEO_CLIENT_ID:$KLINEO_CLIENT_SECRET" \
  -H "x-klineo-organization-id: $KLINEO_ORGANIZATION_ID" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "scope=scores:read"
```

```json
{
  "access_token": "<opaque access token>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "scores:read"
}
```

The `scope` field is a space-separated subset of the client's granted scopes. Omitting it uses the client's grant. Requesting a scope outside the grant is rejected. Copy `access_token` into your bearer request. Access tokens expire after one hour; request a new token with the client credentials when needed. There is no refresh-token grant in this endpoint.

The token response includes `Cache-Control: no-store` and `Pragma: no-cache`. Invalid client authentication returns `401` with `{"error":"invalid_client"}` rather than the usual v2 error envelope. See [Errors and retries](/developers/errors/) for handling this exception.

## Rotate, expire, or revoke

| Operation | Route | Requirements |
| --- | --- | --- |
| Inspect current credential | `GET /developer-credentials/{id}` | Interactive session and organization membership |
| Rotate the secret | `POST /developer-credentials/{id}/rotate` | Organization owner or KlineO operator; recent interactive authentication; `Idempotency-Key`; strong `If-Match`; body with `reason` |
| Revoke the credential | `PATCH /developer-credentials/{id}` | Organization owner or KlineO operator; `Idempotency-Key`; strong `If-Match`; body with `status: "REVOKED"` and `reason` |

Load the latest resource version before rotating or revoking. Preserve the quotes in its strong `ETag` when passing `If-Match`, or wrap the returned `resourceVersion` in double quotes. Revocation preserves the credential's historical evidence and is terminal through the generic status route.

API-key rotation immediately replaces the accepted secret. OAuth-client rotation replaces the secret accepted for future token exchanges; an already-issued access token may remain valid until expiry while the client is active and its grant remains current. Revoke the OAuth client to reject its existing tokens on subsequent requests. Both credential kinds are rejected after their configured credential expiry, even if an OAuth token's own expiry is later.

Rotation also returns its replacement secret once. If you need different scopes, issue a new credential with the new grant and revoke the old one; the generic credential status route does not edit scopes.

## Interactive browser sessions

The app signs users in through a one-time email link. The server sets an HttpOnly, SameSite=Lax cookie at path `/`. Secure deployments use `__Host-klineo_session`; insecure development uses `klineo_session`. Let the app and your browser manage this cookie rather than manufacturing session values.

Sessions have a configurable lifetime; the implementation defaults to 12 hours. Logging out revokes the server session and clears the cookie. A valid session can still require a recent authentication ceremony before an authority-sensitive operation. Service credentials cannot substitute for that ceremony or grant execution authority.

Continue with [Scopes and permissions](/developers/scopes/) and [Errors and retries](/developers/errors/).
