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

# Bot tokens API

> Mint and revoke the tt_bot_ tokens the crawl-report middleware uses.

## What these are

A bot token is the credential the [`@traceten/ai-crawl` middleware](/install/ai-crawler-tracking) sends when it reports an AI crawler hit from your server. One token per deployment is the shape we expect.

They are a **different credential family** from your API keys. A `tt_bot_` token authenticates crawl reports and nothing else. It cannot read your analytics and it cannot change your configuration.

```
https://api.traceten.com/v1/account/bot-tokens
```

Listing needs `config:write`. Which of your deployments hold live crawl-write credentials, and which are dormant, is not an analytics read.

Revoking needs `config:write`. Minting needs `credentials:write` **and** `ingest:write`: it hands back a secret, and a key that cannot itself ingest must not be able to manufacture one that can.

Minting and revoking also require that the person behind your key is still an account-wide admin. See [who is behind a key](/api/authentication#who-is-behind-a-key). Listing does not make that check, because it would put a call to your identity provider on a read path, but a Clerk session still has to be an account-wide admin to use it.

## `GET /v1/account/bot-tokens`

```bash theme={null}
curl "https://api.traceten.com/v1/account/bot-tokens?site_id=ttid_7Rb4TrC1dTbnD8w3s1TS12" \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

```json theme={null}
{
  "tokens": [
    {
      "id": "b21c7d90-3f4e-4a15-8c62-0d9e1a3b7f45",
      "prefix": "tt_bot_example1",
      "name": "prod-web",
      "site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
      "snippet_key": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
      "site_domain": "example.com",
      "last_used_at": "2026-08-29T09:14:00.881Z",
      "revoked_at": null,
      "created_at": "2026-07-11T16:20:00.000Z"
    }
  ],
  "revoked": [],
  "max_active_per_site": 5,
  "max_active_per_account": 30
}
```

`site_id` is optional and narrows the list to one site. A `site_id` you cannot reach returns an empty list rather than someone else's tokens.

Metadata only. `token_hash` is never selected, and the plaintext does not exist anywhere to return.

## `POST /v1/account/bot-tokens`

```bash theme={null}
curl -X POST https://api.traceten.com/v1/account/bot-tokens \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12", "name": "prod-web" }'
```

```json theme={null}
{
  "id": "e4a90b18-77d2-4c31-9a05-3b8f2e6c1d70",
  "prefix": "tt_bot_example2",
  "name": "prod-web",
  "site_id": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
  "snippet_key": "ttid_7Rb4TrC1dTbnD8w3s1TS12",
  "plaintext_token": "tt_bot_example2_not_a_real_token_do_not_use",
  "created_at": "2026-08-29T11:40:12.004Z"
}
```

<Warning>
  `plaintext_token` is shown once and cannot be recovered. Store it before you discard the response.
</Warning>

Put `snippet_key` into the middleware's `siteId`, not `id`. The edge compares the value your middleware sends against the snippet key and rejects anything else:

```js theme={null}
import { aiCrawl } from "@traceten/ai-crawl";

app.use(
  aiCrawl({
    siteId: "ttid_7Rb4TrC1dTbnD8w3s1TS12",
    token: process.env.TRACETEN_BOT_TOKEN,
  }),
);
```

Five active tokens per site, and 30 across the whole account. The per-site cap is small on purpose: one per environment is the expected shape, and a large set of live secrets is only attack surface. The account cap leaves room for one token on every site of a large plan, plus some to rotate.

## `DELETE /v1/account/bot-tokens/{tokenId}`

```bash theme={null}
curl -X DELETE https://api.traceten.com/v1/account/bot-tokens/e4a90b18-77d2-4c31-9a05-3b8f2e6c1d70 \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

```json theme={null}
{
  "id": "e4a90b18-77d2-4c31-9a05-3b8f2e6c1d70",
  "revoked_at": "2026-08-29T11:52:03.771Z"
}
```

The edge stops accepting the token within 60 seconds, which is how long each location caches a verification result. Rotate by minting the replacement first, deploying it, then revoking the old one.

## How these are stored

Bot tokens are stored as a SHA-256 hash of the plaintext. API keys are stored with Argon2id. The difference is deliberate: a bot token is verified at the edge on the crawl ingestion path, where a memory-hard hash costs real CPU per request, and the token is 32 bytes of random data, so there is no low-entropy secret for that hash to protect.

Either way, we never store the plaintext and cannot show it to you again.

## Errors

| Status | `error`               | Cause                                                                                     |
| ------ | --------------------- | ----------------------------------------------------------------------------------------- |
| `401`  | `unauthorized`        | Missing, malformed, revoked or expired key.                                               |
| `403`  | `insufficient_scope`  | Missing `credentials:write`, `config:write` or `ingest:write`. `required_scope` names it. |
| `403`  | `forbidden`           | Not an account-wide admin, or the calling key is scoped to a single site.                 |
| `404`  | `not_found`           | Unknown token id, or a `site_id` this credential cannot reach.                            |
| `422`  | `token_limit_reached` | Five active tokens on that site, or 30 across the account, already.                       |
| `503`  | `clerk_unavailable`   | We could not verify the caller's permissions. Retry.                                      |

## Next

* [AI crawler tracking](/install/ai-crawler-tracking) for the middleware itself
* [AI crawlers API](/api/ai-crawls) to read what the crawlers did
