REST API: send feedback, read themes
Last updated October 25, 2026
Send feedback to Camelus from any server, and read the themes it builds, with plain HTTP requests. Responses never contain original feedback text or personal data: only AI summaries and counts.
The machine-readable version of this page is the OpenAPI document at api.camelus.si/v1/openapi.json. It includes the webhook payloads too.
Authentication
Create an API key in Settings → API & MCP. Keys look like ck_live_abc123_…, are shown once, and belong to a workspace, not to a person. Send the key as a bearer token:
Authorization: Bearer ck_live_abc123_…
Each key has scopes:
| Scope | Allows | Plans |
|---|---|---|
feedback:write |
POST /v1/feedback |
All |
read |
Reading items and themes, and the MCP server | Pro, Studio |
themes:write |
Changing a theme's status | Pro, Studio |
Keep keys on your server. Revoke a key in Settings when you no longer need it.
Send feedback
curl https://api.camelus.si/v1/feedback \
-H "Authorization: Bearer $CAMELUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product": "habits",
"text": "Export uses US dates, I am in France.",
"external_id": "zendesk-4412",
"person": { "id": "u_123", "email": "ana@example.com", "name": "Ana" },
"metadata": { "plan": "pro", "app_version": "2.3.1" }
}'
| Field | Type | Notes |
|---|---|---|
product |
string, required | The product's slug, shown on the product page. |
text |
string, required | The feedback itself. Text beyond 10,000 characters is cut, and the item is marked truncated. |
title |
string | Up to 200 characters. |
external_id |
string | Your id for this message (a ticket number, a row id). Sending the same id twice creates one item. |
person |
object | id (your user id), email, name. All optional. Without them the feedback is anonymous. |
metadata |
object | Anything useful for triage: plan, app version, browser. Up to 4 KB of JSON. |
page_url |
string | The page the feedback is about. |
created_at |
ISO 8601 date-time | When the feedback was given, for imports. Defaults to now. |
A new item returns 202 Accepted with { "id": "k57c…", "status": "received" }.
Retries are safe. A request with the same external_id as an earlier one, or with no external_id and the same text from the same person within 10 minutes, returns 200 OK with the first item's id and "duplicate": true.
Read themes
curl "https://api.camelus.si/v1/themes?product=habits&status=open&sort=score&limit=20" \
-H "Authorization: Bearer $CAMELUS_API_KEY"
sort is score (the default), requesters, mrr, trend or recent. limit goes up to 100. Themes that have only one item so far (candidates) aren't listed.
Change a theme's status, with an optional note for its timeline:
curl -X PATCH https://api.camelus.si/v1/themes/k97d… \
-H "Authorization: Bearer $CAMELUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "planned", "note": "On the roadmap for November" }'
Statuses are open, planned, in_progress, shipped, declined and closed. A change fires theme.status_changed to your destinations, and shipped drafts an email to the people who asked.
Reference
POST /v1/feedback
Stores one feedback item. Returns 202 for a new item, 200 with the same id for a repeat (same external_id, or same text from the same person within 10 minutes). Scope: feedback:write.
GET /v1/feedback/{id}
Processing results of one item: summary, type, sentiment, urgency and theme. No original text or personal data. Scope: read.
GET /v1/themes
Themes ranked by score (or another sort), optionally for one product and status. Scope: read.
GET /v1/themes/{id}
Summary, stats, up to 10 representative item summaries and linked external work. Scope: read.
PATCH /v1/themes/{id}
Sets the status and adds an optional note to the timeline. Fires theme.status_changed to subscribed destinations. Scope: themes:write.
Theme
Returned by GET /v1/themes (in data) and inside webhook payloads.
| Field | Type | Notes |
|---|---|---|
id |
string | |
product |
object | |
title |
string | |
summary |
string or null | |
type |
bug · feature_request · question · complaint · praise · other |
|
status |
open · planned · in_progress · shipped · declined · closed |
|
score |
number | |
trend |
new · rising · falling · stable |
|
requesters |
integer | |
paying_requesters |
integer | |
mrr_affected_cents |
integer or null | null when no billing connector is set up; never estimated |
item_count |
integer | |
sentiment |
object | |
last_item_at |
string or null | |
url |
string | The theme in the Camelus app |
Theme detail
Returned by GET /v1/themes/{id} and PATCH /v1/themes/{id}: every Theme field, plus:
| Field | Type | Notes |
|---|---|---|
representative_items |
object[], optional | Up to 10, chosen by code: most recent, highest weight, diverse sources |
external_links |
object[], optional |
Feedback item
| Field | Type | Notes |
|---|---|---|
id |
string | |
product |
object | |
source |
string | |
status |
received · processing · processed · needs_review · archived · spam |
|
received_at |
string | ISO 8601 date-time, UTC |
external_id |
string or null | |
language |
string or null | |
summary_en |
string or null | |
type |
bug · feature_request · question · complaint · praise · other or null |
|
sentiment |
negative · neutral · positive or null |
|
urgency |
low · medium · high or null |
|
theme_id |
string or null |
Limits
- 60 requests a minute per API key. Past that, the API answers
429with aRetry-Afterheader. - Request bodies up to 128 KB.
- Past your plan's monthly item limit, feedback is still accepted and stored. It's never dropped.
Errors
Errors use standard HTTP status codes and a JSON body:
{ "error": { "code": "invalid_body", "message": "person.email: Invalid email address" } }
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json, invalid_body, invalid_query |
The body isn't JSON, or a field or parameter is missing or invalid. The message names it. |
| 401 | unauthorized |
The key is missing, malformed or revoked. |
| 403 | forbidden, plan_required, source_disabled |
The key lacks the scope, the plan doesn't include the read API, or the product's API source is turned off. |
| 404 | product_not_found, not_found |
No such product slug, item or theme in the key's workspace. |
| 409 | merged |
The theme was merged into another one. |
| 413 | too_large |
The body is too big. |
| 429 | rate_limited |
Too many requests for this key. Wait for the number of seconds in Retry-After. |