{"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-09-21T21:57:58.271Z"},"content":[{"type":"documentation","id":"d7494458-b3b1-4768-a3fa-69798a1c25a9","slug":"api-authentication","title":"API Authentication","url":"https://www.koji.so/docs/api-authentication","summary":"Explains the two credentials of the Koji Headless API: study-scoped pk_live_ API keys sent as Bearer tokens, and per-interview session tokens. Covers key creation, the four permissions, origin allowlists, and security best practices.","content":"# API Authentication\n\nEvery request to the Koji Headless API authenticates with an API key scoped to a single study. The key both identifies the study and controls what the caller may do, which is why you never send a project id to the interview endpoints. This guide covers the key format, where keys come from, the permission model, session tokens, origin allowlists, and how to keep all of it safe.\n\n---\n\n## How authentication works\n\nSend your key in the `Authorization` header with the `Bearer` scheme:\n\n```\nAuthorization: Bearer pk_live_wJalrXUtnFEMI4K7MDENGbPxRfiCYz2K\n```\n\nA complete authenticated request looks like this:\n\n```bash\ncurl -X POST https://www.koji.so/api/v1/interviews/start \\\n  -H \"Authorization: Bearer pk_live_wJalrXUtnFEMI4K7MDENGbPxRfiCYz2K\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"respondent\": { \"external_id\": \"user_8271\", \"display_name\": \"Jamie\" },\n    \"mode\": \"text\"\n  }'\n```\n\nTwo credentials are involved in a full interview session:\n\n- The **API key** (`pk_live_...`) is your developer credential. It identifies the study and carries the permissions that gate each endpoint.\n- The **session token** is a per-interview respondent credential. The start endpoint issues it, and the message and complete endpoints require it in the `X-Session-Token` header.\n\nHere is what each endpoint expects:\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## Key format\n\npk_live_ followed by 32 URL-safe base64 characters. Keys are stored hashed. The full key is shown exactly once, at creation.\n\nTwo practical consequences:\n\n- Copy the key when the create dialog shows it. There is no way to view it again later.\n- Because only a hash is stored, keys cannot be read out of Koji's database. If a key leaks from your side, revoke it and create a new one.\n\n---\n\n## Creating a key\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\nFor rotating, revoking, and auditing keys, see [Managing API Keys](/docs/managing-api-keys).\n\n---\n\n## Permissions\n\nEach key carries a permission set that is checked on every request:\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\nA key missing the required permission gets a 403 with a body like:\n\n```json\n{\n  \"error\": \"API key does not have interview:start permission\"\n}\n```\n\nLeast-privilege setups and the full permission model are covered in [API Key Permissions](/docs/api-permissions).\n\n---\n\n## Session tokens\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 start endpoint returns the token as `session_token` in its 201 response. See [Starting Interviews via API](/docs/starting-interviews-via-api) for the full response shape.\n\n---\n\n## Origin allowlists\n\n- Each key has an optional list of allowed origins, checked against the browser's Origin header.\n- An empty list allows all origins.\n- Requests without an Origin header (curl, server-to-server) always pass the origin check. Origin allowlists protect against browser-based misuse, not server callers.\n- Exact entries match the full origin including scheme, e.g. `https://app.example.com`.\n- Wildcard entries like `*.example.com` match the hostname of any subdomain plus the apex domain (any port). Lookalike domains such as `evil-example.com` do not match.\n- A `*` entry allows all origins explicitly.\n\n---\n\n## Security best practices\n\n- Keep keys on your server whenever you can. Server-to-server calls are the safest way to use the API.\n- If a key must ship to the browser (for example as the embed widget's `api_key` parameter), create a dedicated key with only the permissions that flow needs, and set an origin allowlist so other sites cannot reuse it.\n- Give read access (`interview:read`) only to server-side keys. Transcripts and analysis do not belong in the browser.\n- Treat each `session_token` as a secret for its interview. Do not log it, and never share it between respondents.\n- Revoke keys you no longer use. A revoked key fails with `Invalid API key` on the next request.\n\n---\n\n## Plans and credits\n\nThe headless API is available on every plan, including a free account. Usage is governed by interview credits: each interview consumes credits from the study owner's balance (text and voice at different rates), and the start endpoint rejects new interviews when the study is out of quota or the owner has no credits.\n\n---\n\n## FAQ\n\n### Can one key work across multiple studies?\n\nNo. Keys are scoped to a single study. The key determines which study an interview belongs to, which is why the start endpoint takes no project id. Create one key per study.\n\n### I lost my key. Can I recover it?\n\nNo. Keys are stored hashed and shown in full exactly once, at creation. Create a new key and revoke the old one.\n\n### Do server-to-server calls need an origin allowlist?\n\nNo. Requests without an `Origin` header, such as curl or backend calls, always pass the origin check. Origin allowlists protect against browser-based misuse, not server callers.\n\n### Is the session token a second API key?\n\nNo. The API key is your developer credential and works across all endpoints for its study. The session token is issued per interview and only proves that a message or complete request belongs to that respondent's session. The read endpoint does not use it at all.","category":"API Reference","lastModified":"2026-08-19T21:52:30.323908+00:00","metaTitle":"API Authentication | Koji Headless API","metaDescription":"Authenticate with the Koji Headless API: pk_live_ Bearer keys per study, four permissions, per-interview session tokens, and origin allowlists.","keywords":["api authentication","api key","bearer token","permissions","session token","koji api"],"aiSummary":"Explains the two credentials of the Koji Headless API: study-scoped pk_live_ API keys sent as Bearer tokens, and per-interview session tokens. Covers key creation, the four permissions, origin allowlists, and security best practices.","aiPrerequisites":["creating-your-account"],"aiLearningOutcomes":["Authenticate API requests with a Bearer pk_live_ key","Create an API key with the right permissions from the study Integrate menu","Use the session token as the per-interview second factor","Restrict browser usage of a key with origin allowlists","Apply least-privilege and server-side key practices"],"aiDifficulty":"beginner","aiEstimatedTime":"8 min"}],"pagination":{"total":1,"returned":1,"offset":0}}