Share & Export
Embed dashboards for each person
An embed app puts DataSquares dashboards and reports inside your own product and shows each person their own rows. It works with two keys:
- The company key. A company administrator makes an embed app under Administration → Embedding. It has a client id and a key (a key id and a secret). The secret is shown once and stays on your server: it never reaches a browser.
- The person’s id. For each person, your server signs a short-lived token with that key, naming the person, their tenant, their role, the values their rows are filtered by and the dashboard or report. The browser carries only that token. DataSquares checks it and swaps it, once, for a session bound to your site.
Every view and every Ask AI question is then counted for the person and for their tenant.
Internal or customer-facing
Section titled “Internal or customer-facing”You choose when you make the app, and it can’t be changed afterwards: make another app for the other kind.
| Internal (Business) | Customer-facing (Enterprise) | |
|---|---|---|
| Who sees it | People in your company who are DataSquares users | Your own customers, who are not DataSquares users |
The token’s sub |
The person’s DataSquares email or SCIM id | Your own stable id for the person; no email is needed |
| Their rows | Their own row-level security and masking, exactly as when signed in | The app’s row rules, from the token’s attributes |
| Ask AI | Their own AI credits and your AI settings for their role | Your company’s bought AI credits, never below zero |
| How it is priced | Their seats; no one is added | By views: 20,000 a month included |
An internal person must be a member of the app’s workspace (or a company administrator), active, and matched by exactly one SCIM id or email. They open only what DataSquares itself would open for them: a dashboard their role may view or that is shared with them (or a group of theirs), a report their role may view, a published presentation they have a role on. No user is ever created, and each internal person is one person however a token names them (email or SCIM id). An internal token carries no attributes.
A customer whose sub happens to be a member’s email is another person: customers and people in your company are
counted apart, and never share a session, a view or a question.
Make an embed app
Section titled “Make an embed app”- Open Administration → Embedding and choose New embed app.
- Name it and choose Who it is for.
- Tick What it may show: the dashboards, reports and published presentations of this workspace it may open. A token can name only these. Find a dashboard, report or presentation searches them by name; what you have ticked stays listed whatever you search. A presentation is shown in present mode, never its editor, without Ask AI (see Links, embedding and kiosks).
- List the Sites it may be shown on, one per line:
portal.example.com, or*.example.comfor its subdomains (notexample.comitself). A session works only on the site it was opened on. Every site (*) is not allowed. - For a customer-facing app, write its Row rules, one per line as
attribute = field, such asaccount = account_id(see Each person’s rows). With none, the form says that every person sees every row the view shows, and makes the app only once you tick Show every person every row. - Choose whether to Offer Ask AI to people whose token allows it: the people whose token’s role is
viewer_ai, which your server sets in the token (it is not a seat). Choose any monthly AI credit budget per person and per tenant and the questions allowed an hour (20 a person and 200 a tenant unless you change them). - Choose Make the app. Copy the client id, the key id and the secret now: the secret is not shown again. Keep it in your server’s secret store.
Each app card shows its client id, its keys by their last four characters with when each was made and last used, what it shows (a dashboard deleted since says so), its sites, its rules and its Ask AI settings. Edit changes everything but who it is for. Taking a dashboard, a report or a site off the app ends the sessions on it at once, for good: putting it back opens none of them again.
Rotate or revoke a key
Section titled “Rotate or revoke a key”An app has two keys at most, so you can rotate without a gap: choose New key, move your server to the new key, then Revoke the old one. A revoked key signs nothing more, and the sessions it made end at once. Revoke app ends every session of the app, and the app makes no new one.
Sign each person’s token on your server
Section titled “Sign each person’s token on your server”The token is a JWT signed with HMAC-SHA256 (HS256), valid ten minutes at most, used once.
| Part | What it holds |
|---|---|
| Header | alg: HS256 (nothing else is accepted); kid: the key id |
iss |
The app’s client id |
aud |
datasquares (or a list that includes it) |
sub |
The person: your stable id for them (internal: their DataSquares email or SCIM id), up to 255 characters |
tenant |
Their customer account, for a product that serves many companies (optional) |
role |
viewer, or viewer_ai for a viewer who may use Ask AI (absent: viewer) |
attributes |
The values their rows are filtered by, one for each of the app’s row rules: text, a number, true or false, or a list of text and numbers. A name is letters, digits, _, . and -; __proto__, constructor and prototype are refused |
content |
{ "type": "dashboard" | "report" | "presentation", "id": "<the id>" }, one of the app’s content |
iat, exp |
When it was signed and when it expires, in seconds; ten minutes apart at most |
jti |
A nonce, 8 to 128 characters: a token makes one session |
In Node, the React SDK’s server entry signs it with Web Crypto, on Node 20 or later and edge runtimes:
// On YOUR server — never in a browser bundleimport { signEmbedToken } from '@digisquares/datasquares-embed-react/server'
app.get('/analytics-token', requireSignedIn, async (req, res) => { const token = await signEmbedToken({ clientId: process.env.DS_EMBED_CLIENT_ID!, keyId: process.env.DS_EMBED_KEY_ID!, secret: process.env.DS_EMBED_SECRET!, sub: req.user.id, tenant: req.user.accountId, role: req.user.canAskAi ? 'viewer_ai' : 'viewer', attributes: { account: req.user.accountId }, content: { type: 'dashboard', id: 'your-dashboard-id' }, }) res.type('text/plain').send(token)})Any JWT library works the same way, for example jsonwebtoken:
const token = jwt.sign( { iss: clientId, aud: 'datasquares', sub, tenant, role: 'viewer_ai', attributes, content, jti: crypto.randomUUID() }, secret, { algorithm: 'HS256', keyid: keyId, expiresIn: '5m' },)Show it in your page
Section titled “Show it in your page”With React, pass getToken; the component asks your server for a token when the embed starts and again before its
session ends, and hands it over by postMessage to the exact DataSquares origin, never in a URL. See the
React embed SDK.
<DataSquaresEmbed baseUrl="https://app.datasquares.ai" getToken={() => fetch('/analytics-token').then((r) => r.text())} />Without React, put the signed token in the address’s fragment, which a browser never sends to a server:
<iframe src="https://app.datasquares.ai/embed/session#token=SIGNED_TOKEN" width="100%" height="600" frameborder="0" allowfullscreen></iframe>The embed reads the token once and clears it from the address. A plain iframe’s page can’t hand over a new token:
when its session ends (or the page is reloaded without the token), the embed asks for one, waits ten seconds, then
says the session has ended; reload the page with a new token. An iframe whose page answers the embed’s
ds:token-request message with ds:token within those ten seconds (as the SDK does) keeps going, and a token that
comes later still starts it.
Sessions
Section titled “Sessions”A session lasts an hour and works only on the site it was opened on, for every request, the one that reads what its
token stands for (GET /api/shares/{token}) included. Revoking the app or the key that made it ends it; so does
taking its dashboard, report or site off the app, deleting its dashboard or report, and an internal person leaving the
workspace, being deactivated, or no longer being able to open the content (their role changed, or a share was taken
back). A session that has ended
answers 401 EMBED_SESSION_EXPIRED, and the embed asks its page for a new token. Theme, toolbar and parameter options
(theme, toolbar, p_<id>) work in the address as for a share link embed.
Each person’s rows
Section titled “Each person’s rows”Customer-facing. The app’s row rules decide the rows:
- Every person’s token must carry an attribute for every rule, and an attribute without a rule is refused, so access fails closed: a token that does not fit the rules gets no session.
- Each rule filters the model field it names to the person’s value, or to any of their values for a list. The filter applies after every other filter, so a filter, a slicer or a drill can narrow the rows but never widen them.
- A card whose model has no field of that name, or two, is not shown. Name the same field in every model the app’s content uses.
- A card built on a saved SQL query is not shown to a person whose rows the rules filter: its SQL has no field to filter.
- The model’s own row-level security applies too. A person in an embed is not a member of your company and has no role, so a policy for everyone applies to them and a policy aimed at a role shows them none of that table’s rows; labelled fields stay masked.
- An app with no row rules shows every person every row the view shows, narrowed only by that row-level security: the form says so and makes it only once you tick Show every person every row.
Internal. Each person reads as themselves: their role, their row-level security and their masking, exactly as when they sign in, and only content their role or a share opens for them.
In either kind of embed, saved AI output is not shown: AI summaries saved on cards, AI snapshot cards and a report’s AI commentary were written from their author’s rows, which the person may not see in full. A snapshot card and a commentary say so in their place; a live card shows each person their own rows.
Ask AI
Section titled “Ask AI”When the app offers Ask AI, a person whose token’s role is viewer_ai gets the Ask AI panel on the view. It
answers only from that view’s cards, through the same filtered data as the embed, and your
AI controls decide as for any AI: AI and Ask AI on shares and embeds switched on in the
workspace, and the workspace’s budget.
- A customer’s questions are paid from the AI credits your company bought, and stop at zero: they never go below zero. The app’s monthly budgets pause Ask AI for one person, or for one tenant, until the first of the next month (UTC); the view keeps working. Questions are counted an hour per person and per tenant. The answers run at your company’s viewers’ level.
- An internal person asks as themselves, from their own credits, under your AI settings for their role.
- History: each person finds their own conversations on the view again in their next session, and nobody else’s.
- A person sees AI credits only: refusals name no amount, model or provider.
Views and billing
Section titled “Views and billing”A view is one person on one embedded dashboard or report through one app for up to 30 minutes, with all its filters, drills and pages, the way analytics embedding is commonly counted: opening the content counts one when the person has none of it open. Reading what a session’s token stands for opens nothing. Internal embedding’s views are counted but never billed.
Enterprise includes 20,000 customer-facing views a month. Past them, views go on the next invoice at $50 (€45, ₹4,150) per 1,000, plus GST in India, without cutting anyone off: after the month ends and settles, an invoice of its own carries one line, Embedded views past the 20,000 included, with the views and the rate. The workspace’s administrators get the month’s numbers in the app. See Embedded views on the billing page.
Usage and audit
Section titled “Usage and audit”Administration → Usage → Embedded use shows this month’s customer-facing views against the 20,000 included, and the last 30 days by app, by tenant and by person: views, questions and AI credits, in whole credits, the busiest first. The tenants and the people each page on their own, 20 at a time, or 50 or 100. An internal person shows as the user they are.
Erase a person. When one of your customers asks you to delete their data, choose Remove beside them under By person and confirm. Their sessions end at once and their Ask AI conversations are deleted; their views and questions stay counted, without them, so the month’s numbers do not change. A token your server signs for the same id later makes a new person. People in your company have no Remove: their embedded use is erased with the rest of their data.
The audit log records embed.session_started, embed.viewed and embed.question_asked with the
app, the tenant and the person, and embed_app.created, embed_app.updated, embed_app.revoked,
embed_app.key_created, embed_app.key_revoked and embed_person.erased with the administrator who did it.
When a token is refused
Section titled “When a token is refused”POST /api/embed/sessions refuses with one of these codes; the embed tells your page with its ds:error event.
| Code | Why |
|---|---|
401 EMBED_TOKEN_INVALID |
The token itself. details.reason says which check: malformed, algorithm, unknown_key (a revoked key or app too), signature, issuer, audience, expired, not_yet_valid, lifetime, claims, attributes (an attribute that is not a name with a plain value, __proto__, constructor and prototype included) or replayed (its nonce was used) |
403 EMBED_SITE_REFUSED |
The page is not on one of the app’s sites, or does not say where it is (a no-referrer policy around the iframe) |
403 EMBED_CONTENT_REFUSED |
The content is not one of the app’s, or, for an internal app, the person’s role does not open it and it is not shared with them |
403 EMBED_ATTRIBUTES_REFUSED |
The attributes do not fit the row rules: details.missing and details.unknown name them. Internal tokens carry none |
403 EMBED_PERSON_UNKNOWN |
An internal token’s sub matches no one member of the workspace |
403 EMBED_APP_OWNER_GONE |
The app’s maker left; a company administrator opens it and saves it to take it over |
403 PLAN_TIER_REQUIRED |
The workspace’s plan no longer includes the app’s kind: details.feature is internal_embedding or customer_embedding |
What it does not do yet
Section titled “What it does not do yet”- Stories can’t be embedded yet: an app shows dashboards and reports.
- An embed has no export yet, so no export counts as a view.
- There is no scheduled delivery to an embedded person, so none counts as a view.
- Data requests from an embed are limited per address as for share links; Ask AI is also limited per person and per tenant.
- The browser’s framing policy for a session embed is not narrowed to the app’s sites; the session itself refuses every other site.