Semaphor

Unified Security API

Management API for security policy definitions and assignments

Overview

The Unified Security API lets you manage security policies programmatically. Create policy definitions (reusable policy templates) and assignments (bindings between policy definitions and actors) to control connection-level, schema-level, and row-level security across your project.

For a conceptual overview, see Policy Definitions & Assignments.

Base path:

/api/management/v1/projects/{projectId}/unified-security

All endpoints require authentication with an ADMIN role. Request and response bodies use JSON.


Policy Definitions

A policy definition is a connection-scoped policy template. It describes what security rules to apply but not who they apply to.

List Policy Definitions

GET /definitions

Returns all policy definitions in the project, sorted by name.

curl -X GET "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/definitions" \
  -H "Authorization: Bearer <token>"

Response:

{
  "ok": true,
  "data": {
    "definitions": [
      {
        "definition": {
          "id": "usd_abc123",
          "projectId": "p_1234567890",
          "connectionId": "conn_xyz789",
          "name": "Multi-tenant isolation",
          "clsConfig": {
            "connectionTemplate": "host={{db_host}};database={{db_name}}"
          },
          "slsConfig": null,
          "rlsConfig": {
            "rules": [
              {
                "name": "tenant_filter",
                "matcher": { "type": "ALL_TABLES_WITH_COLUMN", "column": "tenant_id" },
                "expression": "tenant_id = {{tenant_id}}"
              }
            ]
          },
          "createdAt": "2025-03-01T10:00:00.000Z",
          "updatedAt": "2025-03-01T10:00:00.000Z"
        },
        "connection": {
          "id": "conn_xyz789",
          "name": "Production Postgres",
          "type": "POSTGRES"
        },
        "assignmentCount": 3
      }
    ]
  }
}

Create Policy Definition

POST /definitions

Creates a new policy definition for a connection.

Request body:

FieldTypeRequiredDescription
connectionIdstringYesID of the connection this policy definition applies to
namestringYesDisplay name for the policy definition
clsConfigCLSConfigNoConnection-level security configuration
slsConfigSLSConfigNoSchema-level security configuration
rlsConfigRLSConfigNoRow-level security configuration

At least one config required

You must provide at least one of clsConfig, slsConfig, or rlsConfig. A policy definition without any config is rejected.

curl -X POST "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/definitions" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "connectionId": "conn_xyz789",
    "name": "Multi-tenant isolation",
    "rlsConfig": {
      "rules": [
        {
          "name": "tenant_filter",
          "matcher": { "type": "ALL_TABLES_WITH_COLUMN", "column": "tenant_id" },
          "expression": "tenant_id = {{tenant_id}}"
        }
      ]
    }
  }'

Response (201):

{
  "ok": true,
  "data": {
    "definition": {
      "id": "usd_abc123",
      "projectId": "p_1234567890",
      "connectionId": "conn_xyz789",
      "name": "Multi-tenant isolation",
      "clsConfig": null,
      "slsConfig": null,
      "rlsConfig": {
        "rules": [
          {
            "name": "tenant_filter",
            "matcher": { "type": "ALL_TABLES_WITH_COLUMN", "column": "tenant_id" },
            "expression": "tenant_id = {{tenant_id}}"
          }
        ]
      },
      "createdAt": "2025-03-01T10:00:00.000Z",
      "updatedAt": "2025-03-01T10:00:00.000Z"
    }
  }
}

Get Policy Definition

GET /definitions/{definitionId}

Returns a single policy definition with its connection details and assignment count.

curl -X GET "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/definitions/{definitionId}" \
  -H "Authorization: Bearer <token>"

Response:

{
  "ok": true,
  "data": {
    "definition": {
      "definition": {
        "id": "usd_abc123",
        "projectId": "p_1234567890",
        "connectionId": "conn_xyz789",
        "name": "Multi-tenant isolation",
        "clsConfig": null,
        "slsConfig": null,
        "rlsConfig": { "rules": [ ... ] },
        "createdAt": "2025-03-01T10:00:00.000Z",
        "updatedAt": "2025-03-01T10:00:00.000Z"
      },
      "connection": {
        "id": "conn_xyz789",
        "name": "Production Postgres",
        "type": "POSTGRES"
      },
      "assignmentCount": 3
    }
  }
}

Update Policy Definition

PATCH /definitions/{definitionId}

Updates an existing policy definition. Provide only the fields you want to change. At least one field must be included.

Request body:

FieldTypeRequiredDescription
namestringNoUpdated display name
clsConfigCLSConfig | nullNoUpdated CLS config. Pass null to remove.
slsConfigSLSConfig | nullNoUpdated SLS config. Pass null to remove.
rlsConfigRLSConfig | nullNoUpdated RLS config. Pass null to remove.
curl -X PATCH "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/definitions/{definitionId}" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Tenant isolation (updated)",
    "slsConfig": {
      "schema": "tenant_schema",
      "allowedSchemas": ["tenant_schema", "shared"]
    }
  }'

Response:

{
  "ok": true,
  "data": {
    "definition": {
      "id": "usd_abc123",
      "name": "Tenant isolation (updated)",
      "slsConfig": {
        "schema": "tenant_schema",
        "allowedSchemas": ["tenant_schema", "shared"]
      },
      ...
    }
  }
}

Delete Policy Definition

DELETE /definitions/{definitionId}

Deletes a policy definition. Fails with 409 Conflict if assignments still reference it.

curl -X DELETE "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/definitions/{definitionId}" \
  -H "Authorization: Bearer <token>"

Response:

{
  "ok": true,
  "data": {
    "definition": {
      "id": "usd_abc123",
      ...
    }
  }
}

Assignments

An assignment binds a policy definition to a specific actor (user, tenant, or all tenants) and provides the concrete parameter values for that actor.

List Assignments

GET /assignments

Returns all assignments in the project with resolved actor details.

curl -X GET "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/assignments" \
  -H "Authorization: Bearer <token>"

Response:

{
  "ok": true,
  "data": {
    "assignments": [
      {
        "assignment": {
          "id": "usa_def456",
          "definitionId": "usd_abc123",
          "scopeType": "TENANT",
          "orgUserId": null,
          "tenantId": "t_acme",
          "tenantUserId": null,
          "params": { "tenant_id": "acme_corp" },
          "createdAt": "2025-03-01T11:00:00.000Z",
          "updatedAt": "2025-03-01T11:00:00.000Z"
        },
        "definition": {
          "id": "usd_abc123",
          "projectId": "p_1234567890",
          "name": "Multi-tenant isolation"
        },
        "connection": {
          "id": "conn_xyz789",
          "name": "Production Postgres",
          "type": "POSTGRES"
        },
        "orgUser": null,
        "tenant": { "id": "t_acme", "name": "Acme Corp" },
        "tenantUser": null
      }
    ]
  }
}

Create Assignment

POST /assignments

Creates a new assignment that binds a definition to an actor.

Request body:

FieldTypeRequiredDescription
definitionIdstringYesID of the definition to assign
scopeTypestringYesOne of ALL_TENANTS, TENANT, TENANT_USER, ORG_USER
orgUserIdstringConditionalRequired when scopeType is ORG_USER
tenantIdstringConditionalRequired when scopeType is TENANT
tenantUserIdstringConditionalRequired when scopeType is TENANT_USER
paramsobjectNoParameter values to fill placeholders in the definition

Scope type validation

Each scope type requires specific fields and rejects others:

Scope TypeRequired FieldsMust Not Set
ALL_TENANTSdefinitionIdorgUserId, tenantId, tenantUserId
TENANTdefinitionId, tenantIdorgUserId, tenantUserId
TENANT_USERdefinitionId, tenantUserIdorgUserId, tenantId
ORG_USERdefinitionId, orgUserIdtenantId, tenantUserId
curl -X POST "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/assignments" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "definitionId": "usd_abc123",
    "scopeType": "TENANT",
    "tenantId": "t_acme",
    "params": {
      "tenant_id": "acme_corp",
      "db_host": "acme.db.example.com"
    }
  }'

Response (201):

{
  "ok": true,
  "data": {
    "assignment": {
      "id": "usa_def456",
      "definitionId": "usd_abc123",
      "scopeType": "TENANT",
      "orgUserId": null,
      "tenantId": "t_acme",
      "tenantUserId": null,
      "params": { "tenant_id": "acme_corp", "db_host": "acme.db.example.com" },
      "createdAt": "2025-03-01T11:00:00.000Z",
      "updatedAt": "2025-03-01T11:00:00.000Z"
    }
  }
}

Get Assignment

GET /assignments/{assignmentId}

Returns a single assignment with its related definition, connection, and actor details.

curl -X GET "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/assignments/{assignmentId}" \
  -H "Authorization: Bearer <token>"

Response:

{
  "ok": true,
  "data": {
    "assignment": {
      "assignment": {
        "id": "usa_def456",
        "definitionId": "usd_abc123",
        "scopeType": "TENANT",
        "tenantId": "t_acme",
        "params": { "tenant_id": "acme_corp" },
        ...
      },
      "definition": { "id": "usd_abc123", "projectId": "p_1234567890", "name": "Multi-tenant isolation" },
      "connection": { "id": "conn_xyz789", "name": "Production Postgres", "type": "POSTGRES" },
      "tenant": { "id": "t_acme", "name": "Acme Corp" },
      "orgUser": null,
      "tenantUser": null
    }
  }
}

Update Assignment

PATCH /assignments/{assignmentId}

Updates an existing assignment. Provide only the fields you want to change. At least one field must be included.

Request body:

FieldTypeRequiredDescription
scopeTypestringNoUpdated scope type
orgUserIdstring | nullNoUpdated org user ID
tenantIdstring | nullNoUpdated tenant ID
tenantUserIdstring | nullNoUpdated tenant user ID
paramsobject | nullNoUpdated parameter values
curl -X PATCH "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/assignments/{assignmentId}" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "params": { "tenant_id": "acme_corp_v2" }
  }'

Response:

{
  "ok": true,
  "data": {
    "assignment": {
      "id": "usa_def456",
      "params": { "tenant_id": "acme_corp_v2" },
      ...
    }
  }
}

Delete Assignment

DELETE /assignments/{assignmentId}

curl -X DELETE "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/assignments/{assignmentId}" \
  -H "Authorization: Bearer <token>"

Response:

{
  "ok": true,
  "data": {
    "assignment": {
      "id": "usa_def456",
      ...
    }
  }
}

Preview

The preview endpoint lets you test how security policies resolve for a given actor without affecting live dashboards. Use it to verify that definitions and assignments produce the expected CLS, SLS, and RLS context before going live.

Preview Security Context

POST /preview

Resolves the full security context for an actor against a connection, including any compiled RLS conditions.

Request body:

FieldTypeRequiredDescription
connectionIdstringYesConnection to resolve against
actorActorYesThe actor to preview
assignmentIdstringNoPreview using a specific persisted assignment
draftAssignmentobjectNoPreview an unsaved assignment (same shape as create assignment body)
ignorePersistedAssignmentsbooleanNoIf true, only use tokenPolicyInput (skip stored assignments)
runtimeParamsobjectNoAdditional runtime parameter values
tokenPolicyInputobjectNoInline CLS/SLS/RLS to merge (simulates token-level policy)
sqlstringNoSQL statement for RLS compilation
referencedEntitiesarrayNoTable references for RLS matcher resolution

Actor object

The actor field uses a discriminated union on kind:

// Organization user
{ "kind": "ORG_USER", "orgUserId": "u_123" }
 
// Tenant (all users in tenant)
{ "kind": "TENANT", "tenantId": "t_acme" }
 
// Specific tenant user
{ "kind": "TENANT_USER", "tenantId": "t_acme", "tenantUserId": "tu_456" }

Example request:

curl -X POST "https://semaphor.cloud/api/management/v1/projects/{projectId}/unified-security/preview" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "connectionId": "conn_xyz789",
    "actor": { "kind": "TENANT", "tenantId": "t_acme" },
    "sql": "SELECT * FROM orders"
  }'

Response:

{
  "ok": true,
  "data": {
    "projectId": "p_1234567890",
    "connectionId": "conn_xyz789",
    "actor": { "kind": "TENANT", "tenantId": "t_acme" },
    "resolved": {
      "cls": {
        "connectionTemplate": null,
        "filePathTemplates": {},
        "params": {}
      },
      "sls": {
        "schema": null,
        "allowedSchemas": [],
        "defaultSchema": null
      },
      "rls": {
        "rules": [
          {
            "name": "tenant_filter",
            "matcher": { "type": "ALL_TABLES_WITH_COLUMN", "column": "tenant_id" },
            "expression": "tenant_id = {{tenant_id}}",
            "params": { "tenant_id": "acme_corp" }
          }
        ]
      },
      "sources": {
        "cls": [],
        "sls": [],
        "rls": ["TENANT_ASSIGNMENT"]
      }
    },
    "compiled": {
      "status": "compiled",
      "rclsConditions": [
        {
          "tableName": "orders",
          "condition": "tenant_id = 'acme_corp'"
        }
      ]
    },
    "meta": {
      "hasAssignments": true,
      "tokenOnly": false
    }
  }
}

The compiled field shows how RLS rules translate to SQL conditions for the given sql or referencedEntities. If no SQL is provided, compiled.status is "not_requested".


Type Reference

CLSConfig

Connection-level security. Controls which database connection or file path the actor uses.

FieldTypeRequiredDescription
connectionTemplatestring | nullNoConnection string template with {{ }} placeholders
filePathTemplatesRecord<string, string>NoFile path templates keyed by source name
paramsRecord<string, string | number | boolean>NoStatic parameter values for CLS resolution

Mutual exclusion

Use either connectionTemplate or filePathTemplates, not both. At least one of connectionTemplate, filePathTemplates, or params must be present.

{
  "connectionTemplate": "host={{db_host}};port=5432;database={{db_name}}",
  "params": { "db_host": "prod.example.com" }
}

SLSConfig

Schema-level security. Controls which database schema the actor queries.

FieldTypeRequiredDescription
schemastring | nullNoFixed schema name
schemaTemplatestring | nullNoSchema name with {{ }} placeholders
allowedSchemasstring[]NoAllowlist of permitted schemas
defaultSchemastring | nullNoFallback schema. Must exist in allowedSchemas if set.

Use either schema or schemaTemplate, not both. At least one field must be present.

{
  "schemaTemplate": "tenant_{{tenant_slug}}",
  "allowedSchemas": ["tenant_acme", "tenant_globex", "shared"],
  "defaultSchema": "shared"
}

RLSConfig

Row-level security. Contains one or more filter rules that inject WHERE conditions at query time.

FieldTypeRequiredDescription
rulesRLSRule[]YesAt least one rule is required
{
  "rules": [
    {
      "name": "region_filter",
      "matcher": { "type": "ALL_TABLES_WITH_COLUMN", "column": "region" },
      "expression": "region IN ({{allowed_regions}})"
    }
  ]
}

RLSRule

A single row-level filter rule.

FieldTypeRequiredDescription
namestringNoHuman-readable name for the rule
descriptionstring | nullNoOptional description
matcherRLSMatcherYesDetermines which tables this rule applies to
expressionstringYesSQL expression injected as a WHERE condition. Supports {{ }} placeholders.
paramsRecord<string, ParamValue>NoStatic param values for this rule
enabledbooleanNoDefaults to true. Set false to disable without removing.

RLSMatcher

Determines which tables an RLS rule applies to. Three variants:

ALL_TABLES_WITH_COLUMN -- applies to every table that contains the named column.

{ "type": "ALL_TABLES_WITH_COLUMN", "column": "tenant_id" }

TABLE_LIST -- applies to an explicit set of tables.

{
  "type": "TABLE_LIST",
  "tables": [
    { "table": "orders" },
    { "schema": "analytics", "table": "events" },
    { "database": "warehouse", "schema": "public", "table": "shipments" }
  ]
}

Each table entry requires a table name. The schema and database fields are optional qualifiers.

SCHEMA -- applies to all tables in a schema, optionally filtered by column.

{ "type": "SCHEMA", "schema": "tenant_data", "column": "org_id" }

Parameter Values

Parameters (params in assignments, rules, and configs) accept the following value types:

TypeExample
string"acme_corp"
number42
booleantrue
string[]["us-east", "us-west"]
number[][1, 2, 3]

Error Handling

All error responses follow this format:

{
  "ok": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message",
    "details": {}
  }
}

Common Error Codes

CodeHTTP StatusDescription
AUTH_FAILED401Missing or invalid authentication
PROJECT_ACCESS_DENIED403User lacks ADMIN role
PROJECT_NOT_FOUND404Project does not exist
NOT_FOUND404Definition or assignment not found
INVALID_REQUEST400Validation failed (see details for field-level errors)
CONFLICT409Cannot delete a definition with existing assignments
INTERNAL_ERROR500Unexpected server error

Validation Errors

When validation fails, the response includes a details object with field-level errors:

{
  "ok": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "Invalid Unified Security definition payload.",
    "details": {
      "fieldErrors": {
        "connectionId": ["Required"]
      },
      "formErrors": []
    }
  }
}