Skip to main content

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:

ParameterTypeDescription
idstringApp 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 (id and name)
  • theme - Current theme (light or dark)
  • roles - Array of the user's resolved role IDs (see Roles)
  • viewToken - Temporary read-only token for this session
  • user - Current user information
  • datasets - Dataset references with metadata
  • openChat(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 access
  • 403 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:

ParameterTypeDescription
idstringApp UUID or natural ID
pathstringAsset 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 exist
  • 403 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:

ParameterTypeDescription
idstringApp UUID or natural ID
pathstringAPI 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:

TypeAPI Access Granted
datasets_search, fields
queries_execute
datasources_query
integrationsrequest
librariescontents/*
apisExact 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

  1. Use the Proxy: Never try to access /api/ directly
  2. Validate Input: Always validate user input before making API calls
  3. Handle Errors: API calls may fail due to permissions or whitelist restrictions
  4. Respect Read-Only: Don't assume write permissions

For App Administrators

  1. Configure Whitelist: Always include an informer.yaml file with an access section
  2. Minimize Access: Only whitelist necessary endpoints
  3. Review Regularly: Audit app API usage periodically
  4. Use Snapshots: Create snapshots before major changes
  5. Test Thoroughly: Test apps with different user roles

For Platform Administrators

  1. Monitor Usage: Track app API calls for abuse
  2. Enforce Limits: Rate-limit app API calls if needed
  3. Review Whitelists: Audit informer.yaml files in apps
  4. Educate Users: Train developers on security best practices