Blend IQ
Power BI analytics and reporting integration
Overview
Blend IQ integrates Microsoft Power BI reports into Blend with hierarchical folder organization and HubSpot-based access control. Uses service principal authentication for Power BI connections.
Platform Routes
Main App Routes
/app/:tenant/apps/blend-iq- Connections dashboard & folder browser/app/:tenant/apps/blend-iq/:reportId- Embedded report viewer/app/:tenant/apps/blend-iq/reports- All reports list/app/:tenant/apps/blend-iq/settings- Report/folder configuration/app/:tenant/apps/blend-iq/settings/add-report- Add report wizard/app/:tenant/apps/blend-iq/settings/test-user- Test user access (admin only)
Admin Routes
/admin/accounts/apps/:tenant/:app/logs- App-specific logs
External App Routes
The standalone TanStack Start app (blendx-iq) provides:
Public Routes
/login- Authentication (HubSpot OAuth, OTP, Password)/auth/hubspot/callback- OAuth callback/logout- Logout
Protected Routes
/- Folder/report browser/report/:reportId- Power BI report viewer/profile- User profile/admin- Admin dashboard
Embed Routes (Auth Required)
/embed- Embedded folder browser/embed/report/:reportId- Embedded report viewer
API Endpoints
All endpoints require X-API-KEY header for authentication.
Data Endpoints
Get App Info
GET /api/app/blend-iq/info
Headers: X-API-KEYReturns tenant logo, theme, and login configuration.
Get Folders & Reports
POST /api/app/blend-iq/reports
Headers: X-API-KEY
Body: { "email": "user@example.com" }Returns hierarchical folder structure with accessible reports for user (respects HubSpot access control).
Response:
{
"user": { "id": "...", "email": "...", "firstName": "...", "lastName": "..." },
"userTeams": [{ "id": "...", "name": "..." }],
"folders": [
{
"id": "...",
"name": "...",
"isPublic": false,
"children": [],
"reports": [{ "id": "...", "reportName": "...", "reportId": "...", "workspaceId": "..." }]
}
],
"reports": []
}Get Report Embed Token
POST /api/app/blend-iq/report/:reportId/embed?email={email}
Headers: X-API-KEYReturns Power BI embed token and configuration.
Response:
{
"success": true,
"embedUrl": "https://app.powerbi.com/reportEmbed?...",
"accessToken": "eyJ0eXAi...",
"reportId": "...",
"reportType": "PowerBIReport"
}Authentication Endpoints
Request OTP
POST /api/app/blend-iq/auth/request-otp
Headers: X-API-KEY
Body: { "email": "user@example.com" }Generates 6-digit OTP (5-minute expiration). Supports both database users and HubSpot users.
Response:
{
"success": true,
"code": "123456",
"expiresAt": "2025-01-31T12:00:00Z"
}User Lookup:
- First checks database user with tenant membership
- Falls back to HubSpot if not found in database or no tenant membership
- Returns 404 if user not found in either system
Verify OTP
POST /api/app/blend-iq/auth/verify-otp
Headers: X-API-KEY
Body: { "email": "user@example.com", "code": "123456" }Verifies OTP and returns user info. Supports both database users and HubSpot users.
Response:
{
"success": true,
"user": {
"id": "...",
"email": "...",
"firstName": "...",
"lastName": "...",
"source": "database" // "database" or "hubspot"
}
}Authentication Priority:
- Database user with tenant membership (returns
source: "database") - HubSpot user (returns
source: "hubspot") - User not found (404 error)
Verify Password
POST /api/app/blend-iq/auth/verify-password
Headers: X-API-KEY
Body: { "email": "user@example.com", "password": "secret123" }Verifies password and returns user info. Only supports database users (passwords not stored for HubSpot-only users).
Response:
{
"success": true,
"user": {
"id": "...",
"email": "...",
"firstName": "...",
"lastName": "...",
"source": "database" // Always "database" for password auth
}
}Database Models
Core Models
DataConnection (type: "power-bi"):
{
id: string
tenantId: string
type: "power-bi"
name: string
config: {
clientId: string
tenantId: string
clientSecret: string
}
isActive: boolean
}BlendIQ_Report:
{
id: string
tenantId: string
appId: string
connectionId: string
reportId: string // Power BI report ID
reportName: string
workspaceId: string
reportType: string // "PowerBIReport" | "PaginatedReport"
embedUrl: string
webUrl: string
description?: string
thumbnailUrl?: string
folderId?: string
order: number
isActive: boolean
isPublic: boolean
}BlendIQ_Folder:
{
id: string
tenantId: string
appId: string
name: string
description?: string
parentFolderId?: string
order: number
isPublic: boolean
}Access Control Models
BlendIQ_FolderAccess:
{
id: string
tenantId: string
folderId: string
type: "team" | "user"
hubspotTeamId?: string // populated when type="team"
hubspotUserId?: string // populated when type="user"
name: string // snapshot: team name or user email
description?: string // snapshot: team description or user full name
}BlendIQ_ReportAccess (same structure as FolderAccess):
{
id: string
tenantId: string
reportId: string
type: "team" | "user"
hubspotTeamId?: string
hubspotUserId?: string
name: string
description?: string
}Power BI Integration
Service Principal Setup
Azure App Registration requires:
- Application (Client) ID
- Directory (Tenant) ID
- Client Secret
- API Permissions:
Content.Create,Dashboard.Read.All,Dataset.Read.All,Report.ReadWrite.All,Workspace.ReadWrite.All - Admin consent granted
Embedding
Uses Power BI JavaScript Client v2.23.1 with AAD token authentication (tokenType: 0).
Requirements:
- Premium capacity or Power BI Embedded (A SKU) required for embedding
- Service principal must be added to workspace as Member or Admin
Error Handling:
- 403: Premium capacity required → fallback to "Open in Power BI" button
- Gateway errors: On-premises data sources → fallback to web URL
- Personal workspace: Special API endpoint handling
Access Control Flow
-
Permission Resolution (priority order):
isPublic = true→ Everyone has access- HubSpot team membership → Check
BlendIQ_FolderAccess/BlendIQ_ReportAccesswithtype="team" - Direct user assignment → Check with
type="user" - No match → No access
-
Inheritance:
- New folders inherit parent folder permissions +
isPublicvalue - New reports inherit parent folder permissions +
isPublicvalue
- New folders inherit parent folder permissions +
-
Cascading Updates:
- Folder permission changes cascade to all descendant folders and reports
isPublicchanges cascade down entire tree
HubSpot Integration
Data Fetching
GET /app/:tenant/apps/blend-iq/api/hubspot-data?type=teams&connectionId=...
GET /app/:tenant/apps/blend-iq/api/hubspot-data?type=users&connectionId=...Returns teams or users from HubSpot for access control assignment.
Access Resolution Query Pattern
// Example: Get accessible folders for user
const folders = await db.blendIQ_Folder.findMany({
where: {
tenantId,
appId,
OR: [
{ isPublic: true },
{
access: {
some: {
OR: [
{ type: "user", hubspotUserId },
{ type: "team", hubspotTeamId: { in: userTeamIds } }
]
}
}
}
]
}
});API Documentation
Interactive Swagger documentation available at:
/api/app/blend-iq/docs- Swagger UI/api/app/blend-iq/docs/json- OpenAPI spec
External App Connection
The standalone TanStack Start app connects via:
- Authentication: All auth endpoints (
/api/app/blend-iq/auth/*) - Data Fetching:
/api/app/blend-iq/reportsfor folder/report tree - Embedding:
/api/app/blend-iq/report/:reportId/embedfor Power BI tokens - Configuration:
/api/app/blend-iq/infofor tenant settings
Environment Variables (External App):
API_URL=https://blendx.ai # Blend platform URL
API_KEY=your-tenant-api-key # Tenant API key
HUBSPOT_CLIENT_ID=... # Optional: HubSpot OAuth
HUBSPOT_CLIENT_SECRET=...
SESSION_SECRET=...
ENCRYPTION_KEY=...
