---
title: "Update an existing app"
method: PUT
path: "/app/{id}"
---

# Update an existing app

`PUT /app/{id}`

## Request

- Base URL: `https://charm.ing`
- URL: `https://charm.ing/app/{id}`
- Auth: one of:
  - HTTP bearer
  - HTTP bearer

Storage survives updates. Existing contracts retain synchronous PUT and optional If-Match. ESM saves require Idempotency-Key and If-Match carrying the current desired source revision; accepted source advances desired while active remains the last working build. Omitting ui/styles preserves desired values only for ESM; null clears. Successful builds publish automatically; later saves supersede pending builds.

## Headers

- `Idempotency-Key` string
- `If-Match` string

## Request body

- AppUpdateBody
  - `module` string, required: Replacement server ES module source. Must export a literal canonical `manifest` and a `routes` array. A route handler returns exactly the value declared by `outputSchema`; for `outputSchema: { type: "array", items: ... }`, return the array directly. Charming adds the HTTP transport envelope, so do not add `{ ok, value }` or `{ value }` unless those fields belong to `outputSchema` itself. `default.fetch` is an optional unmatched-request fallback; when absent, Charming supplies a generic 404 handler. Keep the stored contract’s exact schema. ESM source is statically checked and built after acceptance.
  - `ui` string, nullable: ESM browser module: omit to preserve desired UI, pass null to clear. Existing-contract PUT retains replacement semantics.
  - `styles` string, nullable: ESM: omit to preserve desired styles, pass null to clear.
  - `description` string, nullable: Omit to preserve the desired description; pass null to clear.
  - `migrate_contract` boolean: Required as true when changing a stored contract. ESM migration requires the explicit ESM schema, Idempotency-Key and desired-source If-Match; it publishes only after a successful build. Source submissions do not roll back contracts. History can explicitly restore a retained validated existing-contract revision.

## Response `200`

Updated

- AppUpdateResponse
  - `id` string, uuid, required
  - `manifestId` string, required
  - `displayName` string, required
  - `description` string, nullable
  - `url` string, uri, required
  - `capabilities` Capabilities, required (unresolved $ref)
  - `icon` Icon: Optional home-screen / favicon icon. The server composes a colored rounded-square PNG/SVG from `emoji + bg` — NOT a list of image URLs like a W3C web manifest. Omit it to get the default icon. If `emoji` or `bg` is invalid the whole icon is silently dropped (stored as unset, so the default renders) and the create/update response carries a `warnings[]` entry. This warn-and-drop contract is the `manifest.icon` publish path only (`PUT /app/{id}` and its create counterpart) — `PUT /app/{id}/icon` uses the stricter `IconStrict` schema instead.
    - `emoji` string, required: A single emoji, rendered centered (e.g. `⚽`). Extra glyphs are dropped.
    - `bg` string, required: Background color as a hex string (`#rgb`, `#rrggbb`, or `#rrggbbaa`). Named colors (`"green"`), `rgb()`/`hsl()`, and image URLs are rejected.
  - `claimed` boolean, required
  - `revision` integer, required: Server-owned app source revision. New apps start at 1, each successful source write advances it once, and historical null counters project as 0. `If-Match: "<N>"` and `expected_revision` carry this revision through its canonical concurrency grammar.
  - `warnings` string[]: Non-blocking publish feedback (#1126), present only when static validation found UI/backend contract mismatches. The write succeeded.

## Other responses

- `202`: ESM source accepted for a background build. This response does not mean the app has published. Poll statusUrl until a terminal state; new creation has no runnable app or URL until published.
- `400`: Invalid module/ui/styles, unexpected body keys, or malformed If-Match.
- `401`: Token mismatch or wrong scope.
- `403`: Bearer is for a different app.
- `404`: App not found.
- `409`: Idempotency or manifest identity conflict.
- `412`: If-Match did not match the current server revision; response carries the current ETag.
- `413`: Source exceeds the contract limits.
- `422`: Invalid manifest, import/dependency mismatch, or required explicit migration.
- `428`: ESM saves require desired-source If-Match.
- `429`: Build capacity is full; retry after the returned delay.

---

[API](https://skmtc.dev/charm/apis/charming-api.md) · [All operations](https://skmtc.dev/charm/apis/charming-api/llms.txt) · [OpenAPI document](https://skmtc.dev/charm/apis/charming-api/revisions/a9d06c419b3a?raw)
