Les descriptions des endpoints et des champs restent en anglais, telles que l'API les publie. C'est un choix délibéré : vous lisez ici ce que vous verrez aussi dans les réponses.
GET /v1/kb/collections#
List collections. Every collection on your account, with its document and chunk counts.
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/collections" \
-H "Authorization: Bearer VL_API_KEY"
Exemple de réponse
{
"items": [
{
"doc_count": 12,
"id": "col_9f2a1c7d",
"name": "Τιμοκατάλογος",
"vector_count": 143
}
]
}
POST /v1/kb/collections#
Create a collection. Creates a collection, a named set of documents your agents can answer from. Collections belong to your account, not to one agent: any number of agents may attach the same collection, and attaching never changes it. Supply id to make the call repeatable (a second create with the same id returns 409), or omit it for a generated one.
Corps de la requête
application/json Schéma : KbCollectionRequest
| Champ |
Type |
Requis |
Description |
default_lang |
string |
|
|
description |
string |
|
|
id |
string |
|
|
name |
string |
Oui |
|
Réponses
| Code |
Description |
201 |
Created. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
409 |
The collection id is already taken (collection_exists), or your plan's collection allowance is used up (kb_collection_limit). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/collections" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"default_lang": "el",
"description": "Price list and billing rules.",
"name": "Τιμοκατάλογος"
}'
Une réponse réussie renvoie un KnowledgeCollection.
Exemple de réponse
{
"default_lang": "el",
"doc_count": 0,
"id": "col_9f2a1c7d",
"name": "Τιμοκατάλογος",
"vector_count": 0
}
DELETE /v1/kb/collections/{id}#
Delete a collection. Deletes an empty collection. A collection that still holds documents returns 409, pass ?cascade=true to delete its documents and remove them from the search index in the same call.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
id |
path |
string |
Oui |
Collection id. |
cascade |
query |
boolean |
|
Also delete every document in the collection. |
Réponses
| Code |
Description |
204 |
Deleted. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
409 |
The collection still holds documents and cascade was not set (collection_not_empty). |
4XX |
Request error (validation, not-found, etc.). |
501 |
The collection holds indexed documents and this deployment has no search index, so the cascade cannot remove their passages (vector_store_unavailable). |
502 |
The search index could not be reached (vector_store_error), or passages could not be removed from it (kb_purge_failed). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X DELETE "https://api.voiceland.ai/v1/kb/collections/{id}" \
-H "Authorization: Bearer VL_API_KEY"
GET /v1/kb/collections/{id}#
Get a collection. Returns one collection by id.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
id |
path |
string |
Oui |
Collection id. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/collections/{id}" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KnowledgeCollection.
Exemple de réponse
{
"doc_count": 12,
"id": "col_9f2a1c7d",
"name": "Τιμοκατάλογος"
}
PATCH /v1/kb/collections/{id}#
Update a collection. Changes the name, description, or default language. Omitted fields are left alone; the documents inside are untouched.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
id |
path |
string |
Oui |
Collection id. |
Corps de la requête
application/json Schéma : KbCollectionPatchRequest
| Champ |
Type |
Requis |
Description |
default_lang |
string |
|
|
description |
string |
|
|
name |
string |
|
|
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X PATCH "https://api.voiceland.ai/v1/kb/collections/{id}" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Τιμές και εκπτώσεις 2026"
}'
Une réponse réussie renvoie un KnowledgeCollection.
Exemple de réponse
{
"description": "Τιμές και εκπτώσεις 2026",
"id": "col_9f2a1c7d"
}
GET /v1/kb/collections/{id}/documents#
List documents. One page of the documents in a collection, in id order. The response omits each document's body, fetch a single document for its text.
**Paging.** Pass the next_cursor from the previous response back as ?cursor= to get the page after it. **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": status is applied after each read, so a page can legitimately match nothing and still have more behind it. Follow the cursor until it comes back empty and your account's whole collection is covered, however large it is.
Ordering is by document id within the collection, not by when a document was last edited: a page cannot sort documents it has not read, and a listing that claimed an order it could not keep across pages would repeat and skip documents.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
id |
path |
string |
Oui |
Collection id. |
status |
query |
string |
|
Filter by lifecycle state: draft, in_review, published, archived. |
audience |
query |
string |
|
Only documents that declare this audience. Matched against the stored, lower-cased value, so the case you send does not matter. A document that declares no audience is not returned, absent is not "every audience". |
source_authority |
query |
string |
|
Only documents with this authority: official, verified, community or unverified. A document that declares none is not returned. Anything outside the four is a 400 rather than an empty page, so a typo cannot read as "you have no official documents". |
limit |
query |
integer |
|
Documents per page, 1 to 100 (default 50). Larger values are capped rather than refused, keep following next_cursor for the rest. |
cursor |
query |
string |
|
The next_cursor from the previous page. Omit for the first page. A cursor belongs to the collection that issued it. |
Réponses
| Code |
Description |
200 |
Success. |
400 |
status is not one of the four lifecycle states, source_authority is not one of the four authority levels, or limit is not a positive whole number (bad_request); or cursor was not issued by this listing for this collection (invalid_cursor), start again without it. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such collection on your account (collection_not_found). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/collections/{id}/documents" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbDocumentsResponse.
Exemple de réponse
{
"items": [
{
"chunk_count": 7,
"id": "doc_4b1e77a0",
"status": "published",
"title": "Τιμολόγηση 2026",
"version": "1.1.0"
}
],
"next_cursor": "Y29sXzlmMmExYzdkI2RvY180YjFlNzdhMA"
}
POST /v1/kb/collections/{id}/documents#
Add a document. Adds a document to the collection. Five shapes are accepted on this one route:
- **inline text**, JSON with title + body. Indexed synchronously; responds **201** with the document.
- **file upload**, multipart/form-data with a file part (PDF, DOCX, HTML, Markdown, TXT, CSV) plus optional title, lang, tags, categories, audience, source_authority fields. Send the fields BEFORE the file.
- **a web page**, JSON {"url": "https://example.com"}. The page is fetched, extracted and indexed.
- **your website**, JSON {"website": "https://example.com"}, and the one to reach for. You do not have to know where your sitemap lives: we read your robots.txt, then try the conventional paths, and crawl the sitemap we find. A site that publishes none is crawled by FOLLOWING ITS LINKS instead, same result, one document made of many pages. A deep link is reduced to the site it belongs to, so pasting the page you had open works. The **202** carries a discovery block saying which sitemap was found and where it was declared, or, when there was none, which addresses were tried and that links will be followed. Takes the same optional scope as a sitemap.
- **a sitemap**, JSON {"sitemap": "https://example.com/sitemap.xml"} with an optional scope (allow/deny path prefixes, max_pages). **Leave max_pages out and you get every page**, there is no default cap; a limit applies when you state one or when your plan sets one, and the job says when it stopped short and why. The whole crawl becomes ONE document made of many pages, so each answer still links to the exact page it came from. Add scope.recrawl_interval ("24h", between 1h and 8760h, one hour to one year) to have the source re-fetched on a timer, the re-fetch is conditional, so a page that has not changed costs you nothing, and the schedule only runs while the document is published. It applies to a single url as well as to a sitemap. You send it as a duration string; the document reports it back as a number of nanoseconds.
Everything except inline text responds **202** with the draft document AND an ingestion job, poll GET /kb/jobs/{jobID} for progress. Every shape starts as a draft at version 1.0.0 and is NOT searchable until you publish it.
**Standardised taxonomy.** Beside the free-form tags and categories, every shape accepts two typed fields that search and the document listing can narrow by:
- audience, who the document is written for (["customers", "partners"], «συνεργάτες»). Your own vocabulary, in any language, but it is stored lower-cased with runs of spaces collapsed and matched EXACTLY, so pick the words once and reuse them. At most 8 values, 64 characters each. Nothing is enforced; if you have no opinion of your own, these are the words we suggest so that two collections agree: customers, prospects, partners, employees, agents, public, internal.
- source_authority, how much weight the document carries: official (your own published position), verified (third-party material somebody checked), community (written by users), unverified (explicitly not checked). Nothing else is accepted.
**Leaving either one out is not a value.** A document with no source_authority has made no claim, and unverified says the opposite, that somebody looked. Filters honour the difference: narrowing a search to unverified returns the documents marked so, never the ones nobody has labelled. Nothing on the platform fills these in for you.
If you have been keeping the same information in metadata (metadata.audience and the like), it stays exactly where it is and keeps working, it is not read as a fallback and nothing was migrated, because a value nobody validated should not silently become a retrieval filter. Move it across when you are ready, one document at a time.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
id |
path |
string |
Oui |
Collection id. |
Corps de la requête
application/json Schéma : KbDocumentRequest
| Champ |
Type |
Requis |
Description |
audience |
array of string |
|
|
body |
string |
|
|
categories |
array of string |
|
|
effective_from |
string |
|
|
effective_until |
string |
|
|
id |
string |
|
|
lang |
string |
|
|
metadata |
map of string |
|
|
scope |
KbCrawlScope |
|
|
sitemap |
string |
|
|
source_authority |
string |
|
|
tags |
array of string |
|
|
title |
string |
Oui |
|
url |
string |
|
|
website |
string |
|
|
Corps de la requête
multipart/form-data
Réponses
| Code |
Description |
201 |
Created as a draft (inline text). |
202 |
Accepted for ingestion (upload, url or sitemap). |
400 |
The address could not be parsed or is not http(s) (invalid_url); the address resolves somewhere we will not fetch from (url_not_allowed); scope.recrawl_interval is not a duration or is outside the 1h, 8760h range (recrawl_interval_out_of_range); effective_from/effective_until could not be parsed as RFC3339 or does not close after it opens (bad_request); or a platform-managed metadata key was set, anything starting crawl_ or vl_, plus filename and content_type (reserved_metadata_key). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
409 |
A quota was reached, documents on the account (kb_document_limit), upload size (kb_upload_limit), or pages per crawl (kb_crawl_page_limit), the document id is already taken by another document anywhere on your account (document_exists), or that document already has an ingestion job in flight (kb_job_in_flight), so wait for it to finish or cancel it. |
413 |
The request body exceeds the accepted size (body_too_large). |
415 |
The uploaded file is not one of the supported document types (unsupported_document_type). |
4XX |
Request error (validation, not-found, etc.). |
501 |
This deployment has no document storage (document_storage_unavailable) or no search index (vector_store_unavailable) configured; inline documents still work. |
502 |
The stored copy of your file or crawled pages could not be read or written (document_storage_error). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/collections/{id}/documents" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "# Τιμολόγηση\n\nΗ χρέωση γίνεται ανά λεπτό ομιλίας…",
"categories": [
"Τιμολόγηση/Εκπτώσεις"
],
"lang": "el",
"tags": [
"pricing"
],
"title": "Τιμολόγηση 2026"
}'
Une réponse réussie renvoie un KnowledgeDocument.
Exemple de réponse
{
"collection_id": "col_9f2a1c7d",
"id": "doc_4b1e77a0",
"status": "draft",
"version": "1.0.0"
}
DELETE /v1/kb/documents/{docID}#
Delete a document. Deletes the document and everything derived from it, in order: any ingestion still running is cancelled, its passages are removed from the search index, the stored copy of your file or crawled pages is erased, and then the document itself. Permanent, archive instead if you may want the text back.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
204 |
Deleted. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document on your account (document_not_found). |
4XX |
Request error (validation, not-found, etc.). |
501 |
The document has passages in search and this deployment has no search index, so they cannot be removed (vector_store_unavailable). Deleting a never-indexed document is unaffected. |
502 |
The search index could not be reached (vector_store_error), or passages could not be removed from it (kb_purge_failed). The stored copy of your file or crawled pages could not be read or written (document_storage_error). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X DELETE "https://api.voiceland.ai/v1/kb/documents/{docID}" \
-H "Authorization: Bearer VL_API_KEY"
GET /v1/kb/documents/{docID}#
Get a document. Returns one document including its full text.
A document with a re-crawl schedule also carries recrawl_state: last_crawled_at and next_due_at. Read the ABSENCE of next_due_at beside a non-zero recrawl_interval as meaningful rather than as an error, only a published document is ever re-fetched, so that combination is the usual answer to "I set a schedule and nothing is happening". The listing does not carry it; ask for the document.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document on your account (document_not_found). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/documents/{docID}" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KnowledgeDocument.
Exemple de réponse
{
"id": "doc_4b1e77a0",
"status": "published",
"title": "Τιμολόγηση 2026",
"version": "1.1.0"
}
PATCH /v1/kb/documents/{docID}#
Update a document. Edits the title, text, language, tags, categories, audience, source authority, metadata, validity window or re-crawl schedule.
**Sending body is a redaction, and it is destructive.** The text you send becomes the whole content of the document, and everything derived from the old text is destroyed rather than left to reappear:
- the passages already in search are removed, so the old text stops being answerable the moment you save;
- a published document drops back to draft, publish again to make the new text live. A draft stays a draft, and editing anything other than body leaves a published document published;
- **on an uploaded or crawled document, the stored copy of your file or crawled pages is erased, any ingestion still running on it is cancelled, and the document's source becomes inline.** From then on the text on the row is the only content of record: re-index and publish rebuild from what you sent, never from the file you originally uploaded or the page we crawled. source.url is kept as provenance for citations, but nothing re-fetches it.
That last point is the difference between an edit and a redaction, and it is one-way. If you need the original bytes back, upload them again as a new document. If you only meant to correct a typo in a file you still want us to hold, edit the file and re-upload it instead.
**Moving the validity window also takes a published document out of search**, and for the same reason a text edit does: the window is stamped into every passage when you publish, so a row whose dates moved while its passages still carry the old ones is a document that answers on a day it should not. It is a NARROWER action than a body edit, nothing stored is destroyed and the source is not flipped to inline, the document simply drops back to draft, and publishing again re-stamps the passages. Send "" on either side to clear it.
recrawl_interval sets or clears the schedule (a Go duration, "24h", between 1h and 8760h, one hour to one year; "" stops it). It applies to documents whose source is a url or a sitemap, and only a published one is ever re-fetched.
audience and source_authority are the standardised taxonomy, see the create call for what the values mean. Send "audience": [] or "source_authority": "" to take a claim back off a document; there is no word in the vocabulary for "we have not said", so clearing is how you say it. **Re-labelling does NOT take a published document out of search**, it is a label, not content, and the same is true of tags and categories. The passages already in search keep the labels they were published with until you publish again, so a search narrowed by the new label finds the document only after the next publish.
metadata replaces the map you own. Keys the platform manages, anything starting crawl_ or vl_, plus filename and content_type, cannot be set and are preserved across the replacement (400 reserved_metadata_key if you send one). The same rule applies when you CREATE a document. metadata.audience and friends are NOT reserved and are NOT read as a fallback for the typed fields: if you have been keeping this information there it keeps working untouched.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Corps de la requête
application/json Schéma : KbDocumentPatchRequest
| Champ |
Type |
Requis |
Description |
audience |
array of string |
|
|
body |
string |
|
|
categories |
array of string |
|
|
effective_from |
string |
|
|
effective_until |
string |
|
|
lang |
string |
|
|
metadata |
map of string |
|
|
recrawl_interval |
string |
|
|
source_authority |
string |
|
|
tags |
array of string |
|
|
title |
string |
|
|
Réponses
| Code |
Description |
200 |
Success. |
400 |
A platform-managed metadata key was supplied (reserved_metadata_key); effective_from/effective_until could not be parsed as RFC3339 or from is not before until (bad_request); or recrawl_interval is not a duration, is outside 1h, 8760h (recrawl_interval_out_of_range), or names a document with no address to fetch again (recrawl_not_applicable); or the new primary lang is already held by one of the document's translations (primary_language_conflict), delete that variant first, or the row would carry the same language twice. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document on your account, or it was deleted while the edit was in flight (document_not_found). The edit is discarded rather than recreating the document. |
4XX |
Request error (validation, not-found, etc.). |
501 |
The edit takes a published document out of search and this deployment has no search index, so its passages cannot be removed (vector_store_unavailable); or it redacts an uploaded document and this deployment has no document storage, so the stored bytes cannot be erased (document_storage_unavailable). Editing an unpublished inline document is unaffected. |
502 |
The search index could not be reached (vector_store_error), or passages could not be removed from it (kb_purge_failed). The stored copy of your file or crawled pages could not be read or written (document_storage_error). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X PATCH "https://api.voiceland.ai/v1/kb/documents/{docID}" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "# Τιμολόγηση\n\nΑναθεωρημένες τιμές…"
}'
Une réponse réussie renvoie un KnowledgeDocument.
Exemple de réponse
{
"id": "doc_4b1e77a0",
"source": {
"kind": "inline"
},
"status": "draft",
"version": "1.1.0"
}
POST /v1/kb/documents/{docID}/archive#
Archive a document. Takes the document out of search while keeping the text and its history. Safe to repeat. Publish again to bring it back.
The passages are removed from the index before the document is marked archived, and the removal is attempted whether or not the document is recorded as indexed, so an archive can only ever end with less in search than it started with, never more.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
403 |
Signed in as a console user without the account_admin role (role_required). API keys are unaffected. |
404 |
No such document on your account, or it was deleted while the archive was in flight (document_not_found). |
4XX |
Request error (validation, not-found, etc.). |
501 |
The document has passages in search and this deployment has no search index, so they cannot be removed (vector_store_unavailable). Archiving a never-indexed document is unaffected. |
502 |
The search index could not be reached (vector_store_error), or passages could not be removed from it (kb_purge_failed). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/documents/{docID}/archive" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KnowledgeDocument.
Exemple de réponse
{
"chunk_count": 0,
"id": "doc_4b1e77a0",
"status": "archived"
}
GET /v1/kb/documents/{docID}/chunks#
Preview passages. Shows how the document splits into retrievable passages, with the heading each one sits under. count is what the current text would produce; indexed_count is what search actually holds, they differ when the text was edited after the last publish.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document on your account (document_not_found). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/documents/{docID}/chunks" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbChunksResponse.
Exemple de réponse
{
"count": 7,
"document_id": "doc_4b1e77a0",
"indexed_count": 7,
"items": [
{
"chunk_id": "doc_4b1e77a0#0",
"index": 0,
"section": "Τιμολόγηση",
"text": "Η χρέωση γίνεται ανά λεπτό ομιλίας…"
}
],
"status": "published",
"version": "1.1.0"
}
POST /v1/kb/documents/{docID}/publish#
Publish a document. Makes the document answerable: the text is split into passages, indexed for search, and the version's minor number is bumped so an answer can name the exact revision it used. Safe to repeat, republishing replaces the previous passages rather than duplicating them.
Publishable from any state: a draft, a document **in review** (this is the approval step), a published one you are re-indexing, or an archived one you are bringing back.
An inline document is indexed inside the call (**200**). An uploaded or crawled one cannot be, its content is not on the row, so publishing it responds **202** with {document, job} and the passages appear once the job reports indexed.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
200 |
Success. |
202 |
Queued for indexing (uploaded or crawled document). |
400 |
The text produces no retrievable passages (document_empty), or it splits into more passages than one document may hold (document_too_large), split it into several documents. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
403 |
Signed in as a console user without the account_admin role (role_required). API keys are unaffected. |
404 |
No such document on your account, or it was deleted while the publish was in flight (document_not_found). Any passages already written are taken back out, so a deleted document is never resurrected by a publish that raced it. |
409 |
That document already has an ingestion job in flight (kb_job_in_flight), wait for it to finish or cancel it. |
4XX |
Request error (validation, not-found, etc.). |
501 |
Publishing needs a search index and this deployment has none configured (vector_store_unavailable). |
502 |
The text could not be turned into vectors by the embedding provider (embed_failed). The search index could not be reached (vector_store_error), or passages could not be removed from it (kb_purge_failed). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/documents/{docID}/publish" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KnowledgeDocument.
Exemple de réponse
{
"chunk_count": 7,
"id": "doc_4b1e77a0",
"status": "published",
"version": "1.1.0"
}
POST /v1/kb/documents/{docID}/recrawl#
Fetch this document's source again. Goes back to the address this document came from and reads it again, because the PAGE changed, not because we did.
**This is the other half of re-index, and the difference matters.** /reindex rebuilds passages from the copy we already keep, so it is right when our chunking or embedding model improved and useless when your website changed. /recrawl is the opposite: it re-fetches, and the fetch is conditional, a page that has not changed costs you nothing.
The kind of walk is the document's own: a document built from a sitemap re-walks that sitemap, one built by following links walks them again, and a single page re-fetches that page. You do not choose, and cannot: it is what the document is made of.
Refused with **400** not_a_remote_document when there is no address to go back to, text you typed, a file you uploaded, a bucket import. One job at a time per document, because this one reaches out to somebody's web server.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
202 |
Queued. |
400 |
This document has no address to fetch again (not_a_remote_document). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document on your account (document_not_found). |
409 |
An ingestion job is already running on it (kb_job_in_flight). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/documents/{docID}/recrawl" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbIngestResponse.
Exemple de réponse
{
"document": {
"id": "doc_4b1e77a0",
"status": "published"
},
"job": {
"id": "job_1c4f9ab2",
"kind": "sitemap",
"status": "queued"
}
}
POST /v1/kb/documents/{docID}/reindex#
Re-index a document. Queues the document to be extracted, split and embedded again, after the platform's chunking improves, or once you have corrected the collection's language. It re-reads the copy we already keep of your file or crawled pages, so re-indexing never hits your website again, which is exactly why it will NOT see a page you have edited. Use /recrawl for that.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
202 |
Queued. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document on your account (document_not_found). |
409 |
That document already has an ingestion job in flight (kb_job_in_flight), wait for it to finish or cancel it. |
4XX |
Request error (validation, not-found, etc.). |
501 |
This deployment has no search index configured, so nothing can be re-indexed (vector_store_unavailable). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/documents/{docID}/reindex" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbIngestResponse.
Exemple de réponse
{
"document": {
"id": "doc_4b1e77a0",
"status": "published"
},
"job": {
"id": "job_1c4f9ab2",
"kind": "reindex",
"status": "queued"
}
}
POST /v1/kb/documents/{docID}/review-flag/dismiss#
Dismiss a review flag. Closes a review flag by saying the document was not at fault. **A reason is required**: the flag exists because a human confirmed an answer was wrong, so a dismissal is a claim that the cause was somewhere else, and one with no reason attached is indistinguishable from clearing the queue.
The other way out is to publish the document again, which closes the flag as resolved with no extra call: republishing states its case by changing the content.
A decision is one-shot. What reopens a closed flag is a NEW confirmed-wrong report naming the same document.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Corps de la requête
application/json Schéma : KbReviewDismissRequest
| Champ |
Type |
Requis |
Description |
dismissed_by |
string |
|
|
reason |
string |
Oui |
|
Réponses
| Code |
Description |
200 |
The closed flag. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
403 |
Signed in as a console user without the account_admin role (role_required). API keys are unaffected. |
404 |
No such document on your account (document_not_found), or it carries no review flag (review_flag_not_found). |
409 |
The flag is already closed (review_flag_closed). |
422 |
No reason was given (reason_required). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/documents/{docID}/review-flag/dismiss" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dismissed_by": "maria@acme.example",
"reason": "The document is correct; the agent quoted the wrong section."
}'
Une réponse réussie renvoie un KnowledgeReviewFlag.
Exemple de réponse
{
"collection_id": "col_9f2a1c7d",
"document_id": "doc_4b1e77a0",
"report_count": 3,
"resolution_note": "The document is correct; the agent quoted the wrong section.",
"resolved_by": "maria@acme.example",
"state": "dismissed"
}
POST /v1/kb/documents/{docID}/submit#
Submit a document for review. Marks a draft as ready for somebody to check before it goes live: the status becomes in_review. Nothing about search changes, a document in review is no more answerable than a draft, so this is a hand-off, not a publish.
Only a **draft** can be submitted. Repeating the call on a document already in review succeeds and changes nothing.
A **published** document cannot be sent back for review by this call: its passages are live in search, and a status change alone would leave them answering. Edit its text, that takes it out of search and returns it to draft, then submit. Archiving is not a route back: an archived document cannot be submitted either, and the only way out of archived is to publish it.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
403 |
Signed in as a console user without the account_admin role (role_required). API keys are unaffected. |
404 |
No such document on your account (document_not_found). |
409 |
The document is not in a state that can be submitted for review (invalid_transition), the message names the state it is in and the way out of it; or an ingestion job is still running on it (kb_job_in_flight), in which case the move would make that job throw its own work away, so wait for it to finish or cancel it. Repeating a move the document has already made is never refused, job or no job. |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/documents/{docID}/submit" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KnowledgeDocument.
Exemple de réponse
{
"id": "doc_4b1e77a0",
"status": "in_review",
"version": "1.0.0"
}
GET /v1/kb/documents/{docID}/variants#
List a document's languages. Every language the document holds, the primary one first. The primary is in the list because search falls back to it, a language picker built from this list offers exactly the choices search can honour.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document on your account (document_not_found). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/documents/{docID}/variants" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbVariantsResponse.
Exemple de réponse
{
"document_id": "doc_4b1e77a0",
"items": [
{
"chunk_count": 7,
"lang": "el",
"primary": true
},
{
"chunk_count": 6,
"lang": "en",
"primary": false
}
],
"primary_lang": "el"
}
POST /v1/kb/documents/{docID}/variants#
Add or replace a language version. Stores another language of the same document. A variant carries content only, the status, version, tags, categories and validity window stay on the document, so there is one thing to review and publish rather than one per language.
Search prefers the language the person is searching in and falls back to the document's own, so a document with no Greek version still answers a Greek question, in the language it has.
Adding a language to a **published** document takes it out of search until you publish again, exactly as editing its text does: what search holds is stamped when you publish, and quietly re-indexing inside this call would spend an embedding batch you did not ask for.
Available for documents with inline text. A document ingested from a file, a page or a sitemap gets its language from its source.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Corps de la requête
application/json Schéma : KbVariantRequest
| Champ |
Type |
Requis |
Description |
body |
string |
Oui |
|
lang |
string |
Oui |
Language tag for this version. It may not repeat the document's primary language. |
title |
string |
|
|
Réponses
| Code |
Description |
200 |
Replaced an existing language. |
201 |
Added. |
400 |
lang is missing or is not a language tag, body is empty, the document is not an inline one (variants_not_supported), or this language's text splits into more passages than one document may hold (document_too_large), the cap is a TOTAL across every language, because the languages share one document. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document on your account (document_not_found). |
409 |
The document already carries the maximum number of languages, or this one repeats its primary language (variant_conflict). |
413 |
This language's text is too large (body_too_large), the languages share one document. |
4XX |
Request error (validation, not-found, etc.). |
502 |
The search index could not be reached (vector_store_error), or passages could not be removed from it (kb_purge_failed). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/documents/{docID}/variants" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "# Pricing\n\nBilling is per minute of speech…",
"lang": "en",
"title": "Pricing 2026"
}'
Une réponse réussie renvoie un KbVariantResponse.
DELETE /v1/kb/documents/{docID}/variants/{lang}#
Delete one language. Removes that language and its passages from search. The other languages are untouched, the removal names this language only. The primary language cannot be deleted this way; deleting the document removes it.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
lang |
path |
string |
Oui |
Language tag. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
204 |
Deleted. |
400 |
That language is the document's primary one (primary_language). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document (document_not_found), or it has no version in that language (variant_not_found). |
4XX |
Request error (validation, not-found, etc.). |
502 |
The search index could not be reached (vector_store_error), or passages could not be removed from it (kb_purge_failed). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X DELETE "https://api.voiceland.ai/v1/kb/documents/{docID}/variants/{lang}" \
-H "Authorization: Bearer VL_API_KEY"
GET /v1/kb/documents/{docID}/variants/{lang}#
Get one language. Returns that language's title and text, or 404 if the document does not have it. It does NOT fall back to the primary, a console asking "is there a Greek version" has to be able to get a no.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
lang |
path |
string |
Oui |
Language tag. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document (document_not_found), or it has no version in that language (variant_not_found). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/documents/{docID}/variants/{lang}" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbVariantResponse.
Exemple de réponse
{
"body": "# Pricing…",
"chunk_count": 6,
"lang": "en",
"primary": false,
"title": "Pricing 2026"
}
PATCH /v1/kb/documents/{docID}/variants/{lang}#
Update one language. Changes that language's title or text. The language itself cannot be changed, delete it and add the other one. Editing the primary language goes through PATCH /kb/documents/{docID} instead, which is the call that also removes the old text from search and from storage.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
lang |
path |
string |
Oui |
Language tag. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Corps de la requête
application/json Schéma : KbVariantPatchRequest
| Champ |
Type |
Requis |
Description |
body |
string |
|
|
title |
string |
|
|
Réponses
| Code |
Description |
200 |
Success. |
400 |
That language is the document's primary one (primary_language), the document is not an inline one (variants_not_supported), or the new text splits into more passages than the document may hold across all its languages (document_too_large). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such document (document_not_found), or it has no version in that language (variant_not_found). |
4XX |
Request error (validation, not-found, etc.). |
502 |
The search index could not be reached (vector_store_error), or passages could not be removed from it (kb_purge_failed). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X PATCH "https://api.voiceland.ai/v1/kb/documents/{docID}/variants/{lang}" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "# Pricing\n\nBilling is per minute…"
}'
Une réponse réussie renvoie un KbVariantResponse.
Exemple de réponse
{
"chunk_count": 0,
"lang": "en",
"primary": false
}
POST /v1/kb/documents/{docID}/withdraw#
Take a document out of review. Returns a document in review to draft, the same call whether the author is withdrawing it or a reviewer is sending it back. Nothing about search changes.
Only a document **in review** can be withdrawn. Repeating the call on a draft succeeds and changes nothing. A published document is not withdrawn but archived.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
docID |
path |
string |
Oui |
Document id. |
collection_id |
query |
string |
|
The document's collection. Optional: document ids are unique across your whole knowledge base, so we can always find the document without it. Supplying it, every listing and every create response gives it to you, saves one lookup. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
403 |
Signed in as a console user without the account_admin role (role_required). API keys are unaffected. |
404 |
No such document on your account (document_not_found). |
409 |
The document is not in a state that can be withdrawn from review (invalid_transition), the message names the state it is in and the way out of it; or an ingestion job is still running on it (kb_job_in_flight), in which case the move would make that job throw its own work away, so wait for it to finish or cancel it. Repeating a move the document has already made is never refused, job or no job. |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/documents/{docID}/withdraw" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KnowledgeDocument.
Exemple de réponse
{
"id": "doc_4b1e77a0",
"status": "draft",
"version": "1.0.0"
}
POST /v1/kb/import-inline#
Import an agent's inline knowledge. Turns the free-text knowledge you gave an agent into a knowledge-base document, so it can be searched, versioned, cited and shared with other agents instead of being re-sent to the model on every single turn.
**Your agent is not changed.** The inline text stays exactly where it is and keeps working, so you can import, publish, attach, listen to the agent answer from search, and only then remove the inline copy by hand.
**Safe to repeat.** The document id is derived from the agent name, so calling it again updates the same document, and if the text has not changed, nothing at all is written (changed: false). Set attach: true to also add the collection to the agent's knowledge sources.
audience and source_authority set the standardised taxonomy (see the create call). Changing either one counts as a change, so a re-import that only re-labels the document is written, and, since it changes a label rather than the text, it does not take a published document out of search.
Corps de la requête
application/json Schéma : KbImportInlineRequest
| Champ |
Type |
Requis |
Description |
agent |
string |
Oui |
The agent whose inline knowledge is read. The agent is NOT modified. |
attach |
boolean |
|
Also add the collection to the agent's knowledge sources. Default false: attaching changes what a LIVE agent retrieves from. |
audience |
array of string |
|
|
categories |
array of string |
|
|
collection_id |
string |
Oui |
|
document_id |
string |
|
Overrides the id derived from the agent name, how you deliberately import one agent's knowledge into more than one document. |
knowledge |
string |
|
Text to import instead of reading the agent's prompt. For a console that has the text before it has ever been saved, and for agents whose prompt was not composed here. |
lang |
string |
|
|
source_authority |
string |
|
|
tags |
array of string |
|
|
title |
string |
|
|
Réponses
| Code |
Description |
200 |
Success. |
400 |
The agent has no inline knowledge to import (no_inline_knowledge), agent or collection_id is missing, or the document_id you supplied is not a valid id (bad_request); or the import would change the primary language of an existing document to one its translations already hold (primary_language_conflict). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such agent (agent_not_found) or collection (collection_not_found). |
409 |
Your plan's document allowance is used up (kb_document_limit), or the derived document id is taken by another collection (document_exists). |
413 |
The inline text is larger than one document may be (body_too_large). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/import-inline" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent": "front-desk",
"attach": true,
"collection_id": "col_9f2a1c7d"
}'
Une réponse réussie renvoie un KbImportInlineResponse.
Exemple de réponse
{
"agent": "front-desk",
"attached": true,
"changed": true,
"created": true,
"document": {
"id": "inline_front-desk",
"status": "draft",
"version": "1.0.0"
},
"source": "system_prompt"
}
GET /v1/kb/jobs#
List ingestion jobs. One page of your ingestion jobs, what is queued, running, indexed or failed. A failed job carries a one-line reason. Jobs are kept for 30 days; the document row is the lasting record.
**Paging.** Pass the next_cursor from the previous response back as ?cursor= to get the page after it. **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": status is applied after each read, so a page can legitimately match nothing and still have more behind it. On this listing that is the ordinary case rather than the edge one, because asking for the failed jobs filters a history that is mostly queued ones. Follow the cursor until it comes back empty and your whole job history is covered, however much you have ingested.
Ordering is by key, collection, then document, then job id, not by when a job was created: a page cannot sort jobs it has not read, and a listing that claimed an order it could not keep across pages would repeat and skip jobs. Sort a page yourself for an activity feed, or narrow to one document_id, whose whole history is a handful of rows.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
status |
query |
string |
|
Filter by status: queued, processing, indexed, failed, cancelled. |
collection_id |
query |
string |
|
Only jobs for documents in this collection. |
document_id |
query |
string |
|
Only jobs for this document. Requires collection_id, a job is filed under its document's collection, so jobs are listed within one collection rather than across your account. Every 202 that starts a job returns both ids. |
limit |
query |
integer |
|
Jobs per page, 1 to 100 (default 50). Larger values are capped rather than refused, keep following next_cursor for the rest. |
cursor |
query |
string |
|
The next_cursor from the previous page. Omit for the first page. A cursor belongs to the scope that issued it, one taken from a collection- or document-scoped listing is not valid on a wider one. |
Réponses
| Code |
Description |
200 |
Success. |
400 |
status is not one of the five job states, document_id was sent without collection_id, or 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. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/jobs" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbJobsResponse.
Exemple de réponse
{
"items": [
{
"attempts": 1,
"document_id": "doc_4b1e77a0",
"id": "job_1c4f9ab2",
"kind": "sitemap",
"progress": {
"chunks_written": 96,
"pages_done": 17,
"pages_found": 42
},
"status": "processing"
}
],
"next_cursor": "Y29sXzlmMmExYzdkI2RvY180YjFlNzdhMCNqb2JfMWM0ZjlhYjI"
}
DELETE /v1/kb/jobs/{jobID}#
Cancel an ingestion job. Asks a queued or running job to stop. Best-effort: a job already writing its last passages may still finish, so the response returns the job so you can see which happened. A job that has already finished returns 409.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
jobID |
path |
string |
Oui |
Job id. |
collection_id |
query |
string |
|
The job's collection. Give it together with document_id for a direct lookup. |
document_id |
query |
string |
|
The job's document id. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such job on your account (job_not_found). |
409 |
The job has already finished and cannot be cancelled (job_already_finished). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X DELETE "https://api.voiceland.ai/v1/kb/jobs/{jobID}" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KnowledgeJob.
Exemple de réponse
{
"id": "job_1c4f9ab2",
"status": "cancelled"
}
GET /v1/kb/jobs/{jobID}#
Get an ingestion job. One job with its live progress counters. Pass collection_id and document_id together when you have them (the 202 that created the job returned both) to make this a direct lookup instead of a walk.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
jobID |
path |
string |
Oui |
Job id. |
collection_id |
query |
string |
|
The job's collection. Give it together with document_id for a direct lookup. |
document_id |
query |
string |
|
The job's document id. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such job on your account (job_not_found). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/jobs/{jobID}" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KnowledgeJob.
Exemple de réponse
{
"document_id": "doc_4b1e77a0",
"id": "job_1c4f9ab2",
"kind": "upload",
"progress": {
"chunks_written": 12,
"pages_done": 1,
"pages_found": 1
},
"status": "indexed"
}
GET /v1/kb/object-sources#
List your object-storage sources. Every bucket you have registered. Secrets are never included, has_secret says one is stored, nothing returns its value.
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/object-sources" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbObjectSourcesResponse.
Exemple de réponse
{
"items": [
{
"access_key_id": "AKIA…",
"bucket": "acme-docs",
"endpoint": "s3.eu-central-1.amazonaws.com",
"has_secret": true,
"id": "objsrc_a1b2c3d4",
"prefix": "handbook/"
}
]
}
POST /v1/kb/object-sources#
Register your own object storage. Registers a bucket of yours, AWS S3 or anything that speaks the S3 API, so you can import documents straight out of it instead of uploading them one at a time.
**`secret_access_key` is write-only.** It is stored encrypted and is never returned by this call, by the list, by the GET, or by the secrets API, there is no request that reads it back. `has_secret` is how you confirm one is stored. To change it, PATCH this source with a new `secret_access_key`; that is the only way to rotate it.
**`endpoint` is a host, not a URL** (`s3.eu-central-1.amazonaws.com`, `objects.example.gr`). We always connect over https on port 443, because anything else would put your access key on the wire in the clear. An `https://example.com prefix is accepted and stripped; a path, a port we do not reach, credentials in the endpoint, or an address on a private network are refused (`endpoint_not_allowed`), and the same check runs again, against the address we actually connect to, every time an import runs.
**Give us the narrowest credentials that work.** A read-only key scoped to `prefix` is all an import needs: we only ever LIST and GET. `prefix` is also a boundary, an import may narrow it, never widen it, so a source registered at `handbook/` can never be made to read the rest of the bucket.
Corps de la requête
application/json Schéma : KbObjectSourceRequest
| Champ |
Type |
Requis |
Description |
access_key_id |
string |
Oui |
|
bucket |
string |
Oui |
|
endpoint |
string |
Oui |
|
id |
string |
|
|
name |
string |
|
|
prefix |
string |
|
|
region |
string |
|
|
secret_access_key |
string |
Oui |
|
Réponses
| Code |
Description |
201 |
Registered. |
400 |
bucket, access_key_id or secret_access_key is missing, or the id is not a valid id (bad_request); or the endpoint is not one we will connect to (endpoint_not_allowed), a private or link-local address, an internal-only name, a non-https scheme, a port other than 443, or the platform's own storage. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
409 |
A source with that id already exists (object_source_exists). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/object-sources" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"access_key_id": "AKIA…",
"bucket": "acme-docs",
"endpoint": "s3.eu-central-1.amazonaws.com",
"name": "Handbook bucket",
"prefix": "handbook/",
"region": "eu-central-1",
"secret_access_key": "…"
}'
Une réponse réussie renvoie un KbObjectSourceView.
Exemple de réponse
{
"access_key_id": "AKIA…",
"bucket": "acme-docs",
"endpoint": "s3.eu-central-1.amazonaws.com",
"has_secret": true,
"id": "objsrc_a1b2c3d4",
"name": "Handbook bucket",
"prefix": "handbook/",
"region": "eu-central-1"
}
DELETE /v1/kb/object-sources/{sourceID}#
Remove an object-storage source. Removes the registration and erases the stored credential.
**It refuses while documents imported from this source still exist** (409 object_source_in_use, naming one of them). That is deliberate: those documents are your indexed content, and deleting a piece of configuration must not destroy them. Delete the documents you no longer want first, deleting a document removes its passages and the copies of your files we kept, and then remove the source.
Nothing in your own bucket is touched, ever. We only ever read from it.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
sourceID |
path |
string |
Oui |
Source id. |
Réponses
| Code |
Description |
204 |
Removed. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such source on your account (object_source_not_found). |
409 |
A document imported from this source still exists (object_source_in_use). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X DELETE "https://api.voiceland.ai/v1/kb/object-sources/{sourceID}" \
-H "Authorization: Bearer VL_API_KEY"
GET /v1/kb/object-sources/{sourceID}#
Get one object-storage source. The registration as stored. has_secret is checked against the stored credential on this call, so it is the honest answer to "is this source usable"; the secret itself is never returned.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
sourceID |
path |
string |
Oui |
Source id. |
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such source on your account (object_source_not_found). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/object-sources/{sourceID}" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbObjectSourceView.
Exemple de réponse
{
"bucket": "acme-docs",
"has_secret": true,
"id": "objsrc_a1b2c3d4"
}
PATCH /v1/kb/object-sources/{sourceID}#
Update an object-storage source. Changes what is registered. Omitted fields are left alone.
**This is how you rotate the credential**: send a new secret_access_key and it replaces the stored one. There is no way to clear it, a source with no credential cannot be imported from, and no way to read the old one back.
Documents already imported from this source keep their content. The next import (or re-index that has to re-read the bucket) uses the new settings, so changing the endpoint or bucket redirects future reads without touching what is already indexed.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
sourceID |
path |
string |
Oui |
Source id. |
Corps de la requête
application/json Schéma : KbObjectSourcePatchRequest
| Champ |
Type |
Requis |
Description |
access_key_id |
string |
|
|
bucket |
string |
|
|
endpoint |
string |
|
|
name |
string |
|
|
prefix |
string |
|
|
region |
string |
|
|
secret_access_key |
string |
|
|
Réponses
| Code |
Description |
200 |
Success. |
400 |
The new endpoint is not one we will connect to (endpoint_not_allowed), or access_key_id was sent empty (bad_request). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
404 |
No such source on your account (object_source_not_found). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X PATCH "https://api.voiceland.ai/v1/kb/object-sources/{sourceID}" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"secret_access_key": "…"
}'
Une réponse réussie renvoie un KbObjectSourceView.
Exemple de réponse
{
"bucket": "acme-docs",
"has_secret": true,
"id": "objsrc_a1b2c3d4"
}
POST /v1/kb/object-sources/{sourceID}/import#
Import from your object storage. Reads every object under the prefix and indexes them, through exactly the same extraction and chunking an upload goes through, PDFs, Word files, Markdown, HTML, CSV and plain text.
**One import is one document**, made of every file under the prefix, the same way a sitemap crawl produces one document made of many pages. Each passage still carries the file it came from (s3://bucket/key) so an answer cites the exact file. If you want one document per file, import a narrower prefix per document.
Responds **202** with {document, job}: the document exists immediately as a draft, and the files become searchable once the job reports indexed and you publish. Poll GET /kb/jobs/{id}.
**Re-importing is cheap and safe, and it is how you pick up changes.** Call this again with the id of a document this same source already produced and it REFRESHES that document rather than making a new one: every file whose ETag has not changed is served from the copy we kept, not downloaded from your bucket, and not re-embedded, and when nothing under the prefix has changed at all the job finishes having written nothing. A refresh is not an edit: the title, the labels and the published status you set are left alone, and the prefix stays whatever that document was imported at (the request's prefix is ignored). Poll or schedule this as often as you like. POST /kb/documents/{id}/reindex is a different thing, it rebuilds passages from the copies we already hold and never looks at your bucket, so it will not see a new or edited file.
Your plan's limits are the upload limits: the same per-file size cap, and a per-import ceiling on how many files one import reads. Hitting the ceiling is reported as a warning on the job rather than a failure, narrow the prefix and import the rest separately.
audience and source_authority are the standardised taxonomy and mean exactly what they mean on the create call, a bucket of files is one document, so the labels are the document's, not the files'.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
sourceID |
path |
string |
Oui |
Source id. |
Corps de la requête
application/json Schéma : KbObjectImportRequest
| Champ |
Type |
Requis |
Description |
audience |
array of string |
|
|
categories |
array of string |
|
|
collection_id |
string |
Oui |
|
id |
string |
|
|
lang |
string |
|
|
metadata |
map of string |
|
|
prefix |
string |
|
|
source_authority |
string |
|
|
tags |
array of string |
|
|
title |
string |
|
|
Réponses
| Code |
Description |
202 |
Accepted, the document exists as a draft and the job will fill it. |
400 |
collection_id is missing or the document id is not a valid id (bad_request); the prefix is outside the source's own prefix (prefix_outside_source); a platform-managed metadata key was supplied (reserved_metadata_key); or the source's endpoint is no longer one we will connect to (endpoint_not_allowed). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
403 |
Signed in as a console user without the account_admin role (role_required). API keys are unaffected. |
404 |
No such source (object_source_not_found) or collection (collection_not_found). |
409 |
Your plan's document allowance is used up (kb_document_limit); the document id belongs to something this source did not produce, another source's import, or a document you wrote or uploaded (document_exists), and re-importing would overwrite it; or that document already has an ingestion job in flight (kb_job_in_flight). |
4XX |
Request error (validation, not-found, etc.). |
501 |
Importing needs a search index and this deployment has none configured (vector_store_unavailable). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/object-sources/{sourceID}/import" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"collection_id": "col_9f2a1c7d",
"prefix": "handbook/policies/",
"tags": [
"hr"
],
"title": "Employee handbook"
}'
Une réponse réussie renvoie un KbIngestResponse.
Exemple de réponse
{
"document": {
"id": "doc_4b1e77a0",
"source": {
"kind": "object",
"object_prefix": "handbook/policies/",
"object_source_id": "objsrc_a1b2c3d4",
"url": "s3://acme-docs/handbook/policies/"
},
"status": "draft"
},
"job": {
"id": "job_7c1d",
"kind": "object",
"status": "queued"
}
}
GET /v1/kb/review-flags#
List documents flagged for review. One page of the review queue: documents a human traced a wrong answer back to. A flag is raised when somebody resolves an inaccuracy report as confirmed_wrong and names the document that caused it (POST /feedback/reports/{id}/resolve), and it carries the ids of the reports behind it so an author can read why the document is suspect.
**Ordering is by how many reports point at each document**, most first, then by collection and document id. That is a ranking rather than a key order, so the read is bounded: truncated: true means the ranking is over the flags examined and not over every flag on your account. Use collection_id to narrow it, or the analytics route for the whole picture.
**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**: state is applied after each read, so a short page, including an empty one, is normal and means "ask again".
A flag leaves the queue in one of two ways: publish the document again (the fix speaks for itself, and the flag closes as resolved), or dismiss it with a reason. A new confirmed-wrong report on a closed flag reopens it.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
state |
query |
string |
|
Filter by review state: open, resolved or dismissed. Anything else is a 400 rather than an empty page, so a typo cannot read as "nothing to review". |
collection_id |
query |
string |
|
Only flags on documents in this collection. Also narrows the read, so a collection-scoped queue gets much further before it truncates. |
limit |
query |
integer |
|
Flags 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. |
Réponses
| Code |
Description |
200 |
Success. |
400 |
state is not one of open/resolved/dismissed 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. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/review-flags" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbReviewFlagsResponse.
Exemple de réponse
{
"items": [
{
"agent_name": "front-desk",
"collection_id": "col_9f2a1c7d",
"document_id": "doc_4b1e77a0",
"reason": "confirmed_wrong_answer",
"report_count": 3,
"report_ids": [
"fbr_9d8c7b6a",
"fbr_5e4d3c2b",
"fbr_1a2b3c4d"
],
"state": "open"
}
],
"next_cursor": "",
"truncated": false
}
GET /v1/kb/review-flags/analytics#
Which documents cause wrong answers. Ranks your documents by how many answers a human confirmed wrong and traced back to them. This is the number that says where your knowledge is actually wrong, as opposed to where the agent is.
knowledge_fault_rate is the share of confirmed-wrong answers a reviewer traced to a document at all; linked_to_document beside confirmed_wrong is what makes that honest: a rate of zero means "nobody filled in which document", not "your knowledge is fine".
It counts REPORTS, not open flags: a document you fixed last week still shows the answers it broke, because it broke them. The definition field ships the exact arithmetic.
The read is bounded; truncated: true means there were more confirmed-wrong reports than one request folds.
Réponses
| Code |
Description |
200 |
Success. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/review-flags/analytics" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbReviewAnalyticsResponse.
Exemple de réponse
{
"confirmed_wrong": 7,
"documents": 2,
"items": [
{
"agents": [
"front-desk"
],
"categories": [
"outdated"
],
"confirmed_wrong": 3,
"document_id": "doc_4b1e77a0",
"share": 0.6
}
],
"knowledge_fault_rate": 0.7142857142857143,
"linked_to_document": 5,
"scanned": 7,
"truncated": false
}
POST /v1/kb/search#
Search your knowledge base. Searches your **published** documents and returns ranked passages with the matched words marked in a snippet. Natural-language questions work, the query is matched both by meaning and by word, so a Greek question finds a Greek document written with or without accents.
filters.lang is the language of the person searching, not a hard filter: a document with no version in that language still answers, in the language it has. filters.collections, filters.tags, filters.categories, filters.audience and filters.source_authority narrow; nothing here can widen a search beyond your own published content, and a document outside its validity window never appears.
filters.audience and filters.source_authority are the standardised taxonomy (see the create call). Both are lists and both match ANY of the values you send, so "source_authority": ["official", "verified"] is "trustworthy material only". **A document that declares neither is not returned by either filter**, absence is not a wildcard, and "nobody labelled this" must not be answerable as "this is official". An authority outside the four is a 400, not an empty result set.
One thing to know about timing: the labels are stamped onto a document's passages when you PUBLISH it. Re-labelling an already-published document does not take it out of search, and it does not change what these filters match until you publish again.
**This call is rate limited**, it embeds your query, which is a per-request cost, and the allowance comes from your plan. A refusal is 429 with retry-after; the current allowance is on every response as x-ratelimit-limit.
A search that matches nothing is a normal 200 with outcome: "zero" and no results. Those queries are what the zero-result list in GET /me/search-stats is built from, they are the questions your customers asked that your content does not answer yet.
Corps de la requête
application/json Schéma : KbSearchRequest
| Champ |
Type |
Requis |
Description |
filters |
KbSearchFilters |
|
|
limit |
integer |
|
How many passages to return. |
query |
string |
Oui |
What to search for. Natural-language questions work, the query is matched both by meaning and by word. |
snippet_runes |
integer |
|
How long each snippet may be, in characters. |
Réponses
| Code |
Description |
200 |
Success. |
400 |
query is empty, filters.source_authority names something outside official / verified / community / unverified (bad_request), or a filter list is longer than we accept. |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
413 |
The query is longer than we accept (query_too_large). |
429 |
Too many searches for your plan's per-minute allowance (search_rate_limit). Wait retry-after seconds; the allowance itself is on every response as x-ratelimit-limit. |
4XX |
Request error (validation, not-found, etc.). |
501 |
This deployment has no search index configured (vector_store_unavailable). |
502 |
The text could not be turned into vectors by the embedding provider (embed_failed). The search index could not be reached (vector_store_error). The request made no change; retry the call. |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/search" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"audience": [
"customers"
],
"collections": [
"col_9f2a1c7d"
],
"lang": "el",
"source_authority": [
"official",
"verified"
]
},
"limit": 5,
"query": "πόσο κοστίζει το λεπτό ομιλίας;"
}'
Une réponse réussie renvoie un KbSearchResponse.
Exemple de réponse
{
"count": 1,
"outcome": "hit",
"query": "πόσο κοστίζει το λεπτό ομιλίας;",
"results": [
{
"chunk_id": "doc_4b1e77a0#0",
"collection_id": "col_9f2a1c7d",
"document_id": "doc_4b1e77a0",
"highlights": [
{
"end": 51,
"start": 41
},
{
"end": 66,
"start": 52
}
],
"lang": "el",
"rank": 1,
"score": 0.0328,
"section": "Τιμολόγηση",
"snippet": "…Η χρέωση γίνεται ανά λεπτό ομιλίας…",
"title": "Τιμολόγηση 2026"
}
],
"took_ms": 41
}
POST /v1/kb/search/selection#
Record that a result was opened. Counts a search result the person actually opened. It is what turns the search report's selection rate into a real number, without it, "how often does a search lead anywhere" cannot be answered.
Send the query exactly as it was searched; we key the count by the query, not by the document.
Corps de la requête
application/json Schéma : KbSelectionRequest
| Champ |
Type |
Requis |
Description |
document_id |
string |
|
The result that was opened. Recorded for context; the selection rate is per query. |
query |
string |
Oui |
The query exactly as it was searched. The count is keyed by the query, not by the document. |
Réponses
| Code |
Description |
204 |
Counted. |
400 |
query is empty (bad_request). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
429 |
Too many calls for your plan's per-minute allowance (search_rate_limit). Wait retry-after seconds; the allowance itself is on every response as x-ratelimit-limit. |
4XX |
Request error (validation, not-found, etc.). |
501 |
This deployment has no search index configured (vector_store_unavailable). |
5XX |
Server or upstream error. |
Exemple de requête
curl -X POST "https://api.voiceland.ai/v1/kb/search/selection" \
-H "Authorization: Bearer VL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document_id": "doc_4b1e77a0",
"query": "πόσο κοστίζει το λεπτό ομιλίας;"
}'
GET /v1/kb/suggest#
Autocomplete and did-you-mean. Completions for what someone is typing, drawn from your own documents' titles and headings, your tags and categories, and the searches that actually found something. When nothing completes the prefix, did_you_mean offers the nearest known word, accent-insensitive, in Greek and English.
It answers from memory and never searches, so it is safe to call as the user types. The first call after a deployment can legitimately have nothing to say: ready: false means the word list for your account is still being built, and it is not an error.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
q |
query |
string |
|
What the user has typed so far. |
lang |
query |
string |
|
The language they are typing in. |
limit |
query |
integer |
|
How many suggestions to return. |
Réponses
| Code |
Description |
200 |
Success. |
400 |
limit is not a positive whole number (bad_request). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
413 |
q is longer than we accept (query_too_large). |
429 |
Too many calls for your plan's per-minute allowance (search_rate_limit). Wait retry-after seconds; the allowance itself is on every response as x-ratelimit-limit. |
4XX |
Request error (validation, not-found, etc.). |
501 |
This deployment has no search index configured (vector_store_unavailable), or suggestions are switched off (suggest_unavailable). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/kb/suggest" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbSuggestResponse.
Exemple de réponse
{
"ready": true,
"suggestions": [
{
"kind": "title",
"score": 3,
"text": "τιμοκατάλογος 2026"
}
]
}
GET /v1/me/search-stats#
Search report. What people searched for, whether they found anything, and whether they opened it, per day, plus the top queries and the **zero-result list**.
The zero-result list is the content-gap report: every question your customers asked that your published content could not answer. It is the shortest route from "the agent said it doesn't know" to knowing what to write next.
Defaults to the last 30 days. truncated is true when the window held more distinct queries than one request reads, narrow the window rather than raising the limit.
Paramètres
| Nom |
Emplacement |
Type |
Requis |
Description |
from |
query |
string |
|
Start of the window (2026-01-31, or RFC3339). Defaults to 30 days ago. |
to |
query |
string |
|
End of the window. Defaults to now. |
limit |
query |
integer |
|
How many queries to return in each list. |
Réponses
| Code |
Description |
200 |
Success. |
400 |
from/to could not be parsed as a date or an RFC3339 instant, from is after to, or limit is not a positive whole number (bad_request); or the window is longer than we report on (window_too_large). |
401 |
Missing or invalid API key. |
402 |
Your plan does not include the knowledge base (feature_not_entitled). |
4XX |
Request error (validation, not-found, etc.). |
5XX |
Server or upstream error. |
Exemple de requête
curl "https://api.voiceland.ai/v1/me/search-stats" \
-H "Authorization: Bearer VL_API_KEY"
Une réponse réussie renvoie un KbSearchStatsResponse.
Exemple de réponse
{
"from": "2026-07-01",
"to": "2026-07-31",
"totals": {
"hits": 1102,
"queries": 1284,
"selection_rate": 0.498,
"selections": 640,
"zero": 182,
"zero_rate": 0.142
},
"zero_results": [
{
"count": 37,
"outcome": "zero",
"query": "επιστροφη χρηματων"
}
]
}
Schémas#
Error#
Error envelope returned for non-2xx responses.
| Champ |
Type |
Requis |
Description |
error |
object |
Oui |
|
KbChunkPreview#
| Champ |
Type |
Requis |
Description |
chunk_id |
string |
Oui |
|
index |
integer |
Oui |
|
section |
string |
|
|
text |
string |
Oui |
|
KbChunksResponse#
| Champ |
Type |
Requis |
Description |
count |
integer |
Oui |
Passages the CURRENT text would produce. |
document_id |
string |
Oui |
|
indexed_count |
integer |
Oui |
Passages the search index actually holds. The two disagree exactly when the text was edited after the last publish. |
items |
array of KbChunkPreview |
Oui |
|
status |
string |
Oui |
|
version |
string |
|
|
KbCollectionPatchRequest#
| Champ |
Type |
Requis |
Description |
default_lang |
string |
|
|
description |
string |
|
|
name |
string |
|
|
KbCollectionRequest#
| Champ |
Type |
Requis |
Description |
default_lang |
string |
|
|
description |
string |
|
|
id |
string |
|
|
name |
string |
Oui |
|
KbCrawlScope#
| Champ |
Type |
Requis |
Description |
allow |
array of string |
|
|
deny |
array of string |
|
|
max_pages |
integer |
|
|
recrawl_interval |
string |
|
|
respect_robots |
boolean |
|
|
KbDiscoveryResult#
| Champ |
Type |
Requis |
Description |
checked |
array of string |
|
|
sitemap |
string |
|
|
strategy |
string |
Oui |
|
via |
string |
|
|
KbDocumentPatchRequest#
| Champ |
Type |
Requis |
Description |
audience |
array of string |
|
|
body |
string |
|
|
categories |
array of string |
|
|
effective_from |
string |
|
|
effective_until |
string |
|
|
lang |
string |
|
|
metadata |
map of string |
|
|
recrawl_interval |
string |
|
|
source_authority |
string |
|
|
tags |
array of string |
|
|
title |
string |
|
|
KbDocumentRequest#
| Champ |
Type |
Requis |
Description |
audience |
array of string |
|
|
body |
string |
|
|
categories |
array of string |
|
|
effective_from |
string |
|
|
effective_until |
string |
|
|
id |
string |
|
|
lang |
string |
|
|
metadata |
map of string |
|
|
scope |
KbCrawlScope |
|
|
sitemap |
string |
|
|
source_authority |
string |
|
|
tags |
array of string |
|
|
title |
string |
Oui |
|
url |
string |
|
|
website |
string |
|
|
KbDocumentsResponse#
| Champ |
Type |
Requis |
Description |
items |
array of KnowledgeDocument |
Oui |
|
next_cursor |
string |
Oui |
|
KbImportInlineRequest#
| Champ |
Type |
Requis |
Description |
agent |
string |
Oui |
The agent whose inline knowledge is read. The agent is NOT modified. |
attach |
boolean |
|
Also add the collection to the agent's knowledge sources. Default false: attaching changes what a LIVE agent retrieves from. |
audience |
array of string |
|
|
categories |
array of string |
|
|
collection_id |
string |
Oui |
|
document_id |
string |
|
Overrides the id derived from the agent name, how you deliberately import one agent's knowledge into more than one document. |
knowledge |
string |
|
Text to import instead of reading the agent's prompt. For a console that has the text before it has ever been saved, and for agents whose prompt was not composed here. |
lang |
string |
|
|
source_authority |
string |
|
|
tags |
array of string |
|
|
title |
string |
|
|
KbImportInlineResponse#
| Champ |
Type |
Requis |
Description |
agent |
string |
Oui |
|
attached |
boolean |
Oui |
|
changed |
boolean |
Oui |
False when the text was identical to what the document already held, in which case nothing was written at all, so polling this endpoint cannot take a published document offline. |
created |
boolean |
Oui |
|
document |
KnowledgeDocument |
|
One piece of knowledge. Only a published document inside its validity window is searchable. |
source |
string |
Oui |
Where the text came from: the agent's system prompt, or the request. |
KbIngestResponse#
KbJobsResponse#
| Champ |
Type |
Requis |
Description |
items |
array of KnowledgeJob |
Oui |
|
next_cursor |
string |
Oui |
|
KbObjectImportRequest#
| Champ |
Type |
Requis |
Description |
audience |
array of string |
|
|
categories |
array of string |
|
|
collection_id |
string |
Oui |
|
id |
string |
|
|
lang |
string |
|
|
metadata |
map of string |
|
|
prefix |
string |
|
|
source_authority |
string |
|
|
tags |
array of string |
|
|
title |
string |
|
|
KbObjectSourcePatchRequest#
| Champ |
Type |
Requis |
Description |
access_key_id |
string |
|
|
bucket |
string |
|
|
endpoint |
string |
|
|
name |
string |
|
|
prefix |
string |
|
|
region |
string |
|
|
secret_access_key |
string |
|
|
KbObjectSourceRequest#
| Champ |
Type |
Requis |
Description |
access_key_id |
string |
Oui |
|
bucket |
string |
Oui |
|
endpoint |
string |
Oui |
|
id |
string |
|
|
name |
string |
|
|
prefix |
string |
|
|
region |
string |
|
|
secret_access_key |
string |
Oui |
|
KbObjectSourceView#
| Champ |
Type |
Requis |
Description |
access_key_id |
string |
Oui |
|
bucket |
string |
Oui |
|
created_at |
string |
|
|
endpoint |
string |
Oui |
|
has_secret |
boolean |
Oui |
|
id |
string |
Oui |
|
name |
string |
|
|
prefix |
string |
|
|
region |
string |
|
|
updated_at |
string |
|
|
updated_by |
string |
|
|
KbObjectSourcesResponse#
KbReviewAnalyticsResponse#
| Champ |
Type |
Requis |
Description |
confirmed_wrong |
integer |
Oui |
|
definition |
string |
Oui |
|
documents |
integer |
Oui |
|
items |
array of KnowledgeDocumentFaultRow |
Oui |
|
knowledge_fault_rate |
number |
Oui |
|
linked_to_document |
integer |
Oui |
|
scanned |
integer |
Oui |
|
truncated |
boolean |
Oui |
|
KbReviewDismissRequest#
| Champ |
Type |
Requis |
Description |
dismissed_by |
string |
|
|
reason |
string |
Oui |
|
KbReviewFlagsResponse#
| Champ |
Type |
Requis |
Description |
items |
array of KnowledgeReviewFlag |
Oui |
|
next_cursor |
string |
Oui |
|
truncated |
boolean |
Oui |
|
KbSearchFilters#
| Champ |
Type |
Requis |
Description |
audience |
array of string |
|
|
categories |
array of string |
|
|
collections |
array of string |
|
Narrow to these collections. Empty searches everything published on your account. |
lang |
string |
|
The language of the person searching. NOT a hard filter: a document with no version in that language still answers, in the language it has. Omitting it means no preference. |
source_authority |
array of string |
|
|
tags |
array of string |
|
|
KbSearchHighlight#
| Champ |
Type |
Requis |
Description |
end |
integer |
Oui |
|
start |
integer |
Oui |
|
KbSearchRequest#
Search your published knowledge.
| Champ |
Type |
Requis |
Description |
filters |
KbSearchFilters |
|
|
limit |
integer |
|
How many passages to return. |
query |
string |
Oui |
What to search for. Natural-language questions work, the query is matched both by meaning and by word. |
snippet_runes |
integer |
|
How long each snippet may be, in characters. |
KbSearchResponse#
| Champ |
Type |
Requis |
Description |
count |
integer |
Oui |
|
outcome |
string |
Oui |
hit when something matched, zero when nothing did. A zero is a normal 200, those queries are what the zero-result report is built from. |
query |
string |
Oui |
|
results |
array of KbSearchResult |
Oui |
|
took_ms |
integer |
Oui |
Server-side time for the search, in milliseconds. |
KbSearchResult#
| Champ |
Type |
Requis |
Description |
chunk_id |
string |
Oui |
|
collection_id |
string |
Oui |
|
document_id |
string |
Oui |
|
highlights |
array of KbSearchHighlight |
|
Byte ranges into snippet (not into text) where the query matched. |
lang |
string |
|
|
rank |
integer |
Oui |
|
score |
number |
Oui |
Fused relevance. Comparable WITHIN one response and not across responses, do not build a threshold on it. |
section |
string |
|
|
snippet |
string |
Oui |
The passage trimmed around the match, for display. |
text |
string |
|
The whole passage, as the model would see it. |
title |
string |
|
|
url |
string |
|
|
version |
string |
|
The document revision this passage was indexed from. |
KbSearchStatsDay#
| Champ |
Type |
Requis |
Description |
day |
string |
Oui |
|
hits |
integer |
Oui |
|
queries |
integer |
Oui |
|
results |
integer |
Oui |
|
selection_rate |
number |
Oui |
Selections divided by queries, for that day. Zero when nothing reports selections. |
selections |
integer |
Oui |
|
zero |
integer |
Oui |
|
zero_rate |
number |
Oui |
|
KbSearchStatsQuery#
| Champ |
Type |
Requis |
Description |
count |
integer |
Oui |
|
last_seen_at |
string (date-time) |
|
|
outcome |
string |
Oui |
|
query |
string |
Oui |
|
selections |
integer |
Oui |
|
KbSearchStatsResponse#
| Champ |
Type |
Requis |
Description |
days |
array of KbSearchStatsDay |
Oui |
|
from |
string |
Oui |
|
to |
string |
Oui |
|
top_queries |
array of KbSearchStatsQuery |
Oui |
|
totals |
KbSearchStatsDay |
Oui |
The whole window summed. |
truncated |
boolean |
Oui |
True when the window held more distinct queries than one request reads. Narrow the window rather than raising the limit. |
zero_results |
array of KbSearchStatsQuery |
Oui |
The content-gap report: questions your customers asked that your published content could not answer. |
KbSelectionRequest#
| Champ |
Type |
Requis |
Description |
document_id |
string |
|
The result that was opened. Recorded for context; the selection rate is per query. |
query |
string |
Oui |
The query exactly as it was searched. The count is keyed by the query, not by the document. |
KbSuggestResponse#
| Champ |
Type |
Requis |
Description |
built_at |
string (date-time) |
|
|
did_you_mean |
string |
|
The nearest known word when nothing completed the prefix. |
partial |
boolean |
|
True when the word list was built from a bounded scan rather than your whole corpus. |
ready |
boolean |
Oui |
False while the word list for your account is still being built. Not an error, call again shortly. |
stale |
boolean |
|
True when the word list is past its refresh age and a rebuild is under way. |
suggestions |
array of Suggestion |
Oui |
|
KbVariantPatchRequest#
| Champ |
Type |
Requis |
Description |
body |
string |
|
|
title |
string |
|
|
KbVariantRequest#
| Champ |
Type |
Requis |
Description |
body |
string |
Oui |
|
lang |
string |
Oui |
Language tag for this version. It may not repeat the document's primary language. |
title |
string |
|
|
KbVariantResponse#
| Champ |
Type |
Requis |
Description |
body |
string |
|
|
chunk_count |
integer |
Oui |
|
lang |
string |
Oui |
|
primary |
boolean |
Oui |
True for the document's own language, which lives on the document rather than in the variant map. |
title |
string |
|
|
KbVariantsResponse#
| Champ |
Type |
Requis |
Description |
document_id |
string |
Oui |
|
items |
array of KbVariantResponse |
Oui |
|
primary_lang |
string |
|
|
KnowledgeCollection#
A shared container of documents. Agents attach collections by id; many agents may hold the same one, and attaching never writes to it.
| Champ |
Type |
Requis |
Description |
created_at |
string (date-time) |
|
|
default_lang |
string |
|
Language assumed for documents in this collection that do not state one. |
description |
string |
|
|
doc_count |
integer |
Oui |
Documents in the collection, all lifecycle states included. |
id |
string |
Oui |
|
name |
string |
Oui |
|
tenant_slug |
string |
Oui |
|
updated_at |
string (date-time) |
|
|
vector_count |
integer |
Oui |
Passages currently in the search index for this collection. It trails a publish or an archive by the length of that operation and is recounted on success, so a value that disagrees with the documents' chunk counts means the last write did not finish. |
KnowledgeDocument#
One piece of knowledge. Only a published document inside its validity window is searchable.
| Champ |
Type |
Requis |
Description |
audience |
array of string |
|
|
body |
string |
|
The text, for a document typed in directly. Empty for uploaded and crawled documents, their content of record is the stored file or the crawled pages, not this field. |
categories |
array of string |
|
|
chunk_count |
integer |
Oui |
Passages the last successful index wrote for the PRIMARY language. A multilingual document's total is the sum of this and each variant's own count. |
collection_id |
string |
Oui |
|
created_at |
string (date-time) |
|
|
effective_from |
string (date-time) |
|
The document is not searchable before this instant, even while published. Absent means no lower bound. |
effective_until |
string (date-time) |
|
The document stops being searchable at this instant, even while published, a price list that expires, a policy that lapses. Absent means no upper bound. Nothing writes when the moment arrives; every search checks the window, so an expired document simply stops appearing. |
id |
string |
Oui |
|
lang |
string |
|
|
metadata |
map of string |
|
Your own key/value map. Keys the platform manages, anything starting crawl_ or vl_, plus filename and content_type, cannot be written and survive a replacement that omits them. |
recrawl_interval |
integer |
|
How often the source is re-fetched, in NANOSECONDS. You set it as a duration string ("24h") and read it back as a number, 86400000000000 is one day. Zero means no schedule. Only a published document with an address is ever re-fetched, the re-fetch is conditional, and a source that has not changed costs nothing. |
recrawl_state |
KnowledgeRecrawlState |
|
|
review_flag |
KnowledgeReviewFlag |
|
|
source |
KnowledgeSource |
Oui |
Where the content came from: typed in (inline), uploaded (upload) or fetched (url). Kept as provenance for citations after a redaction, even though nothing re-fetches it then. |
source_authority |
string |
|
|
stats |
KnowledgeUsageStats |
|
Retrieval counters for this document. Read-only, and present only where it was asked for: single-document reads and the document listing hydrate it, everything else leaves it out. Absent therefore means "not requested", never "never retrieved". |
status |
string |
Oui |
draft, in_review, published or archived. Publishing indexes the text; archiving removes it from search while keeping it. |
tags |
array of string |
|
|
tenant_slug |
string |
Oui |
|
title |
string |
Oui |
|
updated_at |
string (date-time) |
|
|
updated_by |
string |
|
|
variants |
map of KnowledgeDocumentVariant |
|
Other languages of the same document, keyed by language tag. A variant carries content only, status, version, tags, categories and the validity window stay here on the document, so there is one thing to review and publish rather than one per language. |
version |
string |
|
Semantic version, bumped on each publish so an answer can name the exact revision it used. |
KnowledgeDocumentFaultRow#
| Champ |
Type |
Requis |
Description |
agents |
array of string |
|
|
categories |
array of string |
|
|
confirmed_wrong |
integer |
Oui |
|
document_id |
string |
Oui |
|
first_reported_at |
string (date-time) |
|
|
last_reported_at |
string (date-time) |
|
|
share |
number |
Oui |
|
KnowledgeDocumentVariant#
One language of a document: title and text only.
| Champ |
Type |
Requis |
Description |
body |
string |
|
|
chunk_count |
integer |
Oui |
Passages the last index wrote for THIS language. |
lang |
string |
Oui |
|
source |
KnowledgeSource |
|
Where the content came from: typed in (inline), uploaded (upload) or fetched (url). Kept as provenance for citations after a redaction, even though nothing re-fetches it then. |
title |
string |
|
|
KnowledgeJob#
| Champ |
Type |
Requis |
Description |
attempts |
integer |
Oui |
How many times this job has been picked up. A retryable failure schedules another attempt; the ladder is what bounds total work, not the page cap. |
collection_id |
string |
Oui |
|
created_at |
string (date-time) |
|
|
document_id |
string |
|
|
error |
string |
|
|
finished_at |
string (date-time) |
|
|
id |
string |
Oui |
|
kind |
string |
Oui |
|
lang |
string |
|
|
lease_until |
string (date-time) |
|
|
next_attempt_at |
string (date-time) |
|
|
progress |
KnowledgeJobProgress |
Oui |
Live counters, pages found, pages done, pages skipped, passages written. They only move forward within one attempt and reset when an attempt restarts. |
requested_by |
string |
|
|
source |
KnowledgeSource |
Oui |
Where the content came from: typed in (inline), uploaded (upload) or fetched (url). Kept as provenance for citations after a redaction, even though nothing re-fetches it then. |
started_at |
string (date-time) |
|
|
status |
string |
Oui |
|
tenant_slug |
string |
Oui |
|
KnowledgeJobProgress#
| Champ |
Type |
Requis |
Description |
chunks_written |
integer |
Oui |
|
pages_done |
integer |
Oui |
|
pages_found |
integer |
Oui |
|
pages_skipped |
integer |
Oui |
|
KnowledgeRecrawlState#
| Champ |
Type |
Requis |
Description |
last_crawled_at |
string (date-time) |
|
|
next_due_at |
string (date-time) |
|
|
KnowledgeReviewFlag#
| Champ |
Type |
Requis |
Description |
agent_name |
string |
|
|
collection_id |
string |
Oui |
|
created_at |
string (date-time) |
|
|
document_id |
string |
Oui |
|
first_flagged_at |
string (date-time) |
|
|
last_flagged_at |
string (date-time) |
|
|
reason |
string |
Oui |
|
reopened_at |
string (date-time) |
|
|
report_count |
integer |
Oui |
|
report_ids |
array of string |
|
|
resolution_note |
string |
|
|
resolved_at |
string (date-time) |
|
|
resolved_by |
string |
|
|
state |
string |
Oui |
|
tenant_slug |
string |
Oui |
|
updated_at |
string (date-time) |
|
|
KnowledgeSource#
Where the content came from: typed in (inline), uploaded (upload) or fetched (url). Kept as provenance for citations after a redaction, even though nothing re-fetches it then.
| Champ |
Type |
Requis |
Description |
kind |
string |
Oui |
|
object_prefix |
string |
|
|
object_source_id |
string |
|
|
s3_key |
string |
|
|
url |
string |
|
|
KnowledgeUsageStats#
| Champ |
Type |
Requis |
Description |
cited_count |
integer |
Oui |
How many answers actually cited it. Retrieved but never cited, over a long window, is a document search keeps finding and the model keeps not using. |
cited_wrong_count |
integer |
Oui |
Reserved for explicit negative feedback. Nothing writes it yet, so it is always zero. |
collection_id |
string |
Oui |
|
document_id |
string |
|
|
last_cited_at |
string (date-time) |
|
|
last_retrieved_at |
string (date-time) |
|
|
retrieved_count |
integer |
Oui |
How many searches and agent turns this document's passages were returned for. |
tenant_slug |
string |
Oui |
|
updated_at |
string (date-time) |
|
|
Suggestion#
| Champ |
Type |
Requis |
Description |
kind |
string |
Oui |
|
score |
number |
Oui |
|
text |
string |
Oui |
|