API リファレンス
Localization
Management API: GET /sites/{id}/locales, POST /sites/{id}/locales, PUT /sites/{id}/locales/order, …
List the website's locales (primary first, then the visitor language-switcher order) with status, auto-translate and URL prefix, plus the plan's locale limit and every locale code Kanorio supports. The primary locale is the source language; change it with localization.locales.set_primary while the website has a single locale. Errors: FORBIDDEN, WEBSITE_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Response 200
dataobject[]requireddata[].localestringrequireddata[].isPrimarybooleanrequireddata[].status"DRAFT" | "TRANSLATING" | "PUBLISHED" | "DISABLED"requiredDRAFT = added but hidden from visitors and the Delivery API; TRANSLATING = the full-site translation job is running (switches to PUBLISHED when done); PUBLISHED = live; DISABLED = retired.
data[].autoTranslatebooleanrequiredMachine-translate new and changed content automatically. Fields edited by a person, an agent or an external tool are never overwritten by machine translation.
data[].pathPrefixstring | nullrequiredURL prefix of a secondary locale; null for the primary locale (served at the root).
data[].sortOrderintegerrequiredPosition in the visitor language switcher (primary first).
data[].addedAtstringrequireddata[].publishedAtstring | nullrequiredFirst time the locale went live; null if it has never been live.
hasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
primaryLocalestringrequiredmaxLocalesintegerrequiredPlan limit, counting the primary locale.
supportedLocalesstring[]requiredLocale codes that can be added.
Errors
FORBIDDEN403WEBSITE_NOT_FOUND404
MCP tool localization_locales_list
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/locales" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"data": [
{
"locale": "en-US",
"isPrimary": true,
"status": "PUBLISHED",
"autoTranslate": true,
"pathPrefix": null,
"sortOrder": 0,
"addedAt": "2026-10-01T02:30:00.000Z",
"publishedAt": "2026-10-01T02:30:00.000Z"
},
{
"locale": "fr-FR",
"isPrimary": false,
"status": "PUBLISHED",
"autoTranslate": true,
"pathPrefix": "fr",
"sortOrder": 1,
"addedAt": "2026-10-01T02:30:00.000Z",
"publishedAt": "2026-10-06T08:00:00.000Z"
},
{
"locale": "de",
"isPrimary": false,
"status": "TRANSLATING",
"autoTranslate": true,
"pathPrefix": "de",
"sortOrder": 2,
"addedAt": "2026-10-06T08:00:00.000Z",
"publishedAt": null
}
],
"hasMore": false,
"nextCursor": null,
"primaryLocale": "en-US",
"maxLocales": 5,
"supportedLocales": [
"en-US",
"en-GB",
"fr-FR",
"fr-CA",
"de",
"es-ES",
"ja",
"zh-Hant"
]
}Add a secondary locale. With autoTranslate (default true) it starts as TRANSLATING and a full-site translation job translates every record, then the locale goes live automatically; poll it with localization.jobs.list or localization.jobs.get. With autoTranslate false it starts as DRAFT: write translations with localization.translations.upsert, then publish it with localization.locales.update (status PUBLISHED). pathPrefix defaults to the language code (en), or the full code when two regional variants share a language (en-us, en-gb). Pass confirm: true. Safe to retry: adding a locale that is already on the website returns LOCALE_ALREADY_ADDED with details.locale (its current state). Errors: FORBIDDEN, CONFIRM_REQUIRED, UNSUPPORTED_LOCALE, LOCALE_ALREADY_ADDED, LOCALE_LIMIT_REACHED, INVALID_PATH_PREFIX, PATH_PREFIX_TAKEN, PATH_PREFIX_CONFLICT.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
localestringrequiredBCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).
autoTranslatebooleanDefault true.
pathPrefixstringURL prefix for this locale (lowercase letters, digits and hyphens, 2-30 chars), e.g. en or en-gb. Visitors reach the locale at /{prefix}/... Changing a live locale's prefix 301-redirects the old prefix.
confirmbooleanrequiredMust be true: with autoTranslate the whole site is translated and the locale goes live when done.
Response 201
localeobjectrequiredlocale.localestringrequiredlocale.isPrimarybooleanrequiredlocale.status"DRAFT" | "TRANSLATING" | "PUBLISHED" | "DISABLED"requiredDRAFT = added but hidden from visitors and the Delivery API; TRANSLATING = the full-site translation job is running (switches to PUBLISHED when done); PUBLISHED = live; DISABLED = retired.
locale.autoTranslatebooleanrequiredMachine-translate new and changed content automatically. Fields edited by a person, an agent or an external tool are never overwritten by machine translation.
locale.pathPrefixstring | nullrequiredURL prefix of a secondary locale; null for the primary locale (served at the root).
locale.sortOrderintegerrequiredPosition in the visitor language switcher (primary first).
locale.addedAtstringrequiredlocale.publishedAtstring | nullrequiredFirst time the locale went live; null if it has never been live.
jobIdstring | nullrequiredFull-site translation job started for this locale (null when autoTranslate is false); follow it with localization.jobs.get.
Errors
FORBIDDEN403CONFIRM_REQUIRED400UNSUPPORTED_LOCALE400LOCALE_ALREADY_ADDED409LOCALE_LIMIT_REACHED403INVALID_PATH_PREFIX400PATH_PREFIX_TAKEN409PATH_PREFIX_CONFLICT409
MCP tool localization_locales_add
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/locales" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"locale":"de","autoTranslate":true,"confirm":true}'{
"locale": {
"locale": "de",
"isPrimary": false,
"status": "TRANSLATING",
"autoTranslate": true,
"pathPrefix": "de",
"sortOrder": 2,
"addedAt": "2026-10-06T08:00:00.000Z",
"publishedAt": null
},
"jobId": "ltj_example01"
}Set the order of secondary locales in the visitor language switcher. Locales left out keep their relative order after the listed ones; the primary locale is always first. Errors: FORBIDDEN, LOCALE_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
orderstring[]requiredSecondary locale codes in the new order.
Response 200
dataobject[]requireddata[].localestringrequireddata[].isPrimarybooleanrequireddata[].status"DRAFT" | "TRANSLATING" | "PUBLISHED" | "DISABLED"requiredDRAFT = added but hidden from visitors and the Delivery API; TRANSLATING = the full-site translation job is running (switches to PUBLISHED when done); PUBLISHED = live; DISABLED = retired.
data[].autoTranslatebooleanrequiredMachine-translate new and changed content automatically. Fields edited by a person, an agent or an external tool are never overwritten by machine translation.
data[].pathPrefixstring | nullrequiredURL prefix of a secondary locale; null for the primary locale (served at the root).
data[].sortOrderintegerrequiredPosition in the visitor language switcher (primary first).
data[].addedAtstringrequireddata[].publishedAtstring | nullrequiredFirst time the locale went live; null if it has never been live.
hasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
Errors
FORBIDDEN403LOCALE_NOT_FOUND404
MCP tool localization_locales_reorder
curl -X PUT "https://app.kanorio.com/api/v1/sites/{id}/locales/order" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"order":["de","fr-FR"]}'{
"data": [
{
"locale": "en-US",
"isPrimary": true,
"status": "PUBLISHED",
"autoTranslate": true,
"pathPrefix": null,
"sortOrder": 0,
"addedAt": "2026-10-01T02:30:00.000Z",
"publishedAt": "2026-10-01T02:30:00.000Z"
},
{
"locale": "de",
"isPrimary": false,
"status": "TRANSLATING",
"autoTranslate": true,
"pathPrefix": "de",
"sortOrder": 1,
"addedAt": "2026-10-06T08:00:00.000Z",
"publishedAt": null
},
{
"locale": "fr-FR",
"isPrimary": false,
"status": "PUBLISHED",
"autoTranslate": true,
"pathPrefix": "fr",
"sortOrder": 2,
"addedAt": "2026-10-01T02:30:00.000Z",
"publishedAt": "2026-10-06T08:00:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Change the primary (source) locale. Allowed while the website has a single locale (the content language changes: fonts, HTML lang, system text); a multilingual website returns PRIMARY_SWAP_NOT_AVAILABLE. Setting the current primary locale again is a no-op. Pass confirm: true. Errors: FORBIDDEN, CONFIRM_REQUIRED, UNSUPPORTED_LOCALE, PRIMARY_SWAP_NOT_AVAILABLE.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
localestringrequiredNew primary locale code (any code in supportedLocales).
confirmbooleanrequiredMust be true: the website's content language changes.
Response 200
fromstringrequiredtostringrequiredchangedbooleanrequired
Errors
FORBIDDEN403CONFIRM_REQUIRED400UNSUPPORTED_LOCALE400PRIMARY_SWAP_NOT_AVAILABLE409
MCP tool localization_locales_set_primary
curl -X PUT "https://app.kanorio.com/api/v1/sites/{id}/locales/primary" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"locale":"en-GB","confirm":true}'{
"from": "en-US",
"to": "en-GB",
"changed": true
}Update a locale
/sites/{id}/locales/{locale}Update a locale: status PUBLISHED puts it live (adds it to the language switcher, hreflang, sitemap and the Delivery API) and DRAFT takes it offline (its URL prefix then 301-redirects to the primary locale); autoTranslate true starts a job that translates only what is missing or outdated; pathPrefix changes the URL prefix (a live locale's old prefix 301-redirects to the new one). Errors: FORBIDDEN, LOCALE_NOT_FOUND, PRIMARY_LOCALE_LOCKED, INVALID_PATH_PREFIX, PATH_PREFIX_TAKEN, PATH_PREFIX_CONFLICT.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
localestring · pathrequiredBCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).
Request body
status"DRAFT" | "PUBLISHED"autoTranslatebooleanpathPrefixstringURL prefix for this locale (lowercase letters, digits and hyphens, 2-30 chars), e.g. en or en-gb. Visitors reach the locale at /{prefix}/... Changing a live locale's prefix 301-redirects the old prefix.
Response 200
localeobjectrequiredlocale.localestringrequiredlocale.isPrimarybooleanrequiredlocale.status"DRAFT" | "TRANSLATING" | "PUBLISHED" | "DISABLED"requiredDRAFT = added but hidden from visitors and the Delivery API; TRANSLATING = the full-site translation job is running (switches to PUBLISHED when done); PUBLISHED = live; DISABLED = retired.
locale.autoTranslatebooleanrequiredMachine-translate new and changed content automatically. Fields edited by a person, an agent or an external tool are never overwritten by machine translation.
locale.pathPrefixstring | nullrequiredURL prefix of a secondary locale; null for the primary locale (served at the root).
locale.sortOrderintegerrequiredPosition in the visitor language switcher (primary first).
locale.addedAtstringrequiredlocale.publishedAtstring | nullrequiredFirst time the locale went live; null if it has never been live.
Errors
FORBIDDEN403LOCALE_NOT_FOUND404PRIMARY_LOCALE_LOCKED409INVALID_PATH_PREFIX400PATH_PREFIX_TAKEN409PATH_PREFIX_CONFLICT409
MCP tool localization_locales_update
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/locales/{locale}" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"PUBLISHED"}'{
"locale": {
"locale": "de",
"isPrimary": false,
"status": "PUBLISHED",
"autoTranslate": true,
"pathPrefix": "de",
"sortOrder": 2,
"addedAt": "2026-10-06T08:00:00.000Z",
"publishedAt": "2026-10-06T08:00:00.000Z"
}
}Locale remove
/sites/{id}/locales/{locale}Remove a secondary locale. If it was ever live, its URL prefix 301-redirects to the matching primary-locale pages. Its translations are kept, so adding the same locale back later only translates what is missing. Pass confirm: true. Errors: FORBIDDEN, CONFIRM_REQUIRED, LOCALE_NOT_FOUND, PRIMARY_LOCALE_LOCKED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
localestring · pathrequiredBCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).
confirmboolean | "true" | "false" · queryMust be true: visitors lose this language version.
Response 200
localestringrequiredremovedtruerequired
Errors
FORBIDDEN403CONFIRM_REQUIRED400LOCALE_NOT_FOUND404PRIMARY_LOCALE_LOCKED409
MCP tool localization_locales_remove
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/locales/{locale}?confirm=true" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"locale": "de",
"removed": true
}List translation rows of one record type (newest first), optionally narrowed to one record, one locale or one status. Rows carry metadata only; read the text and the source with localization.translations.get. Translating counts as editing the record, so the record type's own permission applies (e.g. commerce:manage for products). Errors: FORBIDDEN, INVALID_CURSOR.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
entityType"page" | "global_sections" | "site_settings" | "store_settings" | "post" | "post_category" | "post_tag" | "author" | "product" | "product_option" | "pricing_tier" | "product_category" | "deliverable" | "deliverable_unit" | "form" | "top_bar_banner" | "modal_announcement" | "floating_widget" | "collection_item" | "message_template" · queryrequiredTranslatable record type. page (pages.list ids), global_sections, site_settings and store_settings (the site itself: entityId defaults to websiteId), post / post_category / post_tag / author (blog), product / product_option / pricing_tier / product_category, deliverable / deliverable_unit, form, top_bar_banner / modal_announcement / floating_widget, collection_item (custom collection items), message_template (an automated email whose content the owner changed; the id of the customized template — untouched emails use the built-in version for every language).
entityIdstring · queryOnly this record.
localestring · queryOnly this locale.
status"DRAFT" | "PUBLISHED" · querylimitinteger · queryMaximum number of items to return, 1–100. Defaults to 20.
cursorstring · queryOpaque cursor from the previous page's nextCursor. Omit for the first page.
Response 200
dataobject[]requireddata[].entityTypestringrequireddata[].entityIdstringrequireddata[].localestringrequireddata[].status"DRAFT" | "PUBLISHED"requiredPUBLISHED = the published copy is live (it may differ from the draft copy until the record is published again).
data[].translatedBy"MACHINE" | "HUMAN" | "AGENT" | "EXTERNAL" | "SEO_FIX"requiredMost significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.
data[].fieldCountintegerrequiredNumber of translated field paths in the draft copy.
data[].sourceHashstringrequiredSource fingerprint the draft copy was written against.
data[].updatedAtstringrequireddata[].publishedAtstring | nullrequiredhasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
Errors
FORBIDDEN403INVALID_CURSOR400
MCP tool localization_translations_list
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/translations?entityType=product&locale=fr-FR&limit=20" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"data": [
{
"entityType": "product",
"entityId": "prod_example01",
"locale": "fr-FR",
"status": "PUBLISHED",
"translatedBy": "AGENT",
"fieldCount": 2,
"sourceHash": "c1a0e6f2b47d93e8",
"updatedAt": "2026-10-06T08:00:00.000Z",
"publishedAt": "2026-10-01T02:30:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Retrieve a translation
/sites/{id}/translations/{entityType}/{entityId}/{locale}Get one record's translation in one locale together with its translatable source fields: each field's path, current source text, text format (profile), fingerprint and status (missing, current or outdated). Pass source.sourceHash back as sourceHash to localization.translations.upsert. translation is null when nothing has been translated yet. Errors: FORBIDDEN, VALIDATION, ENTITY_NOT_FOUND, LOCALE_NOT_FOUND, LOCALE_IS_PRIMARY.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
entityTypestring · pathrequiredTranslatable record type. page (pages.list ids), global_sections, site_settings and store_settings (the site itself: entityId defaults to websiteId), post / post_category / post_tag / author (blog), product / product_option / pricing_tier / product_category, deliverable / deliverable_unit, form, top_bar_banner / modal_announcement / floating_widget, collection_item (custom collection items), message_template (an automated email whose content the owner changed; the id of the customized template — untouched emails use the built-in version for every language).
entityIdstring · pathrequiredRecord id. Required except for global_sections and site_settings, whose id is the websiteId.
localestring · pathrequiredBCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).
Response 200
entityTypestringrequiredentityIdstringrequiredlocalestringrequiredtranslationobject | nullrequiredtranslation.entityTypestringrequiredtranslation.entityIdstringrequiredtranslation.localestringrequiredtranslation.status"DRAFT" | "PUBLISHED"requiredPUBLISHED = the published copy is live (it may differ from the draft copy until the record is published again).
translation.translatedBy"MACHINE" | "HUMAN" | "AGENT" | "EXTERNAL" | "SEO_FIX"requiredMost significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.
translation.fieldCountintegerrequiredNumber of translated field paths in the draft copy.
translation.sourceHashstringrequiredSource fingerprint the draft copy was written against.
translation.updatedAtstringrequiredtranslation.publishedAtstring | nullrequiredtranslation.fieldDataobjectrequiredDraft copy: field path → translated text.
translation.publishedFieldDataobject | nullrequiredLive copy (null until published).
translation.fieldMetaobjectrequiredtranslation.slugstring | nullrequiredtranslation.publishedSlugstring | nullrequiredsourceobjectrequiredsource.sourceHashstringrequiredFingerprint of all source fields; required by localization.translations.upsert.
source.fieldsobject[]requiredsource.fields[].pathstringrequiredField path to use as a key in fieldData (array indexes and section ids already filled in).
source.fields[].valuestringrequiredCurrent source text in the primary locale (editable draft).
source.fields[].profile"plain" | "inline" | "body" | "article" | "email"requiredText format: plain = no markup; inline = inline Markdown only (bold, italic, links); body / article = Markdown blocks (article also keeps images and headings); email = email-safe Markdown. Keep the same markup in the translation.
source.fields[].sourceHashstringrequiredsource.fields[].status"missing" | "current" | "outdated"requiredmissing = no translation yet; current = translated from this exact source; outdated = the source changed after it was translated.
Errors
FORBIDDEN403VALIDATION400ENTITY_NOT_FOUND404LOCALE_NOT_FOUND404LOCALE_IS_PRIMARY422
MCP tool localization_translations_get
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/translations/{entityType}/{entityId}/{locale}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"entityType": "product",
"entityId": "prod_example01",
"locale": "fr-FR",
"translation": {
"entityType": "product",
"entityId": "prod_example01",
"locale": "fr-FR",
"status": "PUBLISHED",
"translatedBy": "AGENT",
"fieldCount": 2,
"sourceHash": "c1a0e6f2b47d93e8",
"updatedAt": "2026-10-06T08:00:00.000Z",
"publishedAt": "2026-10-01T02:30:00.000Z",
"fieldData": {
"title": "Pain au levain de campagne",
"description": "Fermentation lente de 36 heures, farine complète **moulue sur meule**."
},
"publishedFieldData": {
"title": "Pain au levain de campagne",
"description": "Fermentation lente de 36 heures, farine complète **moulue sur meule**."
},
"fieldMeta": {
"title": {
"translatedBy": "AGENT",
"sourceHash": "5e1b0c9a"
},
"description": {
"translatedBy": "MACHINE",
"sourceHash": "9f3d2a71"
}
},
"slug": null,
"publishedSlug": null
},
"source": {
"sourceHash": "c1a0e6f2b47d93e8",
"fields": [
{
"path": "title",
"value": "Country Sourdough",
"profile": "plain",
"sourceHash": "5e1b0c9a",
"status": "current"
},
{
"path": "description",
"value": "Slow-fermented for 48 hours with **stone-ground** whole wheat.",
"profile": "body",
"sourceHash": "0b7c44d2",
"status": "outdated"
}
]
}
}Translation upsert
/sites/{id}/translations/{entityType}/{entityId}/{locale}Write translated text for some fields of one record in one secondary locale. Only the given paths change; an empty string clears that field (visitors see the primary text). Paths must come from localization.translations.get source.fields. sourceHash must equal the current source.sourceHash; if the source changed since you read it the write is rejected with SOURCE_HASH_MISMATCH (details.sourceHash) — read it again and re-translate the outdated fields. Fields you write are locked against machine re-translation; if their source changes later they are re-translated using your wording as reference. slug sets this locale's URL slug (one segment, lowercase; pages translate only their last segment). null makes the locale follow the primary slug. Changing a live slug adds a 301 from the old URL in that locale. Pages, global sections, site settings, blog posts and collection items keep translations as drafts that go live when the record is published (or with localization.translations.publish); every other record type goes live on write. Errors: FORBIDDEN, VALIDATION, ENTITY_NOT_FOUND, LOCALE_NOT_FOUND, LOCALE_IS_PRIMARY, INVALID_FIELD_PATH (details.paths), SOURCE_HASH_MISMATCH, INVALID_SLUG, SLUG_TAKEN.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
entityTypestring · pathrequiredTranslatable record type. page (pages.list ids), global_sections, site_settings and store_settings (the site itself: entityId defaults to websiteId), post / post_category / post_tag / author (blog), product / product_option / pricing_tier / product_category, deliverable / deliverable_unit, form, top_bar_banner / modal_announcement / floating_widget, collection_item (custom collection items), message_template (an automated email whose content the owner changed; the id of the customized template — untouched emails use the built-in version for every language).
entityIdstring · pathrequiredRecord id. Required except for global_sections and site_settings, whose id is the websiteId.
localestring · pathrequiredBCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).
Request body
fieldDataobjectField path → translated text, keeping the source's Markdown for inline / body / article fields. May be empty when only slug changes.
slugstring | nullThis locale's URL slug: one segment of letters (any script), digits and hyphens, lowercase. null follows the primary slug; omit to keep it. Records without a URL (forms, settings) reject it with INVALID_SLUG.
sourceHashstringrequiredsource.sourceHash from localization.translations.get.
translatedBy"AGENT" | "EXTERNAL"AGENT (default) for AI agents; EXTERNAL for translation-management tools and human translators outside Kanorio.
Response 200
translationobjectrequiredtranslation.entityTypestringrequiredtranslation.entityIdstringrequiredtranslation.localestringrequiredtranslation.status"DRAFT" | "PUBLISHED"requiredPUBLISHED = the published copy is live (it may differ from the draft copy until the record is published again).
translation.translatedBy"MACHINE" | "HUMAN" | "AGENT" | "EXTERNAL" | "SEO_FIX"requiredMost significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.
translation.fieldCountintegerrequiredNumber of translated field paths in the draft copy.
translation.sourceHashstringrequiredSource fingerprint the draft copy was written against.
translation.updatedAtstringrequiredtranslation.publishedAtstring | nullrequiredtranslation.fieldDataobjectrequiredDraft copy: field path → translated text.
translation.publishedFieldDataobject | nullrequiredLive copy (null until published).
translation.fieldMetaobjectrequiredtranslation.slugstring | nullrequiredtranslation.publishedSlugstring | nullrequiredlivebooleanrequiredtrue when visitors already see this text; false when it waits for the record to be published.
Errors
FORBIDDEN403VALIDATION400ENTITY_NOT_FOUND404LOCALE_NOT_FOUND404LOCALE_IS_PRIMARY422INVALID_FIELD_PATH400 · details.pathsSOURCE_HASH_MISMATCH409INVALID_SLUG400SLUG_TAKEN409
MCP tool localization_translations_upsert
curl -X PUT "https://app.kanorio.com/api/v1/sites/{id}/translations/{entityType}/{entityId}/{locale}" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fieldData":{"description":"Fermentation lente de 48 heures, farine complète **moulue sur meule**."},"sourceHash":"c1a0e6f2b47d93e8","translatedBy":"AGENT"}'{
"translation": {
"entityType": "product",
"entityId": "prod_example01",
"locale": "fr-FR",
"status": "PUBLISHED",
"translatedBy": "AGENT",
"fieldCount": 2,
"sourceHash": "c1a0e6f2b47d93e8",
"updatedAt": "2026-10-06T08:00:00.000Z",
"publishedAt": "2026-10-01T02:30:00.000Z",
"fieldData": {
"title": "Pain au levain de campagne",
"description": "Fermentation lente de 48 heures, farine complète **moulue sur meule**."
},
"publishedFieldData": {
"title": "Pain au levain de campagne",
"description": "Fermentation lente de 48 heures, farine complète **moulue sur meule**."
},
"fieldMeta": {
"title": {
"translatedBy": "AGENT",
"sourceHash": "5e1b0c9a"
},
"description": {
"translatedBy": "AGENT",
"sourceHash": "0b7c44d2"
}
},
"slug": null,
"publishedSlug": null
},
"live": true
}Delete a translation
/sites/{id}/translations/{entityType}/{entityId}/{locale}Delete one record's translation in one locale (draft and live copies); visitors see the primary text until it is translated again. With auto-translate on, the next change to the record is machine-translated again. Errors: FORBIDDEN, VALIDATION, TRANSLATION_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
entityTypestring · pathrequiredTranslatable record type. page (pages.list ids), global_sections, site_settings and store_settings (the site itself: entityId defaults to websiteId), post / post_category / post_tag / author (blog), product / product_option / pricing_tier / product_category, deliverable / deliverable_unit, form, top_bar_banner / modal_announcement / floating_widget, collection_item (custom collection items), message_template (an automated email whose content the owner changed; the id of the customized template — untouched emails use the built-in version for every language).
entityIdstring · pathrequiredRecord id. Required except for global_sections and site_settings, whose id is the websiteId.
localestring · pathrequiredBCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).
Response 200
deletedtruerequired
Errors
FORBIDDEN403VALIDATION400TRANSLATION_NOT_FOUND404
MCP tool localization_translations_delete
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/translations/{entityType}/{entityId}/{locale}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"deleted": true
}Publish a translation
/sites/{id}/translations/{entityType}/{entityId}/{locale}/publishPut one record's draft translation in one locale live without publishing the record's primary-language draft (for pages, global sections, site settings, blog posts and collection items, whose translations otherwise go live when the record is published). Visitors see it only where the record itself is live. For those record types this needs the record's publish permission (site:publish, blog:publish or collections:publish), not just edit access. Errors: FORBIDDEN, VALIDATION, TRANSLATION_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
entityTypestring · pathrequiredTranslatable record type. page (pages.list ids), global_sections, site_settings and store_settings (the site itself: entityId defaults to websiteId), post / post_category / post_tag / author (blog), product / product_option / pricing_tier / product_category, deliverable / deliverable_unit, form, top_bar_banner / modal_announcement / floating_widget, collection_item (custom collection items), message_template (an automated email whose content the owner changed; the id of the customized template — untouched emails use the built-in version for every language).
entityIdstring · pathrequiredRecord id. Required except for global_sections and site_settings, whose id is the websiteId.
localestring · pathrequiredBCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).
Response 200
translationobjectrequiredtranslation.entityTypestringrequiredtranslation.entityIdstringrequiredtranslation.localestringrequiredtranslation.status"DRAFT" | "PUBLISHED"requiredPUBLISHED = the published copy is live (it may differ from the draft copy until the record is published again).
translation.translatedBy"MACHINE" | "HUMAN" | "AGENT" | "EXTERNAL" | "SEO_FIX"requiredMost significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.
translation.fieldCountintegerrequiredNumber of translated field paths in the draft copy.
translation.sourceHashstringrequiredSource fingerprint the draft copy was written against.
translation.updatedAtstringrequiredtranslation.publishedAtstring | nullrequiredtranslation.fieldDataobjectrequiredDraft copy: field path → translated text.
translation.publishedFieldDataobject | nullrequiredLive copy (null until published).
translation.fieldMetaobjectrequiredtranslation.slugstring | nullrequiredtranslation.publishedSlugstring | nullrequired
Errors
FORBIDDEN403VALIDATION400TRANSLATION_NOT_FOUND404
MCP tool localization_translations_publish
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/translations/{entityType}/{entityId}/{locale}/publish" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"translation": {
"entityType": "page",
"entityId": "pg_example789",
"locale": "fr-FR",
"status": "PUBLISHED",
"translatedBy": "AGENT",
"fieldCount": 2,
"sourceHash": "c1a0e6f2b47d93e8",
"updatedAt": "2026-10-06T08:00:00.000Z",
"publishedAt": "2026-10-01T02:30:00.000Z",
"fieldData": {
"title": "Pain au levain de campagne",
"description": "Fermentation lente de 36 heures, farine complète **moulue sur meule**."
},
"publishedFieldData": {
"title": "Pain au levain de campagne",
"description": "Fermentation lente de 36 heures, farine complète **moulue sur meule**."
},
"fieldMeta": {
"title": {
"translatedBy": "AGENT",
"sourceHash": "5e1b0c9a"
},
"description": {
"translatedBy": "MACHINE",
"sourceHash": "9f3d2a71"
}
},
"slug": null,
"publishedSlug": null
}
}Translation translate
/sites/{id}/translations/{entityType}/{entityId}/{locale}/translateMachine-translate one record into one secondary locale now (synchronous; only missing and outdated fields; fields written by a person, an agent or an external tool are re-translated using their wording as reference). version draft (default) translates the editable draft and follows the record's publishing; version published translates the live content and goes live immediately. Counts toward the website's daily translation limit (no AI credits). Errors: FORBIDDEN, VALIDATION, ENTITY_NOT_FOUND, LOCALE_NOT_FOUND, LOCALE_IS_PRIMARY, DAILY_LIMIT_REACHED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
entityTypestring · pathrequiredTranslatable record type. page (pages.list ids), global_sections, site_settings and store_settings (the site itself: entityId defaults to websiteId), post / post_category / post_tag / author (blog), product / product_option / pricing_tier / product_category, deliverable / deliverable_unit, form, top_bar_banner / modal_announcement / floating_widget, collection_item (custom collection items), message_template (an automated email whose content the owner changed; the id of the customized template — untouched emails use the built-in version for every language).
entityIdstring · pathrequiredRecord id. Required except for global_sections and site_settings, whose id is the websiteId.
localestring · pathrequiredBCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).
Request body
version"draft" | "published"
Response 200
outcome"done" | "skipped" | "partial"requiredskipped = already up to date; partial = some fields failed (failedPaths), retry later.
failedPathsstring[]requiredtranslationobject | nullrequiredtranslation.entityTypestringrequiredtranslation.entityIdstringrequiredtranslation.localestringrequiredtranslation.status"DRAFT" | "PUBLISHED"requiredPUBLISHED = the published copy is live (it may differ from the draft copy until the record is published again).
translation.translatedBy"MACHINE" | "HUMAN" | "AGENT" | "EXTERNAL" | "SEO_FIX"requiredMost significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.
translation.fieldCountintegerrequiredNumber of translated field paths in the draft copy.
translation.sourceHashstringrequiredSource fingerprint the draft copy was written against.
translation.updatedAtstringrequiredtranslation.publishedAtstring | nullrequiredtranslation.fieldDataobjectrequiredDraft copy: field path → translated text.
translation.publishedFieldDataobject | nullrequiredLive copy (null until published).
translation.fieldMetaobjectrequiredtranslation.slugstring | nullrequiredtranslation.publishedSlugstring | nullrequired
Errors
FORBIDDEN403VALIDATION400ENTITY_NOT_FOUND404LOCALE_NOT_FOUND404LOCALE_IS_PRIMARY422DAILY_LIMIT_REACHED429
MCP tool localization_translations_translate
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/translations/{entityType}/{entityId}/{locale}/translate" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"version":"draft"}'{
"outcome": "done",
"failedPaths": [],
"translation": {
"entityType": "product",
"entityId": "prod_example01",
"locale": "fr-FR",
"status": "PUBLISHED",
"translatedBy": "AGENT",
"fieldCount": 2,
"sourceHash": "c1a0e6f2b47d93e8",
"updatedAt": "2026-10-06T08:00:00.000Z",
"publishedAt": "2026-10-01T02:30:00.000Z",
"fieldData": {
"title": "Pain au levain de campagne",
"description": "Fermentation lente de 36 heures, farine complète **moulue sur meule**."
},
"publishedFieldData": {
"title": "Pain au levain de campagne",
"description": "Fermentation lente de 36 heures, farine complète **moulue sur meule**."
},
"fieldMeta": {
"title": {
"translatedBy": "AGENT",
"sourceHash": "5e1b0c9a"
},
"description": {
"translatedBy": "MACHINE",
"sourceHash": "9f3d2a71"
}
},
"slug": null,
"publishedSlug": null
}
}List the current (running or most recent) full-site translation job of each locale, with progress and failed records. Errors: FORBIDDEN.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Response 200
dataobject[]requireddata[].idstringrequireddata[].localestringrequireddata[].kind"FULL" | "PRIMARY_SWAP"requireddata[].status"PENDING" | "RUNNING" | "PAUSED" | "SUCCEEDED" | "FAILED"requiredPAUSED = waiting for the daily translation limit or the model provider; resumes on its own at resumeAt.
data[].stagestring | nullrequireddata[].progressobject | nullrequireddata[].progress.stagestringrequireddata[].progress.totalintegerrequiredRecords to translate.
data[].progress.doneintegerrequireddata[].progress.failedintegerrequireddata[].progress.skippedintegerrequiredRecords already up to date.
data[].progress.callsintegerrequiredModel calls used.
data[].progress.failedEntitiesobject[]requiredAt most 50, most recent first.
data[].errorCodestring | nullrequiredCANCELLED when cancelled; otherwise the failure reason.
data[].resumeAtstring | nullrequireddata[].createdAtstringrequireddata[].completedAtstring | nullrequiredhasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
Errors
FORBIDDEN403
MCP tool localization_jobs_list
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/translation-jobs" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"data": [
{
"id": "ltj_example01",
"locale": "de",
"kind": "FULL",
"status": "RUNNING",
"stage": "translating",
"progress": {
"stage": "translating",
"total": 42,
"done": 18,
"failed": 0,
"skipped": 3,
"calls": 11,
"failedEntities": []
},
"errorCode": null,
"resumeAt": null,
"createdAt": "2026-10-06T08:00:00.000Z",
"completedAt": null
},
{
"id": "ltj_example00",
"locale": "fr-FR",
"kind": "FULL",
"status": "SUCCEEDED",
"stage": "done",
"progress": {
"stage": "translating",
"total": 42,
"done": 18,
"failed": 0,
"skipped": 3,
"calls": 11,
"failedEntities": []
},
"errorCode": null,
"resumeAt": null,
"createdAt": "2026-10-06T08:00:00.000Z",
"completedAt": "2026-10-01T02:30:00.000Z"
}
],
"hasMore": false,
"nextCursor": null
}Run the full-site translation job for a secondary locale (again): it translates only records that are missing or whose source changed, never overwrites fields written by a person, an agent or an external tool, and pauses (not fails) at the daily translation limit. Use it after a job failed or was cancelled, or to fill gaps on a locale with autoTranslate off. If a job for that locale is already queued, running or paused, returns it with alreadyRunning true. Errors: FORBIDDEN, LOCALE_NOT_FOUND, UNSUPPORTED_LOCALE.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
localestringrequiredBCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).
Response 201
jobIdstringrequiredalreadyRunningbooleanrequired
Errors
FORBIDDEN403LOCALE_NOT_FOUND404UNSUPPORTED_LOCALE400
MCP tool localization_jobs_retry
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/translation-jobs" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"locale":"de"}'{
"jobId": "ltj_example01",
"alreadyRunning": false
}Get one full-site translation job by id. Poll every 10-30 seconds until status is SUCCEEDED or FAILED. Errors: FORBIDDEN, JOB_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
jobIdstring · pathrequiredJob id from localization.jobs.list or localization.jobs.retry.
Response 200
jobobjectrequiredjob.idstringrequiredjob.localestringrequiredjob.kind"FULL" | "PRIMARY_SWAP"requiredjob.status"PENDING" | "RUNNING" | "PAUSED" | "SUCCEEDED" | "FAILED"requiredPAUSED = waiting for the daily translation limit or the model provider; resumes on its own at resumeAt.
job.stagestring | nullrequiredjob.progressobject | nullrequiredjob.progress.stagestringrequiredjob.progress.totalintegerrequiredRecords to translate.
job.progress.doneintegerrequiredjob.progress.failedintegerrequiredjob.progress.skippedintegerrequiredRecords already up to date.
job.progress.callsintegerrequiredModel calls used.
job.progress.failedEntitiesobject[]requiredAt most 50, most recent first.
job.errorCodestring | nullrequiredCANCELLED when cancelled; otherwise the failure reason.
job.resumeAtstring | nullrequiredjob.createdAtstringrequiredjob.completedAtstring | nullrequired
Errors
FORBIDDEN403JOB_NOT_FOUND404
MCP tool localization_jobs_get
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/translation-jobs/{jobId}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"job": {
"id": "ltj_example01",
"locale": "de",
"kind": "FULL",
"status": "RUNNING",
"stage": "translating",
"progress": {
"stage": "translating",
"total": 42,
"done": 18,
"failed": 0,
"skipped": 3,
"calls": 11,
"failedEntities": []
},
"errorCode": null,
"resumeAt": null,
"createdAt": "2026-10-06T08:00:00.000Z",
"completedAt": null
}
}Cancel a job
/sites/{id}/translation-jobs/{jobId}/cancelCancel a queued or paused full-site translation job (a running batch finishes first). Translations already written are kept. Errors: FORBIDDEN, JOB_NOT_FOUND, JOB_NOT_CANCELLABLE.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
jobIdstring · pathrequired
Response 200
jobobjectrequiredjob.idstringrequiredjob.localestringrequiredjob.kind"FULL" | "PRIMARY_SWAP"requiredjob.status"PENDING" | "RUNNING" | "PAUSED" | "SUCCEEDED" | "FAILED"requiredPAUSED = waiting for the daily translation limit or the model provider; resumes on its own at resumeAt.
job.stagestring | nullrequiredjob.progressobject | nullrequiredjob.progress.stagestringrequiredjob.progress.totalintegerrequiredRecords to translate.
job.progress.doneintegerrequiredjob.progress.failedintegerrequiredjob.progress.skippedintegerrequiredRecords already up to date.
job.progress.callsintegerrequiredModel calls used.
job.progress.failedEntitiesobject[]requiredAt most 50, most recent first.
job.errorCodestring | nullrequiredCANCELLED when cancelled; otherwise the failure reason.
job.resumeAtstring | nullrequiredjob.createdAtstringrequiredjob.completedAtstring | nullrequired
Errors
FORBIDDEN403JOB_NOT_FOUND404JOB_NOT_CANCELLABLE409
MCP tool localization_jobs_cancel
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/translation-jobs/{jobId}/cancel" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"job": {
"id": "ltj_example01",
"locale": "de",
"kind": "FULL",
"status": "FAILED",
"stage": "cancelled",
"progress": {
"stage": "translating",
"total": 42,
"done": 18,
"failed": 0,
"skipped": 3,
"calls": 11,
"failedEntities": []
},
"errorCode": "CANCELLED",
"resumeAt": null,
"createdAt": "2026-10-06T08:00:00.000Z",
"completedAt": "2026-10-06T08:00:00.000Z"
}
}