<!-- llms.txt: https://workos.com/llms.txt -->

# Provision your first environment

## Before you start

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.

## Get an access token

Exchange your platform credentials for an access token using the OAuth 2.0 client credentials grant.

:::code-group

```bash language="curl" title="Request" tab="1"
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"
```

```json language="json" title="Response" tab="2"
{
  "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

Create a team for the user who's building the app. Pass the email address of the person who should administer it.

:::code-group

```bash language="curl" title="Request" tab="1"
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
```

```json language="json" title="Response" tab="2"
{
  "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.

## Create an environment

Each of your user's app deployments should get its own environment. New environments are sandbox environments unless you ask for production.

:::code-group

```bash language="curl" title="Request" tab="1"
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
```

```json language="json" title="Response" tab="2"
{
  "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.

> **Note:** 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`.

## Mint an environment API key

Everything downstream of the environment happens through the public WorkOS API, which needs an environment-scoped secret key.

:::code-group

```bash language="curl" title="Request" tab="1"
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
```

```json language="json" title="Response" tab="2"
{
  "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.

## Configure the environment

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.

```bash title="Register a redirect URI"
curl -X POST https://api.workos.com/user_management/redirect_uris \
  --header 'Content-Type: application/json' \
  --header "Authorization: Bearer $ENVIRONMENT_API_KEY" \
  --data '{ "uri": "https://alice-app.example.com/callback" }'
```

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](https://workos.com/docs/reference/authkit/create-cors-origin) if the app calls the WorkOS API from the browser.
- [Set a JWT template](https://workos.com/docs/reference/authkit/jwt-template/update) if a downstream service expects particular claims.
- [Register a webhook endpoint](https://workos.com/docs/reference/webhooks/create) 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.

## Hand off to the app

Pass two values into your user's app as environment variables:

```bash title=".env"
WORKOS_CLIENT_ID=client_01JN22VQ4FZY3K0NGY18EPZFV4
WORKOS_API_KEY=sk_example_123456789
```

From here your user's app is an ordinary AuthKit app. Point them at the [AuthKit quick start](https://workos.com/docs/authkit) for their framework, or ship the integration in your platform's project template so it works on first deploy.

## Invite the rest of the team

The admin invitation goes out when you create the team. To add more people later, invite them by email with a role.

:::code-group

```bash language="curl" title="Request" tab="1"
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
```

```json language="json" title="Response" tab="2"
{
  "object": "workos_team_invitation",
  "email": "bob@example.com",
  "role_slug": "member"
}
```

:::

## Check that you still have access

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.

:::code-group

```bash language="curl" title="Request" tab="1"
curl --request GET \
  --url "https://api.workos.com/platform/teams/team_01K4BN3VKH8AFZC5J334QZY3PM" \
  --header "Authorization: Bearer $PLATFORM_ACCESS_TOKEN"
```

```json language="json" title="Response" tab="2"
{
  "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.

## Going to production

Sandbox environments are enough to build against, but your user's real deployment needs a production environment, and that requires billing on their team.

1. Read the team and check `production_state`.
2. If it isn't `Active`, prompt your user to add billing in the WorkOS Dashboard.
3. 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.
