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

# Authentication and scopes

> How the Titan MCP server authenticates agents, which scope each tool requires, and how to scope a key to exactly what your agent needs.

The MCP server authenticates with the same Titan API keys as the rest of the platform. There is no separate agent login and no OAuth flow—one key, sent as a bearer token.

```http theme={null}
Authorization: Bearer titan_sk_...
```

## Two layers of authorization

Every call passes two checks:

<Steps>
  <Step title="Connection check">
    The `/mcp` endpoint verifies the key is valid and carries **at least one** `mcp:*` scope. A key with no MCP scope cannot connect at all.
  </Step>

  <Step title="Per-tool check">
    Each tool then verifies its **own** scope. A key scoped only to `mcp:search` connects fine, but calling `titan_fetch` returns `forbidden`.
  </Step>
</Steps>

This is deliberate. It lets you issue a narrow key to an agent that should only ever search, without trusting the agent to restrain itself.

## Scope per tool

| Tool                   | Required scope         |
| ---------------------- | ---------------------- |
| `titan_search`         | `mcp:search`           |
| `titan_fetch`          | `mcp:fetch`            |
| `titan_crawl`          | `mcp:crawl`            |
| `titan_run_template`   | `mcp:templates:run`    |
| `titan_get_run`        | `mcp:runs:read`        |
| `titan_list_templates` | None beyond connecting |

<Note>
  Any agent that starts a run should also carry `mcp:runs:read`. Without it, a run that exceeds the synchronous wait window returns a `run_id` the agent cannot poll.
</Note>

## Create a key

Create keys with a JWT from an authenticated session:

```bash theme={null}
curl -sS -X POST "$AUTH_URL/api/v1/api-keys" \
  -H "Authorization: Bearer $JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Research agent",
    "scopes": ["mcp:search", "mcp:fetch", "mcp:runs:read"]
  }'
```

The `key` in the response is shown once. Store it in a secret manager, not in a config file you commit.

## Recommended scope sets

Grant the smallest set that does the job.

| Agent type             | Scopes                                     | Rationale                                           |
| ---------------------- | ------------------------------------------ | --------------------------------------------------- |
| **Research assistant** | `mcp:search`, `mcp:fetch`, `mcp:runs:read` | Discovers and reads pages; never crawls whole sites |
| **Read-only reader**   | `mcp:fetch`, `mcp:runs:read`               | You supply the URLs; the agent only reads them      |
| **Site indexer**       | `mcp:crawl`, `mcp:fetch`, `mcp:runs:read`  | Maps a site, then reads selected pages              |
| **Full access**        | All five                                   | Development and prototyping                         |

<Warning>
  Scopes are fixed when a key is created and cannot be widened afterwards. To change what an agent can do, create a new key and revoke the old one.
</Warning>

## Key handling

| Practice                                     | Why                                                                                          |
| -------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Send keys in the `Authorization` header only | The server rejects keys in query parameters, which leak into logs and history                |
| Use one key per agent                        | Revoke a compromised agent without disrupting the others                                     |
| Keep keys out of version control             | Use your client's secret input mechanism—see [Connect your client](/docs/mcp/connect-your-client) |
| Rotate on a schedule                         | Long-lived keys accumulate exposure                                                          |

## Ownership and isolation

The key identifies a Titan user, and every downstream call inherits that identity:

* Runs are created under **your** account and count against **your** credits.
* `titan_get_run` can only read runs owned by the same user. An agent cannot poll someone else's `run_id`.
* Results, datasets, and exports enforce the same ownership as the Task Service API.

## Auth errors

| Response                            | Meaning                               | Fix                                                        |
| ----------------------------------- | ------------------------------------- | ---------------------------------------------------------- |
| `401 missing_authentication`        | No bearer token in the request        | Confirm your client forwards the `Authorization` header    |
| `401 invalid_api_key`               | Key is unknown, revoked, or malformed | Verify the key starts with `titan_sk_` and is still active |
| `401 query_string_api_key_rejected` | Key was passed in the URL             | Move it into the `Authorization` header                    |
| `403 insufficient_scopes`           | Key carries no `mcp:*` scope          | Create a new key with MCP scopes                           |
| `forbidden` in a tool result        | Key lacks that tool's specific scope  | Create a new key including the missing scope               |

Full error semantics are in [Errors and warnings](/docs/mcp/errors-and-warnings).

## Next steps

* [Connect your client](/docs/mcp/connect-your-client) — where the key goes in each client
* [Authentication and API keys](/docs/get-started/authentication-and-api-keys) — the full platform scope catalog
* [Credits and usage](/docs/mcp/credits-and-usage) — what each authorized call costs
