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

# API Authentication

Authenticate with the HolyDocs REST API using API keys or Better Auth session tokens.

## Overview

The HolyDocs REST API supports two authentication methods: **API keys** for programmatic access (CLI, CI/CD, server-to-server) and **Better Auth session tokens** for the dashboard. Every authenticated endpoint accepts either, validated by the same `Authorization: Bearer <token>` header.

## API Key Authentication

API keys are the recommended method for CLI tools, CI/CD pipelines, and server-to-server integrations.

### Getting an API Key — Easiest Path

If you have the [HolyDocs CLI](/cli/login) installed, run:

```bash
holydocs login
```

This opens a browser-based device authorization flow — you sign in once and the CLI automatically receives a write-scoped API key (90-day expiry) and stores it in `~/.holydocs/config.json`. For CI or headless environments, you can instead use `holydocs login --api-key ...` or set `HOLYDOCS_API_KEY`.

Once logged in, you can make authenticated management calls directly from the terminal with [`holydocs api`](/cli/api):

```bash
holydocs api get /projects
```

### Creating an API Key Manually

For custom scopes, longer expiries, or non-CLI integrations, create keys in the dashboard:

1. Go to **Settings → API Keys** at [app.holydocs.com/settings/api-keys](https://app.holydocs.com/settings/api-keys)
2. Click **Create API Key**
3. Enter a name, select the permissions, and optionally set an expiry
4. Copy the key — it is only shown once and stored hashed (SHA-256) on the server

### API Key Format

API keys use the `hd_` prefix followed by a 32-character hex suffix derived from a UUID:

```
hd_a1b2c3d4e5f67890a1b2c3d4e5f67890
```

There are no `hd_live_*` / `hd_test_*` environment prefixes — staging and production each have their own dashboards and issue keys against their own databases.

### Using API Keys

Include the API key in the `Authorization` header as a bearer token:

```bash
curl -X GET "https://api.holydocs.com/api/v1/projects" \
  -H "Authorization: Bearer hd_a1b2c3d4e5f67890a1b2c3d4e5f67890"
```

This is the only supported transport — there is no `X-API-Key` header.

If your key doesn't carry an organization context (rare — only happens for legacy keys), you can pass the org explicitly via an `X-Organization-Id` header alongside the bearer token:

```bash
curl -X GET "https://api.holydocs.com/api/v1/projects" \
  -H "Authorization: Bearer hd_a1b2c3d4e5f67890a1b2c3d4e5f67890" \
  -H "X-Organization-Id: org_abc123"
```

### Permissions

API keys carry a `permissions` array that's validated at creation time. Common values:

| Permission | Description |
|---|---|
| `read` | Read-only access to projects, deployments, analytics, etc. |
| `write` | Full write access (create/update/delete projects, trigger deployments). Keys issued by `holydocs login` use this. |
| `admin` | Same as `write` plus organization-level operations |

Permissions are stored as a JSON array on each key. The CLI's device flow always requests `["write"]`. Keys created in the dashboard can specify any combination.

## Better Auth Session Tokens

The dashboard and web-based tools use [Better Auth](https://www.better-auth.com/) for authentication, with session tokens issued via email/password sign-in or social providers (GitHub, Google).

### How It Works

1. User signs in via the dashboard at `app.holydocs.com/login`
2. Better Auth issues a session token and stores it in an HTTP-only cookie
3. The dashboard reads the token client-side and includes it in API requests as `Authorization: Bearer <token>`
4. The API verifies the token by calling Better Auth's `verifyToken()` and resolves the user's `userId`, `orgId`, and role

The auth middleware accepts both API keys and session tokens transparently — it inspects the token prefix and routes to the appropriate verifier.

If a session token is valid but doesn't carry an `orgId` claim, the dashboard sends an `X-Organization-Id` header as a fallback. The API only honors this header when the token is already verified — it never grants access to an org the user doesn't already belong to.

## Base URL

All API requests use the base URL:

```
https://api.holydocs.com/api/v1
```

## Rate Limiting

API requests are rate-limited per plan:

| Plan | Requests per Minute |
|---|---|
| Free | 60 |
| Starter | 120 |
| Pro | 600 |
| Business | 3,000 |
| Enterprise | 6,000 |

Rate limit headers are included in every response:

```
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 1708200060
```

## Error Responses

All API errors follow a consistent format:

```json
{
  "error": {
    "code": "AUTH_ERROR",
    "message": "Invalid or expired API key",
    "status": 401
  }
}
```

### Common Error Codes

| Code | Status | Description |
|---|---|---|
| `AUTH_ERROR` | 401 | Missing or invalid authentication |
| `FORBIDDEN` | 403 | Insufficient permissions |
| `NOT_FOUND` | 404 | Resource not found |
| `VALIDATION_ERROR` | 400 | Invalid request body |
| `ALREADY_EXISTS` | 409 | Resource with this identifier already exists |
| `LIMIT_EXCEEDED` | 429 | Rate limit or plan limit exceeded |

## Public Endpoints

Some API endpoints do not require an `Authorization` header:

| Endpoint | Purpose |
|---|---|
| `GET /api/v1/docs/:projectId/search` | Public search API |
| `GET /api/v1/docs/:projectId/search/semantic` | Semantic search |
| `POST /api/v1/assistant/:projectId/chat` | AI assistant chat |
| `POST /api/v1/mcp/:projectId/message` | MCP server |
| `POST /api/v1/cli/device/code` | CLI device authorization — request a new device code |
| `POST /api/v1/cli/device/poll` | CLI device authorization — poll for the issued API key |
| `POST /api/v1/webhooks/github` | GitHub webhooks |
| `POST /api/v1/webhooks/gitlab` | GitLab webhooks |
| `POST /api/v1/webhooks/stripe` | Stripe webhooks |

These endpoints use their own authentication mechanisms (webhook signatures, rate limiting by visitor IP, single-use codes with short TTLs, etc.). Note that `POST /api/v1/cli/device/authorize` — the third endpoint in the device flow — **does** require a session token; it's the call the dashboard makes on behalf of a signed-in user to mint the API key.