Tokens
Apps use the app_view token type in two distinct ways, and it pays to keep
them apart:
| Kind | Created by | Stored? | Purpose |
|---|---|---|---|
| Runtime view tokens | The platform, automatically, when it serves an app's host page | No. Stateless JWTs; all claims (appId, username, roles) travel in the token itself | Authorize the app's own API traffic (proxy calls, handler dispatch, assets) for the duration of a view |
| External-link tokens | A person, via the External Links dialog or this API | Yes. A Token row per link | Share an app with people who have no Informer account |
GET /api/apps/{id}/tokens lists only the second kind. Runtime tokens never
appear in any list because they are never written to the database.
GET /api/apps/{id}/tokens
Get the external-link token rows associated with an app.
Authentication: Required
Permissions Required: None (any user who can read the app)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/tokens"
}
},
"_embedded": {
"inf:app-token": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/tokens/00000000-0000-4000-8000-000000000001"
},
"inf:token-type": {
"href": "https://informer.example.com/api/token-types/app_view"
}
},
"permissions": {
"edit": true,
"delete": true
},
"id": "00000000-0000-4000-8000-000000000001",
"type": "app_view",
"readOnly": true,
"notes": "Quarterly ops review link",
"restrict": null,
"host": null,
"cidr": null,
"data": {
"roles": []
},
"requests": 0,
"blocked": 0,
"lastAccessedAt": null,
"expiresAt": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"username": "admin",
"reportId": null,
"queryId": null,
"visualId": null,
"datasourceId": null,
"datasetId": null,
"appId": "00000000-0000-4000-8000-000000000002",
"assistantId": null,
"oauthClientId": null,
"key": "eyJhbGciOiJIUzI1NiJ9.example-token-1.signature"
}
]
}
}
Rows are embedded under _embedded["inf:app-token"]. Each row's key is the
shareable token string, a JWT signed at response time; it is never stored in
the database (this prevents session stealing by reporting on Informer's own
tables).
Error Responses:
404 Not Found- App doesn't exist or user lacks access
Creating an external link
External-link tokens are created through the general Token API
(POST /api/tokens), not through an app-specific endpoint. This is exactly
the request the External Links dialog sends:
{
"type": "app_view",
"appId": "00000000-0000-4000-8000-000000000001",
"readOnly": true,
"notes": "Quarterly ops review link",
"data": {
"roles": []
}
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/tokens/00000000-0000-4000-8000-000000000002"
},
"inf:token-type": {
"href": "https://informer.example.com/api/token-types/app_view"
}
},
"resource": "Order Desk",
"permissions": {
"edit": true,
"delete": true
},
"id": "00000000-0000-4000-8000-000000000002",
"type": "app_view",
"readOnly": true,
"notes": "Quarterly ops review link",
"restrict": null,
"host": null,
"cidr": null,
"data": {
"roles": []
},
"requests": 0,
"blocked": 0,
"lastAccessedAt": null,
"expiresAt": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"username": "admin",
"reportId": null,
"queryId": null,
"visualId": null,
"datasourceId": null,
"datasetId": null,
"appId": "00000000-0000-4000-8000-000000000001",
"assistantId": null,
"oauthClientId": null,
"user": {
"permissions": {
"changeDomain": true,
"changeProfile": true,
"changePassword": false,
"delete": true,
"edit": true,
"reassign": true,
"superuser": true,
"enable": true,
"changeTheme": true,
"lock": true,
"setGlobalPermissions": true,
"manageLogin": false
},
"displayName": "acme Administrator",
"email": null
},
"assistant": null,
"oauthClient": null,
"key": "eyJhbGciOiJIUzI1NiJ9.example-token-1.signature",
"_embedded": {
"inf:app": {
"_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/00000000-0000-4000-8000-000000000003",
"title": "App Files"
},
"inf:library-contents": {
"href": "https://informer.example.com/api/libraries/00000000-0000-4000-8000-000000000003/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": null,
"defn": {
"entryPoint": "index.html"
},
"settings": {
"sandbox": true,
"passDatasets": true
},
"origin": "ui",
"source": null,
"sourceId": null,
"shared": false,
"defnUpdatedAt": null,
"libraryId": "00000000-0000-4000-8000-000000000003",
"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"
}
}
}
Payload Fields:
| Field | Type | Description |
|---|---|---|
type | string | Must be "app_view" (underscore, not a dash) |
appId | string | App UUID. Natural ids are rejected (appId is a UUID foreign key); resolve the app first if you only have ownerId:slug. |
readOnly | boolean | The External Links dialog always sends true, restricting the link to GETs and routes explicitly marked read-only safe |
notes | string | Free text for tracking where the link is used |
restrict | string | Optional network restriction: host or cidr |
host | string | Hostname or IP, when restrict: "host" |
cidr | string | IP range (e.g. 192.168.1.0/24), when restrict: "cidr" |
data.roles | array | App role ids (from the manifest's roles:) granted to visitors using this link |
Permissions Required: tokens:create
The response is 201 Created. The key in the response is the token string
to embed in the share URL.
The share URL
https://<your-informer>/api/apps/{id}/view?token=<key>
The token travels as a ?token= query parameter on the view URL; visitors
need no Informer account and see only the app.
Editing and revoking
PUT /api/tokens/{tokenId}updates notes, network restrictions, and roles (the External Links dialog's edit mode).DELETE /api/tokens/{tokenId}revokes the link immediately.
What an app_view token can reach
Authorization is a closed allowlist of route ids, checked on every request. A token (runtime or external-link) authorizes only the app-render surfaces:
GET /api/apps/{id}/view/{path*}- the app's host page and SPA routesGET /api/apps/{id}/launch/{path*}- the standalone/home-screen entryGET /api/apps/{id}/launch/icon-180.png- the iOS tile icon/api/apps/{id}/view/api/{path*}- the data-access proxy (all methods, subject to the app's declared API whitelist)/api/apps/{id}/view/_/{path*}- the app's own server-route handlersGET /api/apps/{id}/view/-/{path*}- static assets
Everything else returns 401, even with a valid token for that app: the
app detail GET, _export, _copy, contents, and every other app-scoped
route are outside the allowlist. The token must also match the app in the
URL (appId or natural id), so a token for one app can never be replayed
against another.
Read-only enforcement: when readOnly: true (every external link), the
session additionally rejects any request that is not a GET or not explicitly
marked read-only safe. Runtime view tokens are minted with
readOnly: false because apps legitimately POST through the proxy; their
gating comes from the route allowlist and the app's API whitelist.
Security Considerations
- Treat the key as a password. Anyone holding the URL has the link's access until the row is deleted.
- Tokens are visible to anyone who can read the app via the list route,
including each link's
key. - Scope with roles. Grant link visitors only the app roles they need;
an empty
data.rolesarray grants none. - Pin the network when you can.
restrict: "cidr"keeps a link from working outside your office or VPN range. - The data whitelist still applies. Proxy calls through
/view/api/are validated against the app's declared API access, regardless of who is asking.
Related Endpoints
GET /api/apps/{id}/webhook-tokens- revocable tokens for inbound webhooks (a separate token type)GET /api/tokens/GET /api/tokens/{id}- the general Token APIGET /api/apps/{id}/data-access- the whitelist that bounds proxy calls