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

# Authentication

> Authenticate Zeptar API requests with a session cookie (browser) or an API key (server-side).

## Public endpoints

A small set of endpoints is intentionally unauthenticated and safe to call from anywhere:

* `GET /v1/voices` — list the available TTS voices.

Every other endpoint in this reference requires a session cookie.

## Session cookies

The Zeptar API authenticates requests with a session cookie set by [better-auth](https://better-auth.com). The cookie is named `better-auth.session_token` and is established when the user signs in through the web app.

A browser visiting [app.zeptar.com](https://app.zeptar.com) and the API at `api.zeptar.com` share the cookie automatically — they live on the same parent domain.

### From the browser

No setup required. `fetch('/v1/...')` from a logged-in page already carries the cookie:

```ts theme={null}
const res = await fetch('/v1/organizations', {
  credentials: 'include',
});
```

### From a server (SSR, scripts)

Forward the inbound `Cookie` header to the API. The `apiFetch` helper in `apps/web/lib/zeptar-api-client.ts` does this automatically when called from a Server Component or Server Action.

```ts theme={null}
const cookie = (await headers()).get('cookie') ?? '';

const res = await fetch('https://api.zeptar.com/v1/organizations', {
  headers: { Cookie: cookie },
});
```

### From a curl session (development)

Copy the session cookie out of your browser's devtools and pass it explicitly:

```bash theme={null}
curl https://api.zeptar.com/v1/organizations \
  -H "Cookie: better-auth.session_token=eyJ..."
```

## Unauthenticated responses

A request without a valid session cookie returns:

```http theme={null}
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{ "statusCode": 401, "message": "Unauthorized" }
```

Web clients should treat 401 as a signal to redirect to `/sign-in`. The `apps/web` server helpers (`getOrganizations`, `getPendingWorkspaceInvites`, etc.) do this automatically.

## Permission errors

Endpoints that require a specific role (e.g. removing a member from an organization requires admin) return:

```http theme={null}
HTTP/1.1 403 Forbidden
Content-Type: application/json

{ "statusCode": 403, "error": "not_admin" }
```

The `error` field is a stable machine-readable code. The `message` field, when present, is intended for end users.

## API keys

Server-side clients — CI pipelines, backend services, SDK integrations — authenticate with an API key instead of a session cookie. Issue a key via `POST /v1/api-keys` (session required; see the [API keys overview](/api-reference/api-keys/overview) for the full lifecycle).

Pass the key in the `Authorization` header using the `Bearer` scheme:

```bash theme={null}
curl https://api.zeptar.com/v1/agents \
  -H "Authorization: Bearer zsk_…"
```

```ts theme={null}
const res = await fetch('https://api.zeptar.com/v1/agents', {
  headers: { Authorization: `Bearer ${process.env.ZEPTAR_API_KEY}` },
});
```

Keys start with the prefix `zsk_` and are shown **once** at creation time — store them securely before closing the response. Each key carries per-resource scopes (`agents`, `conversations`, `knowledge_base`, `voices`, `workspace`) at level `none`, `read`, or `write`; a request that exceeds the key's scope returns `403 insufficient_scope`.

API-key management endpoints (create, list, update, revoke) are **session-only** — a Bearer key cannot be used to manage other keys.
