> ## Documentation Index
> Fetch the complete documentation index at: https://weaverse.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Weaverse component configuration uses `settings`. `inspector` is deprecated; never generate it.
> Weaverse Hydrogen themes use React Router v7: import from `react-router`, never `react-router-dom` or `@remix-run/*`.
> Define component schemas with `createSchema()` from `@weaverse/hydrogen` (React Router themes) or `@weaverse/next` (Next.js themes).
> These docs cover @weaverse/hydrogen, @weaverse/react and @weaverse/core 5.x, and @weaverse/next 1.x. Do not use APIs from other major versions.
> For live project data (pages, theme settings, translations), use the Weaverse MCP server: https://docs.weaverse.io/developer-tools/weaverse-mcp

# Validate or apply a translation batch

> Submits a batch of 1-50 translation units read from `GET /projects/{projectId}/translation-units`. `{operation}` is one of:

- `validate`: runs every check (unit membership, value shape, placeholders, source and target generations, duplicates) and writes nothing. Returns `object: "translation_validation"` with a per-unit `outcome` of `apply` or `unchanged`.
- `apply`: runs the same checks under the project lock, then writes only `Translation` rows, atomically. Returns `object: "translation_apply"` with `applied`/`unchanged` counts and an exact primary `readback` of every submitted unit. The cache is invalidated once; if that fan-out fails the write still stands and the response carries `cacheDelivery: "degraded"`.

Any other `{operation}` value returns 404.

Each unit echoes `id`, `sourceGeneration` and `targetGeneration` exactly as listed (`targetGeneration: null` when no target was stored), plus `translatedValue`:

- Text units take a string (max 20,000 characters). `{{name}}` and `{name}` placeholders in the source must appear unchanged in the translation.
- Image units take `{ url, alt?, width?, height? }`; `url` must be `https://` or root-relative.
- `null` clears the overlay so the source shows through. An empty string is rejected. Clearing an absent target, or re-clearing a cleared one, reports `unchanged`.

Concurrency and atomicity:
- Any stale source or target refuses the whole batch with 409 `STALE_TRANSLATION` (`details.unitId`, `details.side`) and writes nothing. Re-read the inventory and resubmit.
- A unit outside this project's writable content for the locale returns 404 `TRANSLATION_UNIT_NOT_FOUND` (`details.unitId`) and writes nothing.
- A unit whose target already holds the requested value is `unchanged` regardless of the generation it carries, so an identical retry converges.
- On `apply`, every submitted unit (including ones the preflight found unchanged) is re-proven against locked source and target state; a unit that moved between preflight and lock fails the whole batch.
- Under the same lock, `apply` re-proves each unit's stored-schema approval (the field must still be a declared text input, or image input for an image unit, of its component type; otherwise 409) and the target's authority (the locale must still resolve to the same configured, non-default language; otherwise 400 or 409). Nothing is written in either case.

Authentication: accepts shop API keys, scoped API keys and delegated MCP OAuth tokens that hold the scope, and project-bound translation agent tokens (`wv_agent_…`, Authorization header only). Agent tokens are accepted only on the translation-unit routes.

Required scope: `translations:write`



## OpenAPI

````yaml https://studio.weaverse.io/api/v1/content/openapi.json post /projects/{projectId}/translations/{operation}
openapi: 3.1.0
info:
  title: Weaverse Content API
  version: 1.0.0
  description: >-
    Authenticated REST API for querying Weaverse-managed content (projects,
    pages, theme settings, languages) and for reading and writing per-locale
    translations.
servers:
  - url: https://studio.weaverse.io/api/v1/content
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Projects
    description: List, read, rename, create, copy and delete projects.
  - name: Pages
    description: List, read, create, update and delete the pages of a project.
  - name: Drafts and publishing
    description: >-
      Edit page drafts, then publish, schedule, restore and manage page
      versions.
  - name: Global sections
    description: Read, edit and publish sections shared across pages.
  - name: Page settings
    description: Read and edit page-level settings such as SEO metadata.
  - name: Assignments
    description: Map Shopify resources and routes to Weaverse pages and templates.
  - name: Theme settings
    description: Read and update a project's theme settings.
  - name: Languages and localization
    description: List project languages and manage locales, static text and glossary.
  - name: Translations
    description: >-
      Read and write per-locale translations, built for external translation
      agents.
  - name: Project structure
    description: Manage project variants and the project hierarchy.
  - name: Backups and portability
    description: Back up, restore, export and import project content.
  - name: Previews and hostnames
    description: Create preview links and manage the hostnames a project accepts.
  - name: Specification
    description: This OpenAPI document.
paths:
  /projects/{projectId}/translations/{operation}:
    summary: '#2873 validate or apply a translation batch (translations:write)'
    description: >-
      validate: every check (membership, value shape, placeholders, source and
      target generations, duplicates), zero writes, per-unit outcome. apply: the
      same checks under the project lock, then writes only Translation rows
      atomically. Any stale source or target refuses the whole batch with
      STALE_TRANSLATION 409 and writes nothing; an identical retry converges to
      unchanged; returns exact primary readback and invalidates once. Every
      submitted unit, including ones the preflight found unchanged, is re-proven
      against locked source and target state; a unit that moved between
      preflight and lock fails the whole batch. Under the same lock apply also
      re-proves each unit's stored-schema approval (the field must still be a
      declared text input, or image input for an image unit, of its component
      type; otherwise 409 and nothing written) and the target's authority (the
      locale must still resolve to the same configured, non-default language;
      otherwise 400/409 and nothing written). translatedValue null clears: an
      absent target stays absent and an existing clear stays a clear, both
      reported unchanged.
    parameters:
      - name: projectId
        in: path
        required: true
        schema:
          type: string
      - name: operation
        in: path
        required: true
        schema:
          type: string
          enum:
            - validate
            - apply
    post:
      tags:
        - Translations
      summary: Validate or apply a translation batch
      description: >-
        Submits a batch of 1-50 translation units read from `GET
        /projects/{projectId}/translation-units`. `{operation}` is one of:


        - `validate`: runs every check (unit membership, value shape,
        placeholders, source and target generations, duplicates) and writes
        nothing. Returns `object: "translation_validation"` with a per-unit
        `outcome` of `apply` or `unchanged`.

        - `apply`: runs the same checks under the project lock, then writes only
        `Translation` rows, atomically. Returns `object: "translation_apply"`
        with `applied`/`unchanged` counts and an exact primary `readback` of
        every submitted unit. The cache is invalidated once; if that fan-out
        fails the write still stands and the response carries `cacheDelivery:
        "degraded"`.


        Any other `{operation}` value returns 404.


        Each unit echoes `id`, `sourceGeneration` and `targetGeneration` exactly
        as listed (`targetGeneration: null` when no target was stored), plus
        `translatedValue`:


        - Text units take a string (max 20,000 characters). `{{name}}` and
        `{name}` placeholders in the source must appear unchanged in the
        translation.

        - Image units take `{ url, alt?, width?, height? }`; `url` must be
        `https://` or root-relative.

        - `null` clears the overlay so the source shows through. An empty string
        is rejected. Clearing an absent target, or re-clearing a cleared one,
        reports `unchanged`.


        Concurrency and atomicity:

        - Any stale source or target refuses the whole batch with 409
        `STALE_TRANSLATION` (`details.unitId`, `details.side`) and writes
        nothing. Re-read the inventory and resubmit.

        - A unit outside this project's writable content for the locale returns
        404 `TRANSLATION_UNIT_NOT_FOUND` (`details.unitId`) and writes nothing.

        - A unit whose target already holds the requested value is `unchanged`
        regardless of the generation it carries, so an identical retry
        converges.

        - On `apply`, every submitted unit (including ones the preflight found
        unchanged) is re-proven against locked source and target state; a unit
        that moved between preflight and lock fails the whole batch.

        - Under the same lock, `apply` re-proves each unit's stored-schema
        approval (the field must still be a declared text input, or image input
        for an image unit, of its component type; otherwise 409) and the
        target's authority (the locale must still resolve to the same
        configured, non-default language; otherwise 400 or 409). Nothing is
        written in either case.


        Authentication: accepts shop API keys, scoped API keys and delegated MCP
        OAuth tokens that hold the scope, and project-bound translation agent
        tokens (`wv_agent_…`, Authorization header only). Agent tokens are
        accepted only on the translation-unit routes.


        Required scope: `translations:write`
      operationId: translationsOperation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TranslationUnitsBatch'
            examples:
              text:
                summary: Translate a text unit
                value:
                  targetLocale: fr-fr
                  units:
                    - id: Y2x6OGszZDJlMDAwOHBvaXU3eXRyNmV3cQBkYXRhLmJ1dHRvblRleHQ
                      sourceGeneration: sha256:i5yxt6P8d6YAZqFxmfpJwVd1EG3A-S_s04MP3XTKlHs
                      targetGeneration: null
                      translatedValue: Acheter maintenant
              image:
                summary: Set a per-locale image
                value:
                  targetLocale: fr-fr
                  units:
                    - id: Y2x6OGszYzlkMDAwN3p4Y3Y4Ym5tMXF3ZQBkYXRhLmltYWdl
                      sourceGeneration: sha256:axHOW2r5Tdlzv_3ImeCIvAI67R7sgUIlyLW9iArkGVY
                      targetGeneration: null
                      translatedValue:
                        url: >-
                          https://cdn.shopify.com/s/files/1/0838/0052/3057/files/summer-hero-fr.jpg
                        alt: Héros d'été
                        width: 1920
                        height: 1080
              clear:
                summary: Clear a translation
                value:
                  targetLocale: fr-fr
                  units:
                    - id: Y2x6OGszYzlkMDAwN3p4Y3Y4Ym5tMXF3ZQBkYXRhLmhlYWRpbmc
                      sourceGeneration: sha256:aNCJfb2kY7k558Hq_scUZeouHvrUoY9eaCam_HIB0hk
                      targetGeneration: sha256:zzj5orBWLJCO5eaC_y6jzBO285hKHUQCL5G_-PIhc3A
                      translatedValue: null
      responses:
        '200':
          description: translation_validation or translation_apply result
          content:
            application/json:
              schema:
                type: object
              example:
                object: translation_apply
                projectId: clz8k2m4n0001qwer7tyu8iop
                targetLocale: fr-fr
                requested: 1
                applied: 1
                unchanged: 0
                readback:
                  - id: Y2x6OGszZDJlMDAwOHBvaXU3eXRyNmV3cQBkYXRhLmJ1dHRvblRleHQ
                    status: applied
                    value: Acheter maintenant
                    generation: sha256:jrrmwDqYx6bx8u4zNin5AXBzTiYGEzO8XIsX0eh8SYQ
        '400':
          description: Invalid batch, value or placeholders (nothing written)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Foreign project or missing scope (INSUFFICIENT_SCOPE)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: >-
            Unknown {operation} (only validate and apply exist), or
            TRANSLATION_UNIT_NOT_FOUND: a unit outside this project (nothing
            written)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            STALE_TRANSLATION: a source or target changed since read (nothing
            written)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                object: error
                code: STALE_TRANSLATION
                message: >-
                  The target of unit
                  Y2x6OGszZDJlMDAwOHBvaXU3eXRyNmV3cQBkYXRhLmJ1dHRvblRleHQ
                  changed since it was read; nothing was written. Re-read the
                  inventory and retry.
                status: 409
                details:
                  unitId: Y2x6OGszZDJlMDAwOHBvaXU3eXRyNmV3cQBkYXRhLmJ1dHRvblRleHQ
                  side: target
components:
  schemas:
    TranslationUnitsBatch:
      type: object
      required:
        - targetLocale
        - units
      properties:
        targetLocale:
          type: string
        units:
          type: array
          minItems: 1
          maxItems: 50
          items:
            type: object
            required:
              - id
              - sourceGeneration
              - targetGeneration
              - translatedValue
            properties:
              id:
                type: string
              sourceGeneration:
                type: string
              targetGeneration:
                type:
                  - string
                  - 'null'
                description: null = no stored target when read
              translatedValue:
                description: >-
                  null clears the overlay; empty string is rejected. Image units
                  take an object.
                oneOf:
                  - type: string
                  - type: 'null'
                  - type: object
                    required:
                      - url
                    properties:
                      url:
                        type: string
                        description: https:// or root-relative
                      alt:
                        type: string
                      width:
                        type: integer
                      height:
                        type: integer
    Error:
      type: object
      required:
        - object
        - code
        - message
        - status
      properties:
        object:
          type: string
          enum:
            - error
        code:
          type: string
          enum:
            - UNAUTHORIZED
            - FORBIDDEN
            - INSUFFICIENT_SCOPE
            - PROJECT_NOT_FOUND
            - PAGE_NOT_FOUND
            - STALE_PAGE
            - STALE_PROJECT
            - STALE_TRANSLATION
            - TRANSLATION_UNIT_NOT_FOUND
            - SERVICE_UNAVAILABLE
            - CONFLICT
            - PRECONDITION_REQUIRED
            - INVALID_PARAMS
            - INTERNAL_ERROR
        message:
          type: string
        status:
          type: integer
        details:
          description: >-
            Optional machine-readable context, for example the clashing handles
            of a 409 CONFLICT.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: WEAVERSE_API_KEY
      description: >-
        Bearer authentication: a shop API key (WEAVERSE_API_KEY), a scoped API
        key, a delegated MCP OAuth token, or (translation-unit routes only) a
        project-bound translation agent token. Scopes: content:read — Read
        projects, pages, theme settings, versions, drafts and translation units.
        content:write — Save drafts and make non-destructive edits (page items,
        metadata, SEO, assignments, languages, glossary). Never publishes or
        deletes. content:publish — Publish drafts, schedule/reschedule/cancel
        publications, restore versions, publish global sections. content:delete
        — Delete pages, global sections, languages, static keys, override
        reverts; required with project:manage for project deletion.
        project:manage — Create/copy/delete projects, settings, variants,
        hierarchy, backups, restore-as-new, previews, hostname policy.
        translations:read — Read translation units only (no general content
        access). translations:write — Apply translation units only; never
        mutates page content. A legacy unscoped API key keeps
        read/write/translation baseline access and every pre-scope endpoint, but
        never gains publish/delete/project-manage implicitly.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.