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

# Authentication and scopes

> Machine credentials for /api/v2, and the complete scope registry.

Every operation on `/api/v2/` is reachable with a machine credential and demands
a scope, named on the operation as `x-required-scope` in the generated reference.

```bash theme={null}
curl https://api.impellabs.tech/api/v2/responses \
  -H "Authorization: Bearer tgcc_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"agent": "agt_...", "input": "Summarise the last quarter."}'
```

One key per workspace, identifying that workspace. Only a hash is stored, so the
secret is shown exactly once — at creation — and no read path in the platform can
reconstruct it. Keys carry an optional expiry, record their last use, and can be
revoked individually.

<Warning>
  Backend only. Never in a browser or a mobile client. Grant the minimum scopes a
  caller needs: that is the whole of what decides how bad an exposure is.

  The website widget uses a **publishable** key (`tegain_site_…`) which is meant
  to be public, is constrained by an origin allowlist and per-IP rate limits, and
  cannot read anything. It is not a workspace key and cannot call `/api/v2/`.
</Warning>

## Scopes do not imply one another

There is no hierarchy, no wildcard and no implication. A key that may read
conversations cannot run an agent. A key that may edit an assistant cannot
preview its drafts. A key that may replay a webhook delivery cannot rotate its
signing secret.

A request outside a key's scopes is **403 naming the missing scope**. The
endpoint is never silently skipped and never quietly narrows to nothing:

```json theme={null}
{
  "error": {
    "code": "permission_denied",
    "message": "This caller is not permitted to perform this operation.",
    "request_id": "req_..."
  }
}
```

## The registry

`adminapi/services/developer.py::SCOPES` is canonical — a key cannot be created
with a scope outside it, so a typo becomes a 400 rather than a key that silently
authorises nothing. There are **42 entries**. They are listed here in full,
including the ones `/api/v2/` never uses, because "what can this key do" should
have one answer and not two.

### The v2 runtime

| Scope                  | Unlocks                                                              |
| ---------------------- | -------------------------------------------------------------------- |
| `responses:create`     | `POST /responses`, and `POST /responses/{id}/cancel`                 |
| `conversations:create` | `POST /conversations`                                                |
| `conversations:read`   | `GET /conversations/{id}`, `GET /conversations/{id}/messages`        |
| `conversations:write`  | `POST /conversations/{id}/messages`                                  |
| `agents:run`           | `POST /agents/{id}/runs`                                             |
| `agents:read`          | `GET /agents/{id}/runs/{run_id}`                                     |
| `agent:preview`        | `"preview": true` — running an agent's **unpublished** configuration |
| `logs:read`            | `GET /logs`, `GET /logs/{run_id}`                                    |

<Note>
  `agent:preview` is issued on its own and is implied by nothing. Asking for a
  preview is free; being allowed is not. Without the scope the answer is **404,
  not 403** — see [Responses](/v2/responses#preview-and-drafts).

  `logs:read` is deliberately its own scope rather than a corner of `usage:read`
  (a billing figure) or `chat:read` (transcript content). It reads the *shape* of
  an execution — status, latency, model, tokens, tool names, error code — and a
  key that may debug an integration should not thereby read what that
  integration's users said. There is no `logs:write`: nothing writes a log
  through an API.
</Note>

### Documents and intelligence

| Scope                | Unlocks                                                                   |
| -------------------- | ------------------------------------------------------------------------- |
| `documents:create`   | `POST /documents`, `POST /documents/uploads`                              |
| `documents:read`     | `GET /documents`, the content, representation and download-link endpoints |
| `documents:delete`   | `DELETE /documents/{id}`                                                  |
| `extractions:create` | `POST /extractions`                                                       |
| `extractions:read`   | `GET /extractions`, `GET /extractions/{id}`                               |
| `reports:create`     | `POST /reports`                                                           |
| `reports:read`       | `GET /reports`, `GET /reports/{id}`                                       |

### Jobs

| Scope         | Unlocks                           |
| ------------- | --------------------------------- |
| `jobs:create` | `POST /jobs`                      |
| `jobs:read`   | `GET /jobs`, `GET /jobs/{job_id}` |
| `jobs:cancel` | `POST /jobs/{job_id}/cancel`      |

Read and cancel are split from create so a read-only monitoring integration
cannot queue work.

### Webhooks

| Scope             | Unlocks                                                                    |
| ----------------- | -------------------------------------------------------------------------- |
| `webhooks:read`   | Endpoints, deliveries, events, the catalogue, settings                     |
| `webhooks:write`  | Replaying a delivery or an event; regenerating the legacy workspace secret |
| `webhooks:manage` | Creating, editing and deleting an endpoint; rotating its signing secret    |

<Warning>
  `webhooks:manage` is deliberately separate from `webhooks:write`. Creating an
  endpoint decides where a workspace's events are sent, and rotating its secret
  decides who can forge them. That is a different authority from replaying a
  delivery — and it is the one an attacker actually wants.
</Warning>

### Knowledge and the v1 platform surface

These predate v2 and are shared with `/api/v1/`. `knowledge:read` and
`knowledge:write` are the two that `/api/v2/knowledge/ingestions` uses.

| Scope                                  | Unlocks                                               |
| -------------------------------------- | ----------------------------------------------------- |
| `knowledge:read` / `knowledge:write`   | Cards, folders, documents, crawls — and v2 ingestions |
| `chat:read` / `chat:write`             | Conversations on the v1 surface                       |
| `leads:read` / `leads:write`           | Leads and lead lists                                  |
| `assistants:read` / `assistants:write` | Assistant configuration                               |
| `tools:read` / `tools:write`           | Tool servers and tool definitions                     |
| `calls:read` / `calls:write`           | Calls                                                 |
| `bookings:read` / `bookings:write`     | Bookings                                              |
| `widgets:read` / `widgets:write`       | Website widget configuration                          |
| `usage:read`                           | Metered usage totals                                  |

<Note>
  `tools:read` exists so that listing tools does not have to demand write. The
  write-only version failed closed but over-demanded, which teaches integrators
  to ask for more scope than they need.
</Note>

### Not issued on a workspace key

Three scopes are in the same registry but never granted to a customer key,
because no customer surface declares them.

| Scope                        | Held by                                         |
| ---------------------------- | ----------------------------------------------- |
| `tenants:write`              | Reseller keys (`tgpk_`) — provisioning a tenant |
| `channels:write`             | Reseller keys — connecting a tenant's channels  |
| `ops:inspect`, `ops:billing` | The internal ops console                        |

The `ops:` pair is the second lock, never the first: every endpoint declaring one
*also* demands a live staff operator record, which no machine credential has.
Issuing one on a customer or partner key authorises nothing. They are in the
registry at all because a declared scope no key could ever hold is a permanently
locked door with a plausible-looking sign on it.

`tenants:write` is coarse on purpose — a partner that can provision a tenant can
already configure it, so splitting read from write there would be a distinction
without a difference. See the [Reseller API](/reseller/authentication) tab.

<Note>
  These docs say **reseller**; the marketing site says **Partner API**. They are
  the same product and the same `tgpk_` credential. The URLs here stay
  `/reseller/…`.
</Note>

## Two endpoints take no key at all

`POST|PUT /documents/uploads/{token}` and `GET /documents/downloads/{token}` are
unauthenticated, and that is deliberate rather than an oversight. The signed
token in the path **is** the credential: it names one upload or one document, for
one purpose, inside one isolation boundary, for a bounded window — and it was
minted only after a credentialed caller passed both the scope check and the
credit gate.

They are documented rather than hidden. Half a flow in a public document is worse
than none: an integrator who can request an upload target and cannot find out
what to do with it will guess.

A grant is re-checked against the row on redemption, for something the signature
cannot cover. A valid grant for a document that has since been deleted is a 404.
A signature proves the link was issued; it does not prove the thing it points at
still exists and is still yours.

## What a workspace key does not reach

The ops console and the reseller console are separate surfaces and cannot be
reached with a workspace key, whatever scopes it holds.
