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.

PUThttps://api.personaizer.com/v1/knowledge/sources/{source_id}/documents
Per callup to 100 products
Processingautomatic

Products

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)
booleantrue or false (JSON literal, never "true"/"yes" strings)
string[]≤50 items · each item ≤100 chars
dateISO-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_stockcurrencyimageslinkstitledescriptionvariants
PUThttps://api.personaizer.com/v1/knowledge/sources/{source_id}/documents
Try it
{
  "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"
    }
  ]
}
Response
{ "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).

POSThttps://api.personaizer.com/v1/knowledge/sources/{source_id}/documents/upload
Bodymultipart/form-data

File

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

POSThttps://api.personaizer.com/v1/knowledge/sources/{source_id}/documents/upload
Try it
Response
{
  "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 }
}
POSThttps://api.personaizer.com/v1/knowledge/sources/documents/upload/preflight
Bodyapplication/json
Checksname · size · pages

Preflight 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.

POSThttps://api.personaizer.com/v1/knowledge/sources/documents/upload/preflight
Try it
{
  "file_name": "product-manuals.pdf",
  "content_length": 5242880,
  "page_count": 12
}
Response
{ "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.

GEThttps://api.personaizer.com/v1/brands
GEThttps://api.personaizer.com/v1/brands/{brand_id}
POSThttps://api.personaizer.com/v1/brands

Brands

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

GEThttps://api.personaizer.com/v1/brands
Try it
Response
{
  "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"
    }
  ]
}
POSThttps://api.personaizer.com/v1/brands
Try it
{
  "display_name": "My Shop",
  "audience_languages": ["ka", "en"],
  "website_url": "https://my-shop.ge",
  "accent_colors": ["#2563eb"],
  "default_currency": "GEL"
}
Response
{ "id": "0197a0ff-…" }
GEThttps://api.personaizer.com/v1/knowledge/sources

Sources

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.

GEThttps://api.personaizer.com/v1/knowledge/sources
Try it
Response
{
  "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 }
  ]
}
POSThttps://api.personaizer.com/v1/knowledge/sources

Create 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.

POSThttps://api.personaizer.com/v1/knowledge/sources
Try it
{
  "name": "my-shop.ge-products",
  "brand_id": "0197a0ff-…"
}
Response
{ "id": "0197a1b4-…", "name": "my-shop.ge-products", "brand_id": "0197a0ff-…" }
DELETEhttps://api.personaizer.com/v1/knowledge/sources/{source_id}/documents

Delete

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.
DELETEhttps://api.personaizer.com/v1/knowledge/sources/{source_id}/documents
Try it
Response
{ "job_id": "0197a1b2-...", "total": 2, "operation": "delete" }
GEThttps://api.personaizer.com/v1/knowledge/documents
Paginatedlimit ≤200
Echosymmetric + status

Read 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

GEThttps://api.personaizer.com/v1/knowledge/documents
Try it
Response
{
  "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
}
GEThttps://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.

GEThttps://api.personaizer.com/v1/knowledge/sources/{source_id}/documents/{id}
Try it
Response
{
  "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 }
}
GEThttps://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.

GEThttps://api.personaizer.com/v1/knowledge/jobs/{job_id}
Try it
Response
{ "job_id": "0197a1b2-...", "status": "completed", "processed": 1, "total": 1 }
GEThttps://core.personaizer.com/api/subscription/limits

Checking 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).

GEThttps://core.personaizer.com/api/subscription/limits
Try it
Response
{
  "usage": {
    "knowledgeUnitsUsed": 12480, "knowledgeUnitsLimit": 100000,
    "storageUnitsUsed": 4.2,     "storageUnitsLimit": 512
  }
}