logoSemaphor

Tokens API

Generate and manage authentication tokens

Overview

The Tokens API allows you to generate secure authentication tokens for embedding Semaphor dashboards or for performing management actions (CRUD) on dashboards and visuals.

There are two types of tokens:

  • Dashboard Token: Scoped to a specific dashboard, used for embedding (read-only or self-service).
  • Project Token: Scoped to a project, used for management actions (CRUD on dashboards, visuals, etc.).

Token Types

Dashboard Token

  • Purpose: Embed a dashboard (read-only or self-service)
  • Required fields: dashboardId, dashboardSecret
  • Optional fields:
    • For read-only: cls, rcls, params, config
    • For self-service: tenantId, endUserId, endUserEmail, displayName (plus above)
  • Default expiry: 30 minutes (1800 seconds)

Project Token

  • Purpose: Access to multiple dashboards within a project with flexible user management
  • Required fields:
    • type (must be 'project')
    • projectId
    • projectSecret
  • User identification (one required):
    • endUserId - Direct user ID lookup
    • endUserEmail + (tenantId or tenantName) - Email-based lookup
    • orgUserId - Organization user lookup
  • Optional fields:
    • tokenExpiry - Token expiration in seconds
    • autoCreateEndUser - Enable JIT user provisioning
    • role - User role ('VIEWER' or 'POWER_USER')
    • displayName - Display name for users
    • initialDashboardId - Initial dashboard to load
    • allowedSemanticDomains - Array of domain IDs or names
    • allowEdit - Enable self-service editing
    • cls, rcls, sls - Security policies
    • params, config - UI customization
  • Default expiry: 30 minutes (1800 seconds)

Request Body Reference

FieldTypeRequiredFor Token TypeDescription
typestringYes*projectRequired for project token: must be 'project'. For dashboard token, omit or set to 'dashboard'.
dashboardIdstringYes*dashboardDashboard ID
dashboardSecretstringYes*dashboardDashboard secret
projectIdstringYes*projectProject ID
projectSecretstringYes*projectProject secret
tokenExpiryintegerNobothToken expiry in seconds (default: 1800)
tenantIdstringNobothTenant identifier
tenantNamestringNoprojectTenant name (alternative to tenantId)
endUserIdstringNobothEnd user identifier
endUserEmailstringNobothEnd user email
orgUserIdstringNobothOrg user identifier
orgUserEmailstringNodashboardOrg user email
displayNamestringNobothDisplay name for user
autoCreateEndUserbooleanNoprojectAuto-create users if they don't exist
rolestringNoprojectRole for auto-created users ('VIEWER' or 'POWER_USER')
initialDashboardIdstringNoprojectInitial dashboard to load
allowedSemanticDomainsarrayNoprojectArray of semantic domain IDs or names
allowEditbooleanNobothEnable self-service editing
clsobject/arrayNobothConnection-level security policies
rclsobject/arrayNobothRow-level security policies
slsstringNoprojectSchema-level security policy
paramsobjectNobothUser parameters and preferences
params.timezonestringNobothIANA timezone (e.g., 'America/Chicago'). Legacy field, use calendarContext instead.
params.calendarContextobjectNobothCalendar settings: timezone, week start day, and date anchor. See Calendar Context.
configobjectNobothUI configuration options

*Required for the respective token type.


Example Requests

1. Generate Dashboard Token (Read-Only)

curl -X POST https://semaphor.cloud/api/v1/token \
  -H "Content-Type: application/json" \
  -d '{
    "dashboardId": "d_cf007a8b-19bc-46ad-8787-2915445b7b86",
    "dashboardSecret": "ds_f32f0b30-b7e1-40f9-ba6a-9804a5b9d635",
    "cls": { "name": "store_sales_primary", "params": { "tenant": "tenant_abc_123" } },
    "rcls": { "name": "region_filter", "params": { "state": ["California", "Nevada"] } }
  }'

2. Generate Project Token (Basic)

curl -X POST https://semaphor.cloud/api/v1/token \
  -H "Content-Type: application/json" \
  -d '{
    "type": "project",
    "projectId": "p_1234567890abcdef",
    "projectSecret": "ps_abcdef1234567890",
    "endUserId": "user_123",
    "tokenExpiry": 1800
  }'

3. Generate Project Token with JIT User Provisioning

curl -X POST https://semaphor.cloud/api/v1/token \
  -H "Content-Type: application/json" \
  -d '{
    "type": "project",
    "projectId": "p_1234567890abcdef",
    "projectSecret": "ps_abcdef1234567890",
    "endUserEmail": "newuser@example.com",
    "tenantName": "Acme Corp",
    "autoCreateEndUser": true,
    "role": "VIEWER",
    "displayName": "John Doe",
    "tokenExpiry": 3600
  }'

4. Generate Project Token with Semantic Domains

curl -X POST https://semaphor.cloud/api/v1/token \
  -H "Content-Type: application/json" \
  -d '{
    "type": "project",
    "projectId": "p_1234567890abcdef",
    "projectSecret": "ps_abcdef1234567890",
    "endUserId": "user_123",
    "allowedSemanticDomains": ["sales_data", "marketing"],
    "initialDashboardId": "dashboard_main"
  }'

TypeScript Types

/**
 * Calendar context for timezone-aware date handling.
 */
export type CalendarContext = {
  /**
   * IANA timezone identifier (e.g., 'America/Chicago', 'Europe/London').
   * Defaults to 'UTC' if not specified or invalid.
   */
  tz: string;
  /**
   * First day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday).
   * Defaults to 1 (Monday, ISO standard).
   */
  weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
  /**
   * Date anchor for relative date filters.
   * - 'now': Use current time (default)
   * - { iso: string }: Use fixed ISO date for reproducible views
   */
  anchor?: 'now' | { iso: string };
};
 
/**
 * Parameters for customizing token behavior and formatting.
 */
export type TokenParams = {
  /**
   * Legacy timezone field. Use calendarContext.tz instead.
   * @deprecated Use calendarContext for full timezone control.
   */
  timezone?: string;
  /**
   * Calendar context for timezone-aware date handling and week configuration.
   * Controls how relative date filters (e.g., "this week", "last month") are interpreted.
   */
  calendarContext?: CalendarContext;
  /**
   * Currency formatting preferences.
   */
  currencyFormat?: {
    /**
     * Locale identifier (e.g., 'en-US', 'fr-FR').
     */
    locale: string;
    /**
     * Currency code compliant with ISO 4217 (e.g., 'USD', 'EUR').
     */
    currency: string;
  };
};
 
/**
 * Defines a security policy that can be applied at connection or row level.
 */
export type TokenSecurityPolicy = {
  /**
   * Name of the policy (e.g., 'store_sales_primary', 'region_filter').
   */
  name: string;
  /**
   * Arbitrary parameters associated with the policy.
   * Examples:
   * // Single value
   * { tenant: 'tenant_abc_123' }
   * // Multiple values
   * { state: ['California', 'Nevada', 'Washington'] }
   */
  params: {
    [key: string]: string | number | string[] | number[];
  };
};
 
/**
 * UI and behavior configuration for the embedded dashboard.
 */
export type UIConfig = {
  /**
   * Enables self-service editing and lens creation by the end user. Legacy field. Use config.allowEdit instead.
   */
  allowEdit?: boolean;
  /**
   * Enables advanced mode features in the dashboard.
   * Defaults to true unless explicitly disabled.
   */
  showAdvancedMode?: boolean;
  /**
   * Enables the AI-powered dashboard assistant.
   * Defaults to true unless explicitly disabled.
   */
  showDashboardAssistant?: boolean;
};
 
/**
 * Main payload for generating a secure access token for a dashboard.
 */
export type DashboardTokenRequest = {
  /**
   * Identifier of the dashboard to be accessed.
   */
  dashboardId: string;
  /**
   * Secret key used for validating access to the dashboard.
   */
  dashboardSecret: string;
  /**
   * Token expiry duration in seconds.
   */
  tokenExpiry?: number;
  /**
   * Unique identifier of the tenant.
   */
  tenantId?: string;
  /**
   * Unique identifier of the tenant user accessing the dashboard.
   */
  endUserId?: string;
  /**
   * Email of the tenant user (for personalization, audit, or identification).
   */
  endUserEmail?: string;
  /**
   * Unique identifier of the org user accessing the dashboard.
   */
  orgUserId?: string;
  /**
   * Email of the org user (for personalization, audit, or identification).
   */
  orgUserEmail?: string;
  /**
   * Display name of the end user.
   */
  displayName?: string;
  /**
   * Enables self-service editing and lens creation by the end user.
   */
  allowEdit?: boolean;
  /**
   * Connection-level security policies.
   * Can be a single policy or an array of policies.
   */
  cls?: TokenSecurityPolicy[] | TokenSecurityPolicy;
  /**
   * Row-level security policies.
   * Can be a single policy or an array of policies.
   */
  rcls?: TokenSecurityPolicy[] | TokenSecurityPolicy;
  /**
   * Parameter overrides and preferences (e.g., formatting).
   */
  params?: TokenParams;
  /**
   * UI behavior and feature flags.
   */
  config?: UIConfig;
};
 
/**
 * Main payload for generating a project-level access token.
 */
export type ProjectTokenRequest = {
  /**
   * Must be 'project' for project tokens.
   */
  type: 'project';
  /**
   * Unique identifier of the project.
   */
  projectId: string;
  /**
   * Secret key used for validating access to the project.
   */
  projectSecret: string;
  /**
   * Token expiry duration in seconds.
   */
  tokenExpiry?: number;
  /**
   * Unique identifier of the tenant.
   */
  tenantId?: string;
  /**
   * Name of the tenant (alternative to tenantId).
   */
  tenantName?: string;
  /**
   * Unique identifier of the end user.
   */
  endUserId?: string;
  /**
   * Email of the end user.
   */
  endUserEmail?: string;
  /**
   * Unique identifier of the organization user.
   */
  orgUserId?: string;
  /**
   * Display name for the user.
   */
  displayName?: string;
  /**
   * Automatically create user if they don't exist.
   */
  autoCreateEndUser?: boolean;
  /**
   * Role for auto-created users ('VIEWER' or 'POWER_USER').
   */
  role?: 'VIEWER' | 'POWER_USER';
  /**
   * Initial dashboard to load.
   */
  initialDashboardId?: string;
  /**
   * Array of semantic domain IDs or names to restrict access.
   */
  allowedSemanticDomains?: string[];
  /**
   * Enables self-service editing.
   */
  allowEdit?: boolean;
  /**
   * Connection-level security policies.
   */
  cls?: TokenSecurityPolicy[] | TokenSecurityPolicy;
  /**
   * Row-level security policies.
   */
  rcls?: TokenSecurityPolicy[] | TokenSecurityPolicy;
  /**
   * Schema-level security policy.
   */
  sls?: string;
  /**
   * Parameter overrides and preferences.
   */
  params?: TokenParams;
  /**
   * UI behavior and feature flags.
   */
  config?: UIConfig;
};
 
/**
 * Combined token request type.
 */
export type TokenRequest = DashboardTokenRequest | ProjectTokenRequest;

Security Policies & Params

Connection-Level Security (CLS)

{
  "cls": {
    "name": "store_sales_primary",
    "params": {
      "tenant": "tenant_abc_123"
    }
  }
}

Row-Level Security (RCLS)

{
  "rcls": {
    "name": "region_filter",
    "params": {
      "state": ["California", "Nevada", "Washington"]
    }
  }
}

Multiple Policies

{
  "cls": [
    {
      "name": "store_sales_primary",
      "params": { "tenant": "tenant_abc_123" }
    },
    {
      "name": "data_center_filter",
      "params": { "region": "us-west" }
    }
  ],
  "rcls": [
    {
      "name": "region_filter",
      "params": { "state": ["California", "Nevada"] }
    },
    {
      "name": "department_filter",
      "params": { "department": ["Sales", "Marketing"] }
    }
  ]
}

Calendar Context

The calendarContext parameter controls how timezone-sensitive operations are handled in your dashboards, including:

  • Relative date filters (e.g., "this week", "last month", "year to date")
  • Date aggregations (e.g., "group by week", "group by month")
  • Date formatting in visualizations

CalendarContext Properties

PropertyTypeDefaultDescription
tzstring'UTC'IANA timezone (e.g., 'America/Chicago', 'Europe/London')
weekStart0-61First day of week (0=Sunday, 1=Monday, ..., 6=Saturday)
anchor'now' | { iso: string }'now'Reference time for relative dates. Use fixed ISO for reproducible views.

Example: Setting Calendar Context

curl -X POST https://semaphor.cloud/api/v1/token \
  -H "Content-Type: application/json" \
  -d '{
    "dashboardId": "d_cf007a8b-19bc-46ad-8787-2915445b7b86",
    "dashboardSecret": "ds_f32f0b30-b7e1-40f9-ba6a-9804a5b9d635",
    "params": {
      "calendarContext": {
        "tz": "America/Chicago",
        "weekStart": 0
      }
    }
  }'

Preference Hierarchy

Calendar context is resolved using a 4-level preference hierarchy (highest to lowest priority):

  1. User Preferences - Individual user's saved calendar preferences
  2. Token Context - calendarContext passed in the token request (this API)
  3. Tenant Defaults - Tenant-wide calendar settings
  4. Organization Defaults - Organization-wide calendar settings
  5. System Default - { tz: 'UTC', weekStart: 1, anchor: 'now' }

Each property (tz, weekStart, anchor) is resolved independently. For example, if a user has set their timezone but not their week start preference, the timezone comes from the user while week start falls through to the next level that has it configured.

Legacy Timezone Support

For backward compatibility, params.timezone is still supported:

{
  "params": {
    "timezone": "America/New_York"
  }
}

This is equivalent to:

{
  "params": {
    "calendarContext": {
      "tz": "America/New_York",
      "weekStart": 1,
      "anchor": "now"
    }
  }
}

We recommend using calendarContext for new integrations as it provides full control over week start and date anchoring.


Error Handling & Best Practices

Common token API errors:

Error CodeDescriptionSolution
INVALID_CREDENTIALSDashboard ID or secret is incorrectVerify credentials
TOKEN_EXPIREDToken has expiredGenerate new token
INVALID_TOKENToken format is invalidCheck token format
INVALID_SECURITY_POLICYSecurity policy configuration is invalidReview policy syntax
RATE_LIMIT_EXCEEDEDToo many token requestsWait before retrying

Best Practices

1. Token Lifecycle Management

Implement proper token lifecycle management:

class TokenManager {
  constructor() {
    this.token = null;
    this.expiry = null;
  }
 
  async getValidToken() {
    if (this.isTokenValid()) {
      return this.token;
    }
 
    return await this.generateNewToken();
  }
 
  isTokenValid() {
    return this.token && this.expiry && Date.now() < this.expiry;
  }
 
  async generateNewToken() {
    const response = await fetch('https://semaphor.cloud/api/v1/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        dashboardId: process.env.SEMAPHOR_DASHBOARD_ID,
        dashboardSecret: process.env.SEMAPHOR_DASHBOARD_SECRET,
        tokenExpiry: 3600,
      }),
    });
 
    const { data } = await response.json();
    this.token = data.accessToken;
    this.expiry = new Date(data.expiresAt).getTime();
 
    return this.token;
  }
 
  async invalidateToken() {
    if (this.token) {
      await fetch('https://semaphor.cloud/api/v1/invalidate-token', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${this.token}` },
      });
      this.token = null;
      this.expiry = null;
    }
  }
}

2. Multi-Tenant Token Generation

Handle multiple tenants with different configurations:

async function generateTenantToken(tenantConfig, userInfo) {
  const tokenRequest = {
    dashboardId: tenantConfig.dashboardId,
    dashboardSecret: tenantConfig.dashboardSecret,
    tenantId: tenantConfig.tenantId,
    endUserId: userInfo.id,
    endUserEmail: userInfo.email,
    allowEdit: tenantConfig.allowEdit,
    tokenExpiry: 7200,
  };
 
  // Add security policies if configured
  if (tenantConfig.securityPolicies) {
    tokenRequest.cls = tenantConfig.securityPolicies.cls;
    tokenRequest.rcls = tenantConfig.securityPolicies.rcls;
  }
 
  // Add user preferences
  if (userInfo.preferences) {
    tokenRequest.params = {
      currencyFormat: userInfo.preferences.currency,
      timezone: userInfo.preferences.timezone,
      locale: userInfo.preferences.locale,
    };
  }
 
  const response = await fetch('https://semaphor.cloud/api/v1/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(tokenRequest),
  });
 
  return response.json();
}

3. Token Validation

Regularly validate tokens to ensure they're still active:

async function validateTokenPeriodically(token, interval = 300000) {
  // 5 minutes
  setInterval(async () => {
    try {
      const response = await fetch(
        'https://semaphor.cloud/api/v1/token/validate',
        {
          headers: { 'Authorization': `Bearer ${token}` },
        }
      );
 
      const { data } = await response.json();
 
      if (!data.valid) {
        console.warn('Token is no longer valid');
        // Handle token invalidation
      }
    } catch (error) {
      console.error('Token validation failed:', error);
    }
  }, interval);
}

4. Error Recovery

Implement robust error handling and recovery:

async function generateTokenWithRetry(maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch('https://semaphor.cloud/api/v1/token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          dashboardId: process.env.SEMAPHOR_DASHBOARD_ID,
          dashboardSecret: process.env.SEMAPHOR_DASHBOARD_SECRET,
        }),
      });
 
      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error.message);
      }
 
      return await response.json();
    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
 
      // Wait before retrying (exponential backoff)
      await new Promise((resolve) =>
        setTimeout(resolve, Math.pow(2, attempt) * 1000)
      );
    }
  }
}

Rate Limits

  • Token Generation: 100 requests per minute per dashboard
  • Token Validation: 1000 requests per minute per token
  • Token Invalidation: 100 requests per minute per token