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

# Authentication

> Create scoped API keys in your Topo workspace, then pass them as a Bearer token on every request. Learn available scopes and security best practices.

Every request to the Topo API must be authenticated with an API key. Keys are scoped to the exact permissions your integration needs, so you can grant read-only access to one key and full write access to another — all within the same workspace.

## Create an API key

<Steps>
  <Step title="Open your workspace settings">
    Navigate to **Settings → Developers → API Keys** in the Topo dashboard.
  </Step>

  <Step title="Create a new key">
    Click **Create Key**, give it a descriptive name (e.g. `crm-sync-production`), and select the scopes your integration requires.
  </Step>

  <Step title="Copy and store the key securely">
    Your key is shown **once** immediately after creation. Copy it to a secure secret store (such as your CI/CD secrets manager or a vault). Topo does not store the raw key value — if you lose it, you must rotate to a new one.
  </Step>
</Steps>

<Warning>
  API keys are only displayed once at creation time. Store your key in a secrets manager immediately — you cannot retrieve it again from the Topo dashboard.
</Warning>

## Pass the key in requests

Include your API key as a Bearer token in the `Authorization` header of every request:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl https://api.topo.io/v1/me \
  -H "Authorization: Bearer topo_xxxxxxxxxxxx"
```

Requests without a valid `Authorization` header are rejected with a `401 Unauthorized` response before they reach any endpoint logic.

## Available scopes

Scopes follow a `resource:action` naming convention. Grant only the scopes your integration actually uses.

| Scope                      | What it allows                                                                    |
| -------------------------- | --------------------------------------------------------------------------------- |
| `contacts:read`            | Read contacts and their custom variables                                          |
| `contacts:write`           | Upsert contacts, reassign owners, and set or delete contact and account variables |
| `accounts:read`            | Read accounts and their custom variables                                          |
| `accounts:write`           | Reassign an account's owner and set or delete account variables                   |
| `contact_lists:read`       | Read contact lists and their entries                                              |
| `contact_lists:write`      | Create, update, and delete contact lists; add and remove entries                  |
| `account_lists:read`       | Read account lists and their entries                                              |
| `account_lists:write`      | Create, update, and delete account lists; add and remove entries                  |
| `sequences:read`           | Read sequences and sequence templates                                             |
| `sequences:write`          | Enroll contacts, update variables, pause, resume, and stop sequences              |
| `tasks:read`               | Read tasks and their message threads                                              |
| `tasks:write`              | Create and update to-dos; execute, skip, reopen, reassign, and complete tasks     |
| `contact_exclusions:read`  | Read contact exclusion rules                                                      |
| `contact_exclusions:write` | Create, update, and delete contact exclusion rules                                |
| `account_exclusions:read`  | Read account exclusion rules                                                      |
| `account_exclusions:write` | Create, update, and delete account exclusion rules                                |
| `activities:read`          | Read the outreach activity stream                                                 |
| `messages:read`            | Read message content                                                              |
| `events:read`              | Read ingested first-party events                                                  |
| `events:write`             | Ingest first-party events                                                         |
| `webhooks:read`            | Read webhook subscriptions                                                        |
| `webhooks:write`           | Create, update, delete, and test webhook subscriptions                            |
| `users:read`               | Read the users in your organization                                               |
| `crm:write`                | Write properties to the connected CRM record of a contact or an account           |

<Note>
  The `ApiKeyScope` enum also declares `signals:read`, `crm:read`, `tools:execute`, `exclusions:read`, and `exclusions:write` for Topo AI agent capabilities. None of those scopes gate a `/v1` REST endpoint today — the REST exclusion endpoints use the split `contact_exclusions:*` and `account_exclusions:*` scopes.
</Note>

Every endpoint names the scope it requires in its own reference page. `GET /v1/me` is the one exception — it needs a valid key but no particular scope, so you can always use it to inspect a key.

## Verify your key with GET /v1/me

The `/v1/me` endpoint returns the identity and permissions of the calling key — useful for confirming your key is valid and that it carries the scopes you expect.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl https://api.topo.io/v1/me \
  -H "Authorization: Bearer topo_xxxxxxxxxxxx"
```

**Response**

<ResponseField name="id" type="string (UUID)" required>
  Unique identifier of the API key.
</ResponseField>

<ResponseField name="organization_id" type="string (UUID)" required>
  The workspace this key belongs to.
</ResponseField>

<ResponseField name="name" type="string" required>
  The human-readable name you gave this key at creation time.
</ResponseField>

<ResponseField name="key_prefix" type="string" required>
  The leading characters of the raw key value — safe to display in logs or UIs to identify which key was used.
</ResponseField>

<ResponseField name="scopes" type="string[]" required>
  List of permission scopes granted to this key.
</ResponseField>

<ResponseField name="created_at" type="string (ISO 8601)" required>
  Timestamp of when the key was issued.
</ResponseField>

<ResponseField name="last_used_at" type="string (ISO 8601) | null" required>
  Timestamp of the last request made with this key, or `null` if it has never been used.
</ResponseField>

**Example response**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "018f1a2b-3c4d-7e8f-9a0b-1c2d3e4f5a6b",
  "organization_id": "018e9d8c-7b6a-7f5e-4d3c-2b1a0f9e8d7c",
  "name": "crm-sync-production",
  "key_prefix": "topo_xxxx",
  "scopes": ["contacts:read", "contacts:write"],
  "created_at": "2024-11-01T09:00:00Z",
  "last_used_at": "2025-01-15T14:23:11Z"
}
```

## Authentication error responses

| HTTP status        | `type`              | When it occurs                                                          |
| ------------------ | ------------------- | ----------------------------------------------------------------------- |
| `401 Unauthorized` | `UnauthorizedIssue` | The `Authorization` header is missing, malformed, or the key is invalid |
| `403 Forbidden`    | `UnauthorizedIssue` | The key is valid but does not have the required scope for this endpoint |

See [Errors & Rate Limits](/api-reference/errors-rate-limits) for the full error response shape.

## Security best practices

<Tip>
  **One key per integration.** Create a separate API key for each integration or deployment environment (e.g. `crm-sync-staging` vs `crm-sync-production`). This limits the blast radius if a key is compromised and makes it easier to audit usage.
</Tip>

* **Least privilege** — only request the scopes your integration needs. A read-only reporting tool should never have `write` scopes.
* **Rotate regularly** — retire old keys and issue fresh ones periodically, or immediately after any suspected exposure.
* **Never commit keys to source control** — use environment variables or a secrets manager to inject keys at runtime.
* **Monitor `last_used_at`** — keys that haven't been used recently may be safe to revoke.
