# CloudCLI API — Authentication & Usage Guide

How to authenticate to the CloudCLI REST API, with copy-paste `curl` and JavaScript
examples, plus rate-limit and error-handling notes. Endpoint-by-endpoint schemas live
in the [API reference](./index.html) (rendered from `openapi.json`).

> Base path: all endpoints are under **`/api`** (same-origin by default, e.g.
> `https://your-host/api/...`). Replace `https://your-host` with your deployment host.

---

## 1. Authentication methods

CloudCLI accepts two credential types:

| Method | Header | Use it for | Notes |
|---|---|---|---|
| **JWT (Bearer)** | `Authorization: Bearer <token>` | Normal user/session access | Obtained from `POST /api/auth/login`. Valid **7 days**. |
| **API key** | `x-api-key: <key>` | Service/automation access | Only enforced when the server sets the `API_KEY` env var. |

> **Platform mode:** some hosted deployments run in "platform mode" and resolve the
> user server-side — protected routes work without a token. On self-hosted/OSS
> installs you use JWT as below.

---

## 2. JWT: log in and call protected endpoints

### Step 1 — Log in to get a token

**Request:** `POST /api/auth/login` with `{ username, password }`.

```bash
curl -sS -X POST https://your-host/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"your-password"}'
```

**Response `200`:**

```json
{
  "success": true,
  "user": { "id": 1, "username": "admin" },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."
}
```

(No account yet? `POST /api/auth/register` with the same `{username,password}` shape.)

### Step 2 — Send the token on protected requests

```bash
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."

curl -sS https://your-host/api/auth/user \
  -H "Authorization: Bearer $TOKEN"
```

### JavaScript (fetch)

```js
// Log in, then call a protected endpoint.
const base = 'https://your-host/api';

async function login(username, password) {
  const res = await fetch(`${base}/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password }),
  });
  if (!res.ok) throw new Error(`Login failed: ${res.status}`);
  const { token } = await res.json();
  return token;
}

async function getCurrentUser(token) {
  const res = await fetch(`${base}/auth/user`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (res.status === 401 || res.status === 403) throw new Error('Token missing/expired — log in again');
  return res.json();
}
```

### Token lifetime & auto-refresh

- Tokens expire after **7 days** (`exp` claim). After that, a request returns
  **`403 Invalid token`** — log in again.
- **Sliding refresh:** once a token is past half its lifetime, the server includes a
  fresh token in the **`X-Refreshed-Token`** response header. If present, replace your
  stored token with it so the session never hard-expires mid-use.

```js
async function apiFetch(path, token, opts = {}) {
  const res = await fetch(`${base}${path}`, {
    ...opts,
    headers: { ...(opts.headers || {}), Authorization: `Bearer ${token}` },
  });
  const refreshed = res.headers.get('X-Refreshed-Token');
  if (refreshed) saveToken(refreshed); // persist the rotated token
  return res;
}
```

### Streaming endpoints (SSE / EventSource)

`EventSource` can't set headers, so streaming endpoints also accept the token as a
**query parameter**:

```js
const es = new EventSource(`${base}/some/stream?token=${encodeURIComponent(token)}`);
```

> Prefer the `Authorization` header everywhere else — query-param tokens can leak into
> server logs and browser history.

**Worked example — parallel-chat streaming.** Fan out a prompt (`POST /chat/parallel`),
then open each returned `stream_url` as an SSE stream. Each event is `data: <JSON>\n\n`;
frames are `{type:"session-id",…}`, `{role:"assistant",content:"…"}` (text chunks), and a
final `{type:"done"}`.

```js
// 1) fan out → get per-provider stream URLs
const res = await apiFetch('/chat/parallel', token, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ prompt: 'Refactor this function', providers: ['claude', 'gemini'] }),
});
const { streams } = await res.json(); // [{ provider, stream_url }, …]

// 2) consume one provider's SSE stream (token via query param — EventSource can't set headers)
const es = new EventSource(`${base}${streams[0].stream_url}?token=${encodeURIComponent(token)}`);
es.onmessage = (e) => {
  const frame = JSON.parse(e.data);
  if (frame.type === 'done') { es.close(); return; }
  if (frame.role === 'assistant') appendChunk(frame.content);
};
es.onerror = () => es.close(); // EventSource auto-reconnects; close if you don't want that
```

### A worked example: a protected GET with query params

Most endpoints take the JWT plus their own params. Example — working-tree git status,
which needs `project` (absolute path of the project on the server):

```bash
curl -sS -G https://your-host/api/git/status \
  -H "Authorization: Bearer $TOKEN" \
  --data-urlencode "project=/home/me/projects/app"
```

```js
const url = new URL(`${base}/git/status`);
url.searchParams.set('project', '/home/me/projects/app');
const res = await apiFetch(url.pathname + url.search, token); // apiFetch adds the Bearer header
```

### A worked example: an authenticated POST with a JSON body

Endpoints that change state take a JSON body. Example — commit staged files
(`project` + `message` required; `files` optional):

> The project working tree is identified by the **`project`** field/param across
> endpoints (path string). Always check each endpoint's exact schema in the reference.

```bash
curl -sS -X POST https://your-host/api/git/commit \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"project":"/home/me/projects/app","message":"fix: typo"}'
```

```js
await apiFetch('/git/commit', token, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ project: '/home/me/projects/app', message: 'fix: typo' }),
});
```

Each endpoint's exact params and response schema are in the
[API reference](./index.html) — this guide only covers the cross-cutting auth bits.

---

## 3. API key (service / automation)

When the server is configured with an `API_KEY`, send it via the `x-api-key` header on
the routes that require it:

```bash
curl -sS https://your-host/api/<protected-route> \
  -H "x-api-key: $CLOUDCLI_API_KEY"
```

```js
fetch(`${base}/<protected-route>`, { headers: { 'x-api-key': process.env.CLOUDCLI_API_KEY } });
```

- If the server has **no** `API_KEY` set, the key check is skipped (it is an optional
  gate, not a replacement for JWT on user routes).
- Treat the key like a password: never commit it; load from env / a secret store.

---

## 4. Errors

Errors return a JSON body `{ "error": "<message>" }` with a standard status code:

| Status | Meaning | Typical fix |
|---|---|---|
| `400` | Bad request (e.g. missing fields) | Check the request body/params against the reference. |
| `401` | Not authenticated (no/invalid credentials) | Provide a valid `Authorization: Bearer` token or `x-api-key`. |
| `403` | Token invalid/expired | Log in again to obtain a new JWT. |
| `404` | Resource not found | Verify the path and IDs. |
| `429` | Rate limited (if enabled) | Back off and retry — see below. |
| `500` | Server error | Retry; if persistent, check server logs. |

---

## 5. Rate limiting

Rate limiting is **deployment-dependent** (reverse proxy / gateway in front of the API).
Where enabled, handle it defensively:

- A throttled request returns **`429 Too Many Requests`**.
- If a `Retry-After` header is present, wait that many seconds before retrying.
- Use exponential backoff for automated clients.

```js
async function withBackoff(fn, max = 4) {
  for (let attempt = 0; ; attempt++) {
    const res = await fn();
    if (res.status !== 429 || attempt >= max) return res;
    const retryAfter = Number(res.headers.get('Retry-After')) || Math.min(2 ** attempt, 30);
    await new Promise((r) => setTimeout(r, retryAfter * 1000));
  }
}
```

> If your deployment does not enforce rate limits, `429` will simply never occur — the
> backoff wrapper is then a harmless no-op.

---

## 6. Quick reference

```
POST /api/auth/register   { username, password }        → create account
POST /api/auth/login      { username, password }         → { token }
GET  /api/auth/user       Authorization: Bearer <token>  → current user
GET  /api/auth/status                                    → auth/setup status
POST /api/auth/logout     Authorization: Bearer <token>  → end session
```

Full endpoint list, parameters, and response schemas: **[API reference](./index.html)**.
