> ## 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

# Save a page draft

> Saves a new draft head for the page from a full item list. Live content is never changed and no cache is invalidated. Publishing is a separate call that needs `content:publish`.

**Workflow.** Drafts and publishing work together like this:

1. `GET /projects/{projectId}/page-drafts/{pageId}` reads the editable content and its revision tokens.
2. `PUT` on the same path saves a new draft head (`versionId`). Live content is not touched.
3. Publish the draft with `POST /publish-versions/{versionId}`, or schedule it with `POST /versions/{versionId}/schedule`.
4. Manage history and the schedule with `GET /projects/{projectId}/versions`, `/versions/{versionId}` (read, reschedule, replace content, cancel), `publish-now` and `restore`.

**Body**

- `items`: the complete item graph, 1 to 200 items. Each item needs `id` and `type`; `children` are `{ "id": ... }` references and `data` is an object. Every child must reference an item in the list, ids must be unique, and every item must be reachable from the root. A malformed body is 400; a broken graph (duplicate ids, dangling child, orphaned items) is 404 `PAGE_NOT_FOUND`. Nothing is saved in either case.
- The root is the item whose id matches the page's current `rootId`; otherwise the first item.
- `name` (optional): renames the draft. Defaults to the live page name.

**Revision fence.** All three tokens are required and must be copied exactly from the GET response:

- `expectedHeadVersionId`: GET's `headVersionId` (`null` means "no draft yet").
- `expectedPageUpdatedAt`: GET's `pageUpdatedAt`, the exact ISO string or `null`.
- `expectedProjectUpdatedAt`: GET's `projectUpdatedAt`, the exact ISO string or `null`.

`null` is a real baseline for a legacy row and only matches a row that is still `null`. If the draft head, the live page or the project moved since your GET, the save is refused with 409 `STALE_PAGE` and nothing is written. Re-read with GET, reapply your edit and save again.

**Retries.** Retrying a save that already committed returns the same head instead of a conflict.

**Response.** `versionId` is the new draft head. Pass it to `POST /publish-versions/{versionId}` or `POST /versions/{versionId}/schedule`, and send it as `expectedHeadVersionId` on your next save or discard.

Required scope: `content:write`



## OpenAPI

````yaml https://studio.weaverse.io/api/v1/content/openapi.json put /projects/{projectId}/page-drafts/{pageId}
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}/page-drafts/{pageId}:
    summary: Read the editable content / save or discard a page draft
    description: >-
      GET returns the current draft head when one exists, else live content,
      plus the revision tokens a save must replay: headVersionId, pageUpdatedAt
      and projectUpdatedAt. With a draft head, the page and project tokens are
      the live revisions that draft was built on (not the current live ones), so
      a save is refused once live moved past it. PUT saves a draft from a full
      item list (each item: id, type, optional children id refs and record data;
      malformed graphs are 400) (content:write; never touches live). DELETE
      discards the draft head by exact CAS. Addressing is by pageId — no
      locale/handle fallback, so a draft can never land on another assignment.
      projectUpdatedAt is null for a legacy project whose revision was never
      set; the draft save then records that null base (GET never repairs it).
    parameters:
      - name: projectId
        in: path
        required: true
        schema:
          type: string
      - name: pageId
        in: path
        required: true
        schema:
          type: string
    put:
      tags:
        - Drafts and publishing
      summary: Save a page draft
      description: >-
        Saves a new draft head for the page from a full item list. Live content
        is never changed and no cache is invalidated. Publishing is a separate
        call that needs `content:publish`.


        **Workflow.** Drafts and publishing work together like this:


        1. `GET /projects/{projectId}/page-drafts/{pageId}` reads the editable
        content and its revision tokens.

        2. `PUT` on the same path saves a new draft head (`versionId`). Live
        content is not touched.

        3. Publish the draft with `POST /publish-versions/{versionId}`, or
        schedule it with `POST /versions/{versionId}/schedule`.

        4. Manage history and the schedule with `GET
        /projects/{projectId}/versions`, `/versions/{versionId}` (read,
        reschedule, replace content, cancel), `publish-now` and `restore`.


        **Body**


        - `items`: the complete item graph, 1 to 200 items. Each item needs `id`
        and `type`; `children` are `{ "id": ... }` references and `data` is an
        object. Every child must reference an item in the list, ids must be
        unique, and every item must be reachable from the root. A malformed body
        is 400; a broken graph (duplicate ids, dangling child, orphaned items)
        is 404 `PAGE_NOT_FOUND`. Nothing is saved in either case.

        - The root is the item whose id matches the page's current `rootId`;
        otherwise the first item.

        - `name` (optional): renames the draft. Defaults to the live page name.


        **Revision fence.** All three tokens are required and must be copied
        exactly from the GET response:


        - `expectedHeadVersionId`: GET's `headVersionId` (`null` means "no draft
        yet").

        - `expectedPageUpdatedAt`: GET's `pageUpdatedAt`, the exact ISO string
        or `null`.

        - `expectedProjectUpdatedAt`: GET's `projectUpdatedAt`, the exact ISO
        string or `null`.


        `null` is a real baseline for a legacy row and only matches a row that
        is still `null`. If the draft head, the live page or the project moved
        since your GET, the save is refused with 409 `STALE_PAGE` and nothing is
        written. Re-read with GET, reapply your edit and save again.


        **Retries.** Retrying a save that already committed returns the same
        head instead of a conflict.


        **Response.** `versionId` is the new draft head. Pass it to `POST
        /publish-versions/{versionId}` or `POST /versions/{versionId}/schedule`,
        and send it as `expectedHeadVersionId` on your next save or discard.


        Required scope: `content:write`
      operationId: savePageDraft
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PageDraftSave'
            example:
              items:
                - id: root-1
                  type: main
                  children:
                    - id: hero-1
                  data: {}
                - id: hero-1
                  type: hero-image
                  data:
                    heading: Summer collection
                    height: large
              name: Home
              expectedHeadVersionId: null
              expectedPageUpdatedAt: '2026-10-08T10:00:00.000Z'
              expectedProjectUpdatedAt: '2026-10-08T09:00:00.000Z'
      responses:
        '200':
          description: New head version id
          content:
            application/json:
              schema:
                type: object
              example:
                object: page_draft_saved
                pageId: clz7pg9k2m4n6p8r0t2v4x6z
                projectId: clz7prj0a1b2c3d4e5f6g7h8
                versionId: clz9drf2h4j6l8n0p2r4t6v8
                updatedAt: '2026-10-08T10:12:00.000Z'
        '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: Not found (never discloses other tenants)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            STALE_PAGE — the draft head, live page or project moved since GET;
            nothing was saved, re-read
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                object: error
                code: STALE_PAGE
                message: >-
                  The live page or project changed since it was read; nothing
                  was saved — re-read and retry
                status: 409
components:
  schemas:
    PageDraftSave:
      type: object
      required:
        - items
        - expectedHeadVersionId
        - expectedPageUpdatedAt
        - expectedProjectUpdatedAt
      properties:
        items:
          type: array
          maxItems: 200
          items:
            $ref: '#/components/schemas/DraftItem'
        name:
          type: string
        expectedHeadVersionId:
          type:
            - string
            - 'null'
          description: The head this edit started from; null means "no draft yet"
        expectedPageUpdatedAt:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            GET `pageUpdatedAt` exactly as GET returned it (null = a legacy row
            with no revision). The save is refused with 409 STALE_PAGE when it
            no longer matches live.
        expectedProjectUpdatedAt:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            GET `projectUpdatedAt` exactly as GET returned it (null = a legacy
            row with no revision). The save is refused with 409 STALE_PAGE when
            it no longer matches live.
    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.
    DraftItem:
      type: object
      required:
        - id
      properties:
        id:
          type: string
        type:
          type: string
        data:
          type: object
          additionalProperties: true
        children:
          type: array
          items:
            type: object
            required:
              - id
            properties:
              id:
                type: string
  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.