Embeds API

The embeds API manages read-only embeddable widget tokens for sharing live data with external stakeholders. Widgets render boards, KPI charts, portfolios, and dashboards in iframes without requiring authentication. All management procedures require the manage_settings permission. The public data endpoint requires only a valid embed token.

Entity Types

Embed tokens can be created for the following entity types, each with a dedicated renderer:

Entity TypeRendererDescription
boardTable/KanbanFull board view with groups, columns, and items with cell values
kpi_chartKPI GridKPI definition cards with recent values, thresholds, and trend indicators
portfolioOverviewPortfolio-level summary with project metadata
dashboardDashboardCustom dashboard layout with board data

Rate Limiting

The public getPublicData endpoint is rate-limited to 60 requests per minute per token. This is enforced via an in-memory per-process rate limiter. When the limit is exceeded, the endpoint returns a TOO_MANY_REQUESTS error.

embeds.create

Generate a new embed token for a board, KPI chart, portfolio, or dashboard. Returns the token value, the full record, and a ready-to-paste iframe HTML snippet.

Type: Mutation

Input Schema:

{
  entityType: "board" | "kpi_chart" | "portfolio" | "dashboard";
  entityId: string;          // ID of the resource to embed
  label?: string;            // Human-readable label for the token
  config?: {
    theme?: string;          // Visual theme for the embed
    refreshInterval?: number; // Auto-refresh interval in seconds
    showTitle?: boolean;     // Show/hide the resource title
    showTimestamp?: boolean;  // Show/hide last-updated timestamp
  };
  expiresAt?: string;        // Optional ISO 8601 expiration date
}

Output:

{
  token: {
    id: string;
    organizationId: string;
    entityType: string;
    entityId: string;
    token: string;            // Cryptographic cuid2 token string
    label: string | null;
    config: Record<string, unknown>;
    createdById: string;
    expiresAt: Date | null;
    createdAt: Date;
  };
  embedUrl: string;           // e.g., "https://app.probeya.com/api/embed/{token}"
  iframeHtml: string;         // Ready-to-paste HTML: <iframe src="..." ...>
}

Side Effects:

  • Logs embed_token.created activity with entity type, entity ID, and label.

embeds.list

Return all embed tokens for the organization. Optionally filter by a specific entity ID or include revoked tokens.

Type: Query

Input Schema:

{
  entityId?: string;         // Filter by a specific entity
  includeRevoked?: boolean;  // Include revoked tokens (default: false)
}

Output:

Array<{
  id: string;
  organizationId: string;
  entityType: string;
  entityId: string;
  token: string;
  label: string | null;
  config: Record<string, unknown>;
  createdById: string;
  expiresAt: Date | null;
  createdAt: Date;
  embedUrl: string;          // Computed embed URL
  iframeHtml: string;        // Computed iframe snippet
}>

Results are sorted by creation date descending (newest first).

embeds.revoke

Soft-revoke an embed token by setting its expiresAt to the current timestamp. The record is preserved for audit trail purposes rather than being hard-deleted. Once revoked, the getPublicData endpoint will reject the token with a FORBIDDEN error.

Type: Mutation

Input Schema:

{
  tokenId: string;  // The record ID (not the token string)
}

Output: Returns the updated embed token record with expiresAt set to the revocation time.

Error Codes:

CodeCondition
NOT_FOUNDToken not found or belongs to another organization

Side Effects:

  • Logs embed_token.revoked activity with entity type, entity ID, and label.

embeds.getPublicData

Public endpoint to fetch embed data by token. This is the primary data source consumed by the embed viewer page. It does not require authentication — the token itself is the credential.

Type: Query (public procedure)

Input Schema:

{
  token: string;  // The embed token string (not the record ID)
}

Security Measures:

  1. Token must exist and not be expired or revoked (expiresAt check)
  2. Rate-limited to 60 requests/minute per token
  3. Sensitive fields (user emails, internal IDs like createdById, assigneeId) are stripped
  4. Data is strictly read-only

Output (board/dashboard):

{
  entityType: "board" | "dashboard";
  boardName: string;
  config: Record<string, unknown>;
  items: Array<{
    id: string;
    name: string;
    values: Record<string, unknown>;  // columnId -> cell value
    // Note: createdById, assigneeId stripped for security
  }>;
  groups: Array<{
    id: string;
    name: string;
    color: string;
    sortOrder: number;
  }>;
  columns: Array<{
    id: string;
    name: string;
    type: string;
    config: Record<string, unknown>;
    sortOrder: number;
    width: number;
  }>;
}

Output (kpi_chart):

{
  entityType: "kpi_chart";
  config: Record<string, unknown>;
  kpis: Array<{
    id: string;
    name: string;
    category: string;
    unit: string;
    direction: string;
    thresholds: Record<string, number> | null;
    frequency: string;
    values: Array<{
      id: string;
      date: string;
      value: string;
      comment: string | null;
    }>;  // Last 30 values, ordered by date descending
  }>;
}

Error Codes:

CodeCondition
NOT_FOUNDInvalid or unknown token
FORBIDDENToken has been revoked or has expired
TOO_MANY_REQUESTSRate limit exceeded (60 req/min per token)

Permissions

ProcedureRequired Role
embeds.createmanage_settings permission (typically Admin/Owner)
embeds.listmanage_settings permission
embeds.revokemanage_settings permission
embeds.getPublicDataAnyone with a valid, non-expired token

Security

  • Management procedures use createRoleProtectedProcedure("manage_settings") for access control.
  • Embed tokens are generated using cuid2, which produces cryptographically random, unguessable identifiers.
  • The public data endpoint strips sensitive fields (user IDs, emails) before returning data.
  • Revocation is immediate: once expiresAt is set to the current time, subsequent requests are rejected.
  • All data fetched through the public endpoint is scoped to the token’s organizationId, preventing cross-tenant data access.