Knowledge, FAQ, and training API
Use these 86 endpoints to manage knowledge content, FAQ sources, scenarios, training availability, and customer-facing shop memory. Paths below are relative to the Public API base URL.
Who can use this feature?
An admin on Pro or Plus can create the API key. All routes on this page use that API-key access and the explicit scope in their table. GET routes need knowledge:read; writes need knowledge:write. Legacy unscoped keys are not accepted. Size-guide writes and custom-scenario creation also check their feature entitlement or plan limit.
Send X-Api-Key on every request and Content-Type: application/json when sending a body. Your key determines the store; do not send a shop ID. For example:
curl -s 'https://app.chatty.net/v1/knowledge/entries?type=qna&page=1&limit=20' \
-H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'Each row is one endpoint. The request column shows a JSON body, a query-string example, or “No body”. Replace path parameters such as :id with IDs from the matching read/create endpoint. Numeric examples such as 12345 are placeholders. Response examples are excerpts showing the envelope and relevant fields; records may contain additional metadata. Empty-list examples are valid empty results, not the only possible response. All routes can return the shared errors at the end, plus the group-specific errors described in each section.
A successful write does not always mean its content is indexed or its job is finished. Read the returned status and poll the matching status endpoint. Retrying a creation after a timeout can duplicate work; check the saved records before retrying.
Knowledge entries
GET /knowledge/entries uses page (default 1) and limit (default 20), not a Chat API cursor. Optional filters: search, comma-separated type, source, topic, status, state, issue, and dateFrom/dateTo. Types are qna, url, shopify_page, file; a url filter also includes Shopify pages. Issues are conflict, index_error, too_short, too_long, stale. A locale filter currently returns no entries. The list returns data, count, total, and pagination at the top level.
For creation, type defaults to qna; Q&A requires question and answer. URL entries use url; file content uses content and optional filename; page text can use mdContent. Optional title, status, topics (string array), and scope (array of support/sales) accompany the content. Status values are active, draft, and inactive. A requested draft is stored as status: "inactive" with isDraft: true. Omitting status also defaults to draft. Screening can reject content or flag it for review, so read the returned status instead of assuming it is live. allowDuplicate: true permits an independent identical-content copy; it does not bypass all screening.
PATCH accepts answer, question, scope, status, title, topics, and url. Other body fields are ignored. Content edits go through ingestion; a scope-only edit does not rerun content screening. Bulk create and bulk status accept 1–100 entries/IDs. Bulk create returns an outcome per item, including failures even with HTTP 201. Bulk status validates the whole ID selection first but is not an all-or-nothing transaction. Bulk delete requires 1–100 IDs and confirm: true; it deletes only those IDs, not the whole knowledge base.
Group errors: VALIDATION_FAILED, NOT_FOUND, CONFIRM_REQUIRED, DUPLICATE_ENTRY, INGESTION_REVIEW, CONFLICT_PENDING, and SCREEN_*. Duplicate errors may include error.existingId. Read all bulk outcomes before retrying.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /knowledge/entries | knowledge:read | ?type=qna&status=active&page=1&limit=20 | 200 {"data":[],"count":0,"total":0,"pagination":{"hasPre":false,"hasNext":false,"totalPage":1}} |
| GET | /knowledge/entries/:id | knowledge:read | No body | 200 {"data":{"id":"entry_123","type":"qna","question":"How long do returns take?","answer":"Returns are processed within five working days.","status":"active"}} |
| POST | /knowledge/entries | knowledge:write | {"type":"qna","question":"How long do returns take?","answer":"Returns are processed within five working days.","status":"draft"} | 201 {"data":{"id":"entry_123","status":"inactive","isDraft":true,"indexing":"pending"}} |
| POST | /knowledge/entries/bulk | knowledge:write | {"entries":[{"question":"Can I return an item?","answer":"Return unused items within 30 days.","status":"draft"}]} | 201 {"data":[{"success":true,"id":"entry_123","status":"inactive","isDraft":true,"indexing":"pending"}]} |
| POST | /knowledge/entries/bulk-status | knowledge:write | {"ids":["entry_123"],"status":"inactive"} | 200 {"data":{"ids":["entry_123"],"status":"inactive","updated":[{"id":"entry_123","status":"inactive"}]}} |
| DELETE | /knowledge/entries | knowledge:write | {"ids":["entry_123"],"confirm":true} | 200 {"data":{"deleted":["entry_123"],"mutationId":"mutation_123"}} |
| PATCH | /knowledge/entries/:id | knowledge:write | {"answer":"Return unused items within 30 days."} | 200 {"data":{"id":"entry_123","action":"updated","status":"active","entry":{"question":"Can I return an item?","answer":"Return unused items within 30 days."}}} |
| DELETE | /knowledge/entries/:id | knowledge:write | No body | 200 {"success":true} |
Import, export, and revisions
Export accepts format=json (default) or format=csv and returns a downloadable knowledge.json or knowledge.csv, not a data envelope. JSON contains entries; CSV uses the Q&A export representation.
Import accepts format (json or csv), content through text, content, or entries (in that priority), and/or fileIds. Non-string content is JSON-encoded; strings are trimmed. At least content or file IDs are required. This is an asynchronous knowledge import, not the FAQ-source import. Poll the returned jobId: status is queued, importing, applied, partial, or failed. report.applied, report.failed, and report.items report outcomes; HTTP 202 alone does not mean the content is available. Only the latest import job for the shop is exposed; an older job ID may return 404.
Revision reads show a pending revision, publishing applies it to the existing live entry, and deleting discards the pending revision without deleting the live entry. These routes do not create a revision. Missing revisions return NOT_FOUND; import/export input errors use VALIDATION_FAILED. Publishing may also encounter the knowledge ingestion errors listed above.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /knowledge/export | knowledge:read | ?format=json | 200 {"entries":[]} |
| POST | /knowledge/import | knowledge:write | {"format":"json","entries":[{"question":"Can I return an item?","answer":"Return unused items within 30 days."}]} | 202 {"data":{"jobId":"job_123","status":"queued"}} |
| GET | /knowledge/import/:jobId | knowledge:read | No body | 200 {"data":{"jobId":"job_123","status":"queued","progress":0,"report":{"applied":0,"failed":0,"items":[]}}} |
| GET | /knowledge/entries/:id/revision | knowledge:read | No body | 200 {"data":{"revisionOf":"entry_123","question":"Can I return an item?","answer":"Return unused items within 30 days."}} |
| POST | /knowledge/entries/:id/revision/publish | knowledge:write | No body | 200 {"data":{"id":"entry_123","question":"Can I return an item?","answer":"Return unused items within 30 days."}} |
| DELETE | /knowledge/entries/:id/revision | knowledge:write | No body | 200 {"success":true} |
Health and operations
All GETs here have no body. Summary combines status counts and active counts by source type. Coverage returns counts for conflicting, failed-index, long, short, and stale content. Its unresolvedQuestionsPath is an admin path; use /knowledge/unresolved for the API.
Bulk progress describes the latest queued bulk action. Cancellation sets a request flag only while the matching job is queued or processing; it does not undo completed writes. FAQ audit starts a scan; read it again until state is done before using its report. A successful start is not a completed audit.
Duplicate resolution requires candidateId, decision (create, merge, replace), and entry. create keeps an independent entry; merge joins answers and topics into the candidate; replace applies incoming content to the candidate's ID. Inspect the returned saved, skipped, pendingReview, and report, rather than assuming an HTTP 200 saved content. An invalid decision/body returns VALIDATION_FAILED; a missing duplicate candidate currently surfaces as 500 INTERNAL_ERROR.
Entry resync returns NOT_FOUND for an unknown entry. Store-page resync returns a queued jobId. Monitor page sync through /sync/status; its page counters do not prove completion.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /knowledge/summary | knowledge:read | No body | 200 {"data":{"statusCounts":{"active":0,"draft":0,"inactive":0,"conflict":0,"indexError":0},"dataSources":{"qna":0,"url":0,"file":0,"shopify_page":0}}} |
| GET | /knowledge/coverage | knowledge:read | No body | 200 {"data":{"conflict":0,"indexError":0,"long":0,"short":0,"stale":0,"unresolvedQuestionsPath":"/unresolved-questions"}} |
| GET | /knowledge/status-counts | knowledge:read | No body | 200 {"data":{"active":0,"draft":0,"inactive":0,"conflict":0,"indexError":0}} |
| GET | /knowledge/bulk | knowledge:read | No body | 200 {"data":{"progress":0,"state":"idle"}} |
| POST | /knowledge/bulk/cancel | knowledge:write | {"jobId":"job_123"} | 200 {"data":{"cancelled":true}} |
| GET | /knowledge/faq-audit | knowledge:read | No body | 200 {"data":{"state":"idle","progress":0,"report":null}} |
| POST | /knowledge/faq-audit/start | knowledge:write | No body | 200 {"success":true} |
| POST | /knowledge/duplicate-resolution | knowledge:write | {"candidateId":"entry_123","decision":"merge","entry":{"question":"Can I return an item?","answer":"Keep the original packaging."}} | 200 {"data":{"saved":[{"id":"entry_123","action":"updated","status":"active"}],"skipped":[],"pendingReview":[]}} |
| POST | /knowledge/entries/:id/resync | knowledge:write | No body | 200 {"success":true} |
| POST | /knowledge/store-pages/resync | knowledge:write | No body | 200 {"data":{"jobId":"job_123"}} |
Suggestions and unanswered questions
Suggestion lists accept an optional status (pending, accepted, skipped). The HTTP response wraps another list object inside data: use data.data for items, data.summary for counts, and data.generationStatus for generation state. Generation returns HTTP 202 with queued or in_progress; poll the list for results. A failed enqueue returns 500 GENERATION_FAILED.
Accept optionally takes an edited answer; omit it to use the suggested answer. Skip takes no body. An unknown item, accepting an already accepted item, or skipping an accepted item returns 404 NOT_FOUND. Bulk actions are /knowledge/suggestions/bulk-accept and /knowledge/suggestions/bulk-skip, with a nonempty ids array. For bulk skip, a response can be {"data":{"success":true,"skipped":1}}. Their returned count may be smaller than the requested selection. Invalid IDs-body input returns VALIDATION_FAILED; an unknown bulk action returns NOT_FOUND.
Unresolved lists contain only open questions. Optional query parameters: reason, limit (default 20), after/before (record IDs), and hasCount=true. Use data.data and data.pagination; this endpoint does not use the knowledge list's page number. Resolve requires answer, with optional status, and creates a knowledge Q&A from the stored question before marking it resolved. The same validation, screening, and duplicate errors as entry creation apply. Delete dismisses the question by marking it deleted, rather than deleting a knowledge entry.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /knowledge/suggestions | knowledge:read | ?status=pending | 200 {"data":{"data":[],"summary":{"total":0,"pending":0,"accepted":0,"skipped":0},"generationStatus":null}} |
| GET | /knowledge/suggestions/summary | knowledge:read | No body | 200 {"data":{"total":0,"pending":0,"accepted":0,"skipped":0}} |
| POST | /knowledge/suggestions/generate | knowledge:write | No body | 202 {"data":{"status":"queued"}} |
| POST | /knowledge/suggestions/bulk-:action | knowledge:write | {"ids":["suggestion_123"]} | 200 {"data":{"success":true,"accepted":1,"results":{"suggestion_123":"accepted"}}} |
| POST | /knowledge/suggestions/:id/accept | knowledge:write | {"answer":"Return unused items within 30 days."} | 200 {"data":{"success":true}} |
| POST | /knowledge/suggestions/:id/skip | knowledge:write | No body | 200 {"data":{"success":true}} |
| GET | /knowledge/unresolved | knowledge:read | ?limit=20 | 200 {"data":{"data":[],"count":0,"pagination":{"hasPre":false,"hasNext":false}}} |
| POST | /knowledge/unresolved/:id/resolve | knowledge:write | {"answer":"Return unused items within 30 days.","status":"draft"} | 200 {"data":{"id":"entry_123","status":"inactive","isDraft":true,"indexing":"pending"}} |
| DELETE | /knowledge/unresolved/:id | knowledge:write | No body | 200 {"success":true} |
FAQ source categories, questions, and blocks
These are the FAQ source records shown on your storefront, separate from knowledge entries. Lists return all records without pagination. GET and DELETE have no body. A category's display title is named question, not name; creation requires it. Optional category fields include published, featured, icon, and position (omitted means append). Deleting a category moves its questions to the default category and updates affected FAQ blocks; deleting the default category is rejected.
Question creation requires question, answer, and an existing categoryId from your store. published defaults to true; featured is optional. PATCH merges with existing values and validates the destination category, so moving a question requires a valid categoryId. Keep image content as URLs rather than inline base64. Block bodies use blockName, heading, subHeading, display, categorizeFaqs, and a faqs array referencing source questions (questionId, categoryId, question, answer, published). Placement options include showOnProductPages, productPagesMode (all, specific, collections, tags), products, collections, tags, showOnHomepage, showOnCollectionPages, showOnCartPage, showOnBlogPages, showOnSpecificPages, showCustomUrls, and customUrls (one storefront path per line). Read an existing block before changing its layout. PATCH replaces supplied fields, including whole arrays. Block creation returns an id, question creation a questionId, and category creation a categoryId plus position.
Run the FAQ audit before syncing ready FAQ content into knowledge. POST /faq/sync-to-knowledge takes no body and queues the ready selection. Poll GET on the same path; read state, phase, processed, total, progress, and any error. HTTP 202 is only acceptance. Group errors are 400 VALIDATION_FAILED, 404 NOT_FOUND, and 500 INTERNAL_ERROR for other failures. FAQ import, export and delete-all are below.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /faq/categories | knowledge:read | No body | 200 {"data":[{"categoryId":"category_123","question":"Returns","published":true}]} |
| GET | /faq/categories/:id | knowledge:read | No body | 200 {"data":{"categoryId":"category_123","question":"Returns","published":true}} |
| POST | /faq/categories | knowledge:write | {"question":"Returns","published":true} | 201 {"data":{"categoryId":"category_123","position":1}} |
| PATCH | /faq/categories/:id | knowledge:write | {"question":"Returns and refunds"} | 200 {"success":true} |
| DELETE | /faq/categories/:id | knowledge:write | No body | 200 {"success":true} |
| GET | /faq/questions | knowledge:read | No body | 200 {"data":[{"questionId":"question_123","categoryId":"category_123","question":"Can I return an item?","answer":"Return unused items within 30 days.","published":true}]} |
| GET | /faq/questions/:id | knowledge:read | No body | 200 {"data":{"questionId":"question_123","categoryId":"category_123","question":"Can I return an item?","answer":"Return unused items within 30 days.","published":true}} |
| POST | /faq/questions | knowledge:write | {"categoryId":"category_123","question":"Can I return an item?","answer":"Return unused items within 30 days.","published":true} | 201 {"data":{"questionId":"question_123"}} |
| PATCH | /faq/questions/:id | knowledge:write | {"answer":"Return unused items within 30 days.","published":true} | 200 {"success":true} |
| DELETE | /faq/questions/:id | knowledge:write | No body | 200 {"success":true} |
| GET | /faq/blocks | knowledge:read | No body | 200 {"data":[{"id":"block_123","faqs":[]}]} |
| GET | /faq/blocks/:id | knowledge:read | No body | 200 {"data":{"id":"block_123","faqs":[]}} |
| POST | /faq/blocks | knowledge:write | {"faqs":[{"questionId":"question_123","categoryId":"category_123","question":"Can I return an item?","answer":"Return unused items within 30 days.","published":true}]} | 201 {"data":{"id":"block_123"}} |
| PATCH | /faq/blocks/:id | knowledge:write | {"faqs":[]} | 200 {"success":true} |
| DELETE | /faq/blocks/:id | knowledge:write | No body | 200 {"success":true} |
| POST | /faq/sync-to-knowledge | knowledge:write | No body | 202 {"data":{"jobId":"job_123","status":"queued"}} |
| GET | /faq/sync-to-knowledge | knowledge:read | No body | 200 {"data":{"phase":"checking","processed":0,"progress":0,"state":"idle","total":0}} |
Import, export, and delete all FAQs
| Method | Path | Scope | Request example | Response example |
|---|---|---|---|---|
| GET | /faq/export | knowledge:read | ?format=json&status=published | 200 {"data":[{"question":"Can I return an item?","answer":"Return unused items within 30 days.","category":"Returns","published":true,"featured":false}]} |
| POST | /faq/import | knowledge:write | {"rows":[{"question":"Can I return an item?","answer":"Return unused items within 30 days.","category":"Returns","published":true,"featured":false}],"overwrite":false,"publish":true} | 200 {"data":{"success":1,"fail":{"count":0,"detail":{}},"duplicate":0,"categories":1,"countQuestionNew":1}} |
| POST | /faq/delete-all | knowledge:write | {"confirm":"DELETE ALL FAQS"} | 202 {"data":{"jobId":"job_123"}} |
| GET | /faq/delete-all | knowledge:read | No body | 200 {"data":{"jobId":"job_123","state":"running","processed":300,"total":1250,"error":"","updatedAt":1766476800000}} |
Export. format is csv (default) or json. status is all (default) or published, which keeps questions that are published in a published category. All rows come in one response. CSV starts with a byte order mark and has the columns Question, Answer, Category, Published question, Featured question, Published category and Featured category. JSON returns the question flags only. A question in several categories appears once per category.
Import. Send either CSV with Content-Type: text/csv, or JSON: an array of rows, or {"rows":[...],"overwrite":false,"publish":false}. Both take 1–500 rows.
- JSON rows need
question(up to 10,000 characters),answer(up to 100,000),category(up to 1,000),publishedandfeatured. Any invalid row rejects the whole request with400 VALIDATION_FAILED. - CSV must include the headers
Question,Answer,Category,Published questionandFeatured question; extra columns, such as the category columns from an export, are ignored. A CSV file is at most 5 MB, or the route returns413 PAYLOAD_TOO_LARGE. In CSV, a row with an empty value, a flag other thantrue/false, or an inline base64 image is counted infailand skipped; the other rows still import.fail.detaillists the 1-based row numbers per problem. - Categories are matched by exact name. A missing category is created, published only when
publishistrue. - A question with the same text in the same category counts as
duplicateand is skipped, unlessoverwriteistrue; then the existing question is updated.overwriteandpublishdefault tofalseand can also be sent as query parameters. successcounts imported or updated rows,countQuestionNewthe new questions, andcategoriesthe categories used. Chatty also emails the result to the store's email address.
Delete all. This permanently deletes every FAQ question and category, and clears them from FAQ blocks and translations. It needs the exact body {"confirm":"DELETE ALL FAQS"}. The job runs in the background, about 100 questions per second. Poll GET /faq/delete-all until state is done or error; it returns {"data":null} if no deletion has ever run. While a deletion is queued or running, another start returns 409 ALREADY_RUNNING. A job with no progress for 30 minutes is treated as stopped, and a new one can start.
Product FAQs
Use the Shopify product ID for productId (a numeric ID or URL-encoded Shopify GID). PUT accepts a nonempty faqs array; each item requires question and answer, and an id updates that FAQ. The response is an array of saved IDs, not FAQ records. GET is scoped to the product and store. DELETE requires the FAQ to belong to that product/store. Errors: VALIDATION_FAILED for invalid FAQ input, NOT_FOUND for a missing target, and DUPLICATE_ENTRY for an identical product question.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /products/:productId/faqs | knowledge:read | No body | 200 {"data":[{"id":"faq_123","question":"How should I wash this?","answer":"Use a cold wash."}]} |
| PUT | /products/:productId/faqs | knowledge:write | {"faqs":[{"question":"How should I wash this?","answer":"Use a cold wash."}]} | 200 {"data":["faq_123"]} |
| DELETE | /products/:productId/faqs/:id | knowledge:write | No body | 200 {"success":true} |
Scenarios
Create a custom scenario with required name and instruction. Optional intents and exampleMessages default to empty arrays; exampleMessages must contain strings. Optional scope is an array of support/sales that controls retrieval scope. status accepts active, draft, inactive and defaults to draft. Supported intents: product_search, cart, order_tracking, discount, after_sale, human_transfer, loyalty, sizing, general, other, ask_related_product, ask_complementary, shipping_delivery, payment, store_info, product_compare, availability, gift, subscription, order_change.
PATCH can edit custom scenario content and linked training data (linkedItems with type/id, or linkedDocumentIds). Immutable identity fields and keyword are ignored. Default scenarios accept only status and their own settings fields; an unsupported field returns FIELD_NOT_PERMITTED. Default scenarios cannot be deleted. Creation also checks the plan's custom-scenario limit; at present LIMIT_REACHED is returned with HTTP 500, not 403. Use the status endpoint for a status-only change.
The linkable-training-data picker requires type: url, shopify_page, file, qna, in-app, product, collection, or discount. Optional search, status, page (default 1), limit (default 50, maximum 100), and sort (default updatedAt_desc) are accepted; filters vary by source. Results expose id, type, label, and optional subtitle/updatedAt, plus top-level pagination.hasNext. An unsupported type is HTTP 400 with a string error, not the usual coded error object.
Revision routes review an existing pending revision. Publishing updates the same live scenario ID; deleting a revision does not delete the scenario. Missing scenarios or revisions return NOT_FOUND. Input errors return VALIDATION_FAILED. Lists include quality and observability; observability.available: false means no measured usage is available, not zero matches.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /scenarios | knowledge:read | No body | 200 {"data":[{"id":"scenario_123","name":"Returns","status":"draft","isDefault":false,"observability":{"available":false}}]} |
| GET | /scenarios/linkable-training-data | knowledge:read | ?type=qna&page=1&limit=50 | 200 {"data":[{"id":"entry_123","type":"qna","label":"Can I return an item?"}],"pagination":{"hasNext":false}} |
| GET | /scenarios/:id | knowledge:read | No body | 200 {"data":{"id":"scenario_123","name":"Returns","instruction":"Explain the return policy.","status":"draft","intents":["after_sale"],"exampleMessages":[]}} |
| POST | /scenarios | knowledge:write | {"name":"Returns","instruction":"Explain the return policy.","intents":["after_sale"],"status":"draft"} | 201 {"data":{"id":"scenario_123"}} |
| PATCH | /scenarios/:id | knowledge:write | {"instruction":"Ask whether the item is unused before explaining returns.","linkedItems":[{"type":"qna","id":"entry_123"}]} | 200 {"success":true} |
| POST | /scenarios/:id/status | knowledge:write | {"status":"active"} | 200 {"success":true} |
| DELETE | /scenarios/:id | knowledge:write | No body | 200 {"success":true} |
| GET | /scenarios/:id/revision | knowledge:read | No body | 200 {"data":{"revisionOf":"scenario_123","payload":{"instruction":"Explain the updated return policy."}}} |
| POST | /scenarios/:id/revision/publish | knowledge:write | No body | 200 {"data":{"id":"scenario_123","instruction":"Explain the updated return policy."}} |
| DELETE | /scenarios/:id/revision | knowledge:write | No body | 200 {"success":true} |
Size guides
Create requires name and readable size-chart markdown. Optional measurementFields overrides the parsed fields. matchType defaults to automatic, matchLogic defaults to {"collectionIds":[],"collections":[]}, and status defaults to draft (active/draft/inactive are accepted). PUT updates supplied fields; including markdown reparses the chart and preserves explicitly supplied measurementFields. Reads do not parse the chart.
List filters: status, sort (default updatedAt_desc), limit (default 20), after/before record IDs, and hasCount=true. Its page lives inside data, like unresolved questions. Writes also check the current Pro-or-above size-guide entitlement and may return 403 PLAN_REQUIRED. Other errors: VALIDATION_FAILED, NOT_FOUND, chart_unreadable, not_a_size_chart, chart_service_unavailable. The last one is HTTP 503; the two chart-input errors are HTTP 400.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /size-guides | knowledge:read | ?status=active&limit=20 | 200 {"data":{"data":[],"count":0,"pagination":{"hasPre":false,"hasNext":false}}} |
| GET | /size-guides/:id | knowledge:read | No body | 200 {"data":{"id":"guide_123","name":"T-shirt sizes","status":"draft","matchType":"automatic"}} |
| POST | /size-guides | knowledge:write | {"name":"T-shirt sizes","markdown":"Size, Chest (cm)\nS, 90\nM, 100\nL, 110","status":"draft"} | 201 {"data":{"id":"guide_123"}} |
| PUT | /size-guides/:id | knowledge:write | {"status":"active"} | 200 {"success":true} |
| DELETE | /size-guides/:id | knowledge:write | No body | 200 {"success":true} |
Training catalog
These lists return data.data and data.pagination; preserve the outer data wrapper when parsing. Product lists accept search, page (default 1), limit (default 10, maximum 50), sort (default title_asc), and status (active, inactive, not-added, all; default active). Optional selection filters include vendors, tags, metaTags, productIds, collectionIds (or collectionId), collections (titles), and restrictProductIds. Repeat query keys for multiple values. isSmartRecommendation without product/collection conditions returns an empty result with an error message.
Discount lists accept search, status (active, inactive, expiring), page (default 1), limit (default 20), and sort using updatedAt, createdAt, or title with _asc/_desc. expiring selects active discounts ending within seven days. Collection lists accept search, status, sort (default updatedAt_desc), page (default 1), limit (default 20, maximum 100), and hasCount=true.
Status writes require the matching ID array and a boolean enabled. Product IDs are Shopify IDs; collection IDs come from the synced collection list. Enabling products can hit the product allowance while returning HTTP 200: inspect limitReached, toEnableIds, toDisableIds, and the returned success flag before treating every requested product as trained. Collection status writes return 400 INVALID_VALUE with missing collectionIds when collections have not synced yet; run product sync first. Other input errors use VALIDATION_FAILED. Unknown shops use NOT_FOUND; an invalid discount sort currently surfaces as 500 INTERNAL_ERROR.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /training/products | knowledge:read | ?status=active&page=1&limit=10 | 200 {"data":{"data":[],"total":0,"pagination":{"hasPre":false,"hasNext":false,"totalPage":0}}} |
| POST | /training/products/status | knowledge:write | {"productIds":["12345"],"enabled":false} | 200 {"data":{"success":true,"toDisableIds":["12345"]}} |
| GET | /training/discounts | knowledge:read | ?status=active&page=1&limit=20 | 200 {"data":{"data":[],"total":0,"pagination":{"hasPre":false,"hasNext":false,"totalPage":0}}} |
| POST | /training/discounts/status | knowledge:write | {"discountIds":["12345"],"enabled":true} | 200 {"success":true} |
| GET | /training/collections | knowledge:read | ?page=1&limit=20&hasCount=true | 200 {"data":{"data":[],"total":0,"pagination":{"hasPre":false,"hasNext":false,"totalPage":1}}} |
| POST | /training/collections/status | knowledge:write | {"collectionIds":["12345"],"enabled":true} | 200 {"success":true} |
Shop memory and synchronization
Shop memory is knowledge for the customer-facing agent, not a merchant-assistant memory store. Profile and skill reads carry audience: "customer-facing-agent". The profile can be null, and skills can be empty. Skill IDs are numeric strings; deleting an unknown or foreign skill returns 404 NOT_FOUND.
Training accepts an optional force boolean, family (deepseek or anthropic), and quality (quality-pair, cheap-only, agentic). Unrecognized family/quality values fall back to the service defaults. It returns HTTP 202 with jobId; poll /shop-memory/jobs/:id on the Public API. Do not follow the returned legacy statusUrl under /agent-api/. Jobs progress from queued to running, then applied, failed, or skipped. Another active job currently returns 500 JOB_IN_FLIGHT; wait for that job instead of repeatedly queuing work. Missing or cross-shop jobs return 404 NOT_FOUND or 404 SHOP_SCOPE_MISMATCH.
Clearing requires confirm: true and removes both the shop profile and skills; without it, the response is 400 VALIDATION_FAILED. This does not clear customer memory or ordinary knowledge entries.
Sync supports exactly products, discounts, and pages. POST takes no body and returns 202 with a jobId, but that ID is an acknowledgement, not a durable per-job status handle. Read /sync/status for current shop progress. Product/discount done requires numeric synced/total counters and no error. Missing counters do not mean completion; pages.done remains false because the page source has no matching counter pair. An unknown sync resource returns 404 NOT_FOUND.
| Method | Path | Scope | Request example | HTTP and response example |
|---|---|---|---|---|
| GET | /shop-memory/profile | knowledge:read | No body | 200 {"data":null,"audience":"customer-facing-agent"} |
| GET | /shop-memory/skills | knowledge:read | No body | 200 {"data":[],"audience":"customer-facing-agent"} |
| DELETE | /shop-memory/skills/:id | knowledge:write | No body | 200 {"data":{"success":true,"deletedCount":1,"deletedAt":"2026-01-01T00:00:00.000Z"}} |
| POST | /shop-memory/train | knowledge:write | {"force":false} | 202 {"data":{"success":true,"mode":"queued","entity":"shop_memory","action":"train","jobId":"smjob_123","statusUrl":"/agent-api/shop-memory/jobs/smjob_123","config":{},"queuedAt":"2026-01-01T00:00:00.000Z"}} |
| GET | /shop-memory/jobs/:id | knowledge:read | No body | 200 {"data":{"jobId":"smjob_123","status":"queued","force":false}} |
| POST | /shop-memory/clear | knowledge:write | {"confirm":true} | 200 {"data":{"success":true,"cleared":{"cleared":true},"clearedAt":"2026-01-01T00:00:00.000Z"}} |
| POST | /sync/:resource | knowledge:write | No body | 202 {"data":{"jobId":"job_123","status":"queued"}} |
| GET | /sync/status | knowledge:read | No body | 200 {"data":{"products":{"done":false},"discounts":{"done":false},"pages":{"done":false}}} |
Errors
Except for the linkable-training-data type error noted above, failures use {"error":{"code":"…","message":"…"}}. Some include details, reason, or existingId. Do not infer success from a bulk HTTP status alone.
| HTTP | Code | Meaning / action |
|---|---|---|
| 401 | UNAUTHENTICATED, INVALID_API_KEY | Missing, unknown, or revoked key |
| 403 | FORBIDDEN | The key lacks the route's scope |
| 429 | RATE_LIMITED | Wait and retry with backoff |
| 400 | VALIDATION_FAILED, INVALID_VALUE | Correct the request using the relevant section |
| 400 | CONFIRM_REQUIRED | Bulk entry deletion needs confirm: true |
| 403 | FIELD_NOT_PERMITTED, PLAN_REQUIRED | Protected fields/default scenarios, or missing size-guide entitlement |
| 404 | NOT_FOUND, SHOP_SCOPE_MISMATCH | Unknown or out-of-store resource |
| 409 | DUPLICATE_ENTRY | Identical entry or product FAQ already exists |
| 409 | ALREADY_RUNNING | An FAQ delete-all job is already running |
| 413 | PAYLOAD_TOO_LARGE | FAQ CSV import above 5 MB |
| 422 | SCREEN_*, INGESTION_REVIEW, CONFLICT_PENDING | Content was rejected or needs review; do not blindly retry |
| 400 | chart_unreadable, not_a_size_chart | Provide a readable size chart |
| 503 | chart_service_unavailable | Chart extraction service is unavailable |
| 500 | LIMIT_REACHED, JOB_IN_FLIGHT, GENERATION_FAILED | Scenario allowance, an active memory job, or failed suggestion generation; follow the group guidance |
| 500 | INTERNAL_ERROR | Other unexpected failures; inspect saved state before retrying writes |
Example rejected creation:
{"error":{"code":"DUPLICATE_ENTRY","message":"DEDUPE_CONTENT_HASH","existingId":"entry_123"}}Read Authentication and Errors and rate limits for shared key, version, and rate-limit behavior. The MCP tools reference describes grouped tools that call these routes; their availability still depends on authentication and scopes.
Chatty Help Center