---
title: "Create-or-reuse the draft and replace app content"
method: PUT
path: "/api/v2/documents/{identifier}/draft/app"
tags: ["Documents"]
---

# Create-or-reuse the draft and replace app content

`PUT /api/v2/documents/{identifier}/draft/app`

**Alpha.** The app sub-resource may change shape without a deprecation cycle while apps mature. The document routes are stable.

Create (or reuse) the document's main draft and replace the app on it in one call — the app-side counterpart of `PATCH …/draft`'s create-or-reuse behavior, with the same body, validation, and gates as `PUT …/draft/{draftIdentifier}/app`. No auto-publish; the response carries the `draftIdentifier` for `POST …/draft/publish`. Removing the app has no create-or-reuse counterpart — `DELETE` stays draft-explicit.

Last-write-wins, like every app write in the UI: the body is applied as given, with no expected-version precondition.

`app` is a reserved literal under `…/draft/` — the router ranks it above the dynamic `{draftIdentifier}` segment. A draft named `app` would be unaddressable on the draft-scoped app routes; avoid it.

A document carries at most one of a dashboard or an app — never both; workbook-only is valid. The app HTML and settings live only at the app sub-resource routes; the document read carries an `app` slice pointing here, and the whole-document PATCH accepts that slice back only as it was read.

## Path parameters

- `identifier` string, required — Document identifier — either the URL slug (e.g. `abc123`) or the canonical workbook UUID.

## Request body

- DocumentsV2PutAppBody
  - `html` string, required — The complete app HTML document. Replaces the current HTML; capped at 2 MiB of UTF-8 (maxLength counts characters — the byte cap is what the server enforces). Every write appends an immutable revision.
  - `settings` object — When present, replaces the app settings; omitted fields take their locked-down defaults. When absent, the current settings are kept.
    - `allowClipboard` boolean
    - `allowDefaultMapProviders` boolean
    - `allowDownloads` boolean
    - `allowExternalNavigation` boolean
    - `allowInternalNavigation` boolean
    - `externalNavOpensInNewTab` boolean
    - `navAllowedDomains` string[]
    - `navAllowedDomainsEnabled` boolean
    - `safeDomains` string[]
    - `safeDomainsEnabled` boolean

## Response `200`

Draft created (or reused) and app written; the response carries the `draftIdentifier`. `warnings` names any resource hosts the app’s iframe CSP will block until an org admin allows them.

- DocumentsV2PutAppResponse
  - `description` string, nullable, required — Document description.
  - `draftIdentifier` string, required — Identifier of the draft the patch was applied to.
  - `identifier` string, required — Published document identifier the draft targets.
  - `name` string, required — Document name.
  - `app` DocumentsV2AppState, required
    - `settings` DocumentsV2AppSettings, required — The app's sandbox settings: capability toggles plus the safe-domain and navigation allowlists. A write replaces the whole object; omitted fields take their locked-down defaults. Host lists are normalized (deduped, invalid or Omni-owned hosts dropped) before they are stored.
      - `allowClipboard` boolean
      - `allowDefaultMapProviders` boolean
      - `allowDownloads` boolean
      - `allowExternalNavigation` boolean
      - `allowInternalNavigation` boolean
      - `externalNavOpensInNewTab` boolean
      - `navAllowedDomains` string[]
      - `navAllowedDomainsEnabled` boolean
      - `safeDomains` string[]
      - `safeDomainsEnabled` boolean
  - `warnings` string[] — Non-blocking warnings — present only when there are any. Currently: external resource hosts the app's iframe CSP will block until an org admin allows them. The write itself succeeded.

## Other responses

- `400` — Invalid request body (unknown field, empty or oversized `html` — the cap is 2 MiB).
- `401` — Authentication required.
- `403` — Insufficient permissions: no write access to the document, apps not enabled for the organization, or (user-scoped keys) the role does not allow creating apps.
- `404` — Document not found.
- `405` — Method not allowed.
- `409` — The target is not a published document (drafts only attach to published documents).
- `422` — The document is a dashboard, not an app.

## Changes

- **2026-09-04** `d733952abba0` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/omniapp/apis/omni-api/changes/api/v2/documents/:identifier/draft/app/put.md)

---

[API](https://skmtc.dev/omniapp/apis/omni-api.md) · [All operations](https://skmtc.dev/omniapp/apis/omni-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/omniapp/omni-api/revisions/d733952abba0/schema)
