Skip to main content
Every MCP session is scoped to one brand and one user. The brand controls what data the session sees and which records it can write to; the key’s scopes control whether it may write at all; the user is recorded on every action for audit. None of the three is ever passed as a tool input — all are resolved from the transport layer.

Auth context shape

When the server accepts your connection, it resolves a context that looks like:
The brandId is the most important field — every read filters by it, every write rejects cross-brand IDs against it.

Permissions

Every key carries a scope list, chosen when you mint it: read is the default. Calling a write tool with a read-only key returns a 403 with Insufficient scope — the check runs before the tool’s own logic, so nothing is written and no lookup happens. Give an integration the narrowest scope that does its job. An agent that reports on visibility does not need write, and a read-only key cannot archive your tracked questions or spend plan capacity if it leaks.
Keys created before permissions were enforced carry read + write, because that is the access they have actually had. Check the Permissions column in Settings → API Keys and re-mint any that should be read-only.

Transport options

stdio (local clients)

Used by Claude Desktop, Cursor, and other local MCP clients. Brand and user come from environment variables you set in your client config:
The user must be an active member of the brand. The check runs at connection time, so a misconfigured env throws immediately rather than failing later on a tool call.

Streamable HTTP (remote agents)

Used by hosted agents and remote integrations. Auth is an API key in the Authorization header:
The key maps to a stored (brand, user, api_key) tuple. The resolved API key id is recorded on the auth context so every write is traceable back to a specific key.

Brand scoping

Every read query filters by your auth context’s brandId at the database layer. Every write tool that accepts an ID looks up that ID and rejects with a 403 if it belongs to a different brand. There’s no MCP-side workaround — the same guard applies whether the client is Claude Desktop, a hosted agent, or any other caller. Example: approve_ticket({ ticket_code: "OTHER-001" }) resolves the ticket, sees it belongs to a different brand, and rejects with a 403. The audit log records the failed attempt.

API key lifecycle

Create API keys in the Citable web app under Settings → API Keys. Each key:
  • Belongs to one user (the creator) and one brand
  • Cannot be used to act on any other brand
  • Can be revoked anytime — revoked keys fail auth immediately
  • Records last_used_at on every successful call
Rotate by creating a new key, updating your integration, and revoking the old one. Keys are shown once at creation time and never displayed again. Treat them like any other secret.

Rate limits

Calls are limited to 60 per minute per API key (requests that arrive before auth resolves are bucketed by client IP instead). The limit is currently flat — it does not vary by plan tier, and it counts reads and writes together. Response headers tell you where you stand:
RateLimit-Reset is seconds until the window rolls over. Exceeding the limit returns a JSON-RPC error with Rate limit exceeded. If you need a higher limit for a specific integration, get in touch from your account.
Rate limiting is throughput protection, not an authorization control. Use the read scope to stop an integration from writing.