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

# Edit project page content

> Changes what is on a customer's project's pages with a batch of design operations, as `POST /v2/designs/{id}/ops` does for a design: all or nothing, with `dryRun` to try it first. The project's PDF and image exports follow the edit; its order state, product and `userData` don't change.

A submitted project (added to the cart or ordered) answers 409 `project_submitted` unless the batch sends `allowSubmitted: true`: an edit changes what is printed for that order, so check with the merchant first. A mail-merge project answers 409 `project_mail_merge`: it prints from its mail-merge pages, so open it in the editor with a project URL instead. Accounts on the classic editor answer 409 `editor_not_supported`.



## OpenAPI

````yaml openapi.json POST /v2/projects/{id}/ops
openapi: 3.1.0
info:
  title: Print.App API
  version: 2.1.0
  description: >-
    Server-to-server API for Print.App integrations: designs and their page
    content, folders, files, fonts, palettes, product assignments, projects and
    editor links. Authenticate every request with a secret key: `Authorization:
    Bearer sk_…`. Never call it from a browser.
servers:
  - url: https://api.print.app
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Account
    description: The account a secret key belongs to.
  - name: Designs
    description: >-
      The designs (templates) your customers start from: create them at a size,
      manage their pages, and place text, images and shapes on them.
  - name: Design folders
    description: >-
      Folders that organise your designs. A design can sit in more than one
      folder.
  - name: Files
    description: >-
      Your own images and graphics, used in designs and offered to customers in
      the editor.
  - name: Graphic folders
    description: >-
      Folders of files, shown in the editor under Pictures, Backgrounds,
      Textures or Frames.
  - name: Fonts
    description: >-
      The fonts your designs and customers can use: the built-in set, imported
      Google fonts and fonts you upload.
  - name: Palettes
    description: Colour palettes offered in the editor.
  - name: Products
    description: Which designs open for each product (and variant) in your store.
  - name: Projects
    description: Your customers' saved designs.
  - name: Links
    description: >-
      Project URLs (the dashboard's Create new Project URL): shareable links
      that open a design or project in the editor at create.print.app, to share
      with customers or to test a design.
  - name: Connectors
    description: >-
      Links between the options on a store's product page and the editor: the
      choice a customer makes shows the right pages, opens the right design, or
      sets the size, orientation or colour. Read a product page for what a
      connector can point at, and test one against a design before customers
      meet it.
  - name: Resource profiles
    description: >-
      Limits on the fonts, colour palettes and graphic folders the editor
      offers, for the designs that use them.
  - name: PDF profiles
    description: >-
      The settings print PDFs are made with: colour conversion, PDF standard,
      cut lines, image resolution, fonts.
  - name: 3D scenes
    description: >-
      The 3D scenes the editor previews designs on: models with print areas,
      folds, flipbooks and sheets, yours and the Print.App library's. A design
      picks one with `sceneId`.
  - name: Library
    description: >-
      The Print.App library, the store catalogue the dashboard's Import Designs
      and Import from Library open: ready-made designs, and themed kits of
      graphics. Find what suits a product, then bring it into your account: a
      design as your own copy, a kit as a folder that follows it, single
      graphics into a folder.
  - name: Settings
    description: >-
      The account's editor and product page settings: how the editor opens and
      behaves, and the selectors Print.App uses on your product pages. Settings
      that run code in your store, send data elsewhere or delete projects are
      kept to the dashboard.
paths:
  /v2/projects/{id}/ops:
    post:
      tags:
        - Projects
      summary: Edit project page content
      description: >-
        Changes what is on a customer's project's pages with a batch of design
        operations, as `POST /v2/designs/{id}/ops` does for a design: all or
        nothing, with `dryRun` to try it first. The project's PDF and image
        exports follow the edit; its order state, product and `userData` don't
        change.


        A submitted project (added to the cart or ordered) answers 409
        `project_submitted` unless the batch sends `allowSubmitted: true`: an
        edit changes what is printed for that order, so check with the merchant
        first. A mail-merge project answers 409 `project_mail_merge`: it prints
        from its mail-merge pages, so open it in the editor with a project URL
        instead. Accounts on the classic editor answer 409
        `editor_not_supported`.
      operationId: applyProjectOps
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The `projectId`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectOpsBatch'
            example:
              ops:
                - op: item.update
                  target: itm_7f3b
                  content: Ada King
      responses:
        '200':
          description: OK.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectOpsResult'
        '400':
          description: >-
            A parameter or body field is malformed: `param` names it and
            `details` lists every problem found.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: true
                code: invalid_request
                message: width is required.
                param: width
                requestId: FZn-uhymjoEEJow=
        '401':
          description: Missing, unknown, revoked or expired secret key.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: true
                code: unauthorized
                message: >-
                  Missing or invalid secret key. Send it as "Authorization:
                  Bearer sk_…".
                requestId: FZn-uhymjoEEJow=
        '403':
          description: The account is suspended for an unpaid invoice.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: true
                code: account_suspended
                message: >-
                  This account is suspended. Settle the outstanding invoice in
                  the Print.App dashboard.
                requestId: FZn-uhymjoEEJow=
        '404':
          description: >-
            Not found in your account. Another account's resources answer 404
            too.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: true
                code: not_found
                message: Not found.
                requestId: FZn-uhymjoEEJow=
        '409':
          description: >-
            The request conflicts with the current state; `code` says how
            (`folder_not_empty`, `builtin_folder`, `builtin_palette`,
            `editor_not_supported`, `design_changed`, `idempotency_key_reused`,
            `idempotency_in_progress`).
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: true
                code: folder_not_empty
                message: >-
                  The folder still holds designs. Remove them from it (or delete
                  them) first.
                requestId: FZn-uhymjoEEJow=
        '415':
          description: The body is not JSON.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: true
                code: unsupported_media_type
                message: >-
                  Send the request body as JSON, with "Content-Type:
                  application/json".
                requestId: FZn-uhymjoEEJow=
        '422':
          description: >-
            An operation failed while the batch ran. Nothing was saved;
            `details` holds every operation's result.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: true
                code: ops_failed
                message: >-
                  ops[1] (item.update) failed: no item 'logo'. Nothing was
                  saved.
                param: ops
                requestId: FZn-uhymjoEEJow=
                details:
                  - index: 0
                    op: text.add
                    ok: true
                    id: itm_7f3b
                    ref: name
                  - index: 1
                    op: item.update
                    ok: false
                    error: no item 'logo'
        '429':
          description: >-
            Rate limited by the API gateway. Back off and retry. This response
            comes from the gateway, so its body is `{ "message": "Too Many
            Requests" }`, not the standard error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GatewayError'
              example:
                message: Too Many Requests
        '500':
          description: >-
            Something failed on our side. Retry; quote the requestId to support
            if it persists.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: true
                code: internal_error
                message: >-
                  Something went wrong on our side. Retry the request; if it
                  keeps failing, contact support with the requestId.
                requestId: FZn-uhymjoEEJow=
components:
  schemas:
    ProjectOpsBatch:
      title: ProjectOpsBatch
      description: >-
        A batch of design operations on a customer's project, run in order as
        one change: either every operation succeeds and the project is saved, or
        nothing is. The same operations as a design's (the DesignOp schemas).
      type: object
      additionalProperties: false
      required:
        - ops
      properties:
        units:
          type: string
          enum:
            - mm
            - cm
            - in
            - pt
            - px
          description: >-
            The unit of every box and length in the batch. Default: the
            project's unit (mm for projects measured in ft). Text sizes are
            always in points.
        page:
          oneOf:
            - type: integer
              minimum: 1
              maximum: 500
            - type: string
              minLength: 1
              maxLength: 40
          description: >-
            The page item operations go on: a page number from 1, or a page's
            ref. An operation's own `page` wins. Default 1.
        ops:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: object
            required:
              - op
            properties:
              op:
                type: string
          description: >-
            The operations, run in order. Items added in the batch can be
            targeted by later operations through their `ref`.
        dryRun:
          type: boolean
          default: false
          description: >-
            Run the batch and report every operation's result and the resulting
            content, without saving anything.
        allowSubmitted:
          type: boolean
          default: false
          description: >-
            Edit a submitted project (added to the cart or ordered), which
            changes what is printed for that order. Without it such a project
            answers 409 project_submitted. Not needed for a dry run.
      examples:
        - ops:
            - op: item.update
              target: itm_7f3b
              content: Ada King
    ProjectOpsResult:
      title: ProjectOpsResult
      description: >-
        The outcome of a batch that ran on a project: every operation's result,
        the project as saved (unchanged on a dry run), and its content now.
      type: object
      required:
        - object
        - dryRun
        - results
        - project
        - content
      properties:
        object:
          type: string
          const: ops_result
        dryRun:
          type: boolean
        results:
          type: array
          items:
            $ref: '#/components/schemas/OpResult'
        project:
          $ref: '#/components/schemas/Project'
        content:
          $ref: '#/components/schemas/ProjectContent'
    Error:
      title: Error
      description: >-
        Every error the API itself returns. Match on `code`; `message` is for
        people and may be reworded.
      type: object
      required:
        - error
        - code
        - message
        - requestId
      properties:
        error:
          type: boolean
          const: true
        code:
          type: string
          enum:
            - unauthorized
            - account_suspended
            - invalid_request
            - not_found
            - route_not_found
            - idempotency_key_reused
            - idempotency_in_progress
            - folder_not_empty
            - builtin_folder
            - builtin_palette
            - editor_not_supported
            - design_changed
            - payload_too_large
            - unsupported_media_type
            - ops_failed
            - internal_error
            - service_unavailable
          description: >-
            `unauthorized` (401): missing, unknown, revoked or expired secret
            key. `account_suspended` (403): the account has an unpaid invoice.
            `invalid_request` (400): a parameter or body field is malformed —
            see `param` and `details`. `not_found` (404): no such resource in
            your account. `route_not_found` (404): no such endpoint.
            `idempotency_key_reused` (409): the Idempotency-Key was used for a
            different request. `idempotency_in_progress` (409): the first
            request with that key is still running. `folder_not_empty` (409):
            the folder still holds designs or folders. `builtin_folder`,
            `builtin_palette` (409): Print.App's own folders and palettes cannot
            be changed. `editor_not_supported` (409): page content can only be
            edited on accounts that use the new editor. `design_changed` (409):
            the design was saved elsewhere while the batch ran; read it again
            and retry. `payload_too_large` (413): a body or file over the limit.
            `unsupported_media_type` (415): send JSON. `ops_failed` (422): an
            operation in the batch failed, nothing was saved — see `details`.
            `internal_error` (500): retry; contact support with the `requestId`
            if it persists. `service_unavailable` (503): retry in a few seconds.
        message:
          type: string
        param:
          type: string
          description: The parameter or body field at fault, as a path (`pages[0].title`).
        details:
          type: array
          description: >-
            Every problem found, when there was more than one
            (`invalid_request`), or each operation's result (`ops_failed`).
          items:
            type: object
        requestId:
          type: string
          description: >-
            Also sent as the `x-request-id` header. Quote it when contacting
            support.
      examples:
        - error: true
          code: invalid_request
          message: limit must be an integer from 1 to 100.
          param: limit
          requestId: FZn-uhymjoEEJow=
    GatewayError:
      type: object
      properties:
        message:
          type: string
      description: Returned by the API gateway itself (429).
    OpResult:
      title: OpResult
      description: >-
        One operation's result. Also the items of an error's `details` for
        `invalid_request` and `ops_failed` on `POST /v2/designs/{id}/ops`.
      type: object
      required:
        - index
        - op
        - ok
      properties:
        index:
          type:
            - integer
            - 'null'
          description: >-
            The operation's position in `ops`; null for a problem with the batch
            itself.
        op:
          type:
            - string
            - 'null'
        ok:
          type: boolean
        id:
          type: string
          description: The id of the item it added or changed.
        pageId:
          type: string
          description: The id of the page it added.
        ref:
          type:
            - string
            - 'null'
        error:
          type:
            - string
            - 'null'
        path:
          type:
            - string
            - 'null'
          description: The part of the operation at fault.
        warnings:
          type: array
          items:
            type: string
    Project:
      title: Project
      description: A customer's saved design.
      type: object
      required:
        - object
        - id
        - title
        - status
        - product
        - userId
        - userData
        - designId
        - created
        - modified
        - pages
        - exports
      properties:
        object:
          type: string
          const: project
        id:
          type: string
          description: The `projectId` from the editor's `app:saved` event.
        title:
          type:
            - string
            - 'null'
        status:
          type: string
          enum:
            - draft
            - saved
            - submitted
          description: >-
            `submitted`: the customer finished the design (the editor's submit).
            `saved`: saved for later. `draft`: autosaved, not yet saved by the
            customer.
        product:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: >-
            Exactly the `product` object your page passed to `PrintAppClient`
            when the project was created — for example `{ id, ref }`. Use it to
            check the project was made for the product being ordered. It passes
            through the customer's browser, so treat it as the customer's claim
            about the page, not as proof.
        userId:
          type:
            - string
            - 'null'
          description: The `userId` your page passed to `PrintAppClient`, if any.
        userData:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: >-
            Customer details attached to the project by a store plugin or your
            page: `id`, `name`, `email`, `phone`, `info`.
          properties:
            id: {}
            name:
              type: string
            firstName:
              type: string
            lastName:
              type: string
            email:
              type: string
            phone:
              type: string
            info:
              type: string
        designId:
          type:
            - string
            - 'null'
          description: The design (template) the project was made from.
        created:
          type:
            - string
            - 'null'
          format: date-time
        modified:
          type:
            - string
            - 'null'
          format: date-time
          description: Last save.
        pages:
          type: array
          items:
            type: object
            required:
              - index
              - previewUrl
            properties:
              index:
                type: integer
                minimum: 0
              previewUrl:
                type:
                  - string
                  - 'null'
                description: A preview image of the page.
        exports:
          type: object
          required:
            - pdf
            - png
            - jpeg
            - tiff
          description: Print files, rendered on request from the project's current state.
          properties:
            pdf:
              type: string
            png:
              type: string
            jpeg:
              type: string
            tiff:
              type: string
      examples:
        - object: project
          id: project_2b6f1c9e-8d0a-4c3e-9f57-0c1d2e3f4a5b
          title: Business Card
          status: submitted
          product:
            id: sku-123
            ref: cart-line-7f3a
          userId: customer-1001
          userData:
            id: customer-1001
            name: Jane Doe
            email: jane@example.com
          designId: design_01J8Z9K2M3N4P5Q6R7S8T9V0W1
          created: '2026-10-17T09:12:03Z'
          modified: '2026-10-17T09:20:41Z'
          pages:
            - index: 0
              previewUrl: >-
                https://d1p81tn8mwpox2.cloudfront.net/?k=s3%3A%2F%2Fapp-print-cdn%2Fcontent_a1.png&size=1000
            - index: 1
              previewUrl: >-
                https://d1p81tn8mwpox2.cloudfront.net/?k=s3%3A%2F%2Fapp-print-cdn%2Fcontent_b2.png&size=1000
          exports:
            pdf: https://pdf.print.app/project_2b6f1c9e-8d0a-4c3e-9f57-0c1d2e3f4a5b
            png: https://png.print.app/project_2b6f1c9e-8d0a-4c3e-9f57-0c1d2e3f4a5b
            jpeg: >-
              https://jpeg.print.app/project_2b6f1c9e-8d0a-4c3e-9f57-0c1d2e3f4a5b
            tiff: >-
              https://tiff.print.app/project_2b6f1c9e-8d0a-4c3e-9f57-0c1d2e3f4a5b
    ProjectContent:
      title: ProjectContent
      description: >-
        What is on each page of a customer's project, measured as in
        DesignContent: boxes are `[x, y, width, height]` in `unit` from the
        top-left of the trim; text sizes are in points.
      type: object
      required:
        - object
        - projectId
        - unit
        - pages
      properties:
        object:
          type: string
          const: project_content
        projectId:
          type: string
        unit:
          type:
            - string
            - 'null'
        pages:
          type: array
          items:
            $ref: '#/components/schemas/ContentPage'
      examples:
        - object: project_content
          projectId: project_00507c69-a75c-45c9-875c-6e29a808b4af
          unit: mm
          pages:
            - index: 0
              id: page_5b1e
              title: Front
              width: 85
              height: 55
              bleed: 3
              safeMargin: 3
              items:
                - id: itm_7f3b
                  type: text
                  box:
                    - 6
                    - 22
                    - 73
                    - 10
                  text: Ada Lovelace
                  font: open sans
                  weight: 700
                  size: 14
                  align: left
                  color: '#1f2a44'
                  field:
                    title: Name
                  lock: field
    ContentPage:
      title: ContentPage
      description: 'One page of a design or project: its size and what is on it.'
      type: object
      required:
        - index
        - id
        - title
        - width
        - height
        - bleed
        - safeMargin
        - items
      properties:
        index:
          type: integer
          minimum: 0
        id:
          type:
            - string
            - 'null'
        title:
          type:
            - string
            - 'null'
        width:
          type:
            - number
            - 'null'
          description: Trim width.
        height:
          type:
            - number
            - 'null'
          description: Trim height.
        bleed:
          type:
            - number
            - 'null'
        safeMargin:
          type:
            - number
            - 'null'
          description: How far inside the trim the safe zone starts; null without one.
        safeArea:
          type: array
          items:
            type:
              - number
              - 'null'
          minItems: 4
          maxItems: 4
          description: >-
            The safe zone as [x, y, width, height] from the top-left of the
            trim: keep text and key artwork inside it.
        printArea:
          type: boolean
          description: >-
            The page is a garment mockup: boxes are measured from its print
            area.
        panels:
          type: array
          items:
            type: object
            required:
              - 'n'
              - box
            properties:
              'n':
                type: integer
              box:
                type:
                  - array
                  - 'null'
                items:
                  type:
                    - number
                    - 'null'
          description: The panels of a folded page.
        items:
          type: array
          items:
            $ref: '#/components/schemas/ContentItem'
    ContentItem:
      title: ContentItem
      description: >-
        One item on a page. Target it in `POST /v2/designs/{id}/ops` by `id`, or
        by `ref` when it has one.
      type: object
      required:
        - id
        - type
        - box
        - lock
      properties:
        id:
          type: string
        ref:
          type:
            - string
            - 'null'
          description: The name it was added with.
        type:
          type: string
          description: >-
            `text`, `image`, `rect`, `ellipse`, `path`, `group`, `tiles`, or the
            module type (`qrcode`, `calendar`, …).
        box:
          type:
            - array
            - 'null'
          items:
            type:
              - number
              - 'null'
          minItems: 4
          maxItems: 4
        text:
          type:
            - string
            - 'null'
        font:
          type:
            - string
            - 'null'
        weight:
          type:
            - number
            - 'null'
        size:
          type:
            - number
            - 'null'
          description: Font size in points.
        drawnSize:
          type:
            - number
            - 'null'
          description: The size the text is drawn at, when a fitting frame shrank it.
        align:
          type:
            - string
            - 'null'
        color:
          type:
            - string
            - 'null'
          description: Text colour.
        fill:
          type:
            - string
            - 'null'
          description: Shape fill.
        asset:
          type:
            - string
            - 'null'
          description: The file or library graphic it shows.
        module:
          type:
            - string
            - 'null'
        tiles:
          type:
            - number
            - 'null'
          description: How many tiles a pattern has.
        field:
          type: object
          required:
            - title
          properties:
            title:
              type:
                - string
                - 'null'
          description: Customers edit it in the form.
        photoFrame:
          type: boolean
        background:
          type: boolean
        overflow:
          type: boolean
          description: Its text does not fit its box.
        lock:
          type:
            - string
            - 'null'
          description: '`none`, `field`, `decor` or `hard`.'
  headers:
    RequestId:
      description: Identifies the request. Quote it when contacting support.
      schema:
        type: string
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        A secret key from Print.App admin → Domains → Secret keys, sent as
        `Authorization: Bearer sk_…`. Server-side only.

````

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