---
title: "Create App"
description: "Create a new interactive web app and render it inline."
sidebar:
  order: 3
---

<!-- Generated by apps/docs/scripts/generate-mcp-reference.mjs. -->

MCP name: `create_app`

## Tool contract

```json
{
  "_meta": {
    "ui": {
      "resourceUri": "ui://charming/app-v2"
    },
    "ui/resourceUri": "ui://charming/app-v2"
  },
  "annotations": {
    "destructiveHint": true,
    "openWorldHint": true,
    "readOnlyHint": false,
    "title": "Create App"
  },
  "description": "Create a new interactive web app and render it inline. Use this to build a new app from a request. Existing-contract apps return after publication. Eligible authors can select the ESM manifest schema with an idempotency_key to accept a background build; that result contains a buildId to poll with get_app_build, and has no app URL before publication. If `module` reuses a `manifest.id` the caller already owns, the new app replaces the existing app module, UI, and styles in place; use update_app to edit a known app ID by UUID. Apps persist state through a backend `env.storage` (Workers KV) binding and call backend operations from the frontend via `window.charming.api`. The `module`, `ui`, and `description` parameter docs carry the authoring rules — including the fill-viewport outer-container rule on `ui`; the charming:app-guide prompt has the full guide and a canonical example. Read the public docs without a browser using read_docs({ path: \"build-mcp.md\" }); read_docs({}) lists available pages. If a request needs in-app AI calls, external integrations, native apps, scheduled jobs, SQL, notifications, or view-only sharing, do not fake support. Build the closest local-state version, then log the gap with `submit_feedback`. The charming:app-guide prompt has alternatives. The charming:design-guide prompt documents Charming's default visual style for generated `ui` (single accent, warm neutrals, real type hierarchy; no gradients or decorative emoji) — a default that yields to any user-requested aesthetic. When telling the user where to open or share the app, always give them `shareUrl` from the result — never the `url` field, which is machine-only and embeds a write-capable access token.",
  "execution": {
    "taskSupport": "forbidden"
  },
  "inputSchema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "properties": {
      "description": {
        "description": "Search-friendly summary of what this app does, surfaced by list_apps so a future session can match user intent (e.g. \"open my protein tracker\", \"log food\") to this app even when display_name is ambiguous. Write 1-2 sentences covering: (a) what the app tracks/does, (b) the key actions it supports, and (c) synonyms or alternative phrasings the user might say. Example: \"Tracks daily protein intake. Log meals, view weekly totals, set a daily goal. Synonyms: meals, food log, nutrition tracker, calorie counter.\" Limit 500 characters.",
        "maxLength": 500,
        "type": "string"
      },
      "display_name": {
        "description": "Optional display name override",
        "type": "string"
      },
      "expected_revision": {
        "description": "Required when ESM source reuses an existing manifest.id. Pass the desired revision from get_app_source.",
        "maximum": 9007199254740991,
        "minimum": -9007199254740991,
        "type": "integer"
      },
      "idempotency_key": {
        "description": "Required for ESM builds: 8–128 visible ASCII characters. Retry the exact request with the same key to recover its build; use a new key for changed source.",
        "type": "string"
      },
      "migrate_contract": {
        "description": "Set true to migrate an existing legacy manifest.id to ESM, together with expected_revision. Source submissions do not roll back contracts. History can explicitly restore a retained validated existing-contract revision.",
        "type": "boolean"
      },
      "module": {
        "description": "Eligible ESM authors may instead select `$schema: \"https://charm.ing/schema/app-manifest/2026-09-05.json\"`, declare target-specific `dependencies.server` and `dependencies.client`, and use ordinary package imports plus exact versioned platform imports. For ESM persistence, use `import { kv } from 'charming:storage/kv@1.0'` and call `kv.get`/`kv.put` inside route handlers; omit `capabilities`. ESM creation takes its name from `manifest.meta.name`; omit `display_name`. For the existing contract, export a strict canonical `manifest` with `$schema: \"https://charm.ing/schema/app-manifest/2026-07-31.json\"`, `id`, and `meta: { name, icon? }`. The server may insert the exact schema URL on create when the rest of the source is canonical. Unknown manifest keys are rejected. To persist state in the existing contract, `capabilities.imports` must include \"charming:storage/kv@1.0\" (without it `env.storage` is undefined and every read/write throws `storage capability not granted`). A route handler in `export const routes = [...]` receives `(input, { env, ctx, request })` and returns exactly the value declared by `outputSchema`; for an array schema, use `handler: async (_input, { env }) => (await env.storage.get(\"key\")) ?? []`. Charming creates the transport envelope. Do not add a `{ ok, value }` or `{ value }` envelope unless those fields belong to `outputSchema` itself. A named context reads storage as `context.env.storage`. The optional unmatched-request fallback has the separate signature `export default { fetch(request, env, ctx) { ... } }`, where the second argument is the environment itself; it adds no discoverable route metadata. `env.user` is always present, not gated by any import: the caller's public identity (`{ id, handle?, name?, image? }`) or `null`. It lives only on `env` — read `env.user` (or `context.env.user`); there is no `ctx.user`. Full reference: call read_docs with path `llms-full.txt` (https://charm.ing/docs/llms-full.txt). For the existing contract, persistence goes through `env.storage` using Workers KV semantics — `get(key)`, `put(key, value)`, `delete(key)`, `list()`; `.set`/`.add`/`.write`/`.setItem`/`.removeItem` do not exist and throw `TypeError: env.storage.<x> is not a function`. env.storage stores JSON-compatible values directly; do not JSON.stringify before put or JSON.parse after get. Use env.storage for ALL persistence — it is the only storage that survives inside Claude/ChatGPT and syncs across devices. Do NOT keep app data or user state in localStorage/sessionStorage/IndexedDB: those APIs are empty inside chat hosts, so their data silently vanishes there (the most common cause of an app that appears not to save). The \"charming:browser/storage@1.0\" capability (claim-gated) unlocks them but only for throwaway, web-only caching; anything the user expects to keep belongs in env.storage. Export a `routes` array with unique `op` values and `handler` functions. Canonical route fields are `inputSchema`, `outputSchema`, and `annotations`; `method` defaults to `POST`, `path` defaults to `/api/<op>`, input defaults to a closed empty-object schema, and `public` defaults to true. Set all four MCP annotation hints when their defaults do not fit; Charming does not infer them from the HTTP method. A default `fetch` handler is an unmatched-request fallback only and is not discoverable. The following capability declarations apply only to the existing contract. Apps that use a sensitive browser capability must declare its import in `capabilities.imports` — \"charming:browser/microphone@1.0\" (getUserMedia audio), \"charming:browser/camera@1.0\" (getUserMedia video), \"charming:browser/geolocation@1.0\", \"charming:browser/clipboard-read@1.0\" (reading the clipboard), \"charming:browser/display-capture@1.0\" (getDisplayMedia screen share), \"charming:browser/midi@1.0\" (Web MIDI, navigator.requestMIDIAccess), \"charming:browser/device-motion@1.0\" (device orientation/motion: DeviceOrientationEvent/DeviceMotionEvent + iOS requestPermission), \"charming:browser/ambient-light@1.0\" (ambient light, new AmbientLightSensor), or \"charming:browser/storage@1.0\" (native client storage — localStorage/sessionStorage/IndexedDB — web-only); access is granted only after the app is claimed/authenticated. To call external HTTPS APIs from backend code, declare \"charming:network/fetch@1.0\" and list each exact origin in `manifest.permissions.server.fetch`; both are required and public-only. For an endpoint that needs an API key, declare \"charming:secrets/fetch@1.0\" to get `env.fetch` (claimed apps only) — a sealed outbound fetch that substitutes `{{secret:NAME}}` references in request HEADER values or query-parameter VALUES host-side (never a parameter name, the host, path, fragment, or body), so the key never enters app source or the sandbox; write the placeholder literally in the URL string — `URLSearchParams.set(...)` or `encodeURIComponent(...)` percent-encodes it first and it will NOT resolve; the app OWNER opens App settings, then Secrets, at `/<owner-handle>/~/apps/<app-name>/settings/secrets`, while `/app/<id>/secrets` remains the machine HTTP API. The agent only references the NAME. Never embed API keys in source. To render remote images, list each exact https origin in `manifest.permissions.browser[\"img-src\"]`. To make an image render in ANY host — standalone, ChatGPT, AND Claude inline (their injected CSP blocks a cross-origin `<img src>`) — set the src from `const src = await window.charming.images.load(remoteUrl)` (it fetches through Charming and returns a `data:` URL every embed CSP allows). `window.charming.images.proxy(remoteUrl)` returns a same-origin proxy URL that works standalone/ChatGPT but NOT in Claude inline; prefer `images.load(...)` when the app may be embedded. Both enforce the declared origins; neither bypasses them.",
        "type": "string"
      },
      "styles": {
        "description": "Optional CSS for the rendered app",
        "type": "string"
      },
      "team_id": {
        "description": "Optional destination team id. Only a team owner or admin can create an App there. Omit it to create a personal App.",
        "type": "string"
      },
      "ui": {
        "description": "Under the explicit ESM contract, this is an ES module: import declared client packages and import { api, onStateChange } from \"charming:ui/app@1.0\" to call this app and subscribe to its state changes. Under the existing contract, follow the classic JavaScript rules below. Frontend JavaScript rendered into #app. The #app mount point starts empty, so (a) populate its innerHTML before attaching event listeners, and (b) call operations as `window.charming.api('<manifest-id>').<opName>(input)` (operation name = method name, strips the { ok, value } envelope — you receive the value only, never ok) — not `api.operation(name, params)` and not raw fetch(), which fails in the null-origin srcdoc iframe. The signed-in caller is exposed synchronously as `window.charming.user` (`{ id, handle?, name?, image? }`, or `null` for an anonymous visitor) — read `user.name` to greet, attribute, or personalize, instead of asking the user to type their name. PUBLIC fields only (never email); it is a convenience signal, not enforcement. The outermost container MUST fill the viewport — use `<main class=\"min-h-screen\">` (or a grid/flex layout that spans width) as the root shell. Do NOT wrap the root in `max-w-md`, `max-w-2xl`, or `container mx-auto`: those cap the entire app to a narrow central column with wide empty margins on 2K+ monitors — the loudest \"AI-generated app\" tell. If the view is text-heavy (a note, an article, a form with long prose), cap the reading measure on an INNER wrapper only, e.g. `<main class=\"min-h-screen\"><div class=\"mx-auto max-w-2xl\">…</div></main>`. Dashboards, kanban, tables, canvases, galleries, and split views should use the full width. For live updates when an agent mutates state from another session, register `window.charming.onStateChange((e) => { ... })` and update the DOM surgically rather than wiping #app. See the charming:app-guide prompt for a canonical example.",
        "type": "string"
      }
    },
    "required": [
      "module",
      "description"
    ],
    "type": "object"
  },
  "name": "create_app",
  "outputSchema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "additionalProperties": false,
    "anyOf": [
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "advisories": {
            "description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path.",
            "items": {
              "additionalProperties": false,
              "properties": {
                "data": {
                  "additionalProperties": {},
                  "description": "Kind-specific structured payload. Shape varies per advisory kind.",
                  "propertyNames": {
                    "type": "string"
                  },
                  "type": "object"
                },
                "doc_url": {
                  "description": "Optional docs pointer for this advisory kind.",
                  "type": "string"
                },
                "kind": {
                  "description": "Stable advisory identifier (e.g. \"legacy-bridge\").",
                  "type": "string"
                },
                "severity": {
                  "description": "Severity; omitted advisories are treated as 'info'.",
                  "enum": [
                    "info",
                    "warn"
                  ],
                  "type": "string"
                },
                "summary": {
                  "description": "Agent-facing summary. Self-sufficient; no extra context required.",
                  "type": "string"
                },
                "userSummary": {
                  "description": "End-user-facing summary. Set when the advisory should render in-app.",
                  "type": "string"
                }
              },
              "required": [
                "kind",
                "summary"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "appName": {
            "description": "URL-safe slug for the app, unique per owner. Distinct from manifestId and displayName, and stable across edits — use rename_app to change it (the title changing does NOT change the slug).",
            "type": [
              "string",
              "null"
            ]
          },
          "icon": {
            "additionalProperties": false,
            "description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
            "properties": {
              "bg": {
                "description": "The icon background as a hex color (e.g. \"#1d8a4e\").",
                "type": "string"
              },
              "emoji": {
                "description": "The single emoji rendered on the icon.",
                "type": "string"
              }
            },
            "required": [
              "emoji",
              "bg"
            ],
            "type": "object"
          },
          "id": {
            "description": "UUID of the created or updated app.",
            "type": "string"
          },
          "ok": {
            "const": true,
            "description": "Indicates success. Errors arrive as content with isError:true.",
            "type": "boolean"
          },
          "revision": {
            "description": "Server-owned app source revision. Historical null counters read as 0; new apps start at 1; each successful source write advances it once. Pass this value through `expected_revision` when guarding update_app.",
            "maximum": 9007199254740991,
            "minimum": 0,
            "type": "integer"
          },
          "shareUrl": {
            "description": "The link to give humans whenever the user wants to open or share the app. Friendly /<handle>/<app-name> form when the OWNER has a live handle and the app a slug, /app/<uuid> otherwise. Always token-free — safe to show, paste, and send. Not an API base: appending /api/<op> to the friendly form 404s; use `url` for machine calls.",
            "type": "string"
          },
          "url": {
            "description": "Machine/API URL for the app (stable /app/<uuid> form). Embeds a write-capable ?t= access token — NEVER show, paste, or send it to the user. Append /api/<op> to its path for out-of-band operation calls. For anything user-facing, use shareUrl instead.",
            "type": "string"
          },
          "warnings": {
            "description": "Non-blocking publish feedback (#1126): present when static validation found UI/backend contract mismatches or legacy icon input needed a fallback. The write succeeded; fix the named source field.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "ok",
          "id",
          "url",
          "shareUrl",
          "revision",
          "icon"
        ],
        "type": "object"
      },
      {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "acceptedAt": {
            "type": "string"
          },
          "activeRevision": {
            "maximum": 9007199254740991,
            "minimum": 0,
            "type": "integer"
          },
          "appId": {
            "description": "Existing target app ID, or the created app ID after publication.",
            "type": "string"
          },
          "attempts": {
            "maximum": 9007199254740991,
            "minimum": 0,
            "type": "integer"
          },
          "buildId": {
            "description": "Durable build ID. Use get_app_build to inspect progress or source.",
            "type": "string"
          },
          "deadline": {
            "type": "string"
          },
          "desiredRevision": {
            "maximum": 9007199254740991,
            "minimum": 0,
            "type": "integer"
          },
          "elapsedMs": {
            "minimum": 0,
            "type": "number"
          },
          "error": {
            "additionalProperties": false,
            "properties": {
              "column": {
                "maximum": 9007199254740991,
                "minimum": -9007199254740991,
                "type": "integer"
              },
              "kind": {
                "type": "string"
              },
              "line": {
                "maximum": 9007199254740991,
                "minimum": -9007199254740991,
                "type": "integer"
              },
              "message": {
                "type": "string"
              },
              "retryable": {
                "type": "boolean"
              },
              "specifier": {
                "type": "string"
              },
              "target": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "message",
              "retryable"
            ],
            "type": "object"
          },
          "finishedAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "idempotencyExpiresAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "inputDigest": {
            "type": "string"
          },
          "inspectionExpiresAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "intent": {
            "description": "The accepted operation: create, update, migrate, restore, or copy.",
            "type": "string"
          },
          "lockDigest": {
            "type": "string"
          },
          "lockState": {
            "enum": [
              "locked",
              "unresolved"
            ],
            "type": "string"
          },
          "ok": {
            "const": true,
            "type": "boolean"
          },
          "queueDeadline": {
            "type": "string"
          },
          "resolvedDependencies": {
            "additionalProperties": false,
            "properties": {
              "client": {
                "additionalProperties": {
                  "type": "string"
                },
                "propertyNames": {
                  "type": "string"
                },
                "type": "object"
              },
              "server": {
                "additionalProperties": {
                  "type": "string"
                },
                "propertyNames": {
                  "type": "string"
                },
                "type": "object"
              }
            },
            "required": [
              "server",
              "client"
            ],
            "type": "object"
          },
          "retryAfterSeconds": {
            "description": "Wait at least this many seconds before polling again.",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991,
            "type": "integer"
          },
          "revision": {
            "description": "Published app source revision.",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991,
            "type": "integer"
          },
          "source": {
            "additionalProperties": false,
            "description": "Exact accepted source, returned only when include_source is true.",
            "properties": {
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "module": {
                "type": "string"
              },
              "styles": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ui": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "module",
              "ui",
              "styles",
              "description"
            ],
            "type": "object"
          },
          "sourceEtag": {
            "description": "ETag identifying the immutable input accepted for this build.",
            "type": "string"
          },
          "state": {
            "enum": [
              "queued",
              "resolving",
              "building",
              "validating",
              "published",
              "failed",
              "superseded",
              "canceled",
              "expired"
            ],
            "type": "string"
          },
          "statusUrl": {
            "description": "Authenticated HTTP status URL for this build.",
            "type": "string"
          },
          "updatedAt": {
            "type": "string"
          },
          "url": {
            "description": "App URL. Present only after successful publication.",
            "type": "string"
          }
        },
        "required": [
          "ok",
          "buildId",
          "intent",
          "state",
          "sourceEtag",
          "statusUrl",
          "attempts",
          "acceptedAt",
          "updatedAt",
          "finishedAt",
          "queueDeadline",
          "deadline",
          "inspectionExpiresAt",
          "idempotencyExpiresAt",
          "elapsedMs",
          "lockState",
          "inputDigest"
        ],
        "type": "object"
      }
    ],
    "properties": {
      "acceptedAt": {
        "type": "string"
      },
      "activeRevision": {
        "maximum": 9007199254740991,
        "minimum": 0,
        "type": "integer"
      },
      "advisories": {
        "description": "Structured non-fatal advisories, including authoring issues and unread staff feedback responses. Each advisory.summary is also appended to the text content for the LLM path.",
        "items": {
          "additionalProperties": false,
          "properties": {
            "data": {
              "additionalProperties": {},
              "description": "Kind-specific structured payload. Shape varies per advisory kind.",
              "propertyNames": {
                "type": "string"
              },
              "type": "object"
            },
            "doc_url": {
              "description": "Optional docs pointer for this advisory kind.",
              "type": "string"
            },
            "kind": {
              "description": "Stable advisory identifier (e.g. \"legacy-bridge\").",
              "type": "string"
            },
            "severity": {
              "description": "Severity; omitted advisories are treated as 'info'.",
              "enum": [
                "info",
                "warn"
              ],
              "type": "string"
            },
            "summary": {
              "description": "Agent-facing summary. Self-sufficient; no extra context required.",
              "type": "string"
            },
            "userSummary": {
              "description": "End-user-facing summary. Set when the advisory should render in-app.",
              "type": "string"
            }
          },
          "required": [
            "kind",
            "summary"
          ],
          "type": "object"
        },
        "type": "array"
      },
      "appId": {
        "description": "Existing target app ID, or the created app ID after publication.",
        "type": "string"
      },
      "appName": {
        "description": "URL-safe slug for the app, unique per owner. Distinct from manifestId and displayName, and stable across edits — use rename_app to change it (the title changing does NOT change the slug).",
        "type": [
          "string",
          "null"
        ]
      },
      "attempts": {
        "maximum": 9007199254740991,
        "minimum": 0,
        "type": "integer"
      },
      "buildId": {
        "description": "Durable build ID. Use get_app_build to inspect progress or source.",
        "type": "string"
      },
      "deadline": {
        "type": "string"
      },
      "desiredRevision": {
        "maximum": 9007199254740991,
        "minimum": 0,
        "type": "integer"
      },
      "elapsedMs": {
        "minimum": 0,
        "type": "number"
      },
      "error": {
        "additionalProperties": false,
        "properties": {
          "column": {
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "type": "integer"
          },
          "kind": {
            "type": "string"
          },
          "line": {
            "maximum": 9007199254740991,
            "minimum": -9007199254740991,
            "type": "integer"
          },
          "message": {
            "type": "string"
          },
          "retryable": {
            "type": "boolean"
          },
          "specifier": {
            "type": "string"
          },
          "target": {
            "type": "string"
          }
        },
        "required": [
          "kind",
          "message",
          "retryable"
        ],
        "type": "object"
      },
      "finishedAt": {
        "type": [
          "string",
          "null"
        ]
      },
      "icon": {
        "additionalProperties": false,
        "description": "The effective home-screen icon stored for the app. Canonical source declares it at `manifest.meta.icon`.",
        "properties": {
          "bg": {
            "description": "The icon background as a hex color (e.g. \"#1d8a4e\").",
            "type": "string"
          },
          "emoji": {
            "description": "The single emoji rendered on the icon.",
            "type": "string"
          }
        },
        "required": [
          "emoji",
          "bg"
        ],
        "type": "object"
      },
      "id": {
        "description": "UUID of the created or updated app.",
        "type": "string"
      },
      "idempotencyExpiresAt": {
        "type": [
          "string",
          "null"
        ]
      },
      "inputDigest": {
        "type": "string"
      },
      "inspectionExpiresAt": {
        "type": [
          "string",
          "null"
        ]
      },
      "intent": {
        "description": "The accepted operation: create, update, migrate, restore, or copy.",
        "type": "string"
      },
      "lockDigest": {
        "type": "string"
      },
      "lockState": {
        "enum": [
          "locked",
          "unresolved"
        ],
        "type": "string"
      },
      "ok": {
        "const": true,
        "type": "boolean"
      },
      "queueDeadline": {
        "type": "string"
      },
      "resolvedDependencies": {
        "additionalProperties": false,
        "properties": {
          "client": {
            "additionalProperties": {
              "type": "string"
            },
            "propertyNames": {
              "type": "string"
            },
            "type": "object"
          },
          "server": {
            "additionalProperties": {
              "type": "string"
            },
            "propertyNames": {
              "type": "string"
            },
            "type": "object"
          }
        },
        "required": [
          "server",
          "client"
        ],
        "type": "object"
      },
      "retryAfterSeconds": {
        "description": "Wait at least this many seconds before polling again.",
        "exclusiveMinimum": 0,
        "maximum": 9007199254740991,
        "type": "integer"
      },
      "revision": {
        "description": "Server-owned app source revision. Historical null counters read as 0; new apps start at 1; each successful source write advances it once. Pass this value through `expected_revision` when guarding update_app.",
        "maximum": 9007199254740991,
        "minimum": 0,
        "type": "integer"
      },
      "shareUrl": {
        "description": "The link to give humans whenever the user wants to open or share the app. Friendly /<handle>/<app-name> form when the OWNER has a live handle and the app a slug, /app/<uuid> otherwise. Always token-free — safe to show, paste, and send. Not an API base: appending /api/<op> to the friendly form 404s; use `url` for machine calls.",
        "type": "string"
      },
      "source": {
        "additionalProperties": false,
        "description": "Exact accepted source, returned only when include_source is true.",
        "properties": {
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "module": {
            "type": "string"
          },
          "styles": {
            "type": [
              "string",
              "null"
            ]
          },
          "ui": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "module",
          "ui",
          "styles",
          "description"
        ],
        "type": "object"
      },
      "sourceEtag": {
        "description": "ETag identifying the immutable input accepted for this build.",
        "type": "string"
      },
      "state": {
        "enum": [
          "queued",
          "resolving",
          "building",
          "validating",
          "published",
          "failed",
          "superseded",
          "canceled",
          "expired"
        ],
        "type": "string"
      },
      "statusUrl": {
        "description": "Authenticated HTTP status URL for this build.",
        "type": "string"
      },
      "updatedAt": {
        "type": "string"
      },
      "url": {
        "description": "App URL. Present only after successful publication.",
        "type": "string"
      },
      "warnings": {
        "description": "Non-blocking publish feedback (#1126): present when static validation found UI/backend contract mismatches or legacy icon input needed a fallback. The write succeeded; fix the named source field.",
        "items": {
          "type": "string"
        },
        "type": "array"
      }
    },
    "required": [
      "ok"
    ],
    "type": "object"
  }
}
```

## ChatGPT metadata

ChatGPT adds or replaces these fields for progress messages and inline app rendering.

```json
{
  "_meta": {
    "openai/outputTemplate": "ui://charming/chatgpt/app-v2",
    "openai/toolInvocation/invoked": "App created",
    "openai/toolInvocation/invoking": "Creating app…",
    "ui": {
      "resourceUri": "ui://charming/chatgpt/app-v2"
    },
    "ui/resourceUri": "ui://charming/chatgpt/app-v2"
  }
}
```

Found a bug or need a feature? [Tell us](/docs/capabilities/feedback) with `submit_feedback` or `POST /app/{id}/feedback`. Your feedback shapes what we build next.
