Knowledge
Knowledge sync
Load the products and files your personas answer from, over REST. Authenticate every request with your account key (the X-Api-Key header). Knowledge is organised as brand → sources → documents: a source is a named body of content under one brand, and a persona answers from every enabled source of the brand it speaks for. There are two kinds of document: products, sent with the PUT below, and files (documents, images and plain text), sent with the upload. From scratch that is three calls: create a brand → create a source → write documents.
https://api.personaizer.com/v1/knowledge/sources/{source_id}/documentsProducts
Create or update products in one source, matched by id. The source must exist — see Sources below.
Body — each product
Requirements & limits
Per-field requirements
attributes — supported types
| The object | ≤100 keys · each key name ≤100 chars |
string | ≤500 chars |
number | |n| ≤ 1e15 (1×1015) |
boolean | true or false (JSON literal, never "true"/"yes" strings) |
string[] | ≤50 items · each item ≤100 chars |
date | ISO-8601 string, e.g. "2026-03-01" (JSON has no date literal) |
Reserved keys
These are top-level product fields and can't be used as attributes keys — send price, not attributes.price. Keys starting with variant_ are reserved too.
priceoriginal_pricein_stockcurrencyimageslinkstitledescriptionvariantshttps://api.personaizer.com/v1/knowledge/sources/{source_id}/documents{
"items": [
{
"id": "sku-002",
"categories": [
[
"footwear"
]
],
"title": "Red trail runners",
"description": "Lightweight trail shoe with a grippy outsole.",
"attributes": {
"brand": "acme",
"weight_kg": 0.5,
"waterproof": true,
"features": [
"quick-lace",
"grippy-outsole"
],
"released_at": "2026-03-01"
},
"images": [
{
"url": "https://cdn.example.com/red-runners.jpg",
"description": "Red trail runners",
"is_primary": true
}
],
"links": [
{
"url": "https://my-shop.ge/p/sku-002",
"is_primary": true
}
],
"price": 109.99,
"original_price": 129.99,
"currency": "USD",
"in_stock": true
},
{
"id": "hoodie-010",
"categories": [
[
"apparel",
"hoodies"
],
[
"sale"
]
],
"title": "Classic hoodie",
"attributes": {
"material": "cotton"
},
"variants": [
{
"sku": "H-BL-M",
"color": [
"blue"
],
"size": [
"m"
],
"price": 80,
"in_stock": false,
"image": "https://cdn.example.com/hoodie-blue.jpg"
},
{
"sku": "H-WH-M",
"color": [
"white"
],
"size": [
"m"
],
"price": 40,
"in_stock": true,
"image": "https://cdn.example.com/hoodie-white.jpg"
},
{
"sku": "H-WH-L",
"color": [
"white"
],
"size": [
"l"
],
"price": 45,
"in_stock": true
}
],
"currency": "USD"
}
]
}{ "created": 2, "updated": 0, "no_op": 0, "deferred": [] }Validation
All-or-nothing: if any product is invalid, nothing is written and the call returns 422 (see the response tab) with one entry per problem.
Each errors[] entry
Request-level failures use their own status (the console's response tabs): 401 (missing or invalid key), 403 (Persona ID used — needs the account key), 402 (over quota), or 429 (rate limited).
https://api.personaizer.com/v1/knowledge/sources/{source_id}/documents/uploadFile
Upload a document, image or plain-text file into a source.
Validate large files first with POST /v1/knowledge/sources/documents/upload/preflight (name, size, page count) before transferring bytes. The response includes the document's id — poll GET /v1/knowledge/sources/{source_id}/documents/{id} until status.state is ready. File docs come back with kind: "file" and original_file_name / content_type / pages.
Form fields
https://api.personaizer.com/v1/knowledge/sources/{source_id}/documents/upload{
"id": "manuals-v3",
"kind": "file",
"source_id": "0197a1b2-…", "source": "my-shop.ge",
"title": "Manuals",
"original_file_name": "product-manuals.pdf",
"content_type": "pdf",
"pages": 12,
"status": { "state": "pending", "created_at": "...", "processed_at": null }
}https://api.personaizer.com/v1/knowledge/sources/documents/upload/preflightPreflight an upload
Check a file's name, size and page count before uploading it. Returns { ok: true }, or 400 with the reason.
Send the file_name, content_length (bytes), and page_count (for PDF / DOCX / PPTX). The same size and page limits as the upload apply.
https://api.personaizer.com/v1/knowledge/sources/documents/upload/preflight{
"file_name": "product-manuals.pdf",
"content_length": 5242880,
"page_count": 12
}{ "ok": true }Scoping
The account key sees the whole account. Which persona answers from what is decided by brands and sources, not by the key.
Every source belongs to exactly one brand, and a persona speaks for one brand: it answers from every enabled source of that brand, and nothing else. Writing documents to a source is all it takes for the brand's personas to use them — documents surface in conversations once processing completes. To give a persona different knowledge, wire it to another brand in its Knowledge tab; to stop everything answering from a source, switch the source off in the Knowledge Library — nothing is deleted, and switching it back on is instant. Brand ids come back from GET /v1/brands and on every source below.
https://api.personaizer.com/v1/brandshttps://api.personaizer.com/v1/brands/{brand_id}https://api.personaizer.com/v1/brandsBrands
A brand is who an assistant speaks for — identity only. Every source belongs to one, so this is where a cold start begins.
A brand is known by its id alone — take it from the list (or from the create response) and pass it as brand_id when creating a source. Nothing else about a brand is unique. Editing and deleting a brand are dashboard actions — they change what every persona of that brand shows.
Body — create
https://api.personaizer.com/v1/brands{
"brands": [
{
"id": "0197a0ff-…", "display_name": "My Shop",
"logo_url": "https://my-shop.ge/logo.png", "website_url": "https://my-shop.ge",
"description": "Electronics retailer", "accent_colors": ["#2563eb"],
"default_currency": "GEL", "audience_languages": ["ka", "en"],
"created_at": "2026-09-01T10:00:00Z"
}
]
}https://api.personaizer.com/v1/brands{
"display_name": "My Shop",
"audience_languages": ["ka", "en"],
"website_url": "https://my-shop.ge",
"accent_colors": ["#2563eb"],
"default_currency": "GEL"
}{ "id": "0197a0ff-…" }https://api.personaizer.com/v1/knowledge/sourcesSources
Every source of the account — or of one brand with brand_id — with its switch, owning integration and counts.
enabled: false means the source exists but is switched off — its documents are untouched and document_count still reports them. ready_count is how many of those have finished processing and can be answered from; the gap is uploaded-but-still-processing. integration_id is set when a connected site's stream owns the source — only that integration writes it, and every write from this API returns 409 knowledge.source_managed.
https://api.personaizer.com/v1/knowledge/sources{
"sources": [
{ "id": "0197a1b2-…", "name": "my-shop.ge", "brand_id": "0197a0ff-…", "enabled": true, "integration_id": null, "stream_key": null, "document_count": 412, "ready_count": 410 },
{ "id": "0197a1b3-…", "name": "my-shop.ge-posts", "brand_id": "0197a0ff-…", "enabled": false, "integration_id": null, "stream_key": null, "document_count": 38, "ready_count": 38 }
]
}https://api.personaizer.com/v1/knowledge/sourcesCreate a source
Create an empty, enabled source under a brand. Do this once before the first write; the id it returns is what every write names.
name is the source's label — kept as you type it, unique per account ignoring case. Use your site's host (my-shop.ge), or split one site into several sources (my-shop.ge-products) so each can be switched on and off separately. The response carries the source's id: that, never the name, is what the document routes take, so a rename changes nothing else. Creating a name that already exists under the same brand is a no-op; under another brand it's 409 knowledge.source_name_taken — move it in the Knowledge Library instead. brand_id comes from GET /v1/brands; an unknown one is a 404.
https://api.personaizer.com/v1/knowledge/sources{
"name": "my-shop.ge-products",
"brand_id": "0197a0ff-…"
}{ "id": "0197a1b4-…", "name": "my-shop.ge-products", "brand_id": "0197a0ff-…" }https://api.personaizer.com/v1/knowledge/sources/{source_id}/documentsDelete
Permanently deletes documents of one source by id. Deletion runs as an async job — 202 with a job_id to poll.
- ids not in the source are silently skipped; when nothing matches the call returns 200 { deleted: 0 } and no job.
- Poll GET /v1/knowledge/jobs/{job_id} until status is completed or failed.
- A document in an integration's stream can't be deleted here (409 knowledge.source_managed) — the site that syncs it owns it.
https://api.personaizer.com/v1/knowledge/sources/{source_id}/documents{ "job_id": "0197a1b2-...", "total": 2, "operation": "delete" }https://api.personaizer.com/v1/knowledge/documentsRead endpoints
Read the account's catalog back. A document comes back exactly as you sent it, plus a status block (state, processing accounting).
Endpoints
List query params
https://api.personaizer.com/v1/knowledge/documents{
"items": [
{
"id": "sku-002", "kind": "item",
"source_id": "0197a1b2-…", "source": "my-shop.ge", "categories": [["footwear"]],
"title": "Red trail runners",
"price": 109.99, "original_price": 129.99, "currency": "USD", "in_stock": true,
"status": { "state": "ready", "knowledge_units": 1.0 }
}
],
"total_count": 1
}https://api.personaizer.com/v1/knowledge/sources/{source_id}/documents/{id}Read one document
Full detail for a single document — the same typed shape as a list row, including its status block. An id is unique within its source; 404 if the source doesn't hold it.
https://api.personaizer.com/v1/knowledge/sources/{source_id}/documents/{id}{
"id": "hoodie-010", "kind": "item",
"source_id": "0197a1b2-…", "source": "my-shop.ge", "categories": [["apparel", "hoodies"], ["sale"]],
"title": "Classic hoodie",
"attributes": { "material": "cotton" },
"variants": [
{ "sku": "H-BL-M", "color": ["blue"], "size": ["m"], "price": 80, "in_stock": false, "image": "https://cdn.example.com/hoodie-blue.jpg" },
{ "sku": "H-WH-M", "color": ["white"], "size": ["m"], "price": 40, "in_stock": true, "image": "https://cdn.example.com/hoodie-white.jpg" },
{ "sku": "H-WH-L", "color": ["white"], "size": ["l"], "price": 45, "in_stock": true }
],
"price": 40, "currency": "USD", "in_stock": true,
"status": { "state": "ready", "knowledge_units": 1.0 }
}https://api.personaizer.com/v1/knowledge/jobs/{job_id}Job status
Status of an async job — the job_id comes back from a delete. Poll until status is completed or failed; processed / total track progress.
https://api.personaizer.com/v1/knowledge/jobs/{job_id}{ "job_id": "0197a1b2-...", "status": "completed", "processed": 1, "total": 1 }https://core.personaizer.com/api/subscription/limitsChecking your limits
Check remaining quota before large uploads. Knowledge Units (Ku) measure your searchable content; Storage Units (Su) measure raw file storage in MB.
Note the host — this is served from core.personaizer.com, the one /api route that accepts the account key. Uploading beyond your limit returns 402 limits.quota_exceeded (Credit quota exceeded).
https://core.personaizer.com/api/subscription/limits{
"usage": {
"knowledgeUnitsUsed": 12480, "knowledgeUnitsLimit": 100000,
"storageUnitsUsed": 4.2, "storageUnitsLimit": 512
}
}