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

# Authentication and permissions

> How API tokens work, organization-wide and project tokens, roles and permissions, why a request is refused, and how to give each integration only what it needs.

Every External API request carries an API token. The token says which organization the request belongs to, which project it may act in, and - through its role - which endpoints it may call. This page explains each of those checks, the answer you get when one fails, and how to issue tokens that hold no more than they need.

## How a request is checked

Each request passes four checks in order. The first one that fails answers, and nothing after it runs.

```mermaid theme={null}
flowchart TD
  req["Request with<br/>Authorization: Bearer token"] --> tok{"Token valid?"}
  tok -- "missing, unknown<br/>or revoked" --> e401["401"]
  tok -- "yes" --> org["Organization taken<br/>from the token"]
  org --> proj{"Project known?"}
  proj -- "project token" --> own["Token's own project"]
  proj -- "organization token<br/>names a project" --> named{"Project in this<br/>organization and active?"}
  proj -- "organization token<br/>names none" --> e400["400 PROJECT_REQUIRED"]
  named -- "no" --> e404["404 PROJECT_INVALID"]
  named -- "yes" --> perm
  own --> perm{"Role holds the<br/>endpoint's permission?"}
  perm -- "no" --> e403["403 insufficient_permission"]
  perm -- "yes" --> handler["Endpoint runs"]
```

The organization always comes from the token, so no organization or tenant header is ever sent. A record addressed in the path must also belong to the request's project; a call or agent of another project answers `404`.

## API tokens

A token is an 80-character secret sent as a bearer token:

```bash theme={null}
curl "$CALLVA_API/calls" \
  -H "Authorization: Bearer $CALLVA_TOKEN" \
  -H "Accept: application/json"
```

Tokens are created in the dashboard, under the organization's **Settings**, **API Keys**, **Create API Key** - the [Quickstart](https://docs.callva.io/quickstart.md) walks through it. Three choices are made at creation and cannot be changed afterwards: the key's name, its **role**, and its **project scope**.

* CallVA stores only a hash of the token. The plain value is shown once, in the dialog that creates it; nobody can read it again, including CallVA.
* A token does not expire. The API Keys list shows when each key was last used, so a key nothing uses any more is easy to spot.
* Deleting a key in the API Keys list revokes it at once: its next request answers `401`.
* Creating and deleting keys is done by people in the dashboard, with the `api_tokens.create` and `api_tokens.delete` permissions. The External API has no endpoint that creates or lists keys, so a token can never mint another token.

## Organization-wide and project tokens

The project scope chosen at creation makes a token one of two kinds.

| | Project token | Organization-wide token |
| - | - | - |
| Created with **Project Scope** | One project | All projects |
| Acts in | Its own project, always | The project each request names |
| Project in the request | Ignored | Required: `X-Project-ID` header or `project_id` |
| Organization-level permissions | Dropped, whatever its role | Kept, as its role grants them |
| Can create, rename or delete projects | No | With `projects.create`, `projects.update`, `projects.delete` |

### Naming the project

An organization-wide token names the project of each request in one of these places, read in this order:

1. The `X-Project-ID` header.
2. `project_id` in the JSON body - for writes (`POST`, `PUT`, `PATCH`).
3. `project_id` in the query string - for reads and deletes.

A project token ignores all three, so sending a header to a project token does no harm and changes nothing.

> [!NOTE]
>
> An organization-wide token needs a project even for `GET /projects`. To find a project's id, open the project in the dashboard and choose **Project Settings**: the id sits under the page title.

### What a project token cannot do

A project token keeps only the project-level permissions of its role. Permissions that act on the whole organization - projects, members, roles, billing, organization settings, phone numbers and telephony providers - are dropped, even when the role is Admin. The one exception: every project token may read its own project's phone numbers, which placing a call from a chosen number needs.

So a project token with the Admin role still cannot create a project:

```bash theme={null}
curl -X POST "$CALLVA_API/projects" \
  -H "Authorization: Bearer $CALLVA_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"name": "Second project"}'
```

```json 403 Forbidden theme={null}
{ "success": false, "message": "Insufficient permission.", "code": "insufficient_permission" }
```

## Roles and permissions

A **permission** is a resource and an action, written `<resource>.<action>`: `calls.read`, `agents.update`, `outbound_calls.create`. A **role** is a named set of permissions. Every member of an organization and every API token gets its permissions from roles; a token carries exactly one role.

### Built-in roles

Every organization starts with these roles. They are the organization's own copies: they can be edited, renamed or deleted like any role you create.

| Role | For | Permissions |
| - | - | - |
| **Admin** | Full control of the organization | Full access: every permission, including any that later releases add |
| **Analyst** | Reading results, changing nothing | `calls.read`, `transcripts.read`, `recordings.read`, `actions.read`, `knowledge.read`, `issues.read`, `dashboard.read` |
| **Author** | Building agents and their components | `agents.read`, `agents.create`, `agents.update`, `agents.delete`, `assets.read`, `assets.create`, `assets.update`, `assets.delete`, `fields.create`, `fields.update`, `fields.delete`, `field_groups.create`, `field_groups.update`, `field_groups.delete`, `actions.read`, `actions.create`, `actions.update`, `actions.delete`, `knowledge.read`, `knowledge.create`, `knowledge.update`, `issues.read`, `issues.create`, `issues.update`, `shared_views.manage`, `dashboard.read` |
| **Support** | Working calls: placing them, updating them, pressing actions | `calls.read`, `calls.update`, `transcripts.read`, `transcripts.create`, `transcripts.update`, `recordings.read`, `outbound_calls.create`, `actions.read`, `actions.execute`, `knowledge.read`, `issues.read`, `issues.create`, `issues.update`, `dashboard.read` |
| **Accountant** | Billing only | `billing.read`, `billing.manage` |
| **Member** | Day-to-day work on calls and the data model | `calls.read`, `calls.create`, `calls.update`, `calls.delete`, `transcripts.read`, `transcripts.create`, `transcripts.update`, `transcripts.delete`, `recordings.read`, `recordings.delete`, `fields.create`, `fields.update`, `fields.delete`, `field_groups.create`, `field_groups.update`, `field_groups.delete`, `actions.read`, `actions.execute`, `knowledge.read`, `issues.read`, `projects.create`, `settings.read`, `tenant_settings.read`, `phone_numbers.read`, `dashboard.read` |

There is no Owner role. The organization's owner is a person, and holds every permission; a token is never an owner.

### Every permission

> [accordion] All resources and their actions
>
> **Project** permissions apply inside a project. **Organization** permissions apply to the whole organization and are dropped from project tokens.
>
> | Resource | Applies to | Permissions |
> | - | - | - |
> | Calls | Project | `calls.read`, `calls.create`, `calls.update`, `calls.delete` |
> | Transcripts | Project | `transcripts.read`, `transcripts.create`, `transcripts.update`, `transcripts.delete` |
> | Recordings | Project | `recordings.read`, `recordings.delete` |
> | Issues | Project | `issues.read`, `issues.create`, `issues.update`, `issues.delete` |
> | Outbound calls | Project | `outbound_calls.create` |
> | Voice agents | Project | `agents.read`, `agents.create`, `agents.update`, `agents.delete` |
> | Actions | Project | `actions.read`, `actions.create`, `actions.update`, `actions.delete`, `actions.execute` |
> | Assets | Project | `assets.read`, `assets.create`, `assets.update`, `assets.delete` |
> | Fields | Project | `fields.create`, `fields.update`, `fields.delete` |
> | Field groups | Project | `field_groups.create`, `field_groups.update`, `field_groups.delete` |
> | Knowledge | Project | `knowledge.read`, `knowledge.create`, `knowledge.update`, `knowledge.delete` |
> | Archetypes | Project | `archetypes.read`, `archetypes.update` |
> | Automations | Project | `automations.read`, `automations.create`, `automations.update`, `automations.delete`, `automations.manage` |
> | Schedules | Project | `schedules.read`, `schedules.create`, `schedules.update`, `schedules.delete` |
> | Variables | Project | `variables.read`, `variables.create`, `variables.update`, `variables.delete` |
> | Webhook logs | Project | `webhook_logs.read` |
> | Project settings | Project | `settings.read`, `settings.update` |
> | Dashboard | Project | `dashboard.read` |
> | Dashboard layout | Project | `widgets.update` |
> | Shared views | Project | `shared_views.manage` |
> | Project secrets | Project | `project_secrets.read` |
> | Projects | Organization | `projects.create`, `projects.update`, `projects.delete`, `projects.manage` |
> | Team members | Organization | `members.read`, `members.create`, `members.update`, `members.delete` |
> | Roles | Organization | `roles.read`, `roles.create`, `roles.update`, `roles.delete`, `roles.manage` |
> | Billing | Organization | `billing.read`, `billing.manage` |
> | Organization settings | Organization | `tenant_settings.read`, `tenant_settings.update` |
> | Organization secrets | Organization | `tenant_secrets.read` |
> | API keys | Organization | `api_tokens.read`, `api_tokens.create`, `api_tokens.delete` |
> | MCP connections | Organization | `mcp_connections.read`, `mcp_connections.delete` |
> | Phone numbers | Organization | `phone_numbers.read`, `phone_numbers.create`, `phone_numbers.update`, `phone_numbers.delete` |
> | Telephony providers | Organization | `providers.read`, `providers.create`, `providers.update`, `providers.delete`, `providers.manage` |
> | LiveKit SIP | Organization | `telephony.read`, `telephony.create`, `telephony.update`, `telephony.delete`, `telephony.manage` |
>
> Fields have no read permission of their own: reading field and field-group definitions is open to any role that holds `calls.read` or any field or field-group permission. An agent's presets and tools fall under `agents`: reading them is `agents.read`, changing them is `agents.update`, and cloning an agent is `agents.create`.

### Custom roles

When no built-in role fits, make your own under **Settings**, **Roles**, **New role**: name it, then tick the permissions it holds, or give it full access.

* Creating a role needs `roles.create`. You can put into a role, or hand out through a key, only the permissions you hold yourself - unless you hold `roles.manage` (**Grant any permissions**). Only someone with full access can create or hand out a full-access role.
* Editing a role changes what every member and key holding it may do, from their next request.
* A role still held by a member or an API key cannot be deleted; move them to another role, or delete the keys, first.

### How each endpoint states its permission

Every page of the [API reference](https://docs.callva.io/api-reference/introduction.md) opens with the permission the endpoint requires, such as "Requires permission: `calls.read`". The same requirement is in the OpenAPI specification as the operation's `x-permission` field - a string for a single permission, or a rule for the few endpoints whose requirement is worked out per request:

```json theme={null}
"get": {
  "summary": "List calls",
  "x-permission": "calls.read"
}
```

```json theme={null}
"get": {
  "summary": "Get all custom field definitions for an entity type",
  "x-permission": {
    "rule": "field_read",
    "any_of": ["calls.read", "fields.create", "fields.update", "fields.delete",
               "field_groups.create", "field_groups.update", "field_groups.delete"]
  }
}
```

Reading projects (`GET /projects`, `GET /projects/{id}`) needs no permission beyond reaching the project: the rule is `project_reach`, and a token answers only for the projects it reaches.

A software agent can read `x-permission` from the specification to work out, before calling, which role an integration needs.

## Why a request is refused

| Status | `code` | Cause | Fix |
| - | - | - | - |
| `401` | - | No `Authorization: Bearer` header, or the token is unknown, revoked or deleted | Send a valid token; create a new one if it was deleted |
| `400` | `PROJECT_REQUIRED` | An organization-wide token named no project | Send `X-Project-ID`, or `project_id`, or use a project token |
| `404` | `PROJECT_INVALID` | The named project does not exist, is not in the token's organization, or is inactive | Check the project id |
| `403` | `insufficient_permission` | The token's role does not hold the endpoint's permission | Give the integration a token whose role holds it |
| `404` | `RESOURCE_NOT_IN_PROJECT`, or none | The record addressed in the path does not exist in the request's project | Check the id and the project |

> [accordion] 401 - missing or invalid token
>
> No `Authorization` header, or one that does not start with `Bearer `:
>
> ```json 401 Unauthorized theme={null}
> {
>   "error": "Missing authorization token",
>   "message": "Authorization header with Bearer token is required"
> }
> ```
>
> A token CallVA does not know - mistyped, or deleted from the API Keys list:
>
> ```json 401 Unauthorized theme={null}
> {
>   "error": "Invalid or expired token",
>   "message": "The provided token is invalid or has expired"
> }
> ```
>
> These two answers have no `success` field; read the HTTP status.

> [accordion] 400 PROJECT\_REQUIRED - organization-wide token without a project
>
> ```json 400 Bad Request theme={null}
> {
>   "success": false,
>   "error": "Missing project context",
>   "message": "This API token is tenant-scoped. Please provide project_id in the request payload or query parameters, or use a project-scoped token.",
>   "code": "PROJECT_REQUIRED"
> }
> ```

> [accordion] 404 PROJECT\_INVALID - a project the token cannot reach
>
> The same answer for a project that does not exist, one of another organization, and an inactive one, so the response never reveals which:
>
> ```json 404 Not Found theme={null}
> {
>   "success": false,
>   "error": "Invalid project",
>   "message": "Project not found or does not belong to your tenant",
>   "code": "PROJECT_INVALID"
> }
> ```

> [accordion] 403 insufficient\_permission - the role lacks the permission
>
> A token with the Analyst role may read calls, but not agents:
>
> ```bash theme={null}
> curl "$CALLVA_API/agents" \
>   -H "Authorization: Bearer $CALLVA_TOKEN" \
>   -H "Accept: application/json"
> ```
>
> ```json 403 Forbidden theme={null}
> { "success": false, "message": "Insufficient permission.", "code": "insufficient_permission" }
> ```
>
> The refusal is whole: an endpoint never answers part of its result to a token that lacks its permission. To reach it, use a token whose role holds the permission the endpoint's page names.

## Give each token only what it needs

A token is a credential that works from anywhere until it is deleted. Keep what each one can do as small as its job.

**1. Pick or make a role that matches the job**

List the endpoints the integration calls, and read the permission on each one's page. If a built-in role holds those and little else, use it - Analyst for reporting, Support for working calls. Otherwise create a role with exactly those permissions. A dashboard that only reads results needs `calls.read` and `transcripts.read`, not Admin.

**2. Prefer a project token**

If the integration works in one project, create the key for that project. It cannot reach another project's data, and it cannot touch organization-level settings, members, billing or phone numbers whatever its role holds.

**3. One token per integration**

Give each system, script and person its own key, named for it. The API Keys list then tells you what each key is for and when it was last used, and deleting one stops one integration only.

**4. Rotate and revoke**

To rotate a key, create a new one with the same role and scope, switch the integration over, confirm the new key's **Last Used At** moves, then delete the old key. Delete a key at once if it may have leaked, and delete keys that are no longer used.

## MCP connections use OAuth

AI agents that connect over MCP do not use API keys. When an MCP client is pointed at `https://mcp.callva.io/mcp`, it opens a CallVA sign-in in your browser; you choose the organization and the project, and approve. The connection then acts as you, in that one project, and never beyond what your own role holds there. CallVA issues the MCP client its own OAuth tokens, which the External API does not accept, so nothing usable as an API key leaves the flow. Connections are listed, and can be revoked, under **Settings**, **MCP Connections**. See [MCP](https://docs.callva.io/mcp.md) for setup.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.