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

# Authentication

> Agent Auth: self-serve an API key in two calls, then authenticate every request with X-API-Key.

Every OpenFunnel API request authenticates with a single header:

| Header         | Value                   |
| -------------- | ----------------------- |
| `X-API-Key`    | Your API key (`of_...`) |
| `Content-Type` | `application/json`      |

There is no OAuth dance and no dashboard step required, agents and humans get a key the same way. Agents can read the machine-friendly version of this page at [`auth.md`](https://docs.openfunnel.dev/auth.md).

## Get a key (Agent Auth)

<Steps>
  <Step title="Sign up / sign in">
    `POST /api/v1/agent/sign-up` sends a 6-digit code to your email. New users get an account; existing users use this to recover a lost key.

    ```bash theme={"dark"}
    curl -X POST https://api.openfunnel.dev/api/v1/agent/sign-up \
      -H "Content-Type: application/json" \
      -d '{"email": "founder@example.com"}'
    ```

    ```json theme={"dark"}
    { "email": "founder@example.com", "message": "Verification code sent to your email" }
    ```
  </Step>

  <Step title="Verify">
    `POST /api/v1/agent/verify` with the code returns your key. New users receive a fresh key; existing users get their current key back.

    ```bash theme={"dark"}
    curl -X POST https://api.openfunnel.dev/api/v1/agent/verify \
      -H "Content-Type: application/json" \
      -d '{"email": "founder@example.com", "otp_code": "123456"}'
    ```

    ```json theme={"dark"}
    {
      "email": "founder@example.com",
      "api_key": "of_1234567890abcdef...",
      "is_new_user": true
    }
    ```

    <Warning>
      The key is only returned at verification time and is not retrievable later. Store it securely. To recover a lost key, call sign-up again with the same email.
    </Warning>
  </Step>
</Steps>

## Authenticate requests

Send the key as `X-API-Key` on every other endpoint:

<CodeGroup>
  ```bash cURL theme={"dark"}
  curl https://api.openfunnel.dev/api/v1/credits/balance \
    -H "X-API-Key: $OPENFUNNEL_API_KEY" \
    -H "Content-Type: application/json"
  ```

  ```python Python theme={"dark"}
  import os, requests

  resp = requests.get(
      "https://api.openfunnel.dev/api/v1/credits/balance",
      headers={"X-API-Key": os.environ["OPENFUNNEL_API_KEY"]},
  )
  resp.raise_for_status()
  ```

  ```typescript TypeScript theme={"dark"}
  const resp = await fetch(
    "https://api.openfunnel.dev/api/v1/credits/balance",
    {
      method: "GET",
      headers: {
        "X-API-Key": process.env.OPENFUNNEL_API_KEY!,
        "Content-Type": "application/json",
      },
    },
  );
  ```
</CodeGroup>

## Errors and limits

The sign-up and verify endpoints return structured errors:

| Status | `error_code`       | Meaning                                                                  |
| ------ | ------------------ | ------------------------------------------------------------------------ |
| 400    | `OTP_EXPIRED`      | No pending code, call sign-up again.                                     |
| 400    | `OTP_INVALID`      | Wrong code (`details.remaining_attempts` shows how many tries are left). |
| 429    | `RATE_LIMITED`     | Too many sign-up attempts (`details.retry_after_seconds`).               |
| 429    | `OTP_MAX_ATTEMPTS` | Code exhausted after 10 tries, call sign-up again.                       |
| 500    | `INTERNAL_ERROR`   | Transient server error, retry.                                           |

Verification codes expire after 24 hours and allow up to 10 attempts.
