Open edX Tenant API
REST API for programmatic tenant creation in Open edX with eox-tenant.
Features
- Create tenants with automatic MFE configuration
- List all provisioned tenants
- Delete tenants
- Health check endpoint
- Audit logging of all tenant operations
Installation
1. Add to Tutor Config
Edit your Tutor config.yml:
MOUNTS:
- /path/to/openedx-tenant-api
OPENEDX_EXTRA_PIP_REQUIREMENTS:
- -e /mnt/openedx-tenant-api
2. Restart LMS
tutor dev restart lms
3. Install Package and Run Migrations
# Install in running container
docker exec tutor_dev-lms-1 sh -c "pip install -e /mnt/openedx-tenant-api --no-build-isolation"
# Create migrations
docker exec tutor_dev-lms-1 sh -c "cd /openedx/edx-platform && python manage.py lms makemigrations openedx_tenant_api"
# Run migrations
docker exec tutor_dev-lms-1 sh -c "cd /openedx/edx-platform && python manage.py lms migrate openedx_tenant_api"
API Endpoints
| Endpoint | Method | Auth Required | Description |
|---|---|---|---|
/api/tenant/v1/health |
GET | No | Health check |
/api/tenant/v1/list |
GET | Yes (Admin) | List all tenants |
/api/tenant/v1/create |
POST | Yes (Admin) | Create new tenant |
/api/tenant/v1/delete/{tenant_name} |
DELETE | Yes (Admin) | Delete tenant |
Usage
Health Check
curl http://local.openedx.io:8000/api/tenant/v1/health
Response:
{
"status": "healthy",
"eox_tenant_installed": true,
"tenant_count": 2
}
Create Tenant
Requires admin user authentication. Supports Basic Auth, JWT Bearer, or Session auth.
import requests
# Using Basic Authentication (simplest)
response = requests.post(
'http://local.openedx.io:8000/api/tenant/v1/create',
auth=('admin', 'your-password'),
json={
'tenant_name': 'talent1',
'platform_name': 'Talent 1 Learning',
'theme_name': 'indigo',
'org_filter': ['org1'] # Optional
}
)
print(response.json())
# Using cURL with Basic Auth
curl -X POST http://local.openedx.io:8000/api/tenant/v1/create \
-u admin:your-password \
-H "Content-Type: application/json" \
-d '{
"tenant_name": "talent1",
"platform_name": "Talent 1 Learning",
"theme_name": "indigo"
}'
Response:
{
"status": "success",
"tenant_id": 123,
"tenant_name": "talent1",
"lms_url": "http://talent1.local.openedx.io:8000",
"authn_url": "http://talent1.apps.local.openedx.io:1999/authn",
"learner_dashboard_url": "http://talent1.apps.local.openedx.io:1996/learner-dashboard/",
"message": "Tenant created successfully. Add DNS entries to your hosts file."
}
List Tenants
import requests
response = requests.get(
'http://local.openedx.io:8000/api/tenant/v1/list',
headers={'Authorization': 'Bearer YOUR_TOKEN'}
)
print(response.json())
Delete Tenant
import requests
response = requests.delete(
'http://local.openedx.io:8000/api/tenant/v1/delete/talent1',
headers={'Authorization': 'Bearer YOUR_TOKEN'}
)
print(response.json())
Testing via Django Shell
For testing without authentication:
docker exec tutor_dev-lms-1 sh -c "cd /openedx/edx-platform && python manage.py lms shell" << 'EOF'
import django
django.setup()
from django.db import transaction
from django.contrib.auth import get_user_model
from eox_tenant.models import TenantConfig, Route
User = get_user_model()
admin = User.objects.filter(is_superuser=True).first()
tenant_name = 'talent1'
platform_name = 'Talent 1 Learning'
theme_name = 'indigo'
external_key = f'{tenant_name}.local.openedx.io'
lms_domain = f'{tenant_name}.local.openedx.io'
# Generate MFE config
lms_configs = {
'LMS_BASE': f'{lms_domain}:8000',
'SITE_NAME': lms_domain,
'PLATFORM_NAME': platform_name,
'THEME_NAME': theme_name,
'BASE_URL': f'http://{tenant_name}.apps.local.openedx.io',
'LMS_BASE_URL': f'http://{lms_domain}:8000',
'LEARNER_HOME_MICROFRONTEND_URL': f'http://{tenant_name}.apps.local.openedx.io:1996/learner-dashboard/',
'AUTHN_MICROFRONTEND_URL': f'http://{tenant_name}.apps.local.openedx.io:1999/authn',
# ... add other MFE URLs as needed
}
with transaction.atomic():
tenant_config = TenantConfig.objects.create(
external_key=external_key,
lms_configs=lms_configs,
theming_configs={'THEME_NAME': theme_name},
meta={'created_by': admin.username}
)
Route.objects.create(domain=lms_domain, config=tenant_config)
print(f'Tenant {tenant_name} created!')
EOF
Hosts File Configuration
After creating a tenant, add these entries to your hosts file:
127.0.0.1 talent1.local.openedx.io
127.0.0.1 talent1.apps.local.openedx.io
127.0.0.1 studio.talent1.local.openedx.io
MFE Configuration
The API automatically generates MFE URLs for the tenant:
- Authn:
http://{tenant}.apps.local.openedx.io:1999/authn - Learner Dashboard:
http://{tenant}.apps.local.openedx.io:1996/learner-dashboard/ - Account:
http://{tenant}.apps.local.openedx.io:1997/account/ - Profile:
http://{tenant}.apps.local.openedx.io:1995/profile/u/ - Learning:
http://{tenant}.apps.local.openedx.io:2000/learning - Discussions:
http://{tenant}.apps.local.openedx.io:2002/discussions - Gradebook:
http://{tenant}.apps.local.openedx.io:1994/gradebook - Communications:
http://{tenant}.apps.local.openedx.io:1984/communications - ORA Grading:
http://{tenant}.apps.local.openedx.io:1993/ora-grading - Admin Console:
http://{tenant}.apps.local.openedx.io:2025/admin-console - Course Authoring:
http://{tenant}.apps.local.openedx.io:2001/authoring
Architecture
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ External App │────▶│ Tenant API │────▶│ eox-tenant │
│ (Your System) │ │ (Django REST) │ │ (TenantConfig) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌──────────────────┐
│ TenantProvisioningLog │
│ (Audit Trail) │
└──────────────────┘
License
MIT