Core CRUD
Basic app creation, retrieval, update, and deletion operations.
GET /api/apps
Search and filter apps with pagination. The response is a paginated
HAL collection: app items
under _embedded["inf:app"], pagination metadata and aggregations at the top
level.
Authentication: Required
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | — | Full-text search query (searches name, slug, description) |
sort | string | — | Any app attribute (name, createdAt, updatedAt, …); prefix with - for descending. Unknown fields are rejected with 400. |
limit | integer | 20 | Results per page (0 disables the limit) |
start | integer | 0 | Pagination offset |
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps?limit=10&start=0&q=order%2A"
}
},
"start": 0,
"count": 1,
"total": 1,
"aggs": {
"total": 4
},
"permissions": {
"create": true
},
"_embedded": {
"inf:app": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk"
},
"inf:owner": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/owner"
},
"inf:draft": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/draft"
},
"inf:app-shares": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/shares"
},
"inf:app-tags": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/tags"
},
"inf:favorite": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/favorite"
},
"inf:app-tag": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/tags/{tagId}",
"templated": true
},
"inf:export-bundle": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_export"
},
"inf:cloud-publish": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_cloud-publish"
},
"inf:copy": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_copy"
},
"inf:print": [
{
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_print"
},
{
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_print",
"title": "Print to PDF"
}
],
"inf:migrate": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_migrate"
},
"inf:deploy": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_deploy"
},
"inf:app-tokens": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/tokens"
},
"inf:app-roles": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/roles"
},
"inf:app-dependencies": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/dependencies"
},
"inf:app-dependency": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/dependencies/{name}",
"templated": true
},
"inf:app-environment": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/environment"
},
"inf:app-environment-var": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/environment/{key}",
"templated": true
},
"inf:app-suggest-icon": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_suggest-icon"
},
"inf:app-modernize-manifest": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_modernize-manifest"
},
"inf:app-draft-diff": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/draft/_diff"
},
"edit": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_edit"
},
"inf:app-share": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/shares/{principalId}",
"templated": true
},
"share": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/view/?token=%7Btoken%7D",
"title": "Share Page"
}
},
"naturalId": "admin:order-desk",
"isManaged": true,
"permissions": {
"assignTags": true,
"bind": true,
"changeOwner": true,
"copy": true,
"delete": true,
"edit": false,
"manage": true,
"purge": true,
"restore": true,
"run": true,
"share": true,
"switchRoles": true,
"write": true
},
"id": "00000000-0000-4000-8000-000000000001",
"type": "report",
"name": "Order Desk",
"ownerId": "admin",
"slug": "order-desk",
"description": "Track and fulfill open orders",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"contentModifiedAt": null
}
]
}
}
Key Fields:
| Field | Description |
|---|---|
start | Pagination offset |
count | Number of items in this page |
total | Total matching apps |
aggs.total | Total apps accessible to the user |
permissions.create | Whether the user can create apps |
Items carry a trimmed attribute set (id, type, name, ownerId, slug,
description, createdAt, updatedAt) plus per-app permissions and HAL
links. Drafts (editingId set) are excluded.
Permissions Required: None (returns only apps the user can read)
GET /api/apps-list
Get a complete list of all readable apps with tags, sharing info, view statistics, and dataset references. This endpoint returns a plain array (not HAL-wrapped) and is used by report galleries.
Authentication: Required
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
sort | string | name | popular (30-day views), trending (7-day views), or recent (last viewed) |
includeDeleted | string | — | only returns soft-deleted apps; all mixes them in with live ones |
[
{
"driver": "app",
"id": "00000000-0000-4000-8000-000000000001",
"slug": "inventory-watch",
"name": "Inventory Watch",
"description": "Live stock levels with reorder alerts",
"type": "report",
"ownerId": "admin",
"ownerName": "acme Administrator",
"folderId": null,
"shared": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"contentModifiedAt": "2026-01-15T16:20:00.000Z",
"deletedAt": null,
"favorite": false,
"naturalId": "admin:inventory-watch",
"listId": "app:00000000-0000-4000-8000-000000000001",
"typeName": "Magic Report",
"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,
"write": true
},
"image": "/images/icons/app.svg",
"rels": {
"self": "inf:app",
"owner": "inf:app-owner",
"share": "inf:app-share",
"tag": "inf:app-tag"
},
"tags": [],
"viewStats": {
"totalViews": 0,
"uniqueViewers": 0,
"views7d": 0,
"views30d": 0
},
"datasource": {
"ownerId": null,
"type": null,
"name": null,
"id": null,
"shares": [
{
"accessLevel": 0
}
]
}
},
{
"driver": "app",
"id": "00000000-0000-4000-8000-000000000002",
"slug": "order-desk",
"name": "Order Desk",
"description": "Track, fulfill, and audit open orders",
"type": "report",
"ownerId": "admin",
"ownerName": "acme Administrator",
"folderId": null,
"shared": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"contentModifiedAt": "2026-01-15T16:20:00.000Z",
"deletedAt": null,
"favorite": false,
"naturalId": "admin:order-desk",
"listId": "app:00000000-0000-4000-8000-000000000002",
"typeName": "Magic Report",
"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,
"write": true
},
"image": "/images/icons/app.svg",
"rels": {
"self": "inf:app",
"owner": "inf:app-owner",
"share": "inf:app-share",
"tag": "inf:app-tag"
},
"tags": [],
"icon": "storefront",
"viewStats": {
"totalViews": 2,
"uniqueViewers": 1,
"views7d": 2,
"views30d": 2
},
"datasource": {
"ownerId": null,
"type": null,
"name": null,
"id": null,
"shares": [
{
"accessLevel": 0
}
]
}
},
{
"driver": "app",
"id": "00000000-0000-4000-8000-000000000003",
"slug": "retired-dashboard",
"name": "Retired Dashboard",
"description": "Superseded by Order Desk",
"type": "report",
"ownerId": "admin",
"ownerName": "acme Administrator",
"folderId": null,
"shared": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"contentModifiedAt": "2026-01-15T16:20:00.000Z",
"deletedAt": null,
"favorite": false,
"naturalId": "admin:retired-dashboard",
"listId": "app:00000000-0000-4000-8000-000000000003",
"typeName": "Magic Report",
"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,
"write": true
},
"image": "/images/icons/app.svg",
"rels": {
"self": "inf:app",
"owner": "inf:app-owner",
"share": "inf:app-share",
"tag": "inf:app-tag"
},
"tags": [],
"viewStats": {
"totalViews": 1,
"uniqueViewers": 1,
"views7d": 1,
"views30d": 1
},
"datasource": {
"ownerId": null,
"type": null,
"name": null,
"id": null,
"shares": [
{
"accessLevel": 0
}
]
}
},
"… 1 more items (4 total) — omitted from docs"
]
Key Fields:
| Field | Description |
|---|---|
typeName | Localized display label for the app type (resolved from the app-type driver in the requester's language) |
type | Raw driver type (app or report) |
ownerName | Display name of the owning user or team |
isManaged | Whether the app is deployed/managed (CLI or marketplace origin) |
favorite | Whether the current user favorited the app |
viewStats | totalViews, uniqueViewers, views7d, views30d from the view audit |
contentModifiedAt | Most recent file modification in the app's library |
icon | The defn.icon value, when set |
datasource | Stub object (apps have no primary datasource; provided for report-list compatibility) |
Special Behaviors:
- Custom Favicon: If an app has a
favicon.svgfile in its library root,imageis set to/api/apps/{naturalId}/contents/favicon.svg(with a cache-busting?t=parameter) and the default icon moves tofallbackImage - Docs file:
docsFileis present when the app's library root containsdocs.htmlorREADME.md(docs.htmlwins) - Widgets:
widgetsis present when the app's manifest declares dashboard widgets
Permissions Required: None (returns only apps the user can read)
ETag Support: This endpoint supports ETags based on app and tag table changes.
GET /api/apps/recent
Get recently viewed apps for the current user.
Authentication: Required
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 20 | Maximum results (1-100) |
[
{
"appId": "00000000-0000-4000-8000-000000000001",
"name": "Order Desk",
"naturalId": "admin:order-desk",
"type": "report",
"ownerId": "admin",
"viewedAt": "2026-01-15T16:20:00.000Z"
},
{
"appId": "00000000-0000-4000-8000-000000000002",
"name": "Retired Dashboard",
"naturalId": "admin:retired-dashboard",
"type": "report",
"ownerId": "admin",
"viewedAt": "2026-01-15T16:20:00.000Z"
}
]
Behavior:
- Queries the
audit.app_viewtable for the authenticated user - Returns distinct apps ordered by most recently viewed
- Applies read access filtering, and skips drafts and soft-deleted apps
Permissions Required: None (returns only apps the user can still read)
GET /api/apps/{id}
Retrieve a single app by ID or natural ID.
Authentication: Required
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID (ownerId:slug) |
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk"
},
"inf:owner": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/owner"
},
"inf:draft": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/draft"
},
"inf:app-shares": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/shares"
},
"inf:app-tags": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/tags"
},
"inf:favorite": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/favorite"
},
"inf:app-tag": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/tags/{tagId}",
"templated": true
},
"inf:export-bundle": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_export"
},
"inf:cloud-publish": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_cloud-publish"
},
"inf:copy": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_copy"
},
"inf:print": [
{
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_print"
},
{
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_print",
"title": "Print to PDF"
}
],
"inf:migrate": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_migrate"
},
"inf:deploy": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_deploy"
},
"inf:app-tokens": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/tokens"
},
"inf:app-roles": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/roles"
},
"inf:app-dependencies": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/dependencies"
},
"inf:app-dependency": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/dependencies/{name}",
"templated": true
},
"inf:app-environment": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/environment"
},
"inf:app-environment-var": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/environment/{key}",
"templated": true
},
"inf:app-suggest-icon": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_suggest-icon"
},
"inf:app-modernize-manifest": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_modernize-manifest"
},
"inf:app-draft-diff": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/draft/_diff"
},
"edit": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_edit"
},
"inf:app-share": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/shares/{principalId}",
"templated": true
},
"inf:entrypoint": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/view/",
"title": "index.html"
},
"inf:library": {
"href": "https://informer.example.com/api/libraries/admin%3Aorder-desk-files",
"title": "App Files"
},
"inf:library-contents": {
"href": "https://informer.example.com/api/libraries/admin%3Aorder-desk-files/contents/index.html",
"title": "Raw Contents"
},
"share": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/view/?token=%7Btoken%7D",
"title": "Share Page"
}
},
"naturalId": "admin:order-desk",
"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,
"write": true
},
"id": "00000000-0000-4000-8000-000000000001",
"type": "report",
"name": "Order Desk",
"tenant": "acme",
"ownerId": "admin",
"slug": "order-desk",
"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",
"webhookSecret": "encrypted:iv:0000000000000000",
"dbRolePassword": null,
"deletedAt": null,
"editingId": null,
"deployedAt": null,
"editingSession": null,
"editingHeartbeatAt": null,
"editingLockedBy": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"folderId": null,
"datasets": {},
"library": {
"naturalId": "admin:order-desk-files",
"connectionOwner": "admin",
"isLocal": true,
"permissions": {
"assignTags": true,
"changeOwner": true,
"configure": true,
"connect": true,
"edit": true,
"delete": true,
"write": true,
"sync": true,
"rename": true,
"share": true,
"editConnection": true
},
"id": "00000000-0000-4000-8000-000000000002",
"type": "local",
"name": "Order Desk Files",
"slug": "order-desk-files",
"description": null,
"ownerId": "admin",
"data": null,
"folderId": null,
"shared": false,
"assistantAccess": false,
"embedded": true,
"syncedAt": null,
"syncInterval": null,
"snapshotOfId": null,
"chatId": null,
"source": null,
"sourceId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"integration": null
},
"contentModifiedAt": "2026-01-15T16:20:00.000Z",
"tags": []
}
Key Fields:
| Field | Description |
|---|---|
naturalId | Computed as ownerId:slug (URL-encoded as %3A in links) |
datasets | Object keyed by alias containing AppDataset references |
library | Full embedded library object with its own permissions |
tags | Array of tag ids (empty array if none) |
permissions | Per-action flags (edit, delete, share, copy, …) computed for the requester |
isManaged | Whether the app is deployed/managed (CLI or marketplace origin) |
editingId | If non-null, this row is a draft of the app with that ID |
editingSession / editingLockedBy / editingHeartbeatAt | Edit-lock state while someone has the app open in the builder |
defn | Driver-specific definition (e.g., entryPoint, icon) |
settings | App settings (e.g., sandbox, passDatasets) |
source / sourceId | Origin tracking for imported apps |
Permissions Required: None (404 if the user cannot read this app)
Error Responses:
404 Not Found- App does not exist or user lacks read access
POST /api/apps
Create a new app.
Authentication: Required
Permissions Required: apps:create. The role floor is owned by the
app-type driver: Designer for report, Admin for app. The permission
check runs before the license gate, so unauthorized users cannot probe
license state.
Payload:
| Field | Type | Description |
|---|---|---|
type | string | App driver type. Defaults to app, which requires the Apps license; report creates a Magic Report and is available on every tenant. |
name | string | Display name |
slug | string | URL-friendly identifier (derived from name when omitted) |
ownerId | string | Owner principal ID (defaults to the current user) |
description | string | Description text |
defn | object | App definition (driver-specific) |
settings | object | App settings |
datasets | array | Dataset references to attach ({ datasetId, configId, options }) |
{
"name": "Inventory Watch",
"type": "report",
"description": "Live stock levels with reorder alerts"
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch"
},
"inf:owner": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/owner"
},
"inf:draft": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/draft"
},
"inf:app-shares": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/shares"
},
"inf:app-tags": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/tags"
},
"inf:favorite": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/favorite"
},
"inf:app-tag": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/tags/{tagId}",
"templated": true
},
"inf:export-bundle": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_export"
},
"inf:cloud-publish": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_cloud-publish"
},
"inf:copy": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_copy"
},
"inf:print": [
{
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_print"
},
{
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_print",
"title": "Print to PDF"
}
],
"inf:migrate": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_migrate"
},
"inf:deploy": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_deploy"
},
"inf:app-tokens": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/tokens"
},
"inf:app-roles": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/roles"
},
"inf:app-dependencies": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/dependencies"
},
"inf:app-dependency": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/dependencies/{name}",
"templated": true
},
"inf:app-environment": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/environment"
},
"inf:app-environment-var": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/environment/{key}",
"templated": true
},
"inf:app-suggest-icon": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_suggest-icon"
},
"inf:app-modernize-manifest": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_modernize-manifest"
},
"inf:app-draft-diff": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/draft/_diff"
},
"edit": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/_edit"
},
"inf:app-share": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/shares/{principalId}",
"templated": true
},
"inf:entrypoint": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/view/",
"title": "index.html"
},
"inf:library": {
"href": "https://informer.example.com/api/libraries/00000000-0000-4000-8000-000000000001",
"title": "App Files"
},
"inf:library-contents": {
"href": "https://informer.example.com/api/libraries/00000000-0000-4000-8000-000000000001/contents/index.html",
"title": "Raw Contents"
},
"share": {
"href": "https://informer.example.com/api/apps/admin%3Ainventory-watch/view/?token=%7Btoken%7D",
"title": "Share Page"
}
},
"naturalId": "admin:inventory-watch",
"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,
"write": true
},
"id": "00000000-0000-4000-8000-000000000002",
"type": "report",
"name": "Inventory Watch",
"tenant": "acme",
"ownerId": "admin",
"slug": "inventory-watch",
"description": "Live stock levels with reorder alerts",
"defn": {
"entryPoint": "index.html"
},
"settings": {
"sandbox": true,
"passDatasets": true
},
"origin": "ui",
"source": null,
"sourceId": null,
"shared": false,
"defnUpdatedAt": null,
"libraryId": "00000000-0000-4000-8000-000000000001",
"webhookSecret": "encrypted:iv:0000000000000000",
"dbRolePassword": null,
"deletedAt": null,
"editingId": null,
"deployedAt": null,
"editingSession": null,
"editingHeartbeatAt": null,
"editingLockedBy": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"folderId": null,
"contentModifiedAt": "2026-01-15T16:20:00.000Z"
}
The response is 200 OK with a Location header pointing at the new app.
The app, its library, and any dataset bindings are created in one
transaction.
Error Responses:
400 Bad Request- Missing or invalidtype403 Forbidden- User lacks permission to create apps, or the driver's license gate refused the type. License refusals carrycode: "apps_license_required"plusdata: { field, target }so clients can show an upgrade path instead of a generic error.
PUT /api/apps/{id}
Update an app's metadata. This route deliberately accepts only a narrow field set; content and configuration changes go through the draft workflow.
Authentication: Required
Permissions Required: app:write. The role floor is per app type:
Designer for Magic Reports, Admin for full Apps. Not app:edit: the CLI
syncs name, description, and icon on every npm run deploy, so managed
(deployed) apps must accept this route.
Payload:
| Field | Type | Description |
|---|---|---|
name | string | Display name. Renaming re-slugs the app's naturalId. |
description | string | Description text (null or "" clears it) |
defn | object | Only { icon } is accepted. The icon is merged onto the persisted defn; every other defn key is stripped. |
type | string | App type transition (see below) |
shared | boolean | Shared flag |
folderId | string | Folder assignment (null to unfile) |
Unknown fields, including settings and any other defn key, are stripped
(stripUnknown). This is load-bearing: it keeps the consent screen
(defn.apiDescriptions) and security configuration from being rewritten
outside the gated draft → _commit path.
{
"name": "Order Desk",
"description": "Track, fulfill, and audit open orders",
"defn": {
"icon": "storefront"
}
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001"
},
"inf:owner": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/owner"
},
"inf:draft": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/draft"
},
"inf:app-shares": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/shares"
},
"inf:app-tags": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/tags"
},
"inf:favorite": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/favorite"
},
"inf:app-tag": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/tags/{tagId}",
"templated": true
},
"inf:export-bundle": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_export"
},
"inf:cloud-publish": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_cloud-publish"
},
"inf:copy": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_copy"
},
"inf:print": [
{
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_print"
},
{
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_print",
"title": "Print to PDF"
}
],
"inf:migrate": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_migrate"
},
"inf:deploy": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_deploy"
},
"inf:app-tokens": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/tokens"
},
"inf:app-roles": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/roles"
},
"inf:app-dependencies": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/dependencies"
},
"inf:app-dependency": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/dependencies/{name}",
"templated": true
},
"inf:app-environment": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/environment"
},
"inf:app-environment-var": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/environment/{key}",
"templated": true
},
"inf:app-suggest-icon": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_suggest-icon"
},
"inf:app-modernize-manifest": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_modernize-manifest"
},
"inf:app-draft-diff": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/draft/_diff"
},
"edit": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/_edit"
},
"inf:app-share": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/shares/{principalId}",
"templated": true
},
"inf:entrypoint": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/view/",
"title": "index.html"
},
"inf:library": {
"href": "https://informer.example.com/api/libraries/admin%3Aorder-desk-files",
"title": "App Files"
},
"inf:library-contents": {
"href": "https://informer.example.com/api/libraries/admin%3Aorder-desk-files/contents/index.html",
"title": "Raw Contents"
},
"share": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/view/?token=%7Btoken%7D",
"title": "Share Page"
}
},
"naturalId": "admin:order-desk",
"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,
"write": true
},
"id": "00000000-0000-4000-8000-000000000001",
"type": "report",
"name": "Order Desk",
"tenant": "acme",
"ownerId": "admin",
"slug": "order-desk",
"description": "Track, fulfill, and audit open orders",
"defn": {
"icon": "storefront",
"entryPoint": "index.html"
},
"settings": {
"sandbox": true,
"passDatasets": true
},
"origin": "ui",
"source": null,
"sourceId": null,
"shared": false,
"defnUpdatedAt": null,
"libraryId": "00000000-0000-4000-8000-000000000002",
"webhookSecret": "encrypted:iv:0000000000000000",
"dbRolePassword": null,
"deletedAt": null,
"editingId": null,
"deployedAt": null,
"editingSession": null,
"editingHeartbeatAt": null,
"editingLockedBy": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"folderId": null,
"datasets": [],
"library": {
"naturalId": "admin:order-desk-files",
"connectionOwner": "admin",
"isLocal": true,
"permissions": {
"assignTags": true,
"changeOwner": true,
"configure": true,
"connect": true,
"edit": true,
"delete": true,
"write": true,
"sync": true,
"rename": true,
"share": true,
"editConnection": true
},
"id": "00000000-0000-4000-8000-000000000002",
"type": "local",
"name": "Order Desk Files",
"slug": "order-desk-files",
"description": null,
"ownerId": "admin",
"data": null,
"folderId": null,
"shared": false,
"assistantAccess": false,
"embedded": true,
"syncedAt": null,
"syncInterval": null,
"snapshotOfId": null,
"chatId": null,
"source": null,
"sourceId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"integration": null
},
"editing": null,
"contentModifiedAt": "2026-01-15T16:20:00.000Z"
}
Type transitions:
report→app(upgrade): requires the Apps license and Admin role on the team. Refusals are403withcode: "apps_license_required"anddata.reasonof"license"or"role".app→report(downgrade): refused with400. Apps are a strict superset of Magic Reports, so converting back could orphan features.- Same type or
typeomitted: no-op; the rest of the update proceeds.
Error Responses:
404 Not Found- App does not exist or user lacks read access403 Forbidden- User lacks write permission, or a type upgrade failed its license/role gate
DELETE /api/apps/{id}
Soft-delete an app. The row stays in the database with deletedAt set so the
app can be restored within the retention window (default 30 days, configured
via the inf-app.deleteRetentionDays module option). After the window
expires the app is hard-deleted by the daily app-sandbox-cleanup system
job, which also drops the per-app workspace schema and Postgres role.
To irreversibly remove a soft-deleted app immediately, use
POST /api/apps/\{id\}/_purge. To undo a soft-delete before the retention
window expires, use POST /api/apps/\{id\}/_restore.
Authentication: Required
Permissions Required: app:delete (same role floor as app:write:
Designer for Magic Reports, Admin for full Apps)
Soft-delete behavior:
- The app row is retained with
deletedAtset; the workspace schema and per-app role stay intact so the app can be restored. - The app is hidden from default
GET /api/appsandGET /api/apps-listlistings. Soft-deleted apps surface viaGET /api/apps-list?includeDeleted=only(or?includeDeleted=allto mix soft-deleted rows in with live ones). - AppShare, AppDataset, library, and other associated resources are NOT deleted yet — they're preserved for restore.
- Final hard-delete (which fires
drop_app_schema()and removes shares, datasets, library, etc.) happens viaPOST /api/apps/\{id\}/_purgeor automatically when retention expires.
Error Responses:
404 Not Found- App does not exist or user lacks read access403 Forbidden- User lacks delete permission
POST /api/apps/{id}/_restore
Undo the soft-delete on an app. Restores the row (clears deletedAt) and
makes it visible to the default listings again. The workspace schema and
per-app role are untouched (they were never dropped on soft-delete).
Authentication: Required
Permissions Required: app:restore (same role floor as app:write).
The same role required to soft-delete.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID (works on soft-deleted rows) |
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore"
},
"inf:owner": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/owner"
},
"inf:draft": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/draft"
},
"inf:app-shares": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/shares"
},
"inf:app-tags": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/tags"
},
"inf:favorite": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/favorite"
},
"inf:app-tag": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/tags/{tagId}",
"templated": true
},
"inf:export-bundle": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/_export"
},
"inf:cloud-publish": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/_cloud-publish"
},
"inf:copy": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/_copy"
},
"inf:print": [
{
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/_print"
},
{
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/_print",
"title": "Print to PDF"
}
],
"inf:migrate": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/_migrate"
},
"inf:deploy": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/_deploy"
},
"inf:app-tokens": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/tokens"
},
"inf:app-roles": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/roles"
},
"inf:app-dependencies": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/dependencies"
},
"inf:app-dependency": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/dependencies/{name}",
"templated": true
},
"inf:app-environment": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/environment"
},
"inf:app-environment-var": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/environment/{key}",
"templated": true
},
"inf:app-suggest-icon": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/_suggest-icon"
},
"inf:app-modernize-manifest": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/_modernize-manifest"
},
"inf:app-draft-diff": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/draft/_diff"
},
"edit": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/_edit"
},
"inf:app-share": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_restore/shares/{principalId}",
"templated": true
},
"inf:entrypoint": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/view/",
"title": "index.html"
},
"inf:library": {
"href": "https://informer.example.com/api/libraries/admin%3Aretired-dashboard-files",
"title": "App Files"
},
"inf:library-contents": {
"href": "https://informer.example.com/api/libraries/admin%3Aretired-dashboard-files/contents/index.html",
"title": "Raw Contents"
},
"share": {
"href": "https://informer.example.com/api/apps/admin%3Aretired-dashboard/view/?token=%7Btoken%7D",
"title": "Share Page"
}
},
"naturalId": "admin:retired-dashboard",
"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,
"write": true
},
"id": "00000000-0000-4000-8000-000000000001",
"type": "report",
"name": "Retired Dashboard",
"tenant": "acme",
"ownerId": "admin",
"slug": "retired-dashboard",
"description": "Superseded by Order Desk",
"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",
"webhookSecret": "encrypted:iv:0000000000000000",
"dbRolePassword": null,
"deletedAt": null,
"editingId": null,
"deployedAt": null,
"editingSession": null,
"editingHeartbeatAt": null,
"editingLockedBy": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"folderId": null,
"datasets": [],
"library": {
"naturalId": "admin:retired-dashboard-files",
"connectionOwner": "admin",
"isLocal": true,
"permissions": {
"assignTags": true,
"changeOwner": true,
"configure": true,
"connect": true,
"edit": true,
"delete": true,
"write": true,
"sync": true,
"rename": true,
"share": true,
"editConnection": true
},
"id": "00000000-0000-4000-8000-000000000002",
"type": "local",
"name": "Retired Dashboard Files",
"slug": "retired-dashboard-files",
"description": null,
"ownerId": "admin",
"data": null,
"folderId": null,
"shared": false,
"assistantAccess": false,
"embedded": true,
"syncedAt": null,
"syncInterval": null,
"snapshotOfId": null,
"chatId": null,
"source": null,
"sourceId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"integration": null
},
"editing": null,
"contentModifiedAt": "2026-01-15T16:20:00.000Z"
}
Error Responses:
400 Bad Request- The app is not currently soft-deleted403 Forbidden- User lacks write permission404 Not Found- App does not exist (already purged or never existed)
POST /api/apps/{id}/_purge
Irreversibly destroy a soft-deleted app. Hard-deletes the row, drops the workspace schema and per-app role, and removes all associated AppShare, AppDataset, library, and tag-association records via cascade. There is no recovery from a successful purge.
Authentication: Required
Permissions Required: Data Wizard or higher on the owning team. Stricter than soft-delete because there is no recovery path.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID (works on soft-deleted rows) |
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_purge"
}
},
"purged": true
}
Error Responses:
400 Bad Request- The app is not soft-deleted (must call DELETE first)403 Forbidden- User lacks Data Wizard permission404 Not Found- App does not exist
Notes:
- The daily
app-sandbox-cleanupsystem job calls the same code path for expired soft-deletes — purging via this endpoint is the manual equivalent. - The appDb workspace schema is dropped before the row delete so the
drop_app_schema()trigger's downstream DROP ROLE can succeed cleanly when apps live in a separate database from the main Informer DB.
Favorites
Favorites are per-user; all three routes resolve the app through the read-access scope first (404 when the app isn't visible).
GET /api/apps/{id}/favorite
Returns the current user's favorite record for the app, or 404 Not Found
if the app is not favorited.
PUT /api/apps/{id}/favorite
Add an app to the current user's favorites. Idempotent (upsert).
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/favorite"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000002",
"tenant": "acme",
"appId": "00000000-0000-4000-8000-000000000001",
"principalId": "admin",
"reportId": null,
"datasetId": null,
"queryId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"datasourceId": null,
"jobId": null,
"templateId": null,
"assistantId": null,
"libraryId": null,
"integrationId": null,
"toolkitId": null
}
DELETE /api/apps/{id}/favorite
Remove an app from the current user's favorites.