Skip to main content

Core CRUD

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

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:

ParameterTypeDefaultDescription
qstringFull-text search query (searches name, slug, description)
sortstringAny app attribute (name, createdAt, updatedAt, …); prefix with - for descending. Unknown fields are rejected with 400.
limitinteger20Results per page (0 disables the limit)
startinteger0Pagination offset
Search AppsGET /api/apps?q=order&limit=10
q matches name, slug, and description. Results are a paginated HAL collection.
Response · 200
{
"_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
}
]
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

Key Fields:

FieldDescription
startPagination offset
countNumber of items in this page
totalTotal matching apps
aggs.totalTotal apps accessible to the user
permissions.createWhether 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:

ParameterTypeDefaultDescription
sortstringnamepopular (30-day views), trending (7-day views), or recent (last viewed)
includeDeletedstringonly returns soft-deleted apps; all mixes them in with live ones
Flat list for galleriesGET /api/apps-list
Plain array (not HAL). sort accepts popular, trending, or recent; includeDeleted accepts all or only.
Response · 200
[
{
"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"
]
Captured from the API examples test suite; ids and timestamps are normalized.

Key Fields:

FieldDescription
typeNameLocalized display label for the app type (resolved from the app-type driver in the requester's language)
typeRaw driver type (app or report)
ownerNameDisplay name of the owning user or team
isManagedWhether the app is deployed/managed (CLI or marketplace origin)
favoriteWhether the current user favorited the app
viewStatstotalViews, uniqueViewers, views7d, views30d from the view audit
contentModifiedAtMost recent file modification in the app's library
iconThe defn.icon value, when set
datasourceStub object (apps have no primary datasource; provided for report-list compatibility)

Special Behaviors:

  • Custom Favicon: If an app has a favicon.svg file in its library root, image is set to /api/apps/{naturalId}/contents/favicon.svg (with a cache-busting ?t= parameter) and the default icon moves to fallbackImage
  • Docs file: docsFile is present when the app's library root contains docs.html or README.md (docs.html wins)
  • Widgets: widgets is 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:

ParameterTypeDefaultDescription
limitinteger20Maximum results (1-100)
Recently viewed appsGET /api/apps/recent?limit=5
Response · 200
[
{
"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"
}
]
Captured from the API examples test suite; ids and timestamps are normalized.

Behavior:

  • Queries the audit.app_view table 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:

ParameterTypeDescription
idstringApp UUID or natural ID (ownerId:slug)
Get an AppGET /api/apps/admin%3Aorder-desk
The id may be the UUID or the ownerId:slug natural id.
Response · 200
{
"_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": []
}
Captured from the API examples test suite; ids and timestamps are normalized.

Key Fields:

FieldDescription
naturalIdComputed as ownerId:slug (URL-encoded as %3A in links)
datasetsObject keyed by alias containing AppDataset references
libraryFull embedded library object with its own permissions
tagsArray of tag ids (empty array if none)
permissionsPer-action flags (edit, delete, share, copy, …) computed for the requester
isManagedWhether the app is deployed/managed (CLI or marketplace origin)
editingIdIf non-null, this row is a draft of the app with that ID
editingSession / editingLockedBy / editingHeartbeatAtEdit-lock state while someone has the app open in the builder
defnDriver-specific definition (e.g., entryPoint, icon)
settingsApp settings (e.g., sandbox, passDatasets)
source / sourceIdOrigin 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:

FieldTypeDescription
typestringApp driver type. Defaults to app, which requires the Apps license; report creates a Magic Report and is available on every tenant.
namestringDisplay name
slugstringURL-friendly identifier (derived from name when omitted)
ownerIdstringOwner principal ID (defaults to the current user)
descriptionstringDescription text
defnobjectApp definition (driver-specific)
settingsobjectApp settings
datasetsarrayDataset references to attach ({ datasetId, configId, options })
Create an AppPOST /api/apps
type defaults to 'app' (requires the Apps license); type 'report' creates a Magic Report and is available on every tenant.
Request body
{
"name": "Inventory Watch",
"type": "report",
"description": "Live stock levels with reorder alerts"
}
Response · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

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 invalid type
  • 403 Forbidden - User lacks permission to create apps, or the driver's license gate refused the type. License refusals carry code: "apps_license_required" plus data: { 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:

FieldTypeDescription
namestringDisplay name. Renaming re-slugs the app's naturalId.
descriptionstringDescription text (null or "" clears it)
defnobjectOnly { icon } is accepted. The icon is merged onto the persisted defn; every other defn key is stripped.
typestringApp type transition (see below)
sharedbooleanShared flag
folderIdstringFolder 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.

Update name, description, and iconPUT /api/apps/00000000-0000-4000-8000-000000000001
defn accepts only { icon } here; all other defn keys and settings are stripped. Content changes go through the draft workflow.
Request body
{
"name": "Order Desk",
"description": "Track, fulfill, and audit open orders",
"defn": {
"icon": "storefront"
}
}
Response · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

Type transitions:

  • reportapp (upgrade): requires the Apps license and Admin role on the team. Refusals are 403 with code: "apps_license_required" and data.reason of "license" or "role".
  • appreport (downgrade): refused with 400. Apps are a strict superset of Magic Reports, so converting back could orphan features.
  • Same type or type omitted: no-op; the rest of the update proceeds.

Error Responses:

  • 404 Not Found - App does not exist or user lacks read access
  • 403 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 an AppDELETE /api/apps/00000000-0000-4000-8000-000000000001
Responds 200 with an empty body. The row is kept with deletedAt set and disappears from default listings; restore is possible until the retention window expires.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.

Soft-delete behavior:

  • The app row is retained with deletedAt set; the workspace schema and per-app role stay intact so the app can be restored.
  • The app is hidden from default GET /api/apps and GET /api/apps-list listings. Soft-deleted apps surface via GET /api/apps-list?includeDeleted=only (or ?includeDeleted=all to 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 via POST /api/apps/\{id\}/_purge or automatically when retention expires.

Error Responses:

  • 404 Not Found - App does not exist or user lacks read access
  • 403 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:

ParameterTypeDescription
idstringApp UUID or natural ID (works on soft-deleted rows)
Restore a soft-deleted AppPOST /api/apps/00000000-0000-4000-8000-000000000001/_restore
Response · 200
{
"_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"
}
Captured from the API examples test suite; ids and timestamps are normalized.

Error Responses:

  • 400 Bad Request - The app is not currently soft-deleted
  • 403 Forbidden - User lacks write permission
  • 404 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:

ParameterTypeDescription
idstringApp UUID or natural ID (works on soft-deleted rows)
Purge a soft-deleted AppPOST /api/apps/00000000-0000-4000-8000-000000000001/_purge
Irreversible: drops the workspace schema and removes the row. The app must already be soft-deleted.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/00000000-0000-4000-8000-000000000001/_purge"
}
},
"purged": true
}
Captured from the API examples test suite; ids and timestamps are normalized.

Error Responses:

  • 400 Bad Request - The app is not soft-deleted (must call DELETE first)
  • 403 Forbidden - User lacks Data Wizard permission
  • 404 Not Found - App does not exist

Notes:

  • The daily app-sandbox-cleanup system 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).

Favorite an AppPUT /api/apps/00000000-0000-4000-8000-000000000001/favorite
Response · 200
{
"_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
}
Captured from the API examples test suite; ids and timestamps are normalized.

DELETE /api/apps/{id}/favorite

Remove an app from the current user's favorites.

Unfavorite an AppDELETE /api/apps/00000000-0000-4000-8000-000000000001/favorite
DELETE responses here are 200 with an empty body, not 204.
Response · 200 · no body
Captured from the API examples test suite; ids and timestamps are normalized.