Tenant Administration
Learning Objectives
- Create, update, and delete tenants via the portal API
- Understand the tenant ID format and what each part means
- Manage API keys for a tenant (create, list, revoke)
- Monitor tenant usage via the dashboard API
Tenant Data Model
Tenant ID Format
xs_payment_ab3cd4ef
│ │ │
│ │ └── 8-char lower-case base32 (random)
│ └── Organisation slug (from display_name)
└── xScaler tenant prefix
Tenant Lifecycle
Create a Tenant
- Portal UI
- Navigate to
https://portal.xscalerlabs.com→ Tenants → New Tenant - Enter
Display Nameand selectEnvironment - Click Create
[Screenshot: New Tenant modal with Display Name field and Environment dropdown]
- API
# Create a tenant
curl -s -X POST $PORTAL_BASE/tenants \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "Payment Service Production",
"environment": "prod"
}' | jq .
Expected response:
{
"id": "xs_payment_ab3cd4ef",
"display_name": "Payment Service Production",
"environment": "prod",
"region": "eu-west-1",
"metrics_host": "euw1-01.m.xscalerlabs.com",
"logs_host": "euw1-01.l.xscalerlabs.com",
"traces_host": "euw1-01.t.xscalerlabs.com",
"created_at": "2026-06-18T10:00:00Z"
}
List Tenants
curl -s $PORTAL_BASE/tenants \
-H "Authorization: Bearer $JWT_TOKEN" | jq '.[].id'
Get Tenant Details
export TENANT_ID="xs_payment_ab3cd4ef"
curl -s $PORTAL_BASE/tenants/$TENANT_ID \
-H "Authorization: Bearer $JWT_TOKEN" | jq .
Update a Tenant
curl -s -X PATCH $PORTAL_BASE/tenants/$TENANT_ID \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"display_name": "Payment Service EU Production"}' | jq .
API Key Management
Create an API Key
KEY_RESPONSE=$(curl -s -X POST $PORTAL_BASE/tenants/$TENANT_ID/keys \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"display_name": "k8s-prod-collector-1"}')
echo $KEY_RESPONSE | jq .
# Save the key immediately — it is only shown once
export API_KEY=$(echo $KEY_RESPONSE | jq -r '.key')
echo "API Key: $API_KEY"
One-Time Display
The full API key value is shown once at creation. Only the first 8 characters (prefix) are stored for display. Always save the key to your secret management system immediately.
List API Keys
curl -s $PORTAL_BASE/tenants/$TENANT_ID/keys \
-H "Authorization: Bearer $JWT_TOKEN" | jq '.[] | {id, display_name, prefix, created_at}'
Output shows prefix only:
{
"id": "key_abc123",
"display_name": "k8s-prod-collector-1",
"prefix": "xag_a1b2",
"created_at": "2026-06-18T10:00:00Z"
}
Revoke an API Key
# Get key ID first
KEY_ID=$(curl -s $PORTAL_BASE/tenants/$TENANT_ID/keys \
-H "Authorization: Bearer $JWT_TOKEN" | jq -r '.[0].id')
# Revoke
curl -s -X DELETE $PORTAL_BASE/tenants/$TENANT_ID/keys/$KEY_ID \
-H "Authorization: Bearer $JWT_TOKEN"
Revocation Delay
proxy-auth caches key lookups for up to 15 seconds. After revocation, the key may still work for up to 15 seconds before the cache expires.
Usage Monitoring
Dashboard Summary
curl -s $PORTAL_BASE/dashboard/org/summary \
-H "Authorization: Bearer $JWT_TOKEN" | jq '{
billing_plan: .billing_plan,
active_series: .org_active_series,
plan_max_series: .plan_max_active_series,
logs_gb_this_month: .org_logs_gb_ingested_this_month,
pct_used: (.org_active_series / .plan_max_active_series * 100 | floor)
}'
Expected response:
{
"billing_plan": "scale",
"active_series": 1243,
"plan_max_series": 20000,
"logs_gb_this_month": 0.42,
"pct_used": 6
}
Per-Tenant Usage
curl -s $PORTAL_BASE/dashboard/tenants/$TENANT_ID/usage \
-H "Authorization: Bearer $JWT_TOKEN" | jq .
Hands-On Exercise
Exercise 4.1 — Tenant Lifecycle
# Set up environment
export PORTAL_BASE="https://portal.xscalerlabs.com"
export JWT_TOKEN=$(curl -s -X POST $PORTAL_BASE/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"training@example.com","password":"Training123!"}' | jq -r '.token')
# Create three tenants (simulate multi-environment setup)
for ENV in prod staging dev; do
curl -s -X POST $PORTAL_BASE/tenants \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"display_name\": \"Workshop Service $ENV\", \"environment\": \"$ENV\"}" | jq '.id'
done
# List all tenants
curl -s $PORTAL_BASE/tenants \
-H "Authorization: Bearer $JWT_TOKEN" | jq '.[].display_name'
# Create API keys for the prod tenant
PROD_TENANT=$(curl -s $PORTAL_BASE/tenants \
-H "Authorization: Bearer $JWT_TOKEN" | jq -r '.[] | select(.environment=="prod") | .id' | head -1)
# Create active key
ACTIVE_KEY=$(curl -s -X POST $PORTAL_BASE/tenants/$PROD_TENANT/keys \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"display_name": "active-collector-key"}' | jq -r '.key')
# Create backup key
BACKUP_KEY=$(curl -s -X POST $PORTAL_BASE/tenants/$PROD_TENANT/keys \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"display_name": "backup-key"}' | jq -r '.key')
echo "Active: $ACTIVE_KEY"
echo "Backup: $BACKUP_KEY"
Validation
- Three tenants created (prod, staging, dev)
- Two API keys created for prod tenant
-
curl $PORTAL_BASE/tenantslists all three tenants - Usage dashboard shows 0 active series initially
- Portal UI shows tenants in the dashboard
Troubleshooting
Cannot create more than 3 tenants on Free plan
Free plan limits tenant count. In training, use the provided Scale plan Stripe key. Check current plan:
curl -s $PORTAL_BASE/dashboard/org/summary \
-H "Authorization: Bearer $JWT_TOKEN" | jq .billing_plan
Tenant ID not matching expected format
The format xs_<slug>_<base32> is auto-generated. You cannot specify the ID at creation time.
Key Takeaways
Session 4.1 Summary
- Tenant ID format:
xs_<orgslug>_<8-char-lower-base32>— auto-generated, not configurable - Always create two API keys per tenant: active + backup
- API key value is shown once — save immediately to secret manager
- Revocation has up to 15 second propagation delay (proxy-auth cache)
usage-syncruns every 60 seconds to update usage metrics in PostgreSQL
← Previous: Session 4 Overview
Next: Agent Deployment →