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

# Manage groups and API keys

> Walk the full lifecycle: create groups, build a hierarchy, mint and revoke API keys, and delete groups when a customer churns.

In Frontier Gateway, every API key belongs to a **group**: the resource that owns one billable entity's external identifier, model set, rate and usage limits, and place in your organizational hierarchy. You manage groups with `POST/GET/PATCH/DELETE /v1/gateway/groups[...]` and mint, list, or revoke their keys with `POST/GET/DELETE /v1/gateway/groups/{group_id}/api_keys[...]`. This page walks the full lifecycle: creating a group, building a hierarchy, minting a key, listing and revoking keys, and deleting a group.

## Concepts

A **group** is one node in your hierarchy. The group owns:

* A **`metadata.external_entity_id`**: a stable identifier you choose, unique within your workspace. Use it to map the group back to your own system. The same value is included as `externalCustomerId` on every [billing webhook](/frontier-gateway/billing-webhooks) event for the group's keys.
* A **`metadata.name`**: an optional human-readable display name.
* A **model set** (`models[]`): the slugs the group is allowed to call, each with optional rate and usage limits.
* A **`hierarchy`** block: a `limit_enforcement` mode (one of `INDEPENDENT` or `CASCADING`) and an optional `parent_group_id`. Both fields are **immutable** after creation.

A **federated API key** is a credential bound to one group. Keys are minted under the group; rotating credentials for a customer means revoking and reissuing the key without touching the group. Each key has a **prefix** (the substring before the `.` in the full key string) used as the path parameter in every per-key URL. The plaintext secret after the `.` is shown once at creation and is never retrievable; lose it and you must revoke and reissue. A key's model access and limits are derived entirely from its group's effective config; keys don't carry per-key overrides.

To see how limits compose across a hierarchy, see [Rate and usage limits](/frontier-gateway/rate-limits).

## Create a group

Create a group with `POST /v1/gateway/groups`. The body specifies the group's metadata, its complete model configuration, and a `hierarchy` block declaring the inheritance mode and an optional parent. The `models` list defines the group's complete model set with **set semantics**. Slugs added to the list are added to the group, and slugs absent from the list on a later update are removed (cascading to existing keys' access).

```bash theme={"system"}
curl --request POST \
  --url https://api.baseten.co/v1/gateway/groups \
  --header "Authorization: Api-Key $BASETEN_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "metadata": {
      "name": "Acme prod",
      "external_entity_id": "cust_42"
    },
    "models": [
      {
        "slug": "your-org/your-model",
        "rate_limits": [
          { "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000 },
          { "type": "REQUEST", "unit": "MINUTE", "threshold": 100 }
        ],
        "usage_limits": [
          { "type": "TOKEN", "unit": "DAY", "threshold": 10000000 }
        ]
      }
    ],
    "hierarchy": {
      "limit_enforcement": "INDEPENDENT",
      "parent_group_id": null
    }
  }'
```

The response is the new group. Save the `id`. It's the path parameter for every per-group operation that follows.

```json theme={"system"}
{
  "id": "abc123hash",
  "metadata": {
    "name": "Acme prod",
    "external_entity_id": "cust_42"
  },
  "models": [
    {
      "slug": "your-org/your-model",
      "rate_limits": [
        { "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000 },
        { "type": "REQUEST", "unit": "MINUTE", "threshold": 100 }
      ],
      "usage_limits": [
        { "type": "TOKEN", "unit": "DAY", "threshold": 10000000 }
      ]
    }
  ],
  "effective_models": [
    {
      "slug": "your-org/your-model",
      "rate_limits": [
        { "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000, "source_group": "abc123hash" },
        { "type": "REQUEST", "unit": "MINUTE", "threshold": 100, "source_group": "abc123hash" }
      ],
      "usage_limits": [
        { "type": "TOKEN", "unit": "DAY", "threshold": 10000000, "source_group": "abc123hash" }
      ]
    }
  ],
  "hierarchy": {
    "limit_enforcement": "INDEPENDENT",
    "parent_group_id": null
  },
  "created_at": "2026-05-13T12:00:00Z"
}
```

The `models` list must be non-empty on create. To clear models from an existing group later, send `"models": []` on `PATCH`; to remove the group entirely, see [Delete a group](#delete-a-group). For the limit-shape reference, see [Rate and usage limits](/frontier-gateway/rate-limits).

## Build a hierarchy

To nest a group under an existing one, pass the parent's `id` as `hierarchy.parent_group_id`. The child's `limit_enforcement` mode must match the root of its subtree. Pick the mode when you create the root, then every descendant uses the same mode.

```bash theme={"system"}
curl --request POST \
  --url https://api.baseten.co/v1/gateway/groups \
  --header "Authorization: Api-Key $BASETEN_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "metadata": {
      "name": "Acme prod / engineering",
      "external_entity_id": "cust_42_engineering"
    },
    "models": [
      {
        "slug": "your-org/your-model",
        "rate_limits": [
          { "type": "TOKEN", "unit": "MINUTE", "threshold": 700000 }
        ]
      }
    ],
    "hierarchy": {
      "limit_enforcement": "INDEPENDENT",
      "parent_group_id": "abc123hash"
    }
  }'
```

The child group's response includes the same `models` it was configured with, plus an `effective_models` block showing the limits the runtime enforces after walking up the tree. Each limit in `effective_models` carries a `source_group` field pointing to the ancestor (or self) that the limit was anchored to. For a worked example, see [Effective limits and inheritance](/frontier-gateway/rate-limits#effective-limits-and-inheritance).

## List groups

Fetch groups in your workspace with `GET /v1/gateway/groups`. Results are cursor-paginated. Pass `?external_entity_id=` to look up a single group by its external identifier.

```bash theme={"system"}
curl --request GET \
  --url https://api.baseten.co/v1/gateway/groups \
  --header "Authorization: Api-Key $BASETEN_API_KEY"
```

The response includes a `pagination` block with `has_more` and a `cursor` you pass back to fetch the next page:

```json theme={"system"}
{
  "items": [
    {
      "id": "abc123hash",
      "metadata": { "name": "Acme prod", "external_entity_id": "cust_42" },
      "models": [ /* ... */ ],
      "effective_models": [ /* ... */ ],
      "hierarchy": { "limit_enforcement": "INDEPENDENT", "parent_group_id": null },
      "created_at": "2026-05-13T12:00:00Z"
    }
  ],
  "pagination": {
    "has_more": true,
    "cursor": "aVd2Yk54T2d2V0dFWE13R1l4R2k5UVE="
  }
}
```

To fetch the next page, pass the previous response's cursor:

```bash theme={"system"}
curl --request GET \
  --url "https://api.baseten.co/v1/gateway/groups?cursor=aVd2Yk54T2d2V0dFWE13R1l4R2k5UVE=" \
  --header "Authorization: Api-Key $BASETEN_API_KEY"
```

You've drained the result set when the response has `"has_more": false` and `"cursor": null`.

## Update a group

Update a group's mutable fields with `PATCH /v1/gateway/groups/{group_id}`. You can change `metadata.name` and the `models` configuration; the `hierarchy` block (parent and enforcement mode) is immutable after creation. At least one of `metadata.name` or `models` must be provided.

Replacing `models` follows the same set semantics as create: every slug currently on the group but absent from the new list is removed (cascading to existing keys' access), and new slugs are added.

If the group sits in a cascading hierarchy, the new `models` block is validated against both the group's ancestors and its descendants. A `PATCH` that would raise the group above an ancestor's threshold, or lower the group below a descendant's threshold, is rejected with `400 Bad Request: "Child group exceeds parent group limit."`. See [Cascading mode](/frontier-gateway/rate-limits#cascading-mode) for the full ordering rules.

```bash theme={"system"}
curl --request PATCH \
  --url https://api.baseten.co/v1/gateway/groups/abc123hash \
  --header "Authorization: Api-Key $BASETEN_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "models": [
      {
        "slug": "your-org/your-model",
        "rate_limits": [
          { "type": "TOKEN", "unit": "MINUTE", "threshold": 1500000 }
        ]
      }
    ]
  }'
```

The response is the updated group, with refreshed `effective_models` reflecting the new limits and any downstream inheritance.

## Mint an API key

Mint a new API key for an existing group with `POST /v1/gateway/groups/{group_id}/api_keys`. The path parameter is the group's internal `id` (not its `external_entity_id`). The body has a single optional `name` field: a display label for the key. Keys inherit the group's effective model set and limits; you can't restrict a key to a subset of the group's slugs or attach per-key limits.

```bash theme={"system"}
curl --request POST \
  --url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys \
  --header "Authorization: Api-Key $BASETEN_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "prod-key-1"
  }'
```

The response contains the plaintext key, returned exactly once:

```json theme={"system"}
{
  "api_key": "aBcDeFg.<api-key-secret>",
  "prefix": "aBcDeFg",
  "name": "prod-key-1"
}
```

<Warning>
  This is the only time the key is returned in plaintext. Save it now: Baseten doesn't store the secret portion and can't show it to you again. If you lose it, revoke the key and mint a new one.
</Warning>

To rotate a customer's credentials without changing their access or limits, mint a new key under the same group, hand the new key to the customer, then revoke the old one once they've cut over.

## List a group's keys

Fetch the keys belonging to a group with `GET /v1/gateway/groups/{group_id}/api_keys`. Results are cursor-paginated with the same shape as the [group list](#list-groups).

```bash theme={"system"}
curl --request GET \
  --url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys \
  --header "Authorization: Api-Key $BASETEN_API_KEY"
```

```json theme={"system"}
{
  "items": [
    {
      "prefix": "aBcDeFg",
      "name": "prod-key-1"
    }
  ],
  "pagination": {
    "has_more": false,
    "cursor": null
  }
}
```

Per-key responses carry only the `prefix` and `name`. To inspect the model access and limits the key resolves to, fetch its [group](#list-groups) and read the `effective_models` block.

To fetch a single key by prefix, use `GET /v1/gateway/groups/{group_id}/api_keys/{api_key_prefix}`:

```bash theme={"system"}
curl --request GET \
  --url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys/aBcDeFg \
  --header "Authorization: Api-Key $BASETEN_API_KEY"
```

## Revoke a key

Revoke a single key with `DELETE /v1/gateway/groups/{group_id}/api_keys/{api_key_prefix}`. Other keys under the same group are unaffected.

```bash theme={"system"}
curl --request DELETE \
  --url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys/aBcDeFg \
  --header "Authorization: Api-Key $BASETEN_API_KEY"
```

```json theme={"system"}
{
  "prefix": "aBcDeFg"
}
```

<Warning>
  Revocation is irreversible. After this call, the key can't authenticate any request and can't be restored. To restore access for the same downstream customer, mint a new key under the same group.
</Warning>

## Delete a group

When a downstream customer churns, delete their group with `DELETE /v1/gateway/groups/{group_id}`. The call removes the group, revokes every API key in the group, and recursively removes every descendant group and their keys. The `external_entity_id` is freed for reuse; you can call `POST /v1/gateway/groups` again with the same value to provision a fresh group.

```bash theme={"system"}
curl --request DELETE \
  --url https://api.baseten.co/v1/gateway/groups/abc123hash \
  --header "Authorization: Api-Key $BASETEN_API_KEY"
```

```json theme={"system"}
{
  "id": "abc123hash",
  "metadata": {
    "name": "Acme prod",
    "external_entity_id": "cust_42"
  },
  "deleted_at": "2026-05-13T12:34:56Z"
}
```

To revoke a single key without churning the whole group, use [Revoke a key](#revoke-a-key) instead.

## Next steps

* **[Rate and usage limits](/frontier-gateway/rate-limits)**: Token and request thresholds, inheritance modes, and 429 behavior.
* **[Billing webhooks](/frontier-gateway/billing-webhooks)**: Stream signed per-request usage events into your billing pipeline.
