> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kairos.trade/llms.txt
> Use this file to discover all available pages before exploring further.

# Kairos API Authentication: Keys, Scopes, and Rate Limits

> Learn how to obtain your Kairos API key, authenticate requests, and handle common auth errors like 401 Unauthorized and 403 Forbidden.

Most Kairos APIs require credentials, but you can start exploring immediately without them. The Market Data API is completely open — no account or key required. The Data API, Execution API, and Agora Auction API require either an **API key triple** (three headers) for programmatic access or a **session JWT** for first-party apps. This page covers both.

## Which APIs require authentication?

| API | Base URL | Auth Required |
| - | - | :-: |
| Market Data API | `md.kairos.trade` | **No** — all 15 endpoints are free |
| Data API | `data.kairos.trade` | **Yes** — except a handful of `free` endpoints |
| Execution API | `execution.kairos.trade` | **Yes** — except `/health` |
| Agora Auction API | `agora.kairos.trade` | **Yes** — except `/healthz` and `/readyz` |

<Note>
  In the API reference, endpoints marked **free** work without a key even when they belong to an otherwise-authenticated API. The entire Market Data API is free regardless of that label.
</Note>

## Get your API key

You need an account at [app.kairos.trade](https://app.kairos.trade) to create an API key. The process takes under a minute.

<Steps>
  <Step title="Create an account">
    Go to [app.kairos.trade](https://app.kairos.trade) and sign up with your email address.
  </Step>

  <Step title="Open API Key settings">
    After logging in, click your avatar in the top-right corner and go to **Settings → API Keys**.
  </Step>

  <Step title="Create a new key">
    Click **Create API Key**, enter a descriptive name (e.g. `my-trading-bot`), and choose the scopes your application needs. Then click **Create**.
  </Step>

  <Step title="Copy your key">
    Your key is displayed **once**. Copy it and save it somewhere safe — a password manager, secrets manager, or environment variable. You cannot retrieve it again after closing the dialog.
  </Step>
</Steps>

<Warning>
  Never embed your API key in client-side code, commit it to a public repository, or share it in logs. If a key is compromised, revoke it immediately from the API Keys settings page and generate a new one.
</Warning>

## Pass your key in every request

For programmatic access, send all three credential headers together on every authenticated request:

```
X-Client-Id: kairos_ck_...
X-Api-Key: <64 hex chars>
X-Api-Secret: <64 hex chars>
```

All three belong to the same key — a partial set fails with `401`. Here is how that looks in practice:

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl "https://data.kairos.trade/markets/active?provider=polymarket" \
      -H "X-Client-Id: $KAIROS_CLIENT_ID" \
      -H "X-Api-Key: $KAIROS_API_KEY" \
      -H "X-Api-Secret: $KAIROS_API_SECRET"
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    import requests

    headers = {
        "X-Client-Id": os.environ["KAIROS_CLIENT_ID"],
        "X-Api-Key": os.environ["KAIROS_API_KEY"],
        "X-Api-Secret": os.environ["KAIROS_API_SECRET"],
    }

    response = requests.get(
        "https://data.kairos.trade/markets/active",
        params={"provider": "polymarket"},
        headers=headers,
    )
    response.raise_for_status()
    print(response.json())
    ```

    For multiple requests, attach the headers to a `Session` once:

    ```python theme={null}
    import os
    import requests

    session = requests.Session()
    session.headers.update({
        "X-Client-Id": os.environ["KAIROS_CLIENT_ID"],
        "X-Api-Key": os.environ["KAIROS_API_KEY"],
        "X-Api-Secret": os.environ["KAIROS_API_SECRET"],
    })

    markets = session.get("https://data.kairos.trade/markets/active",
                          params={"provider": "polymarket"}).json()
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const headers = {
      "X-Client-Id": process.env.KAIROS_CLIENT_ID,
      "X-Api-Key": process.env.KAIROS_API_KEY,
      "X-Api-Secret": process.env.KAIROS_API_SECRET,
    };

    const response = await fetch(
      "https://data.kairos.trade/markets/active?provider=polymarket",
      { headers }
    );
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    console.log(await response.json());
    ```

    For a reusable helper across your application:

    ```javascript theme={null}
    function kairosRequest(path, options = {}) {
      return fetch(`https://data.kairos.trade${path}`, {
        ...options,
        headers: {
          "X-Client-Id": process.env.KAIROS_CLIENT_ID,
          "X-Api-Key": process.env.KAIROS_API_KEY,
          "X-Api-Secret": process.env.KAIROS_API_SECRET,
          ...options.headers,
        },
      });
    }
    ```
  </Tab>
</Tabs>

### The session JWT (first-party apps only)

The web app authenticates with a short-lived session JWT instead of the triple:

```
Authorization: Bearer <jwt>
```

JWTs are minted by the Kairos login flow and are **not issued to API consumers**. Use the header triple for integrations; the JWT exists so the app can call the same APIs. For a full comparison, see [API Key Auth](/guides/authentication).

## API key scopes

When you create a key, you assign it one or more scopes that control which operations it can perform. Granting only the scopes your application needs limits the blast radius if a key is ever leaked.

| Scope | What it grants |
| - | - |
| `trade:read` | View trade history and metrics (`/trades/*`) |
| `trade:execute` | Submit and cancel orders, plus supported execution mutations such as redeem and CTF split/merge |
| `position:read` | View positions, PnL, and portfolio data (`/pnl/*`, RPC position procedures) |
| `auction:create` | Create and cancel Agora auctions |
| `auction:quote` | Submit, revise, and withdraw Agora quotes |

<Tip>
  For a read-only analytics integration, issue a key with `trade:read` and `position:read`. For a trading bot, add `trade:execute`. Reserve `auction:*` for institutional workflows.
</Tip>

## Rate limits

All authenticated APIs enforce per-key rate limits. When you exceed the limit, the API returns `429 Too Many Requests`.

The response includes headers that tell you how to recover:

```
HTTP/1.1 429 Too Many Requests
Retry-After: 2
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705320000
```

Apply exponential backoff with jitter when you receive a 429:

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import time
    import random
    import requests

    def request_with_retry(url, headers, max_retries=5):
        delay = 1.0
        for attempt in range(max_retries):
            response = requests.get(url, headers=headers)
            if response.status_code != 429:
                response.raise_for_status()
                return response
            retry_after = int(response.headers.get("Retry-After", delay))
            sleep_time = retry_after + random.uniform(0, 0.5)
            print(f"Rate limited. Retrying in {sleep_time:.1f}s (attempt {attempt + 1})")
            time.sleep(sleep_time)
            delay = min(delay * 2, 30)
        raise RuntimeError("Max retries exceeded")
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    async function requestWithRetry(url, options = {}, maxRetries = 5) {
      let delay = 1000;
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        const response = await fetch(url, options);
        if (response.status !== 429) {
          if (!response.ok) throw new Error(`HTTP ${response.status}`);
          return response;
        }
        const retryAfter = parseInt(response.headers.get("Retry-After") ?? "1", 10);
        const sleepMs = retryAfter * 1000 + Math.random() * 500;
        console.log(`Rate limited. Retrying in ${sleepMs}ms (attempt ${attempt + 1})`);
        await new Promise(resolve => setTimeout(resolve, sleepMs));
        delay = Math.min(delay * 2, 30000);
      }
      throw new Error("Max retries exceeded");
    }
    ```
  </Tab>
</Tabs>

<Note>
  The Market Data API also has rate limits, but they are more generous for unauthenticated use. Authenticated callers on all APIs get higher throughput allowances.
</Note>

## Common authentication errors

<Accordion title="401 Unauthorized — Missing or malformed token">
  **Cause:** The `Authorization` header is absent, empty, or not in `Bearer <token>` format.

  **Response body:**

  ```json theme={null}
  { "error": "unauthorized", "message": "Missing or invalid Authorization header" }
  ```

  **Fix:** Ensure every request to an authenticated endpoint includes:

  ```
  X-Client-Id: kairos_ck_...
  X-Api-Key: <64 hex chars>
  X-Api-Secret: <64 hex chars>
  ```

  Double-check there are no extra spaces, missing `Bearer` prefix, or accidental newlines in the header value.
</Accordion>

<Accordion title="401 Unauthorized — Expired or revoked key">
  **Cause:** The API key has been revoked, deleted, or has reached its expiry date.

  **Response body:**

  ```json theme={null}
  { "error": "unauthorized", "message": "API key is invalid or has been revoked" }
  ```

  **Fix:** Go to **Settings → API Keys** in [app.kairos.trade](https://app.kairos.trade), confirm the key is still active, and generate a new one if needed.
</Accordion>

<Accordion title="403 Forbidden — Insufficient scope">
  **Cause:** Your API key does not have the scope required by the endpoint you called. For example, calling `POST /orders` with a key that only has `data:read`.

  **Response body:**

  ```json theme={null}
  { "error": "forbidden", "message": "This key does not have the required scope: execution:write" }
  ```

  **Fix:** Edit the key in **Settings → API Keys** to add the missing scope, or create a new key with the correct scopes for your use case.
</Accordion>

<Accordion title="429 Too Many Requests — Rate limit exceeded">
  **Cause:** Your key has sent more requests than its rate-limit window allows.

  **Response body:**

  ```json theme={null}
  { "error": "rate_limit_exceeded", "message": "Too many requests. Please retry after 2 seconds." }
  ```

  **Fix:** Read the `Retry-After` header and wait at least that many seconds before retrying. Implement exponential backoff with jitter (see the code examples above) to avoid hammering the API in tight loops.
</Accordion>

<Accordion title="403 Forbidden — IP not allowlisted">
  **Cause:** Your account has IP allowlisting enabled and the request originated from an address not on the list.

  **Response body:**

  ```json theme={null}
  { "error": "forbidden", "message": "Request origin is not permitted" }
  ```

  **Fix:** Add your current IP address to the allowlist under **Settings → Security**, or disable IP restrictions if you're testing from a dynamic address.
</Accordion>

## Revoking a key

If you suspect a key has been compromised, revoke it immediately:

1. Go to **Settings → API Keys** in [app.kairos.trade](https://app.kairos.trade).
2. Find the key in the list and click **Revoke**.
3. Confirm revocation — this is immediate and cannot be undone.
4. Generate a new key and update your application configuration.

Revoked keys return `401 Unauthorized` on all future requests.

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Follow a step-by-step guide to your first authenticated API call.
  </Card>

  <Card title="Execution API" icon="bolt" href="/rest/orders">
    Submit orders and manage positions across all supported venues.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.