Embedded experiences
Use POST /api/embed-url for a dashboard or POST /v1/embed/url for DashQ Assistant. Both return a short-lived URL that the browser must use as-is.
Prerequisites
DashQ supplies the gateway base URL and credential for your environment through the approved onboarding channel. Store both in your backend secret manager.
DASHQ_ANALYTICS_GATEWAY_BASE_URL=https://YOUR_ASSIGNED_GATEWAY
DASHQ_ANALYTICS_API_KEY=YOUR_BACKEND_ONLY_SECRET
Do not expose either value in frontend bundles, query strings, support captures, analytics events, or AI prompts.
Backend integration
Derive account, property, and user scope from your authenticated session and authorization store. Treat the browser request as a request for a permitted experience—not as the source of authorization.
app.post("/api/dashq/dashboard", requireUser, async (req, res) => {
const access = await loadDashqEntitlements(req.user.id);
const dashboardId = allowDashboard(req.body.dashboardId, access.dashboardIds);
const response = await fetch(
`${process.env.DASHQ_ANALYTICS_GATEWAY_BASE_URL}/api/embed-url`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.DASHQ_ANALYTICS_API_KEY,
},
body: JSON.stringify({
accountId: access.accountId,
buildingIds: access.buildingIds,
userId: access.dashqUserId,
dashboardId,
startDate: validateDate(req.body.startDate),
endDate: validateDate(req.body.endDate),
}),
},
);
const data = await response.json();
if (!response.ok) return res.status(response.status).json({ error: "DashQ embed unavailable" });
return res.json({ embedUrl: data.embedUrl, expiresInSeconds: data.expiresInSeconds });
});
For Assistant, send a stable server-resolved identity through externalUserId, userId, or userEmail; optional fields are userName, userRole, dashboardId, up to three starterQuestions, and a paired startDate/endDate.
app.post("/api/dashq/assistant", requireUser, async (req, res) => {
const access = await loadDashqEntitlements(req.user.id);
const response = await fetch(
`${process.env.DASHQ_ANALYTICS_GATEWAY_BASE_URL}/v1/embed/url`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.DASHQ_ANALYTICS_API_KEY,
},
body: JSON.stringify({
accountId: access.accountId,
buildingIds: access.buildingIds,
userId: access.dashqUserId,
externalUserId: access.externalUserId,
userEmail: access.userEmail,
userName: access.userName,
userRole: access.userRole,
dashboardId: allowDashboard(req.body.dashboardId, access.dashboardIds),
starterQuestions: allowStarterQuestions(req.body.starterQuestions),
startDate: validateDate(req.body.startDate),
endDate: validateDate(req.body.endDate),
}),
},
);
const data = await response.json();
if (!response.ok) return res.status(response.status).json({ error: "DashQ Assistant unavailable" });
return res.json({
embedUrl: data.embedUrl,
expiresInSeconds: data.expiresInSeconds,
userId: data.userId,
userName: data.userName,
userRole: data.userRole,
});
});
Browser integration
const response = await fetch("/api/dashq/dashboard", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ dashboardId, startDate, endDate }),
});
const { embedUrl } = await response.json();
return <iframe title="DashQ dashboard" src={embedUrl} allow="fullscreen" />;
For Assistant voice, use allow="microphone; fullscreen" and allow the assigned DashQ embed origin in the host page's Permissions-Policy. Do not grant microphone access to dashboard-only embeds.
The first navigation exchanges the signed URL for a short-lived, secure embed session. Do not parse, persist, modify, or reuse the URL. Request a new URL after expiration.
Session lifecycle
Dashboard embed issuance currently returns a 300-second signed URL. On first navigation, the DashQ runtime establishes a short-lived HttpOnly session cookie. Cross-origin iframe refresh requires SameSite=None; DashQ pairs it with Secure outside localhost development and scopes it to the embed runtime.
Your host should retain only the returned URL long enough to set the iframe src. Do not put it in analytics events, browser storage, server logs, support recordings, or cache keys.
Expected dashboard response fields are embedUrl, expiresInSeconds, account/dashboard IDs, normalized dates, building IDs, and resolved user identity fields. Assistant issuance returns its URL, expiry, resolved user name/role/ID when available, and normalized dates.
Dashboard discovery
If users choose a dashboard, create a widget session on your backend and call GET /api/v1/dashboards/catalog. The catalog is the source of truth for the current allowed set. Foundation IDs currently include:
portfolio_foundation_v1lead_to_lease_foundation_v1tour_foundation_v1application_foundation_v1marketing_foundation_v1communication_foundation_v1
Do not present a dashboard that is absent from the scoped catalog.
Reports and insights
PDF reports and generated insights are protected capabilities inside the dashboard runtime. Host applications normally request an embed URL and let the embed use /embed/dashboard/report and /embed/dashboard/insights with its established session.
See Dashboard insights and reports for request scope and response behavior. Controlled backend report workflows can use the runtime contract documented in the API reference.
Backend smoke request
Run smoke requests only from a trusted backend shell using values supplied for your environment:
curl -X POST "$DASHQ_ANALYTICS_GATEWAY_BASE_URL/api/embed-url" \
-H "x-api-key: $DASHQ_ANALYTICS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "YOUR_ACCOUNT_ID",
"dashboardId": "portfolio_foundation_v1",
"buildingIds": ["YOUR_BUILDING_ID"],
"userId": "YOUR_DASHQ_USER_ID",
"startDate": "2026-01-01",
"endDate": "2026-01-31"
}'
Do not paste the command output into a ticket or prompt because the response contains a signed URL.
Assistant smoke request:
curl -X POST "$DASHQ_ANALYTICS_GATEWAY_BASE_URL/v1/embed/url" \
-H "x-api-key: $DASHQ_ANALYTICS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "YOUR_ACCOUNT_ID",
"buildingIds": ["YOUR_BUILDING_ID"],
"externalUserId": "YOUR_STABLE_EXTERNAL_USER_ID",
"userEmail": "developer@example.com",
"userName": "Example User",
"dashboardId": "lead_to_lease_foundation_v1",
"startDate": "2026-01-01",
"endDate": "2026-01-31"
}'
Troubleshooting
| Symptom | Check |
|---|---|
401 | Credential is missing, expired, or for another environment. Rotate it if exposure is possible. |
403 | The account, user, property, dashboard, or feature is outside the provisioned scope. Do not work around the boundary. |
400 | Validate required IDs, paired dates, array types, and allowed dashboard values on your backend. |
| Embed expires | Request a new URL; never extend or reconstruct a signed URL. |
| Assistant identity error | Supply one stable identity resolved from the authenticated user. |
Assistant exchange returns 401 | The signed value can be invalid, expired, already exchanged, or created by the wrong issuance endpoint. Mint a new Assistant URL through POST /v1/embed/url; do not call /v1/embed/exchange from the host app. |
| Voice is blocked | Grant microphone to the assigned embed origin in both iframe allow and the host permissions policy. |
See the API reference for fields and responses and security for deployment controls.