Runtime & View
The view endpoints serve apps to end users and provide a secure proxy for API access.
Architecture Overview
Apps run in a sandboxed environment with:
- Entry Point: HTML served with context injection
- Static Assets: CSS, JS, images served from library
- API Proxy: Whitelisted access to Informer APIs
- Server Route Dispatch: File-convention server-side handlers via
_server/prefix - View Tokens: Read-only tokens created for each view session
Security Model
Direct API Access Blocked: Apps cannot make direct fetch() calls to /api/ endpoints. This prevents malicious app code from bypassing the whitelist using session cookies.
Proxy Required: All API access must go through /api/apps/{id}/view/api/ which enforces:
- Whitelist validation (informer.yaml)
- Read-only restrictions
- App-specific permissions
Referer Check: The server blocks any /api/ request with a Referer header indicating an app view context (unless it's the proxy route itself).
GET /api/apps/{id}/view
Serve the app's entry point (typically index.html) with injected context.
Authentication: Required (session or token)
Permissions Required: Member role (run permission)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
Response:
Returns HTML content with injected JavaScript variables:
<!DOCTYPE html>
<html>
<head>
<title>Sales Dashboard</title>
<script>
// Injected context
window.__INFORMER__ = {
report: {
id: "d4f8a2b1-1234-5678-90ab-cdef12345678",
name: "Sales Dashboard"
},
theme: "light",
roles: ["viewer", "approver"],
viewToken: "abc123def456...",
user: {
username: "john.doe",
displayName: "John Doe"
},
datasets: {
"sales": {
"id": "dataset-uuid-1",
"name": "Sales Data"
}
}
};
</script>
</head>
<body>
<!-- App content -->
</body>
</html>
Context Injection:
The server injects a window.__INFORMER__ object containing:
report- App identity (idandname)theme- Current theme (lightordark)roles- Array of the user's resolved role IDs (see Roles)viewToken- Temporary read-only token for this sessionuser- Current user informationdatasets- Dataset references with metadataopenChat(opts)- Open the AI copilot with context (see Embedded Chat)showCopilot()- Activate the platform copilot button (hidden by default for apps)registerTool(def)- Register a tool the AI copilot can call at runtime
Entry Point:
The entry point is determined by the app's defn.entryPoint field (default: index.html).
Error Responses:
404 Not Found- App doesn't exist or user lacks access403 Forbidden- User lacks run permission
GET /api/apps/{id}/view/-/{path*}
Serve static assets (CSS, JS, images) from the app's library.
Authentication: Required (session or token)
Permissions Required: Member role (run permission)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
path | string | Asset path (e.g., css/styles.css, images/logo.png) |
Response:
Returns the file content with appropriate Content-Type header.
URL Structure:
The /-/ prefix separates assets from API routes:
/view/-/css/styles.css- Static asset/view/api/datasets- API proxy
Example:
<!-- In index.html -->
<link rel="stylesheet" href="/-/css/styles.css">
<script src="/-/js/app.js"></script>
<img src="/-/images/logo.png">
Error Responses:
404 Not Found- App or asset doesn't exist403 Forbidden- User lacks run permission
API Proxy Routes
The proxy routes allow apps to make API calls while enforcing whitelist restrictions.
GET /api/apps/{id}/view/api/{path*}
Proxy GET requests to Informer APIs.
Authentication: App view token (from context)
Permissions Required: Whitelist validation
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
path | string | API path to proxy (e.g., datasets/sales-data/data) |
Example:
// In app code
const response = await fetch('/api/apps/my-app-id/view/api/datasets/sales-data/data?limit=100');
const data = await response.json();
Whitelist Enforcement:
The proxy checks the app's informer.yaml file to ensure the requested path is allowed. See the Data Access section for whitelist configuration.
POST/PUT/PATCH/DELETE /api/apps/{id}/view/api/{path*}
Proxy write requests to Informer APIs.
Authentication: App view token
Permissions Required: Whitelist validation + write permissions
Payload:
Request body is passed through to the target API.
Example:
// In app code
const response = await fetch('/api/apps/my-app-id/view/api/datasets/sales-data/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filters: [...] })
});
Restrictions:
- Write operations are subject to stricter whitelist validation
- Read-only tokens cannot make write requests
- User's actual permissions are checked on the target resource
Server Route Dispatch
When an app's client code fetches a URL containing the _server/ prefix, the proxy dispatches the request to a registered server route handler instead of forwarding it to an Informer API endpoint.
// Client code — works in both dev mode and production
const orders = await fetch('/api/_server/orders');
const data = await orders.json();
Server routes are defined by files in the app's server/ directory and deployed via POST /api/apps/{id}/_deploy. See Server Routes for full documentation.
Snapshots
Apps can have snapshots for version control and rollback.
GET /api/apps/{id}/snapshots
List all snapshots for an app.
Authentication: Required
Permissions Required: Member+ role (write permission)
Response:
[
{
"id": "snapshot-uuid-1",
"appId": "d4f8a2b1-1234-5678-90ab-cdef12345678",
"name": "Before redesign",
"createdBy": "john.doe",
"createdAt": "2024-02-10T10:00:00.000Z"
}
]
POST /api/apps/{id}/snapshots
Create a snapshot of the current app state.
Authentication: Required
Permissions Required: Member+ role (write permission)
Payload:
{
"name": "Before redesign"
}
Response:
Returns the created snapshot.
{
"id": "snapshot-uuid-1",
"appId": "d4f8a2b1-1234-5678-90ab-cdef12345678",
"name": "Before redesign",
"createdBy": "john.doe",
"createdAt": "2024-02-13T10:30:00.000Z"
}
DELETE /api/apps/{id}/snapshots/{snapshotId}
Delete a snapshot.
Authentication: Required
Permissions Required: Member+ role (write permission)
Response:
Returns 204 No Content on success.
POST /api/apps/{id}/snapshots/{snapshotId}/_restore
Restore an app to a previous snapshot.
Authentication: Required
Permissions Required: Member+ role (write permission)
Response:
Returns the restored app.
Behavior:
- App's defn, settings, and library are restored to snapshot state
- Current state is not automatically snapshotted (create one manually if needed)
- Restoration cannot be undone (unless you have another snapshot)
Data Access Whitelist
Apps should include an informer.yaml file in their library root to configure API access:
# informer.yaml
access:
datasets:
- admin:sales-data
- admin:customers
queries:
- admin:monthly-summary
integrations:
- salesforce
apis:
- POST /api/custom/endpoint
Resource types:
| Type | API Access Granted |
|---|---|
datasets | _search, fields |
queries | _execute |
datasources | _query |
integrations | request |
libraries | contents/* |
apis | Exact method + path match |
Default Behavior:
Without an informer.yaml file (or its access section), the proxy denies all requests.
Common Runtime Patterns
Initialize App with Context
// In your app's JavaScript
const context = window.__INFORMER__;
console.log('Running as:', context.user.displayName);
console.log('Available datasets:', Object.keys(context.datasets));
console.log('User roles:', context.roles);
// Use the view token for API calls
fetch(`/api/apps/${context.report.id}/view/api/datasets/${context.datasets.sales.id}/data`, {
headers: {
'Authorization': `Bearer ${context.viewToken}`
}
});
Load Dataset Data
async function loadSalesData() {
const context = window.__INFORMER__;
const datasetId = context.datasets.sales.id;
const response = await fetch(
`/api/apps/${context.report.id}/view/api/datasets/${datasetId}/data?limit=1000`
);
if (!response.ok) {
throw new Error('Failed to load data');
}
const data = await response.json();
return data.items;
}
Run a Query
async function runQuery(filters) {
const context = window.__INFORMER__;
const datasetId = context.datasets.sales.id;
const response = await fetch(
`/api/apps/${context.report.id}/view/api/datasets/${datasetId}/run`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filters })
}
);
return await response.json();
}
Handle Errors
async function fetchWithErrorHandling(path) {
const context = window.__INFORMER__;
const url = `/api/apps/${context.report.id}/view/api/${path}`;
try {
const response = await fetch(url);
if (!response.ok) {
if (response.status === 403) {
throw new Error('This API endpoint is not whitelisted');
} else if (response.status === 404) {
throw new Error('Resource not found');
} else {
throw new Error(`API error: ${response.status}`);
}
}
return await response.json();
} catch (error) {
console.error('API call failed:', error);
throw error;
}
}
Create Snapshot Before Changes
// Create a snapshot
const snapshot = await POST('/api/apps/analytics:sales-dashboard/snapshots', {
name: `Before ${new Date().toLocaleDateString()}`
});
// Make changes...
await PUT('/api/apps/analytics:sales-dashboard/contents/index.html', {
content: newHTML
});
// If something goes wrong, restore
await POST(`/api/apps/analytics:sales-dashboard/snapshots/${snapshot.id}/_restore`);
Security Best Practices
For App Developers
- Use the Proxy: Never try to access
/api/directly - Validate Input: Always validate user input before making API calls
- Handle Errors: API calls may fail due to permissions or whitelist restrictions
- Respect Read-Only: Don't assume write permissions
For App Administrators
- Configure Whitelist: Always include an
informer.yamlfile with anaccesssection - Minimize Access: Only whitelist necessary endpoints
- Review Regularly: Audit app API usage periodically
- Use Snapshots: Create snapshots before major changes
- Test Thoroughly: Test apps with different user roles
For Platform Administrators
- Monitor Usage: Track app API calls for abuse
- Enforce Limits: Rate-limit app API calls if needed
- Review Whitelists: Audit informer.yaml files in apps
- Educate Users: Train developers on security best practices