Kanorio

API 參考

Localization

Management API: GET /sites/{id}/locales, POST /sites/{id}/locales, PUT /sites/{id}/locales/order, …

這頁只有英文版。API 參考、Webhook 事件與變更紀錄維持英文,和程式碼裡的欄位名稱、錯誤碼一致。
Base URL https://app.kanorio.com/api/v1ErrorsPagination

List locales

GET/sites/{id}/locales
betaconsole:viewdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

Response 200

  • dataobject[]required
  • data[].localestringrequired
  • data[].isPrimarybooleanrequired
  • data[].status"DRAFT" | "TRANSLATING" | "PUBLISHED" | "DISABLED"required

    DRAFT = 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[].autoTranslatebooleanrequired

    Machine-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 | nullrequired

    URL prefix of a secondary locale; null for the primary locale (served at the root).

  • data[].sortOrderintegerrequired

    Position in the visitor language switcher (primary first).

  • data[].addedAtstringrequired
  • data[].publishedAtstring | nullrequired

    First time the locale went live; null if it has never been live.

  • hasMorebooleanrequired

    True when another page exists; pass nextCursor to fetch it.

  • nextCursorstring | nullrequired

    Cursor for the next page, or null on the last page.

  • primaryLocalestringrequired
  • maxLocalesintegerrequired

    Plan limit, counting the primary locale.

  • supportedLocalesstring[]required

    Locale codes that can be added.

Errors

  • FORBIDDEN 403
  • WEBSITE_NOT_FOUND 404

MCP tool localization_locales_list

curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/locales" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
Response
{
  "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 locale

POST/sites/{id}/locales
betasite:manage_settingsdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

Request body

  • localestringrequired

    BCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).

  • autoTranslateboolean

    Default true.

  • pathPrefixstring

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

  • confirmbooleanrequired

    Must be true: with autoTranslate the whole site is translated and the locale goes live when done.

Response 201

  • localeobjectrequired
  • locale.localestringrequired
  • locale.isPrimarybooleanrequired
  • locale.status"DRAFT" | "TRANSLATING" | "PUBLISHED" | "DISABLED"required

    DRAFT = 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.autoTranslatebooleanrequired

    Machine-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 | nullrequired

    URL prefix of a secondary locale; null for the primary locale (served at the root).

  • locale.sortOrderintegerrequired

    Position in the visitor language switcher (primary first).

  • locale.addedAtstringrequired
  • locale.publishedAtstring | nullrequired

    First time the locale went live; null if it has never been live.

  • jobIdstring | nullrequired

    Full-site translation job started for this locale (null when autoTranslate is false); follow it with localization.jobs.get.

Errors

  • FORBIDDEN 403
  • CONFIRM_REQUIRED 400
  • UNSUPPORTED_LOCALE 400
  • LOCALE_ALREADY_ADDED 409
  • LOCALE_LIMIT_REACHED 403
  • INVALID_PATH_PREFIX 400
  • PATH_PREFIX_TAKEN 409
  • PATH_PREFIX_CONFLICT 409

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}'
Response
{
  "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"
}

Reorder locales

PUT/sites/{id}/locales/order
betasite:manage_settingsdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

Request body

  • orderstring[]required

    Secondary locale codes in the new order.

Response 200

  • dataobject[]required
  • data[].localestringrequired
  • data[].isPrimarybooleanrequired
  • data[].status"DRAFT" | "TRANSLATING" | "PUBLISHED" | "DISABLED"required

    DRAFT = 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[].autoTranslatebooleanrequired

    Machine-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 | nullrequired

    URL prefix of a secondary locale; null for the primary locale (served at the root).

  • data[].sortOrderintegerrequired

    Position in the visitor language switcher (primary first).

  • data[].addedAtstringrequired
  • data[].publishedAtstring | nullrequired

    First time the locale went live; null if it has never been live.

  • hasMorebooleanrequired

    True when another page exists; pass nextCursor to fetch it.

  • nextCursorstring | nullrequired

    Cursor for the next page, or null on the last page.

Errors

  • FORBIDDEN 403
  • LOCALE_NOT_FOUND 404

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"]}'
Response
{
  "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
}

Set primary

PUT/sites/{id}/locales/primary
betasite:manage_settingsdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

Request body

  • localestringrequired

    New primary locale code (any code in supportedLocales).

  • confirmbooleanrequired

    Must be true: the website's content language changes.

Response 200

  • fromstringrequired
  • tostringrequired
  • changedbooleanrequired

Errors

  • FORBIDDEN 403
  • CONFIRM_REQUIRED 400
  • UNSUPPORTED_LOCALE 400
  • PRIMARY_SWAP_NOT_AVAILABLE 409

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}'
Response
{
  "from": "en-US",
  "to": "en-GB",
  "changed": true
}

Update a locale

PATCH/sites/{id}/locales/{locale}
betasite:manage_settingsdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • localestring · pathrequired

    BCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).

Request body

  • status"DRAFT" | "PUBLISHED"
  • autoTranslateboolean
  • pathPrefixstring

    URL 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

  • localeobjectrequired
  • locale.localestringrequired
  • locale.isPrimarybooleanrequired
  • locale.status"DRAFT" | "TRANSLATING" | "PUBLISHED" | "DISABLED"required

    DRAFT = 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.autoTranslatebooleanrequired

    Machine-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 | nullrequired

    URL prefix of a secondary locale; null for the primary locale (served at the root).

  • locale.sortOrderintegerrequired

    Position in the visitor language switcher (primary first).

  • locale.addedAtstringrequired
  • locale.publishedAtstring | nullrequired

    First time the locale went live; null if it has never been live.

Errors

  • FORBIDDEN 403
  • LOCALE_NOT_FOUND 404
  • PRIMARY_LOCALE_LOCKED 409
  • INVALID_PATH_PREFIX 400
  • PATH_PREFIX_TAKEN 409
  • PATH_PREFIX_CONFLICT 409

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"}'
Response
{
  "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

DELETE/sites/{id}/locales/{locale}
betasite:manage_settingsdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • localestring · pathrequired

    BCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).

  • confirmboolean | "true" | "false" · query

    Must be true: visitors lose this language version.

Response 200

  • localestringrequired
  • removedtruerequired

Errors

  • FORBIDDEN 403
  • CONFIRM_REQUIRED 400
  • LOCALE_NOT_FOUND 404
  • PRIMARY_LOCALE_LOCKED 409

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"
Response
{
  "locale": "de",
  "removed": true
}

List translations

GET/sites/{id}/translations
betacontent:editdefault (120/minute per token)

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 · pathrequired

    Website 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" · queryrequired

    Translatable 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 · query

    Only this record.

  • localestring · query

    Only this locale.

  • status"DRAFT" | "PUBLISHED" · query
  • limitinteger · query

    Maximum number of items to return, 1–100. Defaults to 20.

  • cursorstring · query

    Opaque cursor from the previous page's nextCursor. Omit for the first page.

Response 200

  • dataobject[]required
  • data[].entityTypestringrequired
  • data[].entityIdstringrequired
  • data[].localestringrequired
  • data[].status"DRAFT" | "PUBLISHED"required

    PUBLISHED = 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"required

    Most significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.

  • data[].fieldCountintegerrequired

    Number of translated field paths in the draft copy.

  • data[].sourceHashstringrequired

    Source fingerprint the draft copy was written against.

  • data[].updatedAtstringrequired
  • data[].publishedAtstring | nullrequired
  • hasMorebooleanrequired

    True when another page exists; pass nextCursor to fetch it.

  • nextCursorstring | nullrequired

    Cursor for the next page, or null on the last page.

Errors

  • FORBIDDEN 403
  • INVALID_CURSOR 400

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"
Response
{
  "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

GET/sites/{id}/translations/{entityType}/{entityId}/{locale}
betacontent:editdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • entityTypestring · pathrequired

    Translatable 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 · pathrequired

    Record id. Required except for global_sections and site_settings, whose id is the websiteId.

  • localestring · pathrequired

    BCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).

Response 200

  • entityTypestringrequired
  • entityIdstringrequired
  • localestringrequired
  • translationobject | nullrequired
  • translation.entityTypestringrequired
  • translation.entityIdstringrequired
  • translation.localestringrequired
  • translation.status"DRAFT" | "PUBLISHED"required

    PUBLISHED = 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"required

    Most significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.

  • translation.fieldCountintegerrequired

    Number of translated field paths in the draft copy.

  • translation.sourceHashstringrequired

    Source fingerprint the draft copy was written against.

  • translation.updatedAtstringrequired
  • translation.publishedAtstring | nullrequired
  • translation.fieldDataobjectrequired

    Draft copy: field path → translated text.

  • translation.publishedFieldDataobject | nullrequired

    Live copy (null until published).

  • translation.fieldMetaobjectrequired
  • translation.slugstring | nullrequired
  • translation.publishedSlugstring | nullrequired
  • sourceobjectrequired
  • source.sourceHashstringrequired

    Fingerprint of all source fields; required by localization.translations.upsert.

  • source.fieldsobject[]required
  • source.fields[].pathstringrequired

    Field path to use as a key in fieldData (array indexes and section ids already filled in).

  • source.fields[].valuestringrequired

    Current source text in the primary locale (editable draft).

  • source.fields[].profile"plain" | "inline" | "body" | "article" | "email"required

    Text 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[].sourceHashstringrequired
  • source.fields[].status"missing" | "current" | "outdated"required

    missing = no translation yet; current = translated from this exact source; outdated = the source changed after it was translated.

Errors

  • FORBIDDEN 403
  • VALIDATION 400
  • ENTITY_NOT_FOUND 404
  • LOCALE_NOT_FOUND 404
  • LOCALE_IS_PRIMARY 422

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"
Response
{
  "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

PUT/sites/{id}/translations/{entityType}/{entityId}/{locale}
betacontent:editdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • entityTypestring · pathrequired

    Translatable 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 · pathrequired

    Record id. Required except for global_sections and site_settings, whose id is the websiteId.

  • localestring · pathrequired

    BCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).

Request body

  • fieldDataobject

    Field path → translated text, keeping the source's Markdown for inline / body / article fields. May be empty when only slug changes.

  • slugstring | null

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

  • sourceHashstringrequired

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

  • translationobjectrequired
  • translation.entityTypestringrequired
  • translation.entityIdstringrequired
  • translation.localestringrequired
  • translation.status"DRAFT" | "PUBLISHED"required

    PUBLISHED = 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"required

    Most significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.

  • translation.fieldCountintegerrequired

    Number of translated field paths in the draft copy.

  • translation.sourceHashstringrequired

    Source fingerprint the draft copy was written against.

  • translation.updatedAtstringrequired
  • translation.publishedAtstring | nullrequired
  • translation.fieldDataobjectrequired

    Draft copy: field path → translated text.

  • translation.publishedFieldDataobject | nullrequired

    Live copy (null until published).

  • translation.fieldMetaobjectrequired
  • translation.slugstring | nullrequired
  • translation.publishedSlugstring | nullrequired
  • livebooleanrequired

    true when visitors already see this text; false when it waits for the record to be published.

Errors

  • FORBIDDEN 403
  • VALIDATION 400
  • ENTITY_NOT_FOUND 404
  • LOCALE_NOT_FOUND 404
  • LOCALE_IS_PRIMARY 422
  • INVALID_FIELD_PATH 400 · details.paths
  • SOURCE_HASH_MISMATCH 409
  • INVALID_SLUG 400
  • SLUG_TAKEN 409

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"}'
Response
{
  "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

DELETE/sites/{id}/translations/{entityType}/{entityId}/{locale}
betacontent:editdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • entityTypestring · pathrequired

    Translatable 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 · pathrequired

    Record id. Required except for global_sections and site_settings, whose id is the websiteId.

  • localestring · pathrequired

    BCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).

Response 200

  • deletedtruerequired

Errors

  • FORBIDDEN 403
  • VALIDATION 400
  • TRANSLATION_NOT_FOUND 404

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"
Response
{
  "deleted": true
}

Publish a translation

POST/sites/{id}/translations/{entityType}/{entityId}/{locale}/publish
betacontent:editdefault (120/minute per token)

Put 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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • entityTypestring · pathrequired

    Translatable 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 · pathrequired

    Record id. Required except for global_sections and site_settings, whose id is the websiteId.

  • localestring · pathrequired

    BCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).

Response 200

  • translationobjectrequired
  • translation.entityTypestringrequired
  • translation.entityIdstringrequired
  • translation.localestringrequired
  • translation.status"DRAFT" | "PUBLISHED"required

    PUBLISHED = 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"required

    Most significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.

  • translation.fieldCountintegerrequired

    Number of translated field paths in the draft copy.

  • translation.sourceHashstringrequired

    Source fingerprint the draft copy was written against.

  • translation.updatedAtstringrequired
  • translation.publishedAtstring | nullrequired
  • translation.fieldDataobjectrequired

    Draft copy: field path → translated text.

  • translation.publishedFieldDataobject | nullrequired

    Live copy (null until published).

  • translation.fieldMetaobjectrequired
  • translation.slugstring | nullrequired
  • translation.publishedSlugstring | nullrequired

Errors

  • FORBIDDEN 403
  • VALIDATION 400
  • TRANSLATION_NOT_FOUND 404

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 '{}'
Response
{
  "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

POST/sites/{id}/translations/{entityType}/{entityId}/{locale}/translate
betacontent:editheavy (20/minute per token, plus 20/minute per site)

Machine-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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • entityTypestring · pathrequired

    Translatable 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 · pathrequired

    Record id. Required except for global_sections and site_settings, whose id is the websiteId.

  • localestring · pathrequired

    BCP 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"required

    skipped = already up to date; partial = some fields failed (failedPaths), retry later.

  • failedPathsstring[]required
  • translationobject | nullrequired
  • translation.entityTypestringrequired
  • translation.entityIdstringrequired
  • translation.localestringrequired
  • translation.status"DRAFT" | "PUBLISHED"required

    PUBLISHED = 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"required

    Most significant author across fields: HUMAN > SEO_FIX > AGENT > EXTERNAL > MACHINE.

  • translation.fieldCountintegerrequired

    Number of translated field paths in the draft copy.

  • translation.sourceHashstringrequired

    Source fingerprint the draft copy was written against.

  • translation.updatedAtstringrequired
  • translation.publishedAtstring | nullrequired
  • translation.fieldDataobjectrequired

    Draft copy: field path → translated text.

  • translation.publishedFieldDataobject | nullrequired

    Live copy (null until published).

  • translation.fieldMetaobjectrequired
  • translation.slugstring | nullrequired
  • translation.publishedSlugstring | nullrequired

Errors

  • FORBIDDEN 403
  • VALIDATION 400
  • ENTITY_NOT_FOUND 404
  • LOCALE_NOT_FOUND 404
  • LOCALE_IS_PRIMARY 422
  • DAILY_LIMIT_REACHED 429

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"}'
Response
{
  "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 jobs

GET/sites/{id}/translation-jobs
betaconsole:viewdefault (120/minute per token)

List the current (running or most recent) full-site translation job of each locale, with progress and failed records. Errors: FORBIDDEN.

Parameters

  • idstring · pathrequired

    Website id. Get it from sites.list or from sites.create.

Response 200

  • dataobject[]required
  • data[].idstringrequired
  • data[].localestringrequired
  • data[].kind"FULL" | "PRIMARY_SWAP"required
  • data[].status"PENDING" | "RUNNING" | "PAUSED" | "SUCCEEDED" | "FAILED"required

    PAUSED = waiting for the daily translation limit or the model provider; resumes on its own at resumeAt.

  • data[].stagestring | nullrequired
  • data[].progressobject | nullrequired
  • data[].progress.stagestringrequired
  • data[].progress.totalintegerrequired

    Records to translate.

  • data[].progress.doneintegerrequired
  • data[].progress.failedintegerrequired
  • data[].progress.skippedintegerrequired

    Records already up to date.

  • data[].progress.callsintegerrequired

    Model calls used.

  • data[].progress.failedEntitiesobject[]required

    At most 50, most recent first.

  • data[].errorCodestring | nullrequired

    CANCELLED when cancelled; otherwise the failure reason.

  • data[].resumeAtstring | nullrequired
  • data[].createdAtstringrequired
  • data[].completedAtstring | nullrequired
  • hasMorebooleanrequired

    True when another page exists; pass nextCursor to fetch it.

  • nextCursorstring | nullrequired

    Cursor for the next page, or null on the last page.

Errors

  • FORBIDDEN 403

MCP tool localization_jobs_list

curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/translation-jobs" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
Response
{
  "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
}

Retry a job

POST/sites/{id}/translation-jobs
betasite:manage_settingsdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

Request body

  • localestringrequired

    BCP 47 locale code exactly as listed by localization.locales.list (e.g. en-US, ja, zh-Hant).

Response 201

  • jobIdstringrequired
  • alreadyRunningbooleanrequired

Errors

  • FORBIDDEN 403
  • LOCALE_NOT_FOUND 404
  • UNSUPPORTED_LOCALE 400

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"}'
Response
{
  "jobId": "ltj_example01",
  "alreadyRunning": false
}

Retrieve a job

GET/sites/{id}/translation-jobs/{jobId}
betaconsole:viewdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • jobIdstring · pathrequired

    Job id from localization.jobs.list or localization.jobs.retry.

Response 200

  • jobobjectrequired
  • job.idstringrequired
  • job.localestringrequired
  • job.kind"FULL" | "PRIMARY_SWAP"required
  • job.status"PENDING" | "RUNNING" | "PAUSED" | "SUCCEEDED" | "FAILED"required

    PAUSED = waiting for the daily translation limit or the model provider; resumes on its own at resumeAt.

  • job.stagestring | nullrequired
  • job.progressobject | nullrequired
  • job.progress.stagestringrequired
  • job.progress.totalintegerrequired

    Records to translate.

  • job.progress.doneintegerrequired
  • job.progress.failedintegerrequired
  • job.progress.skippedintegerrequired

    Records already up to date.

  • job.progress.callsintegerrequired

    Model calls used.

  • job.progress.failedEntitiesobject[]required

    At most 50, most recent first.

  • job.errorCodestring | nullrequired

    CANCELLED when cancelled; otherwise the failure reason.

  • job.resumeAtstring | nullrequired
  • job.createdAtstringrequired
  • job.completedAtstring | nullrequired

Errors

  • FORBIDDEN 403
  • JOB_NOT_FOUND 404

MCP tool localization_jobs_get

curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/translation-jobs/{jobId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
Response
{
  "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

POST/sites/{id}/translation-jobs/{jobId}/cancel
betasite:manage_settingsdefault (120/minute per token)

Cancel 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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • jobIdstring · pathrequired

Response 200

  • jobobjectrequired
  • job.idstringrequired
  • job.localestringrequired
  • job.kind"FULL" | "PRIMARY_SWAP"required
  • job.status"PENDING" | "RUNNING" | "PAUSED" | "SUCCEEDED" | "FAILED"required

    PAUSED = waiting for the daily translation limit or the model provider; resumes on its own at resumeAt.

  • job.stagestring | nullrequired
  • job.progressobject | nullrequired
  • job.progress.stagestringrequired
  • job.progress.totalintegerrequired

    Records to translate.

  • job.progress.doneintegerrequired
  • job.progress.failedintegerrequired
  • job.progress.skippedintegerrequired

    Records already up to date.

  • job.progress.callsintegerrequired

    Model calls used.

  • job.progress.failedEntitiesobject[]required

    At most 50, most recent first.

  • job.errorCodestring | nullrequired

    CANCELLED when cancelled; otherwise the failure reason.

  • job.resumeAtstring | nullrequired
  • job.createdAtstringrequired
  • job.completedAtstring | nullrequired

Errors

  • FORBIDDEN 403
  • JOB_NOT_FOUND 404
  • JOB_NOT_CANCELLABLE 409

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 '{}'
Response
{
  "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"
  }
}