Tenants API
Manage Tenants. Requires BrandOwner on the Brand that owns the Tenants (or platform-superadmin for cross-Brand operations). The Brand context is resolved from the bearer token; you don't pass brandId in the path.
List tenants
GET /api/tenants
Returns every Tenant under the caller's Brand.
200 OK
{
"items": [
{
"id": "ten_01",
"slug": "acme-customer-1",
"name": "Acme Customer 1",
"brandId": "acme-agency",
"status": "active",
"createdAt": "2026-05-25T10:30:00.000Z"
}
]
}Create a tenant
POST /api/tenants
Request body
{
"slug": "acme-customer-2",
"name": "Acme Customer 2",
"preset": "sales-b2b" // see ADR-006 team presets
}
200 OK
{ "id": "ten_02", "slug": "acme-customer-2", ... }
400 Bad Request
{ "error": "invalid_slug" }
{ "error": "unknown_preset" }
403 Forbidden
{ "error": "requires_brand_owner" }
409 Conflict
{ "error": "slug_exists",
"detail": "Tenant slug acme-customer-2 already exists for this Brand" }Behind the scenes the create call seeds the agents for the chosen preset (see ADR-006), opens a new audit chain HMAC-signed by AUDIT_HMAC_SECRET, and initialises an empty encrypted-secrets row scoped to the new Tenant.
Get a tenant
GET /api/tenants/:id
200 OK
{
"id": "ten_02",
"slug": "acme-customer-2",
"name": "Acme Customer 2",
"brandId": "acme-agency",
"preset": "sales-b2b",
"agents": [
{ "slug": "sales", "name": "Sales", "kind": "department" },
{ "slug": "sdr", "name": "SDR", "parentSlug": "sales" },
...
]
}Update a tenant
PATCH /api/tenants/:id
Request body
{ "name": "Acme Customer 2 (US)" }
200 OK
{ "id": "ten_02", "name": "Acme Customer 2 (US)", ... }Provision an agent under a tenant
POST /api/tenants/:id/agents
Request body
{ "slug": "marketing" }
200 OK
{ "slug": "marketing", "name": "Marketing", "kind": "department" }Sub-agents auto-expand — provisioning marketing will also create growth, brand, and content if they aren't already present.
Provision an MCP server for a tenant
POST /api/tenants/:id/mcp-servers
Request body
{
"name": "Zapier (Acme Customer 2)",
"url": "https://mcp.zapier.com/v1/.../sse"
}
200 OK
{ "id": "mcp_01", "name": "Zapier (Acme Customer 2)", "active": true }
400 Bad Request
{ "error": "unsafe_mcp_url", "detail": "Private IP not allowed" }Invite a Tenant Owner
POST /api/tenants/:id/owner-invite
Request body
{ "email": "ops@acme-customer-2.com" }
200 OK
{ "id": "inv_01", "email": "ops@acme-customer-2.com" }Tenant health
GET /api/admin/tenants/:id/health — platform-side diagnostic endpoint. Returns counters (audit-chain length, encrypted-secrets count, active MCP servers, last successful agent run).
Delete a tenant
DELETE /api/tenants/:id
200 OK
{ "id": "ten_02", "status": "archived" }Tenants are soft-deleted (status flipped to archived); audit rows are retained. Hard-delete is operator-driven and not exposed over the API.
Source: src/platform/tenants-api.ts / ADR-003 §Tenant.