GET /v1/feedback#
List answer feedback. One page of the raw feedback rows your visitors and reviewers left on individual answers, in key order (call, then turn, then author). This is also the export: follow the cursor to the end and you hold every row.
**Paging.** Pass the next_cursor from the previous response back as ?cursor=. **An empty next_cursor is the only signal that you have seen everything**, a short page, including an empty one, is normal and means "ask again": the agent filter is applied after each read, so a page can match nothing and still have more behind it.
Parameters
| Name |
In |
Type |
Required |
Description |
call_id |
query |
string |
|
Only one call's feedback. |
agent |
query |
string |
|
Only one agent's feedback. Applied after the read, short pages are normal. |
limit |
query |
integer |
|
Rows per page, 1 to 100 (default 50). Larger values are capped rather than refused. |
cursor |
query |
string |
|
The next_cursor from the previous page. Omit for the first page. A cursor belongs to the scope that issued it. |
Responses
| Code |
Description |
200 |
Success. |
400 |
limit is not a positive whole number (bad_request), or cursor was not issued by this listing for this scope (invalid_cursor), start again without it. |
401 |
Missing or invalid API key. |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Example request
curl "https://api.voiceland.ai/v1/feedback" \
-H "Authorization: Bearer VL_API_KEY"
A successful response returns a FeedbackListResponse.
Example response
{
"items": [
{
"agent_name": "front-desk",
"author_id": "widget-user-1a2b",
"author_kind": "visitor",
"call_id": "widget-8c2f1a7e-4b3d-4e21-9c0a-5f6d7e8a9b0c",
"category": "inaccurate",
"rating": "down",
"rating_kind": "thumbs",
"score": 1,
"tenant_slug": "acme",
"turn_id": "t3",
"turn_role": "agent"
}
],
"next_cursor": ""
}
POST /v1/feedback#
Rate an answer. Records your review of one transcript turn. Send **either** rating (up/down) **or** score (1 to 5), a thumb is stored as 5/1 on the same scale, so both controls land in one shape.
turn_id is t<index>, the zero-based position of the turn in GET /calls/{id}/transcript, or o<offset_ms>, the turn's offset_ms. The turn must exist on the persisted call.
**One row per (turn, author): re-submitting replaces your previous rating, it is never counted twice.** Pass author to keep two reviewers' ratings distinct.
A rating on a human reply (a representative's turn) is stored with its role and deliberately excluded from the agent satisfaction numbers, it is not an AI quality signal.
Request body
application/json Schema: FeedbackSubmitRequest
| Field |
Type |
Required |
Description |
agent |
string |
|
|
author |
string |
|
|
call_id |
string |
Yes |
|
category |
string |
|
|
comment |
string |
|
|
rating |
string |
|
|
score |
integer |
|
|
turn_id |
string |
Yes |
|
Responses
| Code |
Description |
200 |
The stored feedback row (replacing any earlier one by the same author on the same turn). |
401 |
Missing or invalid API key. |
404 |
No CDR for that call id (call_not_found), or the turn id names no turn of it (turn_not_found). |
422 |
Neither a rating nor a score was sent, they contradict each other, or the category is not one of inaccurate/outdated/irrelevant/inappropriate/other (invalid_feedback). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Example request
curl -X POST "https://api.voiceland.ai/v1/feedback" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"call_id": "widget-8c2f1a7e-4b3d-4e21-9c0a-5f6d7e8a9b0c",
"category": "outdated",
"comment": "Quotes the 2024 price list.",
"score": 2,
"turn_id": "t3"
}'
Example response
{
"agent_name": "front-desk",
"author_id": "operator",
"author_kind": "operator",
"call_id": "widget-8c2f1a7e-4b3d-4e21-9c0a-5f6d7e8a9b0c",
"category": "outdated",
"rating": "down",
"rating_kind": "scale",
"score": 2,
"tenant_slug": "acme",
"turn_id": "t3",
"turn_role": "agent"
}
GET /v1/feedback/analytics#
Answer satisfaction analytics. Per-agent satisfaction over the stored feedback rows: rated count, up/down split, average score and the satisfaction rate (share of ratings with score ≥ 4), plus the worst-rated turns as a review feed.
Only ratings on AI turns count; ratings on human replies are excluded and reported in human_turns_excluded. The fold reads a bounded number of rows and says so: when truncated is true the numbers cover scanned rows, not the whole history.
Parameters
| Name |
In |
Type |
Required |
Description |
agent |
query |
string |
|
Fold one agent only. |
Responses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Example request
curl "https://api.voiceland.ai/v1/feedback/analytics" \
-H "Authorization: Bearer VL_API_KEY"
A successful response returns a FeedbackAnalyticsResponse.
Example response
{
"human_turns_excluded": 2,
"items": [
{
"agent": "front-desk",
"average_score": 4.1,
"down": 8,
"rated": 41,
"satisfaction_rate": 0.8,
"up": 33
}
],
"scanned": 43,
"truncated": false,
"worst": [
{
"agent": "front-desk",
"call_id": "widget-8c2f…",
"category": "inaccurate",
"rating": "down",
"score": 1,
"turn_id": "t3"
}
]
}
GET /v1/feedback/regressions#
Export confirmed-wrong answers (regression cases). Every report resolved confirmed_wrong, as stable rows an evaluation harness can consume: the snapshotted answer, its sources, the linked knowledge document and the resolution trail. Rows whose call was erased under a data-subject request keep only the hashed call linkage (erased: true).
**Paging.** Pass the next_cursor back as ?cursor=; **an empty next_cursor is the only signal that the export is complete**, the status filter runs after each read, so short pages with a cursor are normal. The row shape is additive-only: fields may be added, never renamed or removed.
Parameters
| Name |
In |
Type |
Required |
Description |
limit |
query |
integer |
|
Rows per page, 1 to 100 (default 50). |
cursor |
query |
string |
|
The next_cursor from the previous page. Omit for the first page. |
Responses
| Code |
Description |
200 |
Success. |
400 |
limit is not a positive whole number (bad_request), or the cursor was not issued by this listing (invalid_cursor). |
401 |
Missing or invalid API key. |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Example request
curl "https://api.voiceland.ai/v1/feedback/regressions" \
-H "Authorization: Bearer VL_API_KEY"
A successful response returns a FeedbackRegressionsResponse.
Example response
{
"items": [
{
"agent": "front-desk",
"answer_text": "Our premium plan costs …",
"call_id_hash": "b1946ac9…",
"category": "inaccurate",
"id": "fbr_9d8c7b6a",
"kb_document_id": "doc_4b1e77a0",
"resolved_by": "maria@acme.example",
"turn_id": "t3"
}
],
"next_cursor": ""
}
GET /v1/feedback/reports#
List inaccuracy reports (review queue). One page of the reported answers, in report-id order. ?status=open is the review queue; the three resolved states are the history.
Each report carries its own **snapshot** of the answer text and the sources that grounded it, taken when the report was filed, so the review works even after the call itself was erased. snapshot_source says whether the copy came from the stored transcript or was claimed by the reporter mid-session.
**Paging.** Pass the next_cursor back as ?cursor=. **An empty next_cursor is the only signal that you have seen everything**, the status filter runs after each read, so short and empty pages with a cursor are normal.
Parameters
| Name |
In |
Type |
Required |
Description |
status |
query |
string |
|
Filter: open, confirmed_wrong, correct or fixed. Applied after the read. |
limit |
query |
integer |
|
Rows per page, 1 to 100 (default 50). |
cursor |
query |
string |
|
The next_cursor from the previous page. Omit for the first page. |
Responses
| Code |
Description |
200 |
Success. |
400 |
status is not one of the four states or limit is not a positive whole number (bad_request); or the cursor was not issued by this listing (invalid_cursor). |
401 |
Missing or invalid API key. |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Example request
curl "https://api.voiceland.ai/v1/feedback/reports" \
-H "Authorization: Bearer VL_API_KEY"
A successful response returns a FeedbackReportsResponse.
Example response
{
"items": [
{
"answer_text": "Our premium plan costs …",
"call_id": "widget-8c2f…",
"category": "inaccurate",
"id": "fbr_9d8c7b6a",
"snapshot_source": "transcript",
"sources": [
{
"chunk_id": "doc_4b1e77a0:0003",
"document_id": "doc_4b1e77a0",
"index": 1,
"title": "Τιμολόγηση 2026"
}
],
"status": "open",
"turn_id": "t3"
}
],
"next_cursor": ""
}
POST /v1/feedback/reports#
Report an inaccurate answer. Files one answer into the review queue. The answer text and the sources that grounded it are **copied from the stored transcript turn at this moment**, never recomputed later, so the report stays reviewable even if the call is erased afterwards.
turn_id is t<index> (transcript item index) or o<offset_ms>, and must name a turn of the persisted call.
Request body
application/json Schema: FeedbackReportCreateRequest
| Field |
Type |
Required |
Description |
agent |
string |
|
|
author |
string |
|
|
call_id |
string |
Yes |
|
category |
string |
|
|
comment |
string |
|
|
turn_id |
string |
Yes |
|
Responses
| Code |
Description |
201 |
The report, open, with the snapshot attached. |
401 |
Missing or invalid API key. |
404 |
No CDR for that call id (call_not_found), or the turn id names no turn of it (turn_not_found). |
422 |
The category is not one of inaccurate/outdated/irrelevant/inappropriate/other (invalid_feedback). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Example request
curl -X POST "https://api.voiceland.ai/v1/feedback/reports" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"call_id": "widget-8c2f1a7e-4b3d-4e21-9c0a-5f6d7e8a9b0c",
"category": "inaccurate",
"comment": "The quoted price is last year'\''s.",
"turn_id": "t3"
}'
Example response
{
"answer_text": "Our premium plan costs …",
"call_id": "widget-8c2f…",
"category": "inaccurate",
"id": "fbr_9d8c7b6a",
"snapshot_source": "transcript",
"status": "open",
"turn_id": "t3"
}
GET /v1/feedback/reports/{id}#
Get an inaccuracy report. One report, snapshot included.
Parameters
| Name |
In |
Type |
Required |
Description |
id |
path |
string |
Yes |
Report id. |
Responses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
404 |
No report with that id (report_not_found). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Example request
curl "https://api.voiceland.ai/v1/feedback/reports/{id}" \
-H "Authorization: Bearer VL_API_KEY"
Example response
{
"category": "inaccurate",
"id": "fbr_9d8c7b6a",
"status": "open",
"turn_id": "t3"
}
POST /v1/feedback/reports/{id}/resolve#
Resolve an inaccuracy report. Records the review decision: confirmed_wrong (the answer is wrong and becomes a regression case, see GET /feedback/regressions), correct (the answer was fine), or fixed (you corrected the knowledge content, so the expected answer changed). Optionally link the knowledge document that caused it with kb_document_id.
**confirmed_wrong with a kb_document_id flags that document for review**: it appears in GET /kb/review-flags with a pointer back to this report, and its cited_wrong_count goes up. kb_document_flagged in the response says whether that happened. It is false when no document was named or the document no longer exists, and the decision itself is recorded either way.
**A decision is one-shot**: resolving an already-resolved report answers 409.
Parameters
| Name |
In |
Type |
Required |
Description |
id |
path |
string |
Yes |
Report id. |
Request body
application/json Schema: FeedbackResolveRequest
| Field |
Type |
Required |
Description |
kb_document_id |
string |
|
|
note |
string |
|
|
resolution |
string |
Yes |
|
resolved_by |
string |
|
|
Responses
| Code |
Description |
200 |
The resolved report. |
401 |
Missing or invalid API key. |
404 |
No report with that id (report_not_found). |
409 |
The report is already resolved (already_resolved). |
422 |
resolution is not confirmed_wrong, correct or fixed (invalid_resolution). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Example request
curl -X POST "https://api.voiceland.ai/v1/feedback/reports/{id}/resolve" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kb_document_id": "doc_4b1e77a0",
"note": "Doc quotes the 2024 price list.",
"resolution": "confirmed_wrong",
"resolved_by": "maria@acme.example"
}'
A successful response returns a FeedbackResolveResponse.
Example response
{
"id": "fbr_9d8c7b6a",
"kb_document_flagged": true,
"kb_document_id": "doc_4b1e77a0",
"status": "confirmed_wrong"
}
Schemas#
AnswerFeedback#
| Field |
Type |
Required |
Description |
agent_name |
string |
|
|
author_id |
string |
Yes |
|
author_kind |
string |
Yes |
|
call_id |
string |
Yes |
|
category |
string |
|
|
comment |
string |
|
|
created_at |
string (date-time) |
|
|
rating |
string |
Yes |
|
rating_kind |
string |
Yes |
|
score |
integer |
Yes |
|
tenant_slug |
string |
Yes |
|
turn_id |
string |
Yes |
|
turn_role |
string |
Yes |
|
updated_at |
string (date-time) |
|
|
AnswerFeedbackReport#
| Field |
Type |
Required |
Description |
agent_name |
string |
|
|
answer_text |
string |
|
|
call_id |
string |
|
|
call_id_hash |
string |
Yes |
|
category |
string |
Yes |
|
comment |
string |
|
|
created_at |
string (date-time) |
|
|
erased |
boolean |
|
|
id |
string |
Yes |
|
kb_document_id |
string |
|
|
reporter_id |
string |
|
|
reporter_kind |
string |
Yes |
|
resolution_note |
string |
|
|
resolved_at |
string (date-time) |
|
|
resolved_by |
string |
|
|
snapshot_source |
string |
Yes |
|
sources |
array of TranscriptSource |
|
|
status |
string |
Yes |
|
tenant_slug |
string |
Yes |
|
turn_id |
string |
Yes |
|
turn_role |
string |
|
|
updated_at |
string (date-time) |
|
|
Error#
Error envelope returned for non-2xx responses.
| Field |
Type |
Required |
Description |
error |
object |
Yes |
|
FeedbackAgentAnalytics#
| Field |
Type |
Required |
Description |
agent |
string |
Yes |
|
average_score |
number |
Yes |
|
down |
integer |
Yes |
|
rated |
integer |
Yes |
|
satisfaction_rate |
number |
Yes |
|
up |
integer |
Yes |
|
FeedbackAnalyticsResponse#
FeedbackListResponse#
| Field |
Type |
Required |
Description |
items |
array of AnswerFeedback |
Yes |
|
next_cursor |
string |
Yes |
|
FeedbackRegressionCase#
| Field |
Type |
Required |
Description |
agent |
string |
|
|
answer_text |
string |
|
|
call_id |
string |
|
|
call_id_hash |
string |
Yes |
|
category |
string |
Yes |
|
erased |
boolean |
|
|
id |
string |
Yes |
|
kb_document_id |
string |
|
|
note |
string |
|
|
reported_at |
string (date-time) |
Yes |
|
resolved_at |
string (date-time) |
Yes |
|
resolved_by |
string |
|
|
sources |
array of TranscriptSource |
|
|
turn_id |
string |
Yes |
|
FeedbackRegressionsResponse#
FeedbackReportCreateRequest#
| Field |
Type |
Required |
Description |
agent |
string |
|
|
author |
string |
|
|
call_id |
string |
Yes |
|
category |
string |
|
|
comment |
string |
|
|
turn_id |
string |
Yes |
|
FeedbackReportsResponse#
FeedbackResolveRequest#
| Field |
Type |
Required |
Description |
kb_document_id |
string |
|
|
note |
string |
|
|
resolution |
string |
Yes |
|
resolved_by |
string |
|
|
FeedbackResolveResponse#
| Field |
Type |
Required |
Description |
agent_name |
string |
|
|
answer_text |
string |
|
|
call_id |
string |
|
|
call_id_hash |
string |
Yes |
|
category |
string |
Yes |
|
comment |
string |
|
|
created_at |
string (date-time) |
|
|
erased |
boolean |
|
|
id |
string |
Yes |
|
kb_document_flagged |
boolean |
Yes |
|
kb_document_id |
string |
|
|
reporter_id |
string |
|
|
reporter_kind |
string |
Yes |
|
resolution_note |
string |
|
|
resolved_at |
string (date-time) |
|
|
resolved_by |
string |
|
|
snapshot_source |
string |
Yes |
|
sources |
array of TranscriptSource |
|
|
status |
string |
Yes |
|
tenant_slug |
string |
Yes |
|
turn_id |
string |
Yes |
|
turn_role |
string |
|
|
updated_at |
string (date-time) |
|
|
FeedbackSubmitRequest#
| Field |
Type |
Required |
Description |
agent |
string |
|
|
author |
string |
|
|
call_id |
string |
Yes |
|
category |
string |
|
|
comment |
string |
|
|
rating |
string |
|
|
score |
integer |
|
|
turn_id |
string |
Yes |
|
FeedbackWorstItem#
| Field |
Type |
Required |
Description |
agent |
string |
|
|
call_id |
string |
Yes |
|
category |
string |
|
|
comment |
string |
|
|
rating |
string |
Yes |
|
score |
integer |
Yes |
|
turn_id |
string |
Yes |
|
TranscriptSource#
| Field |
Type |
Required |
Description |
chunk_id |
string |
Yes |
|
document_id |
string |
Yes |
|
index |
integer |
Yes |
|
title |
string |
|
|
url |
string |
|
|