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

# Authentication

> Create a workspace API key and authenticate requests to the Questra Program API.

All requests to the Program API (except health checks) require a Bearer token. For scripts, backends, and CI, use a **workspace API key**. Browser sessions in the [web app](https://program.questra.ai) authenticate separately through Questra Auth. Claude and other MCP clients use [user OAuth](/mcp), not API keys.

API keys are scoped to the workspace that created them. You do **not** send an organization or workspace header — the key selects the tenant.

## Create an API key

You need the `api_keys:write` permission (workspace **owner** or **admin**). Members can view keys but cannot create them.

<Steps>
  <Step title="Open the web app">
    Sign in at [program.questra.ai](https://program.questra.ai) (or [program.staging.questra.ai](https://program.staging.questra.ai) for staging).
  </Step>

  <Step title="Go to API keys">
    Open **Settings** → **[API keys](https://program.questra.ai/settings/api-keys)**.
  </Step>

  <Step title="Create a key">
    Click **Create key**. Give it a name you will recognize later (for example `Production backend`).
  </Step>

  <Step title="Choose scopes">
    Pick the permissions this key should have. `*` grants full Program API access for the workspace. For a read-only integration, `surveys:read` and `meta:read` are enough to list surveys and call `GET /v1/whoami`.
  </Step>

  <Step title="Copy the secret">
    The full key is shown **once**, prefixed `qpk_live_`. Store it in a secret manager or environment variable. List and get responses only include a `key_hint` (last four characters).
  </Step>
</Steps>

<Warning>
  Treat the secret like a password. Do not commit it to source control or log it. If it leaks, rotate or delete the key immediately.
</Warning>

You can also create keys with `POST /v1/api_keys` if you already have a key (or a session) that includes `api_keys:write`. The create response includes `data.key` once — same as the dashboard.

## Use the key

Pass the secret in the `Authorization` header on every request.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.program.questra.ai/v1/whoami \
    -H "Authorization: Bearer $QUESTRA_API_KEY"
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch('https://api.program.questra.ai/v1/whoami', {
    headers: {
      Authorization: `Bearer ${process.env.QUESTRA_API_KEY}`,
    },
  });
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.get(
      'https://api.program.questra.ai/v1/whoami',
      headers={'Authorization': f'Bearer {os.environ["QUESTRA_API_KEY"]}'},
  )
  ```
</CodeGroup>

A successful `GET /v1/whoami` returns the key's workspace and `auth_method: "api_key"`:

```json theme={null}
{
  "service": "questra-program",
  "workspace_id": "org_...",
  "subject": "apikey:...",
  "auth_method": "api_key",
  "permissions": ["*"]
}
```

Invalid, disabled, or expired keys return **401**. A valid key that lacks a required permission returns **403**.

## Base URLs

| Environment | Base URL                                 |
| ----------- | ---------------------------------------- |
| Production  | `https://api.program.questra.ai`         |
| Staging     | `https://api.program.staging.questra.ai` |

All documented endpoints live under `/v1`. Health (`GET /health`) is unauthenticated and sits outside `/v1`.

## Scopes

Each key stores an explicit list of scopes. The API checks them **before** the handler runs. `resource:write` also satisfies `resource:read`. `*` satisfies every permission.

| Scope                                      | Typical use                                                                   |
| ------------------------------------------ | ----------------------------------------------------------------------------- |
| `*`                                        | Full workspace access                                                         |
| `surveys:read` / `surveys:write`           | Surveys, files, questionnaires, programming, content                          |
| `surveys:share`                            | Sharing surveys with collaborators                                            |
| `integrations:read` / `integrations:write` | Platform connections and upstream surveys                                     |
| `webhooks:read` / `webhooks:write`         | Endpoints, events, and deliveries                                             |
| `workflows:read` / `workflows:write`       | Workflow status and streams                                                   |
| `api_keys:read` / `api_keys:write`         | Manage other keys                                                             |
| `meta:read`                                | Workspace identity and other meta reads (whoami itself accepts any valid key) |

Create requests require a non-empty `scopes` array — there is no implicit full-access default unless you pass `*`.

## Manage keys

From **Settings → API keys**, or the [API keys](/api-reference/overview) resource:

* **Disable** — `PATCH /v1/api_keys/{apiKeyId}` with `{ "enabled": false }`. The key stops working without deleting the record.
* **Rotate** — `POST /v1/api_keys/{apiKeyId}/rotate` issues a new `qpk_live_…` secret and invalidates the old one. Scopes stay the same. Copy the new secret immediately.
* **Delete** — `DELETE /v1/api_keys/{apiKeyId}` revokes the key permanently.

Optional `expires_at` on create or update sets an expiry. Omit or `null` for a key that does not expire.

## Environment variables

```bash theme={null}
export QUESTRA_API_KEY=qpk_live_...
```

```typescript theme={null}
const apiKey = process.env.QUESTRA_API_KEY;
```

Next: [Quickstart](/quickstart) to list surveys with that key.
