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

# Authentication

> Learn how to authenticate requests and manage API keys.

The Reply API uses Bearer token authentication. Every request must include your API key in the `Authorization` header.

## Get your API key

<Steps>
  <Step title="Sign in">
    Sign in to your [Reply.io dashboard](https://run.reply.io).
  </Step>

  <Step title="Open API key settings">
    Go to **Settings → API Key**.
  </Step>

  <Step title="Create or copy a key">
    Create a new API key or copy an existing one. Treat your API key like a password.
  </Step>
</Steps>

## Authenticate a request

Include your API key as a Bearer token:

```http theme={null}
GET /v3/whoami HTTP/1.1
Host: api.reply.io
Authorization: Bearer YOUR_API_KEY
```

### curl example

```bash theme={null}
curl https://api.reply.io/v3/whoami \
  -H "Authorization: Bearer YOUR_API_KEY"
```

## Verify your setup

Call `/v3/whoami` to verify that your API key works. A successful response returns information about the authenticated user:

```json theme={null}
{ "userId": 12345 }
```

## Unauthorized response

If the `Authorization` header is missing, invalid, or contains a revoked API key, the API returns `401 Unauthorized` with an empty response body.

The response includes a `WWW-Authenticate` header:

```text theme={null}
HTTP/1.1 401 Unauthorized
Content-Length: 0
WWW-Authenticate: Bearer
```

Do not expect a JSON error response. Use the status code and headers to handle authentication failures.

<Note>
  This empty-body `401` covers **key** failures — a missing, malformed, or revoked key. A request that presents a valid Team or Organization key but cannot resolve an acting user is different: it returns an `application/problem+json` body with a `code` (see [Organization API key → Rejection responses](#rejection-responses)).
</Note>

## Scopes

Every API key is granted a set of **scopes**. A scope is a permission such as `contacts:read` that controls which endpoints the key can call. Each endpoint requires a single **minimal scope**, shown as a small note near the top of that endpoint's reference description (below any Beta notice) — for example:

<small>*Requires the `contacts:read` scope (or a broader one that includes it).*</small>

Endpoints that need no particular permission say **No scope required** instead — any valid API key can call them (for example, `GET /v3/whoami`).

Scopes are configured **per API key** in **Settings → API Key**. Grant a key only the scopes its integration needs.

### Scope format

A scope is written as `domain:verb`:

* **`domain`** — the area of the API the permission applies to (for example `contacts`, `sequences`, `inbox`).
* **`verb`** — the level of access within that domain:
  * **`read`** — read data.
  * **`write`** — create, update, or delete data.
  * **`operate`** — trigger runtime actions (for example, push a contact to a sequence, start or pause a sequence, or reply to an inbox thread).

### How a key satisfies a required scope

A key can hold a required scope directly, or hold a **broader** scope that includes it:

* **`write` and `operate` each also satisfy a `read` requirement** — a key that can write or operate in a domain can also read in that domain. (`write` and `operate` are independent of each other; neither one includes the other.)
* A **verb wildcard** `domain:*` satisfies any verb in that domain — for example, `contacts:*` satisfies `contacts:read`, `contacts:write`, and `contacts:operate`.
* The **global wildcard** `*:*` satisfies every scope.

For example, an endpoint that requires `contacts:read` can be called by a key granted any of `contacts:read`, `contacts:write`, `contacts:operate`, `contacts:*`, or `*:*`.

### Available scopes

| Domain      | Scopes                                                   | Covers                                                                                                                                            |
| ----------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `settings`  | `settings:read`, `settings:write`                        | Read and modify account settings.                                                                                                                 |
| `contacts`  | `contacts:read`, `contacts:write`, `contacts:operate`    | Read contacts, accounts, and related data; create/update/delete them; and trigger contact-level runtime actions (push to sequence, enrich, etc.). |
| `sequences` | `sequences:read`, `sequences:write`, `sequences:operate` | Read sequences, steps, variants, and templates; create/update/delete them; and start, pause, or resume running sequences.                         |
| `channels`  | `channels:read`, `channels:write`, `channels:operate`    | Read connected email and LinkedIn channels, manage their configuration, and connect/disconnect/re-authorize them.                                 |
| `inbox`     | `inbox:read`, `inbox:write`, `inbox:operate`             | Read inbox threads, messages, and categories; manage inbox configuration; and reply, mark, or categorize threads.                                 |
| `tasks`     | `tasks:read`, `tasks:write`, `tasks:operate`             | Read tasks, create/update/delete them, and complete or execute them.                                                                              |
| `reporting` | `reporting:read`                                         | Read reporting data. **Read-only** — there is no `write` or `operate` verb.                                                                       |
| `webhooks`  | `webhooks:read`, `webhooks:write`, `webhooks:operate`    | Read webhook subscriptions and deliveries, manage subscriptions, and test/enable/disable them.                                                    |
| `ai-sdr`    | `ai-sdr:read`, `ai-sdr:write`, `ai-sdr:operate`          | Read AI-SDR campaigns and configuration, manage them, and run/stop/manage their runtime state.                                                    |
| `other`     | `other:read`, `other:write`, `other:operate`             | Read, manage, or trigger miscellaneous resources not covered by a domain-specific scope (background jobs, etc.).                                  |

A separate `legacy:use` scope governs the older, non-V3 API and does not apply to the V3 endpoints documented here.

## Special API keys

In addition to user API keys, Reply supports two centralized authentication methods:

* **Team keys** for a single team/workspace
* **Organization API keys** for all team/workspaces in an organization

### Team key — team level

A Team key provides team/workspace-wide access.

Unlike a user API key, which always acts as a single user, a Team key can make requests as any user in the team/workspace.

Common use cases include:

* Team-wide automation
* Cross-user reporting
* Bulk operations

#### Permissions and access control

* Only the team/workspace owner can generate and view the Team key.
* The key must be created manually.
* A Team key is unique to its team/workspace.

For a credential that spans every team/workspace in an organization, see [Organization API key](#organization-api-key-—-organization-level) below.

#### Acting on behalf of a user

Use one of the following headers:

* `X-User-Id`
* `X-User-Email`

<Note>
  You only need one header.
</Note>

#### Behavior and permission model

Team keys use the same permission model as user API keys.

| Request                   | Behavior                              |
| ------------------------- | ------------------------------------- |
| Team key only             | Acts as the team/workspace owner      |
| Team key + `X-User-Id`    | Acts as the specified user (by ID)    |
| Team key + `X-User-Email` | Acts as the specified user (by email) |

In all cases, the permissions of the resolved user are applied.

#### Important notes

* If an impersonation header is provided, the request runs with that user's permissions.
* If no impersonation header is provided, the request runs with the team/workspace owner's permissions.
* A Team key does not grant permissions beyond those of the resolved user.

### Organization API key — organization level

An **Organization API** key provides centralized access across every team/workspace in an organization.

Unlike a Team key, which is limited to a single team/workspace, an Organization API key can act on behalf of users across all team/workspaces owned by the organization.

#### Availability

* Organization-scoped
* Works across all team/workspaces in the organization
* Multiple keys can be created
* Keys can be named, revoked, and rotated independently

#### Permissions and access control

By default, Organization Owners can create, view, and revoke organization API keys.

Other organization users can be granted role with permission 'Manage organization API keys' to manage organization API keys.

Keys are managed in: **Settings → API keys**

#### Acting on behalf of a user

Organization API keys always require impersonation.

Use one of the following:

* `X-USER-ID`
* `X-User-Email` together with `X-TEAM-ID`

If no impersonation header is provided, the request is rejected.

If `X-User-Email` is provided without `X-TEAM-ID`, the request is rejected.

Example:

```http theme={null}
GET /v3/whoami HTTP/1.1
Host: api.reply.io
Authorization: Bearer YOUR_ORG_API_KEY
X-USER-ID: 12345
```

#### Behavior and permission model

| Request                                         | Behavior                                                     |
| ----------------------------------------------- | ------------------------------------------------------------ |
| Organization key + `X-USER-ID`                  | Acts as the user with that ID                                |
| Organization key + `X-User-Email` + `X-TEAM-ID` | Acts as the user with that email in the given team/workspace |
| Organization key without impersonation          | Rejected — `403 USER_REQUIRED`                               |
| User belongs to a different organization        | Rejected — `403 TEAM_NOT_ACCESSIBLE`                         |

In all accepted requests, the permissions of the resolved user are applied.

#### Rejection responses

A rejected impersonation request returns `application/problem+json` with a stable `code` you can branch on:

| Condition                                                       | Status | `code`                |
| --------------------------------------------------------------- | ------ | --------------------- |
| No impersonation header (or `X-User-Email` without `X-TEAM-ID`) | `403`  | `USER_REQUIRED`       |
| Resolved user does not exist                                    | `401`  | `USER_NOT_FOUND`      |
| Resolved user belongs to a different organization               | `403`  | `TEAM_NOT_ACCESSIBLE` |

```json theme={null}
{
  "title": "Access forbidden",
  "status": 403,
  "code": "USER_REQUIRED",
  "detail": "This credential requires an acting-user header."
}
```

This is distinct from an invalid or expired key, which returns `401` with an empty body (see [Unauthorized response](#unauthorized-response)).

#### Important notes

* Every request runs with the permissions of the resolved user.
* The resolved user must belong to the same organization that issued the key.
* An Organization API key does not grant permissions beyond those of the resolved user.

## Keep your API key safe

* Never commit API keys to source control.
* Never share API keys in public chats or screenshots.
* Rotate your API key from **Settings → API Key** if you think it has been exposed.
