Tenant Administration
Tenant Administration
Section titled “Tenant Administration”Day-to-day management of tenants in Mantis.
Overview
Section titled “Overview”Tenant administration covers ongoing management tasks:
Viewing Tenants
Section titled “Viewing Tenants”List All Tenants
Section titled “List All Tenants”curl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/tenants"Response:
{ "data": [ { "id": "019b937d-4862-84ae-9c2a-6ac230cc4c7e", "name": "Acme Corp", "description": "Enterprise customer", "contact_email": "ops@acme.com", "created_at": "2024-01-15T10:30:00Z" }, { "id": "019b937d-4862-84ae-9c2a-6ac230cc4c80", "name": "Globex Inc", "description": "SMB customer", "contact_email": "admin@globex.com", "created_at": "2024-01-20T14:00:00Z" } ], "meta": { "total": 2, "page": 1, "limit": 20 }}Get Tenant Details
Section titled “Get Tenant Details”curl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID"Response includes relationships:
{ "id": "019b937d-4862-84ae-9c2a-6ac230cc4c7e", "name": "Acme Corp", "description": "Enterprise customer", "contact_email": "ops@acme.com", "logo_url": "https://acme.com/logo.png", "tags": [ { "id": "tag_region", "key": "region", "value": "us-east" }, { "id": "tag_tier", "key": "tier", "value": "enterprise" } ], "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-02-01T09:15:00Z"}Updating Tenants
Section titled “Updating Tenants”Update Tenant Details
Section titled “Update Tenant Details”curl -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Corporation", "description": "Updated enterprise customer description", "contact_email": "newops@acme.com" }' \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID"Update Fields
Section titled “Update Fields”| Field | Update Behavior |
|---|---|
name | Must remain unique |
description | Set to null to clear |
contact_email | Set to null to clear |
logo_url | Set to null to clear |
Via Lens UI
Section titled “Via Lens UI”- Navigate to Tenancy → Tenants
- Click on the tenant name
- Click Edit
- Update fields
- Click Save Changes
Managing Entitlements
Section titled “Managing Entitlements”View Current Entitlements
Section titled “View Current Entitlements”# List solutionscurl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/solutions"
# List targetscurl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/targets"
# List environmentscurl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/environments"Add Solution Entitlement
Section titled “Add Solution Entitlement”curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "solution_id": "sol_newfeature", "environment_id": "env_staging" }' \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/solutions"Remove Solution Entitlement
Section titled “Remove Solution Entitlement”curl -X DELETE \ -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/solutions/$SOLUTION_ID/environments/$ENV_ID"Entitlement Matrix
Section titled “Entitlement Matrix”Document tenant entitlements:
| Tenant | Solution | Production | Staging | Development |
|---|---|---|---|---|
| Acme Corp | WebApp | ✓ | ✓ | ✗ |
| Acme Corp | API | ✓ | ✓ | ✗ |
| Globex Inc | WebApp | ✓ | ✗ | ✗ |
Managing Variables
Section titled “Managing Variables”Tenant variables live in variable sets, scoped with scope_tenant_id. There is no
/tenants/{id}/variables endpoint.
# List variables in a setcurl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/variable-sets/$VARIABLE_SET_ID/variables"
# Update onecurl -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"value": "https://api-v2.acme.com"}' \ "https://api.mantis.local/api/v1/variable-sets/$VARIABLE_SET_ID/variables/$VARIABLE_ID"
# Delete onecurl -X DELETE \ -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/variable-sets/$VARIABLE_SET_ID/variables/$VARIABLE_ID"Rotating a secret is an update with is_encrypted: true; reads return
"<ENCRYPTED>" instead of the value, so the plaintext never leaves the database.
Viewing Deployments
Section titled “Viewing Deployments”List Tenant Deployments
Section titled “List Tenant Deployments”curl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/deployments"Response:
{ "data": [ { "id": "019b937d-4862-8de0-1111-222233334444", "source_type": "solution", "solution_name": "Solution 019b937d-4862-8de0-1111-222233334444", "state": "success", "progress": 1.0, "tenant_name": "Acme Corp", "environment_name": "Production", "created_at": "2024-02-01T10:00:00Z", "completed_at": "2024-02-01T10:05:32Z", "duration_ms": 332000 } ], "meta": { "total": 150, "page": 1, "limit": 20 }}Paginate Deployments
Section titled “Paginate Deployments”# Paginationcurl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID/deployments?page=2&limit=50"State-based filtering is not currently supported on this endpoint. Use the SQL monitoring queries below for status-filtered analysis.
Deployment Statistics
Section titled “Deployment Statistics”Track tenant health with deployment metrics:
SELECT DATE_TRUNC('day', created_at) AS day, COUNT(*) AS total_deployments, SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) AS successful, SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed, AVG(EXTRACT(EPOCH FROM (completed_at - started_at))) AS avg_duration_secsFROM deployment_historyWHERE tenant_id = $tenant_idAND created_at >= NOW() - INTERVAL '30 days'GROUP BY DATE_TRUNC('day', created_at)ORDER BY day DESC;User Management
Section titled “User Management”List Tenant Users
Section titled “List Tenant Users”curl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/admin/users"System admins see all users; a tenant-scoped caller sees users in their tenant plus tenant-less (global) users (tenant_id IS NULL), resolved by effective_tenant_scope. There is an admin-only tenant_id query parameter to narrow the listing; it is ignored for tenant-scoped callers.
Add User to Tenant
Section titled “Add User to Tenant”curl -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "bob", "email": "bob@acme.com", "password": "secure_password", "tenant_id": "019b937d-4862-84ae-9c2a-6ac230cc4c7e", "role_ids": ["role_viewer"] }' \ "https://api.mantis.local/api/v1/admin/users"Move User Between Tenants
Section titled “Move User Between Tenants”curl -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "tenant_id": "ten_new_tenant" }' \ "https://api.mantis.local/api/v1/admin/users/$USER_ID"Remove Tenant User
Section titled “Remove Tenant User”Disable rather than delete for audit retention:
curl -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"status": "inactive"}' \ "https://api.mantis.local/api/v1/admin/users/$USER_ID"Audit Review
Section titled “Audit Review”View Tenant Audit Logs
Section titled “View Tenant Audit Logs”Audit logs are read from /api/v1/audit/logs. A tenant-scoped caller sees their
tenant’s entries plus global/system entries (tenant_id IS NULL); an admin
with the admin role and no tenant sees all tenants. An admin-only tenant_id
query parameter narrows the listing (it is ignored for tenant-scoped callers).
curl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/audit/logs"Filter by Category
Section titled “Filter by Category”# Authentication eventscurl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/audit/logs?category=authentication"
# Deployment eventscurl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/audit/logs?category=deployment"
# Security eventscurl -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/audit/logs?severity=security"Export Audit Logs
Section titled “Export Audit Logs”audit_log_entries enforces row-level security, so the scope must be declared
or the export silently produces a file containing only system rows. Use \copy
from psql — server-side COPY TO <file> needs superuser or pg_write_server_files.
SET mantis.audit_tenant_id = '<tenant-uuid>';
\copy ( SELECT occurred_at, event_type, event_category, severity, actor_id, action, outcome, metadata FROM audit_log_entries WHERE tenant_id = '<tenant-uuid>' AND occurred_at >= '2024-01-01' AND occurred_at < '2024-02-01' ORDER BY occurred_at) TO 'audit_export.csv' WITH CSV HEADERTenant Lifecycle
Section titled “Tenant Lifecycle”Onboarding Checklist
Section titled “Onboarding Checklist”□ Create tenant□ Configure description and contact□ Attach required solutions to environments□ Assign targets□ Set up variables (including encrypted secrets)□ Add tags for organization□ Create initial users□ Configure SSO (if applicable)□ Document tenant configuration□ Notify tenant of accessDeactivation
Section titled “Deactivation”When a tenant needs to be temporarily disabled:
# Remove all solution entitlementsfor SOL_ENV in $(get_tenant_solutions); do curl -X DELETE "$API_URL/tenants/$TENANT_ID/solutions/$SOL_ENV"done
# Disable all tenant users (valid statuses: active, inactive, locked, pending)for USER_ID in $(get_tenant_users); do curl -X PUT -d '{"status": "inactive"}' \ "https://api.mantis.local/api/v1/admin/users/$USER_ID"doneReactivation
Section titled “Reactivation”# Re-enable usersfor USER_ID in $(get_tenant_users); do curl -X PUT -d '{"status": "active"}' \ "https://api.mantis.local/api/v1/admin/users/$USER_ID"done
# Restore entitlementscurl -X POST -d '{"solution_id": "sol_...", "environment_id": "env_..."}' \ "$API_URL/tenants/$TENANT_ID/solutions"Deleting Tenants
Section titled “Deleting Tenants”Prerequisites
Section titled “Prerequisites”Before deletion:
- Export data - Audit logs, deployment history
- Notify stakeholders - Confirm deletion is authorized
- Disable users first - Prevent new activity
- Document decision - For compliance
Deletion Impact
Section titled “Deletion Impact”| Resource | On Tenant Delete |
|---|---|
| Solution-Environment links | Cascade deleted |
| Target assignments | Cascade deleted |
| Tenant tags | Cascade deleted |
Deletion is blocked (409) while any ON DELETE RESTRICT child rows remain.
Those tables are: variable_sets, scoped_variables, deployment_schedules,
deployment_freezes, promotion_lifecycles, api_keys, environment_states,
environment_state_history, variable_snapshots, storages_local,
storages_git, storages_git_auth, storages_s3, and notification_events.
Remove or reassign these before deleting the tenant.
| Users | Not deleted - orphaned |
| Audit logs | Preserved |
| Deployment history | Preserved |
Delete Tenant
Section titled “Delete Tenant”curl -X DELETE \ -H "Authorization: Bearer $TOKEN" \ "https://api.mantis.local/api/v1/tenants/$TENANT_ID"Post-Deletion Cleanup
Section titled “Post-Deletion Cleanup”No manual user cleanup is needed. The users.tenant_id foreign key is
ON DELETE SET NULL, so PostgreSQL automatically clears it when a tenant is
deleted — there are no orphaned users to find afterward. (If you need the list of
affected users, query them BEFORE deleting the tenant.)
Monitoring and Alerts
Section titled “Monitoring and Alerts”Tenant Health Metrics
Section titled “Tenant Health Metrics”Monitor key metrics per tenant:
| Metric | Alert Threshold | Action |
|---|---|---|
| Failed deployments | >3 in 1 hour | Investigate failures |
| Inactive users | >30 days | Review user access |
| Variable changes | Unexpected | Review audit logs |
| Target disconnections | >1 target | Check target health |
SQL Monitoring Queries
Section titled “SQL Monitoring Queries”Failed Deployments by Tenant:
SELECT t.name AS tenant, COUNT(*) AS failed_countFROM deployment_history dhJOIN tenants t ON t.id = dh.tenant_idWHERE dh.status = 'failed'AND dh.created_at >= NOW() - INTERVAL '24 hours'GROUP BY t.nameORDER BY failed_count DESC;Active Users per Tenant:
SELECT t.name AS tenant, COUNT(*) AS active_users, MAX(u.last_login_at) AS latest_loginFROM users uJOIN tenants t ON t.id = u.tenant_idWHERE u.status = 'active'GROUP BY t.name;Bulk Operations
Section titled “Bulk Operations”Batch Update Variables
Section titled “Batch Update Variables”#!/bin/bash# Update variable across all tenants
VARIABLE_KEY="api_version"NEW_VALUE="v2"
for TENANT_ID in $(curl -s "$API_URL/tenants" | jq -r '.data[].id'); do curl -s -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"key\": \"$VARIABLE_KEY\", \"value\": \"$NEW_VALUE\", \"scope_tenant_id\": \"$TENANT_ID\", \"is_encrypted\": false }" \ "$API_URL/variable-sets/$VARIABLE_SET_ID/variables" echo "Updated $TENANT_ID"doneBatch Grant Entitlements
Section titled “Batch Grant Entitlements”#!/bin/bash# Grant new solution to all enterprise tenants
SOLUTION_ID="sol_new_feature"ENVIRONMENT_ID="env_production"
# Get tenants with the enterprise tag. NOTE: the LIST response rows carry no# `tags` field (tags appear only on the per-tenant detail response), so filter# tags client-side by fetching each tenant's detail, or filter on a field the# list actually returns.ENTERPRISE_TENANTS=$(curl -s "$API_URL/tenants" | jq -r '.data[].id' | while read id; do curl -s "$API_URL/tenants/$id" | jq -r 'select(.tags[]?.value == "enterprise") | .id'done)
for TENANT_ID in $ENTERPRISE_TENANTS; do curl -s -X POST \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"solution_id\": \"$SOLUTION_ID\", \"environment_id\": \"$ENVIRONMENT_ID\" }" \ "$API_URL/tenants/$TENANT_ID/solutions" echo "Granted to $TENANT_ID"doneTroubleshooting
Section titled “Troubleshooting”Tenant Cannot Deploy
Section titled “Tenant Cannot Deploy”-
Check solution entitlement:
Terminal window curl "$API_URL/tenants/$TENANT_ID/solutions" -
Check target assignment:
Terminal window curl "$API_URL/tenants/$TENANT_ID/targets" -
Verify variables:
Terminal window curl "$API_URL/tenants/$TENANT_ID/variables/resolved?solution_id=$SOL&environment_id=$ENV"
User Cannot Access Tenant
Section titled “User Cannot Access Tenant”-
Verify user’s tenant_id:
SELECT tenant_id FROM users WHERE id = $user_id; -
Check user status:
SELECT status FROM users WHERE id = $user_id; -
Verify tenant exists:
SELECT id, name FROM tenants WHERE id = $tenant_id;
Next Steps
Section titled “Next Steps”- Overview - Multi-tenancy concepts
- Data Isolation - Security model
- Users - User management
- Audit Logs - Audit configuration
