---
search:
  tags:
    - Operations
    - POST
seo:
  description: >-
    Creates the caller a private copy of an app they can edit, or of a Template
    they may… Reference for the POST /app/{id}/copy endpoint in the Charming
    API.
sidebar:
  label: Copy an app into a new private app owned by the caller
  badge: POST
title: Copy an app into a new private app owned by the caller
type: openapi-operation
---
Creates the caller a private copy of an app they can edit, or of a Template they may copy, without sending its source. A Template copy records the source as its origin (`copyKind: "template"`). The copy keeps the source code, styles, icon, and network settings; it never carries sharing, Public, signed-in access, Template or Listed state, tokens, secrets, Routines, Webhooks, custom domains, or version history. Without `manifest_id` the copy takes the next free manifest id after the source's (`<id>-2`, `<id>-3`); a `manifest_id` the caller already holds is refused with 409 rather than replacing that app. `include_data: true` also copies the stored data and assets in the same transaction that creates the copy, so a copy exists with all of its data or not at all; it needs owner access to a personally owned source (a team app's data is not copied into a personal account), and the data must fit the 24 MiB a copy carries, the per-app caps, and the caller's storage allowance. Without `idempotency_key` every call creates a new copy and answers 200 with it; a retry with the same key answers with the copy, or the pending build, the first call made, and the same key with a different source, `include_data`, or `manifest_id` answers 409 `idempotency_conflict`. A 409 `manifest_id_conflict` names the app holding the id in `existingAppId` when one does. An ESM app copies through a build; a build that does not publish at once answers 202 with its build status, and the data copies when it publishes. `secretsNotCopied` names the source secrets to set on the copy; it is reported only to callers who can manage the source's secrets.

`POST /app/{id}/copy`

**Request body** (`application/json`)

- `manifest_id` (string) — Manifest id for the copy. Must be free for the caller. Omit it to take the next free id.
- `display_name` (string) — Display name for the copy. Defaults to the source's. Refused for ESM apps, which take their name from manifest.meta.name.
- `idempotency_key` (string) — Names this copy so a retry returns it instead of making another. The same key with a different source, include\_data, or manifest\_id answers 409 idempotency\_conflict.
- `include_data` (boolean) — Also copy the stored data and assets. Defaults to false. Needs owner access to a personally owned source; a team app’s data is not copied.

Request body example:

```json
{
  "manifest_id": "string",
  "display_name": "string",
  "idempotency_key": "string",
  "include_data": true
}
```

**Responses**

- `200` — The copy.
- `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` — Body is not a JSON object, carries an unexpected key, or names an invalid manifest\_id or display\_name. See error.kind.
- `401` — User-scoped auth required.
- `403` — \`forbidden\`: the caller can open the app but may not copy it. \`copy\_data\_forbidden\`: include\_data needs owner access to a personally owned source, and a team app’s data is not copied. \`contract\_not\_enabled\`: ESM copying is not enabled for the caller.
- `404` — App not found, or the caller cannot open it.
- `409` — \`manifest\_id\_conflict\`: the caller already holds the requested manifest\_id; \`existingAppId\` names the app when one holds it. \`idempotency\_conflict\`: the idempotency\_key already copied a different app.
- `413` — The source data exceeds the 24 MiB a copy carries (\`copy\_data\_too\_large\`) or a per-app cap (\`storage\_full\`, \`asset\_count\_exceeded\`, \`asset\_quota\_exceeded\`). Copy it without include\_data.
- `429` — \`storage\_limit\_exceeded\`: the data would put the caller over their storage allowance. \`app\_build\_capacity\`: retry after the returned delay.

Response example, 200:

```json
{
  "ok": true,
  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  "url": "http://example.com",
  "manifestId": "string",
  "appName": "string",
  "displayName": "string",
  "revision": 0,
  "sourceAppId": "fb08c6b7-a844-43c6-b104-98e70c00fc20",
  "copyKind": "app",
  "copiedData": {
    "keys": 0,
    "assets": 0
  },
  "secretsNotCopied": [
    "string"
  ]
}
```

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.
