Skip to main content

Actions

Looking for the product view?
The Help Center explains this surface as users see it: Using Apps.

Action endpoints perform specific operations on apps like copying, printing, and more.

POST /api/apps/{id}/_copy​

Create a copy of an app, including its library and all associated resources.

Authentication: Required

Permissions Required:

  • Member+ role (copy permission) on the source app
  • Member role (create permission) for apps

Managed Apps (deployed or marketplace origin, isManaged: true) cannot be copied: the route returns 403 Forbidden for every caller, superusers included, because a copy would fork the App out of its managed update lifecycle.

Path Parameters:

ParameterTypeDescription
idstringApp UUID or natural ID

Payload:

{
"name": "Sales Dashboard (Copy)",
"progress": "optional-progress-callback-id"
}

Payload Fields:

FieldTypeRequiredDescription
namestringNoName for the copy (default: original name)
progressstringNoProgress callback ID for tracking

Returns the copied app with status 201 Created and a Location header pointing to the new app.

Copy an AppPOST /api/apps/00000000-0000-4000-8000-000000000001/_copy
Clones the app, its library files, entity bindings, and environment rows; the caller becomes the owner. The workspace schema is NOT copied; it is recreated empty by the next migrate/deploy.
Request body
{
"name": "Order Desk (Staging)"
}
Response · 201
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging"
},
"inf:owner": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/owner"
},
"inf:draft": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/draft"
},
"inf:app-shares": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/shares"
},
"inf:app-tags": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/tags"
},
"inf:favorite": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/favorite"
},
"inf:app-tag": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/tags/{tagId}",
"templated": true
},
"inf:export-bundle": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_export"
},
"inf:cloud-publish": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_cloud-publish"
},
"inf:copy": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_copy"
},
"inf:print": [
{
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_print"
},
{
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_print",
"title": "Print to PDF"
}
],
"inf:migrate": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_migrate"
},
"inf:deploy": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_deploy"
},
"inf:app-mcp": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/mcp"
},
"inf:app-tokens": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/tokens"
},
"inf:app-roles": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/roles"
},
"inf:app-dependencies": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/dependencies"
},
"inf:app-dependency": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/dependencies/{name}",
"templated": true
},
"inf:app-environment": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/environment"
},
"inf:app-environment-var": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/environment/{key}",
"templated": true
},
"inf:app-suggest-icon": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_suggest-icon"
},
"inf:app-modernize-manifest": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_modernize-manifest"
},
"inf:app-draft-diff": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/draft/_diff"
},
"edit": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/_edit"
},
"inf:app-share": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/shares/{principalId}",
"templated": true
},
"inf:entrypoint": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/view/",
"title": "index.html"
},
"inf:library": {
"href": "https://informer.example.com/api/libraries/00000000-0000-4000-8000-000000000002",
"title": "App Files"
},
"inf:library-contents": {
"href": "https://informer.example.com/api/libraries/00000000-0000-4000-8000-000000000002/contents/index.html",
"title": "Raw Contents"
},
"share": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk-staging/view/?token=%7Btoken%7D",
"title": "Share Page"
}
},
"naturalId": "admin:order-desk-staging",
"isManaged": false,
"permissions": {
"assignTags": true,
"bind": true,
"changeOwner": true,
"copy": true,
"delete": true,
"edit": true,
"manage": true,
"purge": true,
"restore": true,
"run": true,
"share": true,
"switchRoles": true,
"unmanage": true,
"write": true
},
"id": "00000000-0000-4000-8000-000000000003",
"type": "app",
"name": "Order Desk (Staging)",
"tenant": "acme",
"ownerId": "admin",
"slug": "order-desk-staging",
"description": "Track and fulfill open orders",
"defn": {
"entryPoint": "index.html"
},
"settings": {
"sandbox": true,
"passDatasets": true
},
"origin": "ui",
"source": null,
"sourceId": null,
"shared": false,
"defnUpdatedAt": null,
"libraryId": "00000000-0000-4000-8000-000000000002",
"folderId": null,
"deletedAt": null,
"editingId": null,
"deployedAt": "2026-01-15T16:20:00.000Z",
"editingSession": null,
"editingHeartbeatAt": null,
"editingLockedBy": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"contentModifiedAt": "2026-01-15T16:20:00.000Z"
}
Captured from the API examples test suite; ids and timestamps are normalized.

Copy Behavior:

The copy operation duplicates:

  • All app metadata (name, description, defn, settings)
  • The embedded library (if present) with all files
  • All entity-binding rows (app_entity: dependency slots and whitelist entries)
  • Environment variable rows, encrypted values included (same tenant, so the ciphertext stays valid)
  • The copy is owned by the current user, is not shared by default, and gets a new UUID and slug

Not Copied:

  • Shares (AppShare records)
  • Tags (TagEntity records)
  • The workspace schema and its data (recreated empty by the next migrate/deploy)
  • Agents (AppAgent records — re-created on next deploy)
  • Favorite status
  • Edit history
  • Tokens

Library Copy:

If the app has an embedded library (libraryId):

  1. A new library is created with the name "{original name} (Copy)"
  2. All files from the original library are copied to the new library
  3. The new app references the new library
  4. The copy is fully independent (editing one doesn't affect the other)

Error Responses:

  • 400 Bad Request - Invalid payload or copy operation failed
  • 404 Not Found - App doesn't exist or user lacks access
  • 403 Forbidden - User lacks copy or create permission
  • 403 Forbidden - App is managed (isManaged: true); a copy would fork it out of its managed lifecycle

Transaction Safety:

The entire copy operation runs in a database transaction. If any step fails, the entire operation is rolled back.


POST /api/apps/{id}/_print​

Export an app as a PDF.

Authentication: Required

Permissions Required: Member role (run permission)

Path Parameters:

ParameterTypeDescription
idstringApp UUID or natural ID

Payload:

Print configuration depends on the app driver. For report apps:

{
"format": "letter",
"orientation": "portrait",
"margins": {
"top": 0.5,
"right": 0.5,
"bottom": 0.5,
"left": 0.5
},
"headerTemplate": "<div>...</div>",
"footerTemplate": "<div>...</div>",
"displayHeaderFooter": true,
"printBackground": true
}

Common Payload Fields:

FieldTypeDefaultDescription
formatstring"letter"Paper format (letter, legal, a4, etc.)
orientationstring"portrait"Page orientation (portrait, landscape)
marginsobject{}Page margins in inches
printBackgroundbooleantrueInclude background graphics

Response:

Returns a PDF file as a binary stream.

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="Sales Dashboard.pdf"

[PDF binary data]

Use Cases:

  • Export dashboards for offline viewing
  • Generate reports for email distribution
  • Archive app snapshots
  • Print physical copies

Implementation Notes:

  • Uses headless Chrome/Puppeteer for rendering
  • Captures the current state of the app
  • Respects app's CSS print styles
  • May timeout for very complex apps

Error Responses:

  • 404 Not Found - App doesn't exist or user lacks access
  • 403 Forbidden - User lacks run permission
  • 500 Internal Server Error - PDF generation failed

POST /api/apps/{id}/_unmanage​

Release a CLI-deployed App back to origin: "ui", restoring builder editing.

Authentication: Required

Permissions Required:

  • Admin role (app:write) on the owning team, for type: "app"

Deliberately gated on app:write rather than app:edit. app:edit resolves to app:write && !isManaged, so gating on it would make this permission unsatisfiable on exactly the Apps it exists to release.

Path Parameters:

ParameterTypeDescription
idstringApp UUID or natural ID

No payload. Returns the updated App with origin: "ui" and isManaged: false.

Why this exists: origin: "deployed" is otherwise a one-way door. POST /_edit refuses managed Apps unconditionally (an entity invariant, not a permission, so superusers are refused too), and the only other path that resets origin is the draft-commit flow, which needs a draft, which needs _edit. An App claimed by mistake would be un-editable permanently.

What releasing does not do: it does not sever the App from its source project. The library still holds the deploy's files, package.json in the project still carries the App's UUID, and the next npm run deploy re-claims the App as managed and replaces its files. Releasing is for adopting an App whose source project is gone, not for forking a live one; use _copy for that.

Errors:

StatusCondition
400 Bad RequestApp is already origin: "ui" ("App is not managed")
400 Bad RequestApp is origin: "marketplace" ("Marketplace apps cannot be released: uninstall the app instead")
403 ForbiddenCaller lacks app:write on the owning team
404 Not FoundApp does not exist, or the caller has no read access to it

Marketplace installs are refused because they stay bound to their pack through pack_install.localAppId and keep receiving upgrades; releasing one would leave an App the next sync overwrites. Uninstalling is the supported exit. The refusal is a 400 rather than a 403 because it is a fact about the App's kind, not about the caller.

That protection is not only this route's. A beforeUpdate hook on the App model rejects any write that moves a row off origin: "marketplace", so it holds for every write path, present and future, not just this endpoint. The route's 400 is the clean answer for an API caller; the model hook is what makes it true of the row.

HAL affordances: the App resource advertises inf:app-unmanage only when origin is "deployed", and advertises edit only when the App is not managed. Clients should drive off those rels rather than recomputing from origin.


Other Actions​

Export Bundle​

Apps can be exported as bundles (not yet documented in routes).

Route: GET /api/apps/{id}/_export

Use Case: Export app and its library as a portable bundle for backup or transfer.

Cloud Publish​

Apps can be published to cloud services (not yet documented in routes).

Route: POST /api/apps/{id}/_cloud-publish

Use Case: Publish app to external hosting or app marketplaces.


Common Action Patterns​

Copy and Modify​

// 1. Copy the app
const copy = await POST('/api/apps/analytics:sales-dashboard/_copy', {
name: 'Sales Dashboard - Modified'
});

// 2. Edit the copy
await POST(`/api/apps/${copy.id}/_edit`);

// 3. Modify files in the draft
await PUT(`/api/apps/${draftId}/contents/index.html`, {
content: modifiedHTML
});

// 4. Commit changes
await POST(`/api/apps/${copy.id}/draft/_commit`);

Create PDF Archive​

// Generate PDF of current state
const pdf = await POST('/api/apps/analytics:sales-dashboard/_print', {
format: 'letter',
orientation: 'landscape',
printBackground: true
});

// Save to file or send via email
await fs.writeFile('dashboard-archive.pdf', pdf);

Copy to Different Owner​

// 1. Copy the app (owned by current user)
const copy = await POST('/api/apps/analytics:sales-dashboard/_copy', {
name: 'Sales Dashboard - Marketing Copy'
});

// 2. Transfer ownership to Marketing team
await PUT(`/api/apps/${copy.id}/owner`, {
teamId: 'marketing'
});

Batch Copy​

// Copy multiple apps
const sourceApps = [
'analytics:sales-dashboard',
'analytics:customer-report',
'analytics:inventory-tracker'
];

const copies = [];
for (const appId of sourceApps) {
const copy = await POST(`/api/apps/${appId}/_copy`, {
name: `${appId} (Backup ${new Date().toISOString()})`
});
copies.push(copy);
}

console.log(`Created ${copies.length} backups`);

Permissions Summary​

ActionRequired PermissionDescription
CopyMember+ (copy) + Member (create)Copy source app and create new app
PrintMember (run)Generate PDF from app
UnmanageAdmin (write)Release a deployed app back to origin: "ui"
Export BundleMember+ (write)Export app as bundle
Cloud PublishPublisher (share)Publish to external services