How Customermates is built, how data is isolated, and how external AI access is controlled.
TL;DR — Single-tenant-per-company data model enforced at every layer. Postgres row-level isolation via a company scope. API keys carry user identity. Agents see only what the owning user can see.
Every record belongs to a Company. The company id is enforced in three places:
companyId in every where.@TenantInteractor ensures interactors that forget to scope throw at runtime.Cross-tenant reads are not possible through the public surface. There is no admin panel that bypasses scope.
Three ways to authenticate:
Secure under HTTPS.x-api-key — 64-character token, SHA-256-hashed at rest, tied to a user.All three resolve to the same user and company context. API keys have a display prefix so you can identify them in audit logs.
Per-user, role-driven. Roles carry permissions on resources (contacts, deals, etc.) and actions (read, create, update, delete). Every interactor calls userService.hasPermissionOrThrow(resource, action) before acting.
API keys inherit the permissions of their owning user. No separate key-level ACLs today.
MCP calls are gated by the same API key check. When an AI acts through MCP:
/api/v1/mcp with the user's API key.Safety guardrails specifically for weak models:
null on a relationship array is rejected before reaching the database..env are never logged. The logger redacts anything that looks like a key, token, or password.Every write is logged: user, action, entity, before/after. Queryable from the UI. Exportable as JSON.
Please disclose responsibly to security@customermates.com. PGP key in the repo. We aim to acknowledge within 24 hours.