logoSemaphor

Sharing & Management API

Programmatic dashboard sharing and user management

Manage dashboards, share resources, and control user access programmatically.

Authentication

All endpoints require a JWT token in the Authorization header:

Authorization: Bearer YOUR_ACCESS_TOKEN

See Authentication for token generation.

Base URL

/api/management/v1

Sharing Dashboards

Grant Access

Share a dashboard with users or groups.

POST /dashboards/:id/share

Request:

{
  "shares": [
    {
      "scope": "specific_tenant_user",
      "tenantUserId": "user-123",
      "role": "EDITOR"
    },
    {
      "scope": "group",
      "groupId": "group-456",
      "role": "VIEWER"
    }
  ]
}

Scope options:

  • specific_tenant_user - Share with one tenant user (requires tenantUserId)
  • all_tenant_users - Share with all users in a tenant (requires tenantId)
  • specific_org_user - Share with one org user (requires orgUserId)
  • all_org_users - Share with all org users
  • org_users_only - Share with org users only (excludes tenants)
  • all_tenants_only - Share with all tenants (excludes org users)
  • group - Share with a group (requires groupId)

Access Levels:

The role field grants permissions for this specific dashboard:

  • VIEWER - View and interact only
  • EDITOR - View and modify
  • CONTRIBUTOR - View, modify, and share
  • OWNER - Full control including delete

Important: User roles set the ceiling. Access levels grant or restrict permissions within that ceiling.

Examples:

  • POWER_USER + EDITOR access = ✅ Can edit (access grants permission)
  • POWER_USER + VIEWER access = ❌ View only (access restricts below role ceiling)
  • VIEWER role + EDITOR access = ❌ View only (role blocks entirely)

Use case: Grant users POWER_USER role so they can create dashboards, then share sensitive dashboards with VIEWER access to restrict editing while maintaining their creation privileges.

Response:

{
  "message": "Dashboard shared successfully",
  "shares": [
    {
      "id": "share-abc123",
      "resourceId": "dashboard-xyz",
      "role": "EDITOR",
      "createdAt": "2024-01-15T10:30:00Z"
    }
  ]
}

Revoke Access

DELETE /dashboards/:id/share

Uses the same request format as granting access. Specify the exact scope to revoke.

List Shares

GET /dashboards/:id/shares

Returns all current access permissions organized by type (direct users, groups, tenant-wide, etc.).

Dashboard Management

List Dashboards

GET /dashboards?search=sales&isPrivate=false

Query params:

  • search - Filter by title/description
  • isPrivate - Filter by privacy
  • createdBy - Filter by creator

Create Dashboard

POST /dashboards
{
  "title": "Sales Dashboard",
  "description": "Q1 metrics",
  "isPrivate": false
}

Returns 403 if user role doesn't allow creation.

Update Dashboard

PATCH /dashboards/:id

Include only the fields you want to change:

{
  "title": "New Title",
  "isPrivate": true
}

Delete Dashboard

DELETE /dashboards/:id

Requires OWNER access level.

Duplicate Dashboard

POST /dashboards/:id/duplicate

Optional: provide a title in the request body. Otherwise, appends "(Copy)" to the original title.

Visual Management

Visuals use the same endpoints as dashboards, just replace /dashboards with /visuals:

POST /visuals/:id/share
GET /visuals
PATCH /visuals/:id
DELETE /visuals/:id
POST /visuals/:id/duplicate

Groups

List Groups

GET /groups?type=ORG_GROUP&search=analytics

Query params:

  • search - Search by name
  • type - ORG_GROUP, TENANT_GROUP, or all
  • tenantId - Filter by tenant
  • includeMembers - Include member details
  • page, limit - Pagination

Create Group

POST /groups
{
  "name": "Analytics Team",
  "type": "ORG_GROUP",
  "description": "Team members with analytics access"
}

Organization users only.

Add Members

POST /groups/:id/members
{
  "userIds": ["user-123", "user-456"]
}

Remove Member

DELETE /groups/:id/members/:memberId

Users

List Users

GET /unified-users?type=tenant&search=john

Query params:

  • search - Name or email
  • type - organization or tenant
  • tenantId - Filter by tenant

Get User Context

GET /unified-users/:id

Returns user details and computed permissions:

{
  "userId": "user-123",
  "type": "tenant",
  "role": "POWER_USER",
  "permissions": {
    "canEdit": true,
    "canCreateDashboard": true,
    "canShareWithTenants": false
  }
}

Understanding Permissions

Semaphor has two permission layers:

User Roles (platform-wide - sets the ceiling):

  • VIEWER - Read-only across the platform (cannot edit anything)
  • POWER_USER - Can create and edit dashboards (tenant users)
  • AUTHOR - Can create and edit dashboards across tenants (org users)
  • ADMIN - Full platform access (org users)

Access Levels (per-dashboard - grants permissions):

  • VIEWER, EDITOR, CONTRIBUTOR, OWNER

Your user role is the ceiling. Access levels can grant or restrict permissions on specific dashboards, but can't exceed the role ceiling.

How it works:

  • POWER_USER + EDITOR access = ✅ Can edit (access grants permission)
  • POWER_USER + VIEWER access = ❌ View only (access restricts below ceiling)
  • VIEWER role + EDITOR access = ❌ View only (role blocks entirely)

Permission Matrix

ActionTenant VIEWERTenant POWER_USEROrg AUTHOR/ADMIN
View shared dashboards
Create dashboards
Edit dashboards✓ (owned or EDITOR+ access)✓ (owned or EDITOR+ access)
Share dashboards✓ (owned or CONTRIBUTOR+ access)✓ (owned or CONTRIBUTOR+ access)
Delete dashboards✓ (owned or OWNER access)✓ (owned or OWNER access)
Manage groups

Error Responses

Standard HTTP codes:

  • 200 - Success
  • 201 - Created
  • 400 - Bad request (validation error)
  • 401 - Unauthorized (missing/invalid token)
  • 403 - Forbidden (insufficient permissions)
  • 404 - Not found
  • 500 - Server error

All errors return:

{
  "error": "Human-readable message",
  "details": "Optional context"
}

Code Examples

Share a Dashboard

async function shareDashboard(dashboardId: string, token: string) {
  const response = await fetch(
    `/api/management/v1/dashboards/${dashboardId}/share`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        shares: [
          {
            scope: 'specific_tenant_user',
            tenantUserId: 'user-123',
            role: 'EDITOR'
          }
        ]
      })
    }
  );
 
  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message);
  }
 
  return response.json();
}

Create Group and Add Members

async function createTeam(name: string, memberIds: string[], token: string) {
  // Create the group
  const groupRes = await fetch('/api/management/v1/groups', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name,
      type: 'ORG_GROUP'
    })
  });
 
  const { group } = await groupRes.json();
 
  // Add members
  await fetch(`/api/management/v1/groups/${group.id}/members`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ userIds: memberIds })
  });
 
  return group;
}

Python Client

import requests
 
class SemaphorAPI:
    def __init__(self, base_url: str, token: str):
        self.base_url = base_url
        self.headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }
 
    def share_dashboard(self, dashboard_id: str, shares: list):
        response = requests.post(
            f"{self.base_url}/api/management/v1/dashboards/{dashboard_id}/share",
            headers=self.headers,
            json={'shares': shares}
        )
        response.raise_for_status()
        return response.json()
 
    def list_dashboards(self, search: str = None):
        params = {'search': search} if search else {}
        response = requests.get(
            f"{self.base_url}/api/management/v1/dashboards",
            headers=self.headers,
            params=params
        )
        response.raise_for_status()
        return response.json()['dashboards']
 
# Usage
api = SemaphorAPI('https://api.example.com', 'your-token')
 
# Share with a user
api.share_dashboard('dashboard-123', [
    {
        'scope': 'specific_tenant_user',
        'tenantUserId': 'user-456',
        'role': 'EDITOR'
    }
])
 
# List dashboards
dashboards = api.list_dashboards(search='sales')