Quickstart: your first API call
Everything the Kumkuat app does is available over the REST API. This guide takes you from a workspace to your first authenticated call: you will create a key, check which workspace it belongs to, and list the audiences you can put content in front of.
Before you start
You need a Kumkuat workspace with at least one audience persona in it. If the workspace is new, install a few audiences from the Marketplace or create your own first.
The base URL for every call is:
https://api.kumkuat.ai
1. Create an API key
Keys are created in the app under Settings. There are two kinds, and both are sent the same way:
| Kind | Where | Acts as |
|---|---|---|
| Personal API key | Settings → Personal → Security & API keys | You, with your role in the workspace. Scorecards it generates show in the app only to you. |
| Workspace API key | Settings → Workspace → API & integrations → Workspace API keys (admin or owner only) | The workspace itself. Scorecards it generates show to every member. |
The raw key is shown once, when it is created. Store it in a secret store on your server and never ship it to a browser or a mobile app. Revoking a key cuts off anything using it immediately.
Every key is bound to one workspace. If you integrate several brands or customers, you hold one key per workspace.
Use a workspace key for scoring: it keeps working when a member leaves, and the scorecards it produces are shared with the whole team in the app. Two calls in these guides currently require a personal key from an admin or owner account: listing personas (GET /api/v1/personas) and creating documents (POST /api/v1/documents). If your integration makes those calls, hold both kinds. Every other call in these guides accepts either.
2. Authenticate
Send the key in the X-API-Key header on every request. Authorization: Bearer with the same key is accepted too.
curl https://api.kumkuat.ai/api/v1/workspace \
-H "X-API-Key: $KUMKUAT_API_KEY"
{
"workspace_id": "ws_9738dfeb738a44debb10",
"name": "Entertainment",
"company": "Netflix, Inc.",
"role": "owner",
"is_owner": true,
"is_active": true,
"website": "",
"description": "",
"settings": { "company": "Netflix, Inc.", "…": "…" },
"created_at": "2026-02-13T16:12:22.987726+00:00",
"updated_at": "2026-09-03T16:21:08.541942+00:00"
}
role is the role the key acts with. A personal key carries its owner's role; a workspace key reports member. Show name and company in your own UI so people can see which workspace an integration is connected to.
3. List the audience categories
Personas are filed under categories such as Journalists, Consumer or Investors & Capital Markets. The category list carries the workspace's own display order, so use it to group your audience picker.
curl https://api.kumkuat.ai/api/v1/audience-groups \
-H "X-API-Key: $KUMKUAT_API_KEY"
{
"groups": [
{ "id": "59a69729-01e7-42b3-a6a8-bbb6f28e9c55", "name": "Regulators & Legislators", "display_name": null, "sort_order": 0 },
{ "id": "83cfea06-3058-4daa-b5c7-bd3f568841eb", "name": "Journalists", "display_name": null, "sort_order": 1 },
{ "id": "733a2522-087f-431c-9abc-3945349fd1ba", "name": "Consumer", "display_name": null, "sort_order": 2 },
"…"
]
}
display_name is the label to show when it is set; fall back to name. Personas reference the category by name.
4. List the personas
GET /api/v1/personas?summary=1 returns every persona in the workspace as a compact object of about twenty fields. Add category=Consumer to filter to one category.
- curl
- Python
- Node
curl "https://api.kumkuat.ai/api/v1/personas?summary=1&category=Consumer" \
-H "X-API-Key: $KUMKUAT_API_KEY"
import os
import requests
BASE = "https://api.kumkuat.ai"
HEADERS = {"X-API-Key": os.environ["KUMKUAT_API_KEY"]}
resp = requests.get(f"{BASE}/api/v1/personas", params={"summary": 1, "category": "Consumer"},
headers=HEADERS, timeout=60)
resp.raise_for_status()
for persona in resp.json()["personas"]:
if persona["include_in_audience"] and persona["has_template"]:
print(persona["id"], persona["name"], "-", persona["category"])
const BASE = "https://api.kumkuat.ai";
const headers = { "X-API-Key": process.env.KUMKUAT_API_KEY };
const url = new URL(`${BASE}/api/v1/personas`);
url.searchParams.set("summary", "1");
url.searchParams.set("category", "Consumer");
const resp = await fetch(url, { headers });
if (!resp.ok) throw new Error(`${resp.status} ${await resp.text()}`);
const { personas } = await resp.json();
for (const p of personas.filter((p) => p.include_in_audience && p.has_template)) {
console.log(p.id, p.name, "-", p.category);
}
One entry from the response:
{
"personas": [
{
"id": "8e049a2e-92dd-4077-b6ca-e7154edc7de4",
"name": "Price-Sensitive Subscriber",
"description": "An individual or household primarily driven by cost-effectiveness when making purchasing decisions …",
"category": "Consumer",
"entity_kind": "archetype",
"entity_kind_label": "Segment",
"tone": "friendly",
"communication_style": "Clear, direct, and transparent about pricing and terms …",
"avatar_url": "",
"include_in_audience": true,
"include_in_voice_list": false,
"has_template": true,
"is_active": true,
"is_marketplace_managed": false,
"labels": [],
"created_at": "2026-02-13T16:50:34.332525+00:00",
"updated_at": "2026-07-25T21:09:41.398382+00:00"
},
"…"
],
"total": 74
}
The fields an audience picker needs:
| Field | Use it for |
|---|---|
id | The value you send when scoring content. Stable for the life of the workspace. |
name, description, avatar_url | Display. avatar_url may be empty. |
category | Grouping, matched to name in the category list above. |
entity_kind_label | Individual, Organization or Segment: whether the persona stands for a named individual, an outlet or institution, or an audience segment. |
include_in_audience | false marks a persona kept only as a writing voice. Leave those out of an audience picker. |
has_template | false means the persona has no profile to react from yet. Leave those out too. |
The full persona object, with its profile, linked sources and voice profiles, is available per persona at GET /api/v1/personas/{persona_id}. Without summary=1 the list returns full objects and paginates with limit (default 200, maximum 500) and offset; total tells you whether there are more.
Errors
Every error is JSON with an error message. The status code tells you what kind:
| Status | Body | Meaning |
|---|---|---|
401 | {"error": "API key or bearer token required"} or {"error": "Invalid API key"} | No key, a revoked key, or a typo |
403 | {"code": "permission_denied", "error": "…", "permission": "persona.read"} | The key's role lacks a permission an admin has withheld in Settings → Members & roles |
403 | {"error": "Admin or owner access required"} | The call needs an admin or owner key |
403 | {"error": "Workspace is not active"} | The workspace has been deactivated |
400 | {"error": "personas must be a non-empty array"} | A required field is missing or malformed |
404 | {"error": "Persona not found"} | The id is unknown, or belongs to another workspace |
422 | A list of {"loc", "msg", "type"} entries | A typed request body failed validation |
5xx | {"error": "…"} | Retry with backoff |
Conventions
- JSON in, JSON out. Send
Content-Type: application/jsonon requests with a body. - Identifiers are opaque strings and stay stable for the life of a workspace. Timestamps are ISO 8601 in UTC.
- Work that calls a model can take from seconds to a minute. Set client timeouts of at least two minutes on those calls, or use the background-job variant where one exists and poll
GET /api/v1/jobs/{job_id}. - Lists paginate. Documents use
pageandper_pageand return apaginationobject; personas uselimitandoffsetand returntotal. - Model-backed calls draw on the workspace's run budget. Identical requests are served from cache and cost nothing.
Next steps
Kumkuat API guide
A plain-language tour of the API and the three workflows most teams automate: audiences, scorecards, chat.
Score a draft against your audiences
Send content to a panel and read back grades, judgments, evidence and rewrites.
Integrate audiences into a content platform
The end-to-end blueprint: picker, pre-publish check, revise, publish.
API reference
Every endpoint with request and response schemas.