Authentication
How to authenticate with the Trenchcoat API using API keys and understand rate limits.
Getting an API Key
API keys are managed from your Trenchcoat dashboard. To create one:
- Sign in at app.trenchcoat.io
- Navigate to Settings → API Keys
- Click Create API Key, give it a name, and select the scopes it needs
- Copy the key immediately — it is shown only once
Keys follow the format ct_live_ followed by a random string, for example:
ct_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6Store keys in environment variables, never in source code or version control.
Authenticating Requests
Include your API key in the X-API-Key header on every request:
curl https://app.trenchcoat.io/api/v1/sessions \
-H "X-API-Key: ct_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"There is no other authentication mechanism. The header is required on all endpoints.
Scopes
Scopes describe what Trenchcoat records, from the account owner's point of view. They are not HTTP-verb permissions — there is no separate "write" scope, and every capture level can send data.
| Scope | Records |
|---|---|
read:sessions | Conversations: timing, model, tokens, cost. No content. |
read:events | The above, plus which skills, subagents, MCPs, tools, and commands ran — by name — plus error diagnostics |
read:prompts | The above, plus prompt and response text, verbatim |
read:full | The above, plus tool arguments, shell commands, file paths, and raw API bodies |
admin | Unrestricted; bypasses every check |
Each level implies the ones below it. read:full implies read:prompts, read:events, and read:sessions; read:prompts and read:events each imply read:sessions.
That implication is easy to get wrong when reading a key's scopes programmatically. GET /api/v1/keys/self returns the scopes as stored, so a full-capture key reports exactly ["read:full"] — not the four levels it effectively grants. Expand before comparing.
What each endpoint requires
| Endpoint | Required scope |
|---|---|
POST /api/v1/events | read:sessions |
POST /api/v1/otel/v1/logs | read:sessions |
GET /api/v1/sessions | read:sessions |
GET /api/v1/analytics/overview | read:sessions |
POST /api/v1/evals/scores | read:sessions |
GET /api/v1/analytics/tools | read:events |
GET /api/v1/keys/self | none — any valid key |
Ingestion requires only read:sessions, so a key at any capture level can send events. The level decides what survives ingestion, not whether it is accepted.
Content is stripped server-side
Content above a key's level is removed before the row is written. A client that sends prompt text with a read:sessions key is not rejected — the text is simply dropped.
This is why the plugin's local OTEL_LOG_* variables cannot widen what is captured: they control what Claude Code sends, while the key's scopes control what is stored. To capture more, change the key's scopes.
Two things that are easy to get wrong
Dashboard visibility is not scope-governed. Dashboard pages use your logged-in session and row-level security, never an API key. A scope determines whether data was recorded, not whether a page can display it.
Component names and tool arguments split across two levels. Which skill or tool ran resolves at read:events; the arguments it was called with require read:full. Claude Code's own OTEL_LOG_TOOL_DETAILS flag covers both together, so that one flag is justified by either level.
Rate Limits
Limits are applied per API key over a 60-second window. Exceeding one returns 429.
| Tier | Requests / minute |
|---|---|
| Standard | 60 |
| Premium | 200 |
| Ingestion | 200 |
Keys used for event ingestion are placed on the Ingestion tier. A single request may carry up to 1000 events, so the effective event throughput is far higher than the request limit suggests.
Error Responses
401 — Invalid or Missing Key
Returned when the X-API-Key header is absent or the key does not exist.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing or invalid API key."
}
}403 — Insufficient Scope
Returned when the key exists but does not have the scope required by the endpoint.
{
"error": {
"code": "FORBIDDEN",
"message": "This API key does not have the required scope: read:events"
}
}429 — Rate Limit Exceeded
Returned when the key has sent too many requests within the current window.
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 60 seconds."
}
}The response includes a Retry-After header indicating how many seconds to wait before retrying.