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

# Authentication

> Personal access tokens: creating them, scoping them, revoking them, and the limits and errors that apply.

Programmatic clients authenticate with a **personal access token**: a long-lived credential you
create once from your account and then hold in a script, a server, or a CI secret.

<Warning>
  **`POST /api/auth/register` and `POST /api/auth/login` are browser-only flows and cannot be called
  from a script.** Both verify a reCAPTCHA v3 token, and reCAPTCHA v3 tokens are minted by Google's
  JavaScript inside a real browser, are bound to an action, and expire within minutes. No HTTP
  client can produce one. Registration also requires you to click an emailed verification link
  before your first login. Create your account in the browser, once, then use a token thereafter.
  The reasoning is recorded in [ADR-0006](/docs/adr/0006-programmatic-api-access).
</Warning>

## Creating a token

<Steps>
  <Step title="Create a token">
    Sign in at [haico.gr](https://haico.gr), open **Profile → API keys**, and choose **New key**.

    Pick a **name** you will recognise later, an **access level**, and an **expiry**:

    | Access       | What it allows                                                           |
    | ------------ | ------------------------------------------------------------------------ |
    | `read`       | Safe methods only (`GET`): listing conversations, reading the workspace. |
    | `read,write` | Everything, including running agent turns and editing the workspace.     |

    The default expiry is 90 days. You can also choose 30 days, 1 year, or no expiry.
  </Step>

  <Step title="Copy it immediately">
    The token is displayed **once**. Only a SHA-256 hash of it is stored, so it genuinely cannot be
    shown again. If you lose it, revoke the key and create another.

    ```
    haico_pat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    ```

    Store it the way you would any other secret: an environment variable, a secrets manager, or your
    CI provider's encrypted variables. Never commit it. The fixed `haico_pat_` prefix exists so that
    GitHub secret scanning and push protection can recognise a leaked key.
  </Step>

  <Step title="Send it on every request">
    ```bash theme={null}
    export HAICO_TOKEN="haico_pat_..."

    curl -s https://haico.gr/api/auth/me \
      -H "Authorization: Bearer $HAICO_TOKEN"
    ```

    ```json theme={null}
    {
      "id": 42,
      "username": "researcher",
      "email": "researcher@example.com",
      "role": "user",
      "email_verified": true
    }
    ```
  </Step>
</Steps>

## Managing your tokens

The same operations are available over HTTP, but **only with a browser session token, never with a
personal access token**. If a token could mint tokens, a single leaked credential could issue
replacements, outlive its own revocation, and turn a read-only grant into a writable one.

| Method   | Path             | Purpose                                                        |
| -------- | ---------------- | -------------------------------------------------------------- |
| `GET`    | `/api/keys`      | List your tokens (prefix, label, timestamps; never the secret) |
| `POST`   | `/api/keys`      | Create one; the plaintext is returned exactly once             |
| `DELETE` | `/api/keys/{id}` | Revoke one, effective immediately                              |

Revocation is instant and independent: disabling one token has no effect on your other tokens or on
your browser session.

## Limits and error responses

| Status | Meaning                                                            | What to do                                     |
| ------ | ------------------------------------------------------------------ | ---------------------------------------------- |
| `401`  | Missing, unknown, revoked, or expired token                        | Check the token; create a new one if it lapsed |
| `403`  | A `read` token was used on a request that changes data             | Create a `read,write` token                    |
| `429`  | Over the per-token rate limit (120 requests per minute by default) | Back off; a `Retry-After` header is included   |

Rate limiting is applied **per token**, not per IP address, so a CI host running several legitimate
tokens is not throttled as though it were one client.

<Note>
  The browser session JWT also authenticates every route, but it is an internal mechanism and not
  part of the public contract. It cannot be obtained without a browser, it expires every 24 hours,
  and it cannot be revoked individually. Do not build against it.
</Note>

## Security notes

* **Treat a token like a password.** It carries your full account authority within its scope.
* **Prefer `read` when that is enough.** A read-only token cannot start an agent turn or modify the
  workspace, which bounds the damage if it leaks.
* **Set an expiry.** A forgotten token that never expires is a permanent liability.
* **Revoke rather than rotate secrets.** Revoking one token is instant and affects nothing else.
* **Never put a token in browser code.** Tokens are for confidential clients: servers, scripts, and
  CI. A browser cannot keep a secret, and anything in `localStorage` is reachable by any XSS.

<Card title="Check a token from CI" icon="code" href="/docs/api-guide/recipes">
  A ready-made shell snippet that fails your pipeline early on an expired token.
</Card>
