Embed SDK
The Querri Embed SDK lets you embed dashboards, projects, and the Library into any web application. It creates a secure, sandboxed iframe and handles authentication, theming, and communication automatically.
Zero dependencies. Vanilla JavaScript. ~4KB minified.
Installation
Section titled “Installation”npm install @querri-inc/embedOr load via script tag:
<script src="https://app.querri.com/sdk/querri-embed.js"></script>The package exports two entry points:
| Import | Use |
|---|---|
@querri-inc/embed/server | Server SDK — Node.js backend, manages users/sessions/policies |
@querri-inc/embed (script tag) | Client SDK — browser, renders the embed iframe |
Quick Start
Section titled “Quick Start”The fastest way to get a user-scoped embed running:
1. Backend (Node.js)
import { Querri } from '@querri-inc/embed/server';
const querri = new Querri({ apiKey: process.env.QUERRI_API_KEY, // qk_... orgId: process.env.QUERRI_ORG_ID, // org_... host: 'https://app.querri.com',});
// Create a session for an end userapp.get('/api/querri-token', async (req, res) => { const session = await querri.getSession({ user: { external_id: req.user.id, // your app's user ID email: req.user.email, first_name: req.user.firstName, last_name: req.user.lastName, }, });
res.json({ sessionToken: session.session_token });});2. Frontend (HTML)
<div id="querri-container" style="width: 100%; height: 600px;"></div>
<script src="https://app.querri.com/sdk/querri-embed.js"></script><script> var querri = QuerriEmbed.create('#querri-container', { serverUrl: 'https://app.querri.com', auth: { fetchSessionToken: async function () { var resp = await fetch('/api/querri-token'); var data = await resp.json(); return data.sessionToken; }, }, startView: '/library', chrome: { header: { show: false } }, });
querri.on('ready', function () { console.log('Querri embed loaded'); });</script>Authentication Modes
Section titled “Authentication Modes”The SDK supports three authentication modes. Choose based on your use case:
| Share Key | Server Token | Popup Login | |
|---|---|---|---|
| User login required? | No | No | Yes |
| Server-side code needed? | No | Yes | No |
| Per-user data isolation? | No (anonymous) | Yes | Yes |
| Row-level security? | No | Yes | Yes |
| Best for | Public dashboards | SaaS embedding, white-label | Internal tools |
Mode 1: Share Key (Public Embeds)
Section titled “Mode 1: Share Key (Public Embeds)”For public dashboards and reports that don’t require login. Generate a share key in Querri (Dashboard > Share > Create Link):
QuerriEmbed.create('#container', { serverUrl: 'https://app.querri.com', auth: { shareKey: 'abc123...', org: 'org_abc123', }, startView: '/builder/dashboard/DASHBOARD_UUID',});Mode 2: Server Token (Enterprise / White-Label)
Section titled “Mode 2: Server Token (Enterprise / White-Label)”For embedding Querri behind your own authentication. Your backend uses the server SDK to create a scoped session token:
QuerriEmbed.create('#container', { serverUrl: 'https://app.querri.com', auth: { fetchSessionToken: async function () { var resp = await fetch('/api/querri-token'); var data = await resp.json(); return data.sessionToken; }, }, startView: '/library',});See Server SDK below for the backend implementation, including user provisioning, data upload, sharing, and row-level security.
Mode 3: Popup Login (User Accounts)
Section titled “Mode 3: Popup Login (User Accounts)”For internal tools where users have Querri accounts. The SDK opens a popup for authentication and caches the token for return visits:
QuerriEmbed.create('#container', { serverUrl: 'https://app.querri.com', auth: 'login', startView: '/home',});Configuration
Section titled “Configuration”| Option | Type | Required | Default | Description |
|---|---|---|---|---|
serverUrl | string | Yes | — | Your Querri instance URL |
auth | string or object | Yes | — | Authentication mode |
startView | string | No | null | Initial path (e.g., /library, /builder/dashboard/UUID) |
chrome | object | No | { sidebar: { show: false } } | UI visibility options |
theme | object | No | {} | CSS custom property overrides |
Start Views
Section titled “Start Views”| Path | What it shows |
|---|---|
/library | The user’s data sources (Library page) |
/home | Home page |
/builder/dashboard/UUID | A specific dashboard |
/chat/UUID | A specific project’s chat |
Chrome Options
Section titled “Chrome Options”chrome: { header: { show: false }, // Hide the top navigation bar sidebar: { show: false }, // Hide the sidebar (default)}Events
Section titled “Events”querri.on('ready', function () { /* Loaded and authenticated */ });querri.on('error', function (data) { /* { code, message } */ });querri.on('session-expired', function () { /* Auto-retries in Mode 2/3 */ });querri.on('navigation', function (data) { /* { path } */ });Methods
Section titled “Methods”instance.on(event, callback)— register an event listenerinstance.off(event, callback)— remove an event listenerinstance.destroy()— remove the embed and clean up all resources
Multiple Embeds
Section titled “Multiple Embeds”Each QuerriEmbed.create() call creates an independent instance. You can embed multiple views on a single page, each with its own auth and configuration.
Server SDK
Section titled “Server SDK”The server SDK (@querri-inc/embed/server) manages the full lifecycle of embedded users: provisioning users, uploading data, creating access policies, sharing resources, and generating session tokens.
Initialize
Section titled “Initialize”import { Querri } from '@querri-inc/embed/server';
const querri = new Querri({ apiKey: process.env.QUERRI_API_KEY, // qk_... key orgId: process.env.QUERRI_ORG_ID, // org_... ID host: 'https://app.querri.com', // or http://localhost for dev});You can also pass just the API key as a string: new Querri('qk_...'). The org ID falls back to the QUERRI_ORG_ID environment variable.
getSession — The Main Entry Point
Section titled “getSession — The Main Entry Point”getSession() is the flagship method. It handles user provisioning, policy management, and session creation in a single call:
const session = await querri.getSession({ user: { external_id: 'user-123', // your app's user ID email: 'alice@example.com', first_name: 'Alice', last_name: 'Smith', }, access: { sources: ['SOURCE_UUID'], // data sources this user can see filters: { region: 'US' }, // row-level filter }, ttl: 3600, // session lifetime in seconds (default: 1 hour)});
// session.session_token — pass to the client SDK// session.user_id — Querri's internal user ID// session.external_id — your external_id echoed back// session.expires_in — seconds until expiryWhat getSession() does internally:
- Creates or finds the user by
external_id - Hashes the access spec to generate a deterministic policy name
- Creates the access policy if it doesn’t exist, or finds the existing one
- Assigns the user to the policy
- Creates an embed session token
- Returns the token and user metadata
User shorthand — if you only need the external ID:
const session = await querri.getSession({ user: 'user-123' });Access Options
Section titled “Access Options”You can control what data the user sees in two ways:
Inline filters (recommended for most cases) — the SDK auto-manages the policy:
access: { sources: ['source-uuid-1', 'source-uuid-2'], filters: { region: 'US', // single value department: ['Sales', 'Marketing'], // multiple values (OR) },}Pre-created policy IDs — when you manage policies yourself:
access: { policy_ids: ['policy-uuid-1', 'policy-uuid-2'],}No access block — the user gets full access to all shared resources (no RLS filtering).
Upload Data
Section titled “Upload Data”Upload CSV, Excel, JSON, or Parquet files programmatically:
import { readFileSync } from 'fs';
const file = new Blob([readFileSync('./data.csv')], { type: 'text/csv' });const uploaded = await querri.files.upload(file, 'quarterly-report.csv');
console.log(uploaded.id); // source UUID — use this for policies and sharingconsole.log(uploaded.name); // 'quarterly-report.csv'The file is automatically processed (parsed, columnar storage created) after upload. It appears in the Library once processing completes.
Share Data Sources
Section titled “Share Data Sources”After uploading, you need to share the source so embed users can see it. There are two approaches:
Organization-wide sharing — all users in the org can see the source:
// Not yet available in the SDK — use the API directlyawait fetch('https://app.querri.com/api/v1/sources/SOURCE_UUID/org-share', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.QUERRI_API_KEY}`, 'X-Tenant-ID': process.env.QUERRI_ORG_ID, 'Content-Type': 'application/json', }, body: JSON.stringify({ enabled: true }),});Per-user sharing — share with specific users:
await querri.sharing.shareProject(projectId, userId, 'view');await querri.sharing.shareDashboard(dashboardId, userId, 'view');Access Policies (Row-Level Security)
Section titled “Access Policies (Row-Level Security)”Access policies control which rows each user sees. If you use getSession() with inline access.filters, policies are auto-managed for you. But you can also manage them directly:
Create a policy:
const policy = await querri.policies.create({ name: 'US Region Only', description: 'Only see US rows', source_ids: ['source-uuid'], row_filters: [{ column: 'region', values: ['US'] }],});Assign users to a policy:
await querri.policies.assignUsers(policy.id, { user_ids: ['user-id-1', 'user-id-2'],});Verify what a user can see:
const resolved = await querri.policies.resolve(userId, sourceId);console.log(resolved.where_clause); // "region" IN ('US')Discover available columns (for building policy UIs):
const columns = await querri.policies.columns(sourceId);// [{ source_id, source_name, columns: [{ name, type }] }]How Policies Combine
Section titled “How Policies Combine”- Same column, multiple policies = OR. Policies for
region = USandregion = EU→ user sees US or EU rows. - Different columns = AND. Policies for
region = USanddepartment = Sales→ user sees only rows matching both. - No policies assigned = full access (permissive default).
User Management
Section titled “User Management”// Create or find a user by external IDconst user = await querri.users.getOrCreate('ext-123', { email: 'alice@example.com', first_name: 'Alice', last_name: 'Smith', role: 'viewer',});
// List usersconst page = await querri.users.list({ limit: 50 });for await (const user of page) { console.log(user.email, user.external_id);}
// Update a userawait querri.users.update(user.id, { role: 'editor' });
// Delete a userawait querri.users.del(user.id);User-Scoped Client
Section titled “User-Scoped Client”After creating a session, you can create a client that acts as the user. This is useful for server-side operations that should respect the user’s permissions:
const session = await querri.getSession({ user: 'ext-123' });const userClient = querri.asUser(session);
// These calls are FGA-filtered — only returns what the user has access toconst projects = await userClient.projects.list();const dashboards = await userClient.dashboards.list();const sources = await userClient.data.sources();Embed Sessions
Section titled “Embed Sessions”If you need lower-level session control beyond getSession():
// Create a session directlyconst session = await querri.embed.createSession({ user_id: 'user-id', origin: 'https://myapp.com', ttl: 7200,});
// Refresh before expiryconst refreshed = await querri.embed.refreshSession(session.session_token);
// List active sessionsconst sessions = await querri.embed.listSessions();
// Revoke a specific sessionawait querri.embed.revokeSession(sessionId);
// Revoke all sessions for a userawait querri.embed.revokeUserSessions(userId);Complete Example: Multi-Tenant Embed with RLS
Section titled “Complete Example: Multi-Tenant Embed with RLS”This example shows a complete setup where each customer sees only their data from a shared dataset.
1. One-time setup (upload data and share it):
import { Querri } from '@querri-inc/embed/server';import { readFileSync } from 'fs';
const querri = new Querri({ apiKey: process.env.QUERRI_API_KEY, orgId: process.env.QUERRI_ORG_ID,});
// Upload a dataset with a tenant_id columnconst file = new Blob([readFileSync('./all-tenants-data.csv')]);const source = await querri.files.upload(file, 'tenant-data.csv');console.log('Source ID:', source.id); // Save this
// Share org-wide (all embed users can see it, RLS filters the rows)await fetch(`${process.env.QUERRI_HOST}/api/v1/sources/${source.id}/org-share`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.QUERRI_API_KEY}`, 'X-Tenant-ID': process.env.QUERRI_ORG_ID, 'Content-Type': 'application/json', }, body: JSON.stringify({ enabled: true }),});2. Per-request session creation (in your API route):
app.get('/api/querri-token', async (req, res) => { const session = await querri.getSession({ user: { external_id: req.user.id, email: req.user.email, first_name: req.user.firstName, last_name: req.user.lastName, }, access: { sources: [process.env.QUERRI_SOURCE_ID], filters: { tenant_id: req.user.tenantId }, }, });
res.json({ sessionToken: session.session_token });});3. Frontend embed:
<div id="querri" style="width: 100%; height: calc(100vh - 64px);"></div><script src="https://app.querri.com/sdk/querri-embed.js"></script><script> QuerriEmbed.create('#querri', { serverUrl: 'https://app.querri.com', auth: { fetchSessionToken: async () => { const r = await fetch('/api/querri-token'); return (await r.json()).sessionToken; }, }, startView: '/library', chrome: { header: { show: false } }, });</script>Each tenant sees only their rows. No separate datasets, no per-tenant configuration. One upload, one embed, automatic filtering.
Security
Section titled “Security”- All communication uses
postMessagewith strict origin verification - Credentials never cross the iframe boundary directly
- The iframe installs a fetch interceptor to attach auth headers internally
- HTTPS is required in production
- API keys (
qk_*) should only be used server-side — never expose them in client code - Session tokens (
es_*) are safe for the browser — they are scoped and time-limited
If your site uses a Content Security Policy, allow framing from your Querri instance:
Content-Security-Policy: frame-src https://app.querri.com;Embed UI Configurator
Section titled “Embed UI Configurator”The Querri app includes an interactive Embed UI configurator at Settings > Embed. Use it to:
- Select authentication mode and enter credentials
- Choose which view to embed
- Toggle UI chrome (sidebar, header)
- Apply custom theme colors
- Preview the embed live
- Copy ready-to-use embed code
Error Handling
Section titled “Error Handling”The server SDK throws typed errors for different failure modes:
import { AuthenticationError, NotFoundError, RateLimitError } from '@querri-inc/embed/server';
try { const session = await querri.getSession({ user: 'ext-123' });} catch (err) { if (err instanceof AuthenticationError) { // Invalid API key } else if (err instanceof RateLimitError) { // Back off and retry after err.retryAfter seconds } else if (err instanceof NotFoundError) { // Resource not found }}The SDK automatically retries on 429 and 5xx errors (up to maxRetries, default 2).
API Reference
Section titled “API Reference”For the complete V1 API endpoint reference, see API Reference. The server SDK wraps these endpoints:
| SDK Resource | API Endpoints |
|---|---|
querri.users | /v1/embed/users/* |
querri.embed | /v1/embed/sessions/* |
querri.policies | /v1/access/policies/*, /v1/access/resolve, /v1/access/columns |
querri.files | /v1/files/* |
querri.projects | /v1/projects/* |
querri.dashboards | /v1/dashboards/* |
querri.data | /v1/data/* |
querri.sharing | /v1/projects/*/shares, /v1/dashboards/*/shares |
querri.sources | /v1/sources/* |
querri.keys | /v1/keys/* |
querri.audit | /v1/audit/* |
querri.usage | /v1/usage/* |