One customer tenant maps to one workspace.
Treat a CipherRun workspace as the security boundary for one customer tenant. Each customer gets one row in workspaces; tenant-owned workflows, incidents, runs, usage, credentials, memberships, and cr_ws_... API keys stay attached to that workspace. Your MSP team can operate several workspaces, but a customer’s records do not become a shared operator pool.
The critical rule is simple: data access uses the caller’s resolved workspace ID, not an arbitrary workspace ID supplied as a request parameter. Keep that rule in your onboarding checklist, API client code, and review process. The workspace selected by authentication and membership checks is the workspace every downstream query must use.
workspaceId without changing the authenticated key or verified membership, it is not ready for production.Provision a workspace, mint a key, and confirm the API identity before handing the tenant to an operator.
Open /docs/getting-started →Use the quickstart for the authenticated curl surface and workspace-scoped responses.
Open /docs/api-quickstart →Resolve first. Filter every query.
The public v1 API accepts a Bearer key beginning with cr_ws_. Its middleware looks up the active key, sets req.workspaceId from the stored workspace_id, and rejects invalid or revoked keys before the handler runs. It does not trust a caller-provided tenant selector.
// routes/api-v1.js const row = await findActiveKey(plaintext); req.workspaceId = row.workspace_id; // The handler uses the resolved ID. SELECT * FROM workflows WHERE workspace_id = $1; SELECT ... FROM workflow_runs WHERE workspace_id = $1;
That same pattern covers GET /api/v1/workflows, GET /api/v1/incidents, GET /api/v1/usage, and GET /api/v1/me. A key for Workspace A cannot read Workspace B’s workflows, incidents, runs, usage, or credentials because its stored workspace ownership never changes.
Session routes use the matching RBAC guard. middleware/require-workspace.js reads the route workspace ID, calls getUserRole(customerId, workspaceId), and returns 403 when the caller is not a member. The guard also records the verified ID as req.workspaceId; handlers must continue using that value for membership, runs, settings, integrations, and audit data.
Provision, invite, verify, then issue access.
Run this sequence once for each customer. Keep the workspace ID in your service record, but never use it as a substitute for the authenticated caller’s workspace resolution.
- Create the customer workspace. As an authenticated operator, call
POST /api/workspaceswith{ "name": "Customer SOC", "slug": "customer-soc" }. The creator is added as the workspace’s internaladmin. - Invite the customer team. An admin calls
POST /api/workspaces/:workspaceId/members/invitewith the recipient email and the display roleOwner,Admin, orMember. - Accept the seven-day invite. The recipient signs in with the same email address and opens
GET /api/workspaces/invites/accept?token=.... The server checks the logged-in email against the invite before adding membership. - Mint a workspace API key only as an admin. Use the authenticated admin key-management surface at
/admin/apikeys, which callsPOST /api/workspaces/:workspaceId/apikeys. Treat the plaintext as a one-time secret: deliver it through your approved secret channel, store only what your runbook requires, and rotate or revoke it when ownership changes. - Verify the tenant identity. Call
GET /api/v1/mewith the new key and confirm the returnedworkspace.idandworkspace.namematch the customer record before enabling automation.
curl -sS \ -H "Authorization: Bearer cr_ws_REPLACE_WITH_CUSTOMER_KEY..." \ https://cipherrun.polsia.app/api/v1/me
{
"workspace": { "id": 17, "name": "Customer SOC" },
"auth_kind": "apikey",
"scopes": ["workflows:read", "incidents:read", "me:read"]
}
Bind the external tenant before alerts arrive.
SentinelOne is the concrete managed-provider handoff. An admin binds the external tenant ID to the customer workspace with PUT /api/workspaces/:workspaceId/s1-tenant and a body such as { "s1_tenant_id": "customer-tenant-123" }. The binding is normalized and protected by a database uniqueness rule: one SentinelOne tenant can belong to only one workspace.
curl -sS -X PUT \ -H "Content-Type: application/json" \ -d '{ "s1_tenant_id": "customer-tenant-123" }' \ https://cipherrun.polsia.app/api/workspaces/17/s1-tenant
A duplicate binding returns 409 tenant_already_bound; stop and resolve the ownership conflict rather than overwriting another customer. An inbound POST /api/sentinelone/alert whose tenant_id has no binding returns 404 tenant_not_bound_to_workspace. That response is an onboarding signal, not a reason to retry blindly.
Configure the forwarder with the app’s S1_WEBHOOK_SECRET and send it in the X-SentinelOne-Webhook-Secret header. Verify a test alert is authenticated and that the response includes the resolved workspace_id. The created run also carries s1_tenant_id, s1_alert_id, classification, and workspace lineage in its metadata for /admin/s1 and the customer run history.
Make the role translation explicit.
CipherRun displays customer-friendly roles while the enforcement layer uses stable internal names. Keep this mapping in your MSP operating procedure: Owner → admin, Admin → editor, and Member → viewer.
adminCan manage membership, invitations, role changes, workspace API-key lifecycle, SSO and integration binding, and the opt-in auto-containment setting. Owners can see all workspace-scoped runs and the audit log.
editorCan see workspace-scoped runs and the audit log, but does not receive admin-only invitations, role changes, key lifecycle, SSO/integration binding, or auto-containment controls.
viewerCan see workspace-scoped runs as a member, but cannot access the audit log or administer workspace membership and the protected control-plane settings.
Audit-log access is workspace-scoped and available to internal admin or editor roles through /app/audit-log. Invitations, role changes, API-key issuance/rotation/revocation, SentinelOne binding, and auto-containment are guarded as admin-only actions. Auto-containment is opt-in and defaults off; when enabled, only eligible built-in SentinelOne classifications auto-execute and write an audit row.
Prove the boundary before you automate.
Run this checklist for every customer and retain the results with the tenant handoff record.
- Every workflow, incident, run, usage, and credential query filters by the resolved workspace ID; no client-supplied workspace selector can override it.
- The invite recipient signs in with the exact invited email, and a mismatched email is rejected before membership is created.
- Assign the least-privilege display role: Owner only for control-plane administrators, Admin for operators who need run and audit visibility, Member for viewers.
- Deliver each workspace key once, record its owner and purpose, then test rotation and revocation before production use.
- Test a tenant-binding conflict and confirm the second workspace receives
409 tenant_already_bound. - Configure and test webhook authentication with
S1_WEBHOOK_SECRET; confirm an unauthenticated request is rejected. - Review /app/audit-log after a binding, role, key, or auto-containment operation and confirm the entry belongs to the intended workspace.
- Run a negative cross-tenant test: use Workspace A’s key against the API and attempt to retrieve Workspace B’s data; expect no B records, and verify session routes reject a non-member.
When the workspace identity, SentinelOne binding, role assignment, and negative access test all agree, the customer is ready for managed automation.