Automate tenant management
You can manage Logto Cloud tenants programmatically, including creating tenants and continuing configuration without switching to Console.
This is useful when you need to provision tenants from your own onboarding flow, internal platform, AI agent, or integration automation.
The automation flow is:
- Use a Logto Cloud Personal Access Token (PAT) to call the Logto Cloud API.
- Create a tenant with
POST /api/tenants. - Read the default Machine-to-machine (M2M) application credentials from the creation response.
- Use the default M2M application to get a Management API access token for the new tenant.
- Call the new tenant's Management API to continue provisioning applications, users, roles, resources, organizations, and other settings.
Create a PAT
Create a personal access token (PAT) for your Logto Cloud account:
- In Logto Cloud Console, click your avatar in the top-right corner and select Profile.
- Open the Security page.
- In Personal access tokens, click Create new token.
- Enter a name and choose an expiration of up to 365 days, then confirm.
The token is shown only once, right after you create it. Copy it and store it in a safe place, such as a secret manager. You won't be able to see it again.
We recommend an expiration of 90 days and rotating the token before it expires. You can rename or revoke a token on the same page.
Revoking a token takes effect within about a minute.
A PAT can call the Cloud API routes that require the create:tenant or manage:tenant:self scope:
GET /api/me/regionsto list the regions available to your account.POST /api/tenantsto create a tenant.GET /api/tenantsto list your tenants, andPATCHorDELETE /api/tenants/{tenantId}to rename or delete one.POST /api/tenants/{tenantId}/subscriptionand the other endpoints under/api/tenants/{tenantId}to manage a tenant's plan, invoices, and email logs.
Routes that require the manage:account:self scope, such as billing, licenses, and Console SSO, need an interactive Console session and cannot be called with a PAT.
Before you start
Prepare the following values:
| Variable | Description |
|---|---|
CLOUD_API_ENDPOINT | The Logto Cloud API endpoint. For Logto Cloud, use https://cloud.logto.io. |
LOGTO_CLOUD_PAT | A PAT for your Logto Cloud account. |
TENANT_NAME | The display name of the tenant to create. |
TENANT_TAG | The tenant type. Use development or production. |
REGION_NAME | The region identifier for the tenant. |
Set them as environment variables:
export CLOUD_API_ENDPOINT="https://cloud.logto.io"
export LOGTO_CLOUD_PAT="<logto-cloud-pat>"
export TENANT_NAME="My automated tenant"
export TENANT_TAG="development"
export REGION_NAME="<region-name>"
Get available regions
Before creating a tenant, fetch the regions available to your Logto Cloud account:
curl "$CLOUD_API_ENDPOINT/api/me/regions" \
-H "Authorization: Bearer $LOGTO_CLOUD_PAT"
The response contains the available regions. Use the name value as REGION_NAME when creating the tenant.
Example response:
{
"regions": [
{
"name": "EU",
"displayName": "Europe"
},
{
"name": "US",
"displayName": "United States"
}
]
}
Create a tenant
Call POST /api/tenants with the Logto Cloud PAT:
curl "$CLOUD_API_ENDPOINT/api/tenants" \
-X POST \
-H "Authorization: Bearer $LOGTO_CLOUD_PAT" \
-H "Content-Type: application/json" \
-d '{
"name": "'"$TENANT_NAME"'",
"tag": "'"$TENANT_TAG"'",
"regionName": "'"$REGION_NAME"'"
}'
The response includes the created tenant and a default M2M application. The M2M application is created in the new tenant and has access to the tenant's Management API.
Example response:
{
"id": "new-tenant-id",
"name": "My automated tenant",
"tag": "development",
"indicator": "https://new-tenant-id.logto.app",
"regionName": "EU",
"defaultApplication": {
"id": "default-m2m-app-id",
"secret": "default-m2m-app-secret"
}
}
Save the values you need for the next step:
export TENANT_ID="<response.id>"
export TENANT_ENDPOINT="<response.indicator>"
export DEFAULT_M2M_APP_ID="<response.defaultApplication.id>"
export DEFAULT_M2M_APP_SECRET="<response.defaultApplication.secret>"
A tenant created with tag: "production" starts on the Free plan. To put it on the Pro plan from the same script, see Subscribe a tenant to the Pro plan. A development tenant, like the one in this example, cannot be subscribed through the API.
Get a Management API access token for the new tenant
Use the default M2M application credentials to request an access token from the new tenant:
curl "$TENANT_ENDPOINT/oidc/token" \
-X POST \
-u "$DEFAULT_M2M_APP_ID:$DEFAULT_M2M_APP_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "resource=$TENANT_ENDPOINT/api" \
-d "scope=all"
Example response:
{
"access_token": "eyJ...",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "all"
}
Save the access token:
export MANAGEMENT_API_ACCESS_TOKEN="<response.access_token>"
Continue provisioning the new tenant
Use the Management API access token to call the new tenant's Management API.
For example, list applications:
curl "$TENANT_ENDPOINT/api/applications" \
-H "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN"
Or create an application:
curl "$TENANT_ENDPOINT/api/applications" \
-X POST \
-H "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "My web app",
"type": "SPA",
"oidcClientMetadata": {
"redirectUris": ["https://example.com/callback"],
"postLogoutRedirectUris": ["https://example.com"]
}
}'
At this point, your automation can continue with any Management API operation, such as creating users, applications, API resources, roles, organizations, connectors, or sign-in experience settings.
Full automation example
The following Node.js example creates a tenant, exchanges the returned default M2M credentials for a Management API access token, and lists applications in the new tenant:
const cloudApiEndpoint = 'https://cloud.logto.io';
const logtoCloudPat = process.env.LOGTO_CLOUD_PAT;
const createTenantResponse = await fetch(`${cloudApiEndpoint}/api/tenants`, {
method: 'POST',
headers: {
authorization: `Bearer ${logtoCloudPat}`,
'content-type': 'application/json',
},
body: JSON.stringify({
name: 'My automated tenant',
tag: 'development',
regionName: 'EU',
}),
});
if (!createTenantResponse.ok) {
throw new Error(`Failed to create tenant: ${await createTenantResponse.text()}`);
}
const tenant = await createTenantResponse.json();
const tenantEndpoint = tenant.indicator;
const { id: appId, secret: appSecret } = tenant.defaultApplication;
const tokenResponse = await fetch(`${tenantEndpoint}/oidc/token`, {
method: 'POST',
headers: {
authorization: `Basic ${Buffer.from(`${appId}:${appSecret}`).toString('base64')}`,
'content-type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'client_credentials',
resource: `${tenantEndpoint}/api`,
scope: 'all',
}),
});
if (!tokenResponse.ok) {
throw new Error(`Failed to get Management API token: ${await tokenResponse.text()}`);
}
const { access_token: managementApiAccessToken } = await tokenResponse.json();
const applicationsResponse = await fetch(`${tenantEndpoint}/api/applications`, {
headers: {
authorization: `Bearer ${managementApiAccessToken}`,
},
});
if (!applicationsResponse.ok) {
throw new Error(`Failed to list applications: ${await applicationsResponse.text()}`);
}
const applications = await applicationsResponse.json();
console.log({ tenantId: tenant.id, applications });