> ## Documentation Index
> Fetch the complete documentation index at: https://unkey.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Environments

> Separate your API keys into live and test environments in Unkey. Isolate production traffic from development with environment-scoped keys.

Environments let you issue distinct keys for development, staging, and production. Test keys can't affect real resources, while live keys have full access.

## When to use this

<CardGroup cols={2}>
  <Card title="Safe development" icon="code">
    Developers can test integrations without touching production data.
  </Card>

  <Card title="Automated testing" icon="flask-vial">
    CI/CD pipelines can run tests with keys that won't affect live resources.
  </Card>

  <Card title="Different limits" icon="gauge">
    Test keys might have lower rate limits or be free to use.
  </Card>

  <Card title="Prevent accidents" icon="shield">
    Prefixes like `sk_test_` make it obvious when using non-production keys.
  </Card>
</CardGroup>

## How it works

Environments are implemented using key metadata and prefixes, there's no special "environment" field. This gives you full flexibility to model environments however you want.

Common pattern:

* **Prefix**: `sk_test_` vs `sk_live_` (visible to users)
* **Metadata**: `{ "environment": "test" }` (returned on verification)

## Create environment-specific keys

<CodeGroup>
  ```bash Test key theme={"theme":"kanagawa-wave"}
  curl -X POST https://api.unkey.com/v2/keys.createKey \
    -H "Authorization: Bearer $UNKEY_ROOT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "apiId": "api_...",
      "prefix": "sk_test",
      "meta": {
        "environment": "test"
      },
      "ratelimits": [{
        "name": "requests",
        "limit": 100,
        "duration": 60000
      }]
    }'
  ```

  ```bash Live key theme={"theme":"kanagawa-wave"}
  curl -X POST https://api.unkey.com/v2/keys.createKey \
    -H "Authorization: Bearer $UNKEY_ROOT_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "apiId": "api_...",
      "prefix": "sk_live",
      "meta": {
        "environment": "production"
      },
      "ratelimits": [{
        "name": "requests",
        "limit": 1000,
        "duration": 60000
      }]
    }'
  ```
</CodeGroup>

<Tip>
  Using prefixes makes it obvious to users which environment a key is for. This
  prevents accidental use of test keys in production (or worse, live keys in
  tests).
</Tip>

## Verify and check environment

The metadata is returned during verification, so your API can behave differently:

```bash theme={"theme":"kanagawa-wave"}
curl -X POST https://api.unkey.com/v2/keys.verifyKey \
  -H "Content-Type: application/json" \
  -d '{ "key": "sk_test_abc123..." }'
```

Response:

```json theme={"theme":"kanagawa-wave"}
{
  "meta": { "requestId": "req_..." },
  "data": {
    "valid": true,
    "keyId": "key_...",
    "meta": {
      "environment": "test"
    }
  }
}
```

## Handle environments in your API

```typescript theme={"theme":"kanagawa-wave"}
try {
  const { data } = await unkey.keys.verifyKey({ key: request.apiKey });

  if (!data.valid) {
    return unauthorized();
  }

  const environment = data.meta?.environment ?? "production";

  if (environment === "test") {
    // Use test database, sandbox resources, etc.
    return handleTestRequest(request);
  }

  // Production request
  return handleLiveRequest(request);
} catch (err) {
  console.error(err);
  return Response.json({ error: "Internal error" }, { status: 500 });
}
```

## Common patterns

### Different rate limits per environment

```typescript theme={"theme":"kanagawa-wave"}
// Test: 100/min, Live: 10,000/min
const ratelimits =
  environment === "test"
    ? [{ name: "requests", limit: 100, duration: 60000 }]
    : [{ name: "requests", limit: 10000, duration: 60000 }];

try {
  const { meta, data } = await unkey.keys.createKey({
    apiId,
    prefix: environment === "test" ? "sk_test" : "sk_live",
    meta: { environment },
    ratelimits,
  });
} catch (err) {
  console.error(err);
  return Response.json({ error: "Internal error" }, { status: 500 });
}
```

### Sandbox vs real resources

```typescript theme={"theme":"kanagawa-wave"}
if (data.meta?.environment === "test") {
  // Return mock data or use sandbox
  return {
    charged: false,
    response: mockResponse,
  };
}

// Real billing, real data
try {
  await chargeUser(data.identity.externalId, amount);
  return { charged: true, response: realResponse };
} catch (err) {
  console.error(err);
  return Response.json({ error: "Internal error" }, { status: 500 });
}
```

### Block test keys from production endpoint

```typescript theme={"theme":"kanagawa-wave"}
if (data.meta?.environment === "test" && isProductionEndpoint) {
  return Response.json(
    { error: "Test keys cannot access production endpoints" },
    { status: 403 },
  );
}

// Continue with your API logic
```

## Next steps

<CardGroup cols={2}>
  <Card title="API Keys overview" icon="key" href="/platform/apis/introduction">
    Learn about key metadata and features
  </Card>

  <Card title="Rate limiting" icon="gauge" href="/platform/apis/features/ratelimiting/overview">
    Set different limits per environment
  </Card>
</CardGroup>
