|
| 1 | +--- |
| 2 | +id: automate-tenant-creation |
| 3 | +title: Automate tenant management |
| 4 | +sidebar_position: 8 |
| 5 | +--- |
| 6 | + |
| 7 | +# Automate tenant management |
| 8 | + |
| 9 | +You can manage Logto Cloud tenants programmatically, including creating tenants and continuing configuration without switching to Console. |
| 10 | + |
| 11 | +This is useful when you need to provision tenants from your own onboarding flow, internal platform, AI agent, or integration automation. |
| 12 | + |
| 13 | +The automation flow is: |
| 14 | + |
| 15 | +1. Use a **Logto Cloud Personal Access Token (PAT)** to call the Logto Cloud API. |
| 16 | +2. Create a tenant with `POST /api/tenants`. |
| 17 | +3. Read the default Machine-to-machine (M2M) application credentials from the creation response. |
| 18 | +4. Use the default M2M application to get a Management API access token for the new tenant. |
| 19 | +5. Call the new tenant's Management API to continue provisioning applications, users, roles, resources, organizations, and other settings. |
| 20 | + |
| 21 | +## Before you start \{#before-you-start} |
| 22 | + |
| 23 | +Prepare the following values: |
| 24 | + |
| 25 | +| Variable | Description | |
| 26 | +| -------------------- | ---------------------------------------------------------------------------- | |
| 27 | +| `CLOUD_API_ENDPOINT` | The Logto Cloud API endpoint. For Logto Cloud, use `https://cloud.logto.io`. | |
| 28 | +| `LOGTO_CLOUD_PAT` | A PAT for your Logto Cloud account. | |
| 29 | +| `TENANT_NAME` | The display name of the tenant to create. | |
| 30 | +| `TENANT_TAG` | The tenant type. Use `development` or `production`. | |
| 31 | +| `REGION_NAME` | The region identifier for the tenant. | |
| 32 | + |
| 33 | +Set them as environment variables: |
| 34 | + |
| 35 | +```bash |
| 36 | +export CLOUD_API_ENDPOINT="https://cloud.logto.io" |
| 37 | +export LOGTO_CLOUD_PAT="<logto-cloud-pat>" |
| 38 | +export TENANT_NAME="My automated tenant" |
| 39 | +export TENANT_TAG="development" |
| 40 | +export REGION_NAME="<region-name>" |
| 41 | +``` |
| 42 | + |
| 43 | +## Get available regions \{#get-available-regions} |
| 44 | + |
| 45 | +Before creating a tenant, fetch the regions available to your Logto Cloud account: |
| 46 | + |
| 47 | +```bash |
| 48 | +curl "$CLOUD_API_ENDPOINT/api/me/regions" \ |
| 49 | + -H "Authorization: Bearer $LOGTO_CLOUD_PAT" |
| 50 | +``` |
| 51 | + |
| 52 | +The response contains the available regions. Use the `name` value as `REGION_NAME` when creating the tenant. |
| 53 | + |
| 54 | +Example response: |
| 55 | + |
| 56 | +```json |
| 57 | +{ |
| 58 | + "regions": [ |
| 59 | + { |
| 60 | + "name": "EU", |
| 61 | + "displayName": "Europe" |
| 62 | + }, |
| 63 | + { |
| 64 | + "name": "US", |
| 65 | + "displayName": "United States" |
| 66 | + } |
| 67 | + ] |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +## Create a tenant \{#create-a-tenant} |
| 72 | + |
| 73 | +Call `POST /api/tenants` with the Logto Cloud PAT: |
| 74 | + |
| 75 | +```bash |
| 76 | +curl "$CLOUD_API_ENDPOINT/api/tenants" \ |
| 77 | + -X POST \ |
| 78 | + -H "Authorization: Bearer $LOGTO_CLOUD_PAT" \ |
| 79 | + -H "Content-Type: application/json" \ |
| 80 | + -d '{ |
| 81 | + "name": "'"$TENANT_NAME"'", |
| 82 | + "tag": "'"$TENANT_TAG"'", |
| 83 | + "regionName": "'"$REGION_NAME"'" |
| 84 | + }' |
| 85 | +``` |
| 86 | + |
| 87 | +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. |
| 88 | + |
| 89 | +Example response: |
| 90 | + |
| 91 | +```json |
| 92 | +{ |
| 93 | + "id": "new-tenant-id", |
| 94 | + "name": "My automated tenant", |
| 95 | + "tag": "development", |
| 96 | + "indicator": "https://new-tenant-id.logto.app", |
| 97 | + "regionName": "EU", |
| 98 | + "defaultApplication": { |
| 99 | + "id": "default-m2m-app-id", |
| 100 | + "secret": "default-m2m-app-secret" |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +Save the values you need for the next step: |
| 106 | + |
| 107 | +```bash |
| 108 | +export TENANT_ID="<response.id>" |
| 109 | +export TENANT_ENDPOINT="<response.indicator>" |
| 110 | +export DEFAULT_M2M_APP_ID="<response.defaultApplication.id>" |
| 111 | +export DEFAULT_M2M_APP_SECRET="<response.defaultApplication.secret>" |
| 112 | +``` |
| 113 | + |
| 114 | +## Get a Management API access token for the new tenant \{#get-a-management-api-access-token-for-the-new-tenant} |
| 115 | + |
| 116 | +Use the default M2M application credentials to request an access token from the new tenant: |
| 117 | + |
| 118 | +```bash |
| 119 | +curl "$TENANT_ENDPOINT/oidc/token" \ |
| 120 | + -X POST \ |
| 121 | + -u "$DEFAULT_M2M_APP_ID:$DEFAULT_M2M_APP_SECRET" \ |
| 122 | + -H "Content-Type: application/x-www-form-urlencoded" \ |
| 123 | + -d "grant_type=client_credentials" \ |
| 124 | + -d "resource=$TENANT_ENDPOINT/api" \ |
| 125 | + -d "scope=all" |
| 126 | +``` |
| 127 | + |
| 128 | +Example response: |
| 129 | + |
| 130 | +```json |
| 131 | +{ |
| 132 | + "access_token": "eyJ...", |
| 133 | + "expires_in": 3600, |
| 134 | + "token_type": "Bearer", |
| 135 | + "scope": "all" |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +Save the access token: |
| 140 | + |
| 141 | +```bash |
| 142 | +export MANAGEMENT_API_ACCESS_TOKEN="<response.access_token>" |
| 143 | +``` |
| 144 | + |
| 145 | +## Continue provisioning the new tenant \{#continue-provisioning-the-new-tenant} |
| 146 | + |
| 147 | +Use the Management API access token to call the new tenant's Management API. |
| 148 | + |
| 149 | +For example, list applications: |
| 150 | + |
| 151 | +```bash |
| 152 | +curl "$TENANT_ENDPOINT/api/applications" \ |
| 153 | + -H "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" |
| 154 | +``` |
| 155 | + |
| 156 | +Or create an application: |
| 157 | + |
| 158 | +```bash |
| 159 | +curl "$TENANT_ENDPOINT/api/applications" \ |
| 160 | + -X POST \ |
| 161 | + -H "Authorization: Bearer $MANAGEMENT_API_ACCESS_TOKEN" \ |
| 162 | + -H "Content-Type: application/json" \ |
| 163 | + -d '{ |
| 164 | + "name": "My web app", |
| 165 | + "type": "SPA", |
| 166 | + "oidcClientMetadata": { |
| 167 | + "redirectUris": ["https://example.com/callback"], |
| 168 | + "postLogoutRedirectUris": ["https://example.com"] |
| 169 | + } |
| 170 | + }' |
| 171 | +``` |
| 172 | + |
| 173 | +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. |
| 174 | + |
| 175 | +## Full automation example \{#full-automation-example} |
| 176 | + |
| 177 | +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: |
| 178 | + |
| 179 | +```js |
| 180 | +const cloudApiEndpoint = 'https://cloud.logto.io'; |
| 181 | +const logtoCloudPat = process.env.LOGTO_CLOUD_PAT; |
| 182 | + |
| 183 | +const createTenantResponse = await fetch(`${cloudApiEndpoint}/api/tenants`, { |
| 184 | + method: 'POST', |
| 185 | + headers: { |
| 186 | + authorization: `Bearer ${logtoCloudPat}`, |
| 187 | + 'content-type': 'application/json', |
| 188 | + }, |
| 189 | + body: JSON.stringify({ |
| 190 | + name: 'My automated tenant', |
| 191 | + tag: 'development', |
| 192 | + regionName: 'EU', |
| 193 | + }), |
| 194 | +}); |
| 195 | + |
| 196 | +if (!createTenantResponse.ok) { |
| 197 | + throw new Error(`Failed to create tenant: ${await createTenantResponse.text()}`); |
| 198 | +} |
| 199 | + |
| 200 | +const tenant = await createTenantResponse.json(); |
| 201 | +const tenantEndpoint = tenant.indicator; |
| 202 | +const { id: appId, secret: appSecret } = tenant.defaultApplication; |
| 203 | + |
| 204 | +const tokenResponse = await fetch(`${tenantEndpoint}/oidc/token`, { |
| 205 | + method: 'POST', |
| 206 | + headers: { |
| 207 | + authorization: `Basic ${Buffer.from(`${appId}:${appSecret}`).toString('base64')}`, |
| 208 | + 'content-type': 'application/x-www-form-urlencoded', |
| 209 | + }, |
| 210 | + body: new URLSearchParams({ |
| 211 | + grant_type: 'client_credentials', |
| 212 | + resource: `${tenantEndpoint}/api`, |
| 213 | + scope: 'all', |
| 214 | + }), |
| 215 | +}); |
| 216 | + |
| 217 | +if (!tokenResponse.ok) { |
| 218 | + throw new Error(`Failed to get Management API token: ${await tokenResponse.text()}`); |
| 219 | +} |
| 220 | + |
| 221 | +const { access_token: managementApiAccessToken } = await tokenResponse.json(); |
| 222 | + |
| 223 | +const applicationsResponse = await fetch(`${tenantEndpoint}/api/applications`, { |
| 224 | + headers: { |
| 225 | + authorization: `Bearer ${managementApiAccessToken}`, |
| 226 | + }, |
| 227 | +}); |
| 228 | + |
| 229 | +if (!applicationsResponse.ok) { |
| 230 | + throw new Error(`Failed to list applications: ${await applicationsResponse.text()}`); |
| 231 | +} |
| 232 | + |
| 233 | +const applications = await applicationsResponse.json(); |
| 234 | + |
| 235 | +console.log({ tenantId: tenant.id, applications }); |
| 236 | +``` |
| 237 | + |
| 238 | +## Related resources \{#related-resources} |
| 239 | + |
| 240 | +- [Personal access token](/user-management/personal-access-token) |
| 241 | +- [Interact with Management API](/integrate-logto/interact-with-management-api) |
| 242 | +- [Machine-to-machine quick start](/quick-starts/m2m) |
0 commit comments