Provision your first environment
Walk through provisioning AuthKit for an app on your platform, from platform credentials to a running sign-in flow.
Platform credentials are required to use the Platform API and are provided when onboarding to AuthKit for Platforms. Use your client_id and client_secret to mint access tokens. Store the secrets alongside your other server-side secrets. They are long-lived and grant access to every team your platform creates.
Exchange your platform credentials for an access token using the OAuth 2.0 client credentials grant.
| curl --request POST \ | |
| --url "https://signin.workos.com/oauth2/token" \ | |
| -d "client_id=$PLATFORM_CLIENT_ID" \ | |
| -d "client_secret=$PLATFORM_CLIENT_SECRET" \ | |
| -d "grant_type=client_credentials" |
| { | |
| "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InNzb19vaWRjX2tleV9wYWlyXzAxSlBaWlk3UVA3MTJKMUsyMkI0WlpIMUIzIn0...", | |
| "expires_in": 3600, | |
| "token_type": "bearer" | |
| } |
Tokens are valid for one hour, and requesting a new one does not invalidate the tokens you already hold. Cache the token for the life of a provisioning job rather than requesting one per API call.
Create a team for the user who’s building the app. Pass the email address of the person who should administer it.
| curl --request POST \ | |
| --url "https://api.workos.com/platform/teams" \ | |
| --header "Authorization: Bearer $PLATFORM_ACCESS_TOKEN" \ | |
| --header "Content-Type: application/json" \ | |
| -d @- <<'BODY' | |
| { | |
| "admin_email": "alice@example.com", | |
| "name": "Alice's Team" | |
| } | |
| BODY |
| { | |
| "object": "team", | |
| "id": "team_01K4BN3VKH8AFZC5J334QZY3PM", | |
| "name": "Alice's Team", | |
| "production_state": "Inactive", | |
| "production_enabled_at": null, | |
| "created_at": "2026-01-15T07:41:09.394Z", | |
| "updated_at": "2026-01-15T07:41:09.394Z" | |
| } |
One call does several things: it creates the team and its default environments, emails an admin invitation to admin_email, marks the team’s onboarding complete with AuthKit enabled, and authorizes your platform to act inside the team.
If a WorkOS user already exists with that email address, the request fails with 409 user_already_exists. That happens when your user already has a WorkOS account, which is common enough to be worth a real branch in your code. Ask them for a different address, or route them through a flow where they connect their existing team instead.
Each of your user’s app deployments should get its own environment. New environments are sandbox environments unless you ask for production.
| curl --request POST \ | |
| --url "https://api.workos.com/platform/teams/team_01K4BN3VKH8AFZC5J334QZY3PM/environments" \ | |
| --header "Authorization: Bearer $PLATFORM_ACCESS_TOKEN" \ | |
| --header "Content-Type: application/json" \ | |
| -d @- <<'BODY' | |
| { | |
| "name": "alice-app-preview", | |
| "production": false | |
| } | |
| BODY |
| { | |
| "object": "environment", | |
| "id": "environment_01K4E21A6ZRE7VWNM4776S0JA9", | |
| "name": "alice-app-preview", | |
| "client_id": "client_01JN22VQ4FZY3K0NGY18EPZFV4" | |
| } |
The client_id is what your user’s app needs to start an AuthKit session. Environments created this way have AuthKit turned on already, so there’s no extra step to activate it.
Creating a production environment requires active billing on the team. Check
the team’s production_state before you try, and prompt your user to add
billing in the WorkOS Dashboard if it isn’t Active.
Everything downstream of the environment happens through the public WorkOS API, which needs an environment-scoped secret key.
| curl --request POST \ | |
| --url "https://api.workos.com/platform/teams/team_01K4BN3VKH8AFZC5J334QZY3PM/environments/environment_01K4E21A6ZRE7VWNM4776S0JA9/api_keys" \ | |
| --header "Authorization: Bearer $PLATFORM_ACCESS_TOKEN" \ | |
| --header "Content-Type: application/json" \ | |
| -d @- <<'BODY' | |
| { | |
| "name": "Platform provisioning key", | |
| "expires_at": null | |
| } | |
| BODY |
| { | |
| "object": "key", | |
| "id": "api_key_01K4E21A6ZRE7VWNM4776S0JA9", | |
| "name": "Platform provisioning key", | |
| "expires_at": null, | |
| "value": "sk_example_123456789", | |
| "created_at": "2026-01-15T06:37:41.193Z", | |
| "updated_at": "2026-01-15T06:37:41.193Z" | |
| } |
The value is returned once and never again. Store it encrypted, or inject it straight into your user’s app secrets and drop it from memory.
Configuration happens through the public WorkOS API, authenticated with the key you just minted. These are the same endpoints your users would call themselves, and each one is scoped to the environment its key belongs to, so no environment ID appears in the path.
At minimum, register the app’s redirect URI so AuthKit can complete a sign-in.
The first URI you register becomes the environment’s default, and re-registering an existing URI fails with 422, which your provisioning code should treat as success.
Depending on what your platform’s apps need, you may also want to:
- Add a CORS origin if the app calls the WorkOS API from the browser.
- Set a JWT template if a downstream service expects particular claims.
- Register a webhook endpoint if your platform reacts to what happens inside the environment. Give each environment its own endpoint URL so an incoming webhook identifies the user it belongs to.
Pass two values into your user’s app as environment variables:
From here your user’s app is an ordinary AuthKit app. Point them at the AuthKit quick start for their framework, or ship the integration in your platform’s project template so it works on first deploy.
The admin invitation goes out when you create the team. To add more people later, invite them by email with a role.
| curl --request POST \ | |
| --url "https://api.workos.com/platform/teams/team_01K4BN3VKH8AFZC5J334QZY3PM/invitations" \ | |
| --header "Authorization: Bearer $PLATFORM_ACCESS_TOKEN" \ | |
| --header "Content-Type: application/json" \ | |
| -d @- <<'BODY' | |
| { | |
| "email": "bob@example.com", | |
| "role_slug": "member" | |
| } | |
| BODY |
| { | |
| "object": "workos_team_invitation", | |
| "email": "bob@example.com", | |
| "role_slug": "member" | |
| } |
A team admin can remove your platform’s access at any time. GET on a team or an environment doubles as a health check: a 404 means your platform can no longer act there.
| curl --request GET \ | |
| --url "https://api.workos.com/platform/teams/team_01K4BN3VKH8AFZC5J334QZY3PM" \ | |
| --header "Authorization: Bearer $PLATFORM_ACCESS_TOKEN" |
| { | |
| "object": "team", | |
| "id": "team_01K4BN3VKH8AFZC5J334QZY3PM", | |
| "name": "Alice's Team", | |
| "production_state": "Inactive", | |
| "production_enabled_at": null, | |
| "created_at": "2026-01-15T07:41:09.394Z", | |
| "updated_at": "2026-01-15T07:41:09.394Z" | |
| } |
Run this before any provisioning work you’re about to do on an existing team, and surface the failure to your user rather than retrying. Retrying won’t restore access.
Sandbox environments are enough to build against, but your user’s real deployment needs a production environment, and that requires billing on their team.
- Read the team and check
production_state. - If it isn’t
Active, prompt your user to add billing in the WorkOS Dashboard. - Once it’s
Active, create the environment with"production": true.
Production environments can’t be deleted through the Platform API, so create them deliberately. Sandbox environments your platform created can be deleted, which makes them a good fit for per-branch preview deployments that would otherwise pile up in your user’s dashboard.