{"site":{"name":"Koji","description":"AI-native customer research platform that helps teams conduct, analyze, and synthesize customer interviews at scale.","url":"https://www.koji.so","contentTypes":["blog","documentation"],"lastUpdated":"2026-07-14T18:44:21.103Z"},"content":[{"type":"documentation","id":"b7b13fe1-6961-424f-8a76-016a143733e4","slug":"api-permissions","title":"API Key Permissions","url":"https://www.koji.so/docs/api-permissions","summary":"Deep dive on the four Koji API key permissions, which endpoint each one gates, the exact 403 body when a scope is missing, and recommended least-privilege key setups for browser widgets and server analytics.","content":"# API Key Permissions\n\nEvery Koji API key carries a permission set chosen when the key is created. The four permissions map one-to-one onto the four interview endpoints, which makes least-privilege keys easy to reason about: a key can do exactly what its scopes say and nothing else. This guide covers each permission, what a missing permission looks like on the wire, and the key setups we recommend.\n\n---\n\n## The four permissions\n\n| Permission | What it does | Grants |\n|---|---|---|\n| `interview:start` | Start interviews | `POST /interviews/start` |\n| `interview:chat` | Send messages | `POST /interviews/{id}/message` |\n| `interview:complete` | Complete interviews | `POST /interviews/{id}/complete` |\n| `interview:read` | Read interview data | `GET /interviews/{id}` |\n\n### interview:start\n\nGrants `POST /interviews/start`. This is the entry point of every integration: it creates the interview session and returns the `session_token` your client needs for the rest of the conversation. See [Starting Interviews via API](/docs/starting-interviews-via-api).\n\n### interview:chat\n\nGrants `POST /interviews/{id}/message`. The message endpoint requires this scope on the API key, plus a valid `X-Session-Token` header proving the request belongs to the respondent's session. See [Sending Messages via API](/docs/sending-messages-via-api).\n\n### interview:complete\n\nGrants `POST /interviews/{id}/complete`. Marks the interview finished and triggers AI analysis in the background. Like the message endpoint, it needs both the key scope and the session token. See [Completing Interviews via API](/docs/completing-interviews-via-api).\n\n### interview:read\n\nGrants `GET /interviews/{id}`. Returns the full transcript, respondent info, stats, and analysis. It authenticates with the API key alone: no session token required. That makes it server-to-server friendly, and also the scope you should guard most carefully.\n\n---\n\n## The full credential matrix\n\nPermissions are only half of each endpoint's auth story. This is the complete picture, including the embed bootstrap endpoint, which is the one `/api/v1` endpoint with no permission scope (its optional API key only authenticates the prefill lookup):\n\n| Endpoint | API key | Session token | Permission |\n|---|---|---|---|\n| `POST /interviews/start` | Yes | No | `interview:start` |\n| `POST /interviews/{interview_id}/message` | Yes | Yes | `interview:chat` |\n| `POST /interviews/{interview_id}/complete` | Yes | Yes | `interview:complete` |\n| `GET /interviews/{interview_id}` | Yes | No | `interview:read` |\n| `GET /embed/{project_id}` | Optional | No | None |\n\n---\n\n## What a missing permission looks like\n\nCalling an endpoint with a key that lacks its scope returns HTTP 403 with this exact body:\n\n```json\n{\n  \"error\": \"API key does not have interview:read permission\"\n}\n```\n\nThe `error` string names the missing scope, so it tells you precisely which permission to add. There is no `code` or `details` field anywhere in the API: match on the HTTP status and the `error` string. See [Common Error Codes](/docs/common-error-codes).\n\n---\n\n## Least-privilege recipes\n\n### Client-side widget key\n\nFor a browser integration that drives the conversation itself, create a key with:\n\n- `interview:start`\n- `interview:chat`\n- `interview:complete`\n\nLeave `interview:read` off. The browser needs to run the conversation, but it should never be able to read back transcripts or analysis. Pair this key with an origin allowlist so other sites cannot reuse it (see [API Authentication](/docs/api-authentication)).\n\n### Server analytics key\n\nFor a backend job that pulls results into your warehouse or dashboards, create a key with:\n\n- `interview:read`\n\nIf this key ever leaks, the holder can read interview data for one study but cannot create, drive, or complete interviews.\n\n### Full-lifecycle server key\n\nA backend that both triggers interviews and collects results (see [Research Automation](/docs/research-automation-webhooks)) can hold all four permissions. Keep it strictly server-side.\n\n---\n\n## Choosing permissions at creation\n\nOpen your study, click the three-dot menu → Integrate → Advanced tab → API Keys → Create API Key. Select the permissions the key needs at creation; new keys default to all four.\n\nNarrow the set before you create the key. To change a key's scope later, create a new key with the set you need and revoke the old one.\n\n---\n\n## Keys and session tokens work together\n\nPermissions live on the API key, which is your developer credential: it says which study you are talking to and which operations the caller may perform. The session token is a separate respondent credential:\n\n- Issued by the start endpoint as `session_token`. A bare UUID (no prefix).\n- Scoped to a single interview session; required on the message and complete endpoints.\n- Acts as the respondent-side credential: treat it as a secret for that interview.\n- The read endpoint (GET /interviews/{id}) does not need it. It authenticates with the API key alone.\n\nThe message and complete endpoints check both. A key with the right scope but a wrong token fails with `401 Invalid session token`; a valid token with a key missing the scope fails with the 403 shown above. The two checks are independent, which makes failures easy to attribute.\n\n---\n\n## FAQ\n\n### Can I add a permission to an existing key?\n\nPermissions are selected in the create dialog. To broaden or narrow a key's scope, create a new key with the right set and revoke the old one.\n\n### Why does interview:read not need a session token?\n\nIt is designed for server-to-server retrieval after the respondent's session is over. The API key alone authenticates it, which is exactly why read access belongs on server-side keys only.\n\n### My key has interview:chat but requests still fail with 401. Why?\n\nPermission checks and session checks are separate. A 401 on the message endpoint means the `X-Session-Token` header is missing or does not match the interview. Send the exact `session_token` from the start response.\n\n### Do extra permissions cost anything?\n\nNo. Permissions only gate endpoints. Usage is governed by interview credits on the study owner's account, not by key scopes.","category":"API Reference","lastModified":"2026-07-14T13:56:41.459871+00:00","metaTitle":"API Key Permissions | Koji Headless API","metaDescription":"Understand the four Koji API permissions (interview:start, chat, complete, read), the exact 403 errors, and least-privilege key recipes.","aiSummary":"Deep dive on the four Koji API key permissions, which endpoint each one gates, the exact 403 body when a scope is missing, and recommended least-privilege key setups for browser widgets and server analytics.","aiLearningOutcomes":["Map each of the four permissions to its endpoint","Recognize the exact 403 body for a missing permission","Create least-privilege keys for client-side and server-side use","Distinguish API key permissions from session tokens"],"aiDifficulty":"beginner","aiEstimatedTime":"6 min"}],"pagination":{"total":1,"returned":1,"offset":0}}