> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# Scoping API keys to capabilities

Use scopes to limit a Surface API key to selected Capabilities on a Surface. A scoped
key invokes only the Capabilities that you specify.

## Why scope API keys

Scoping applies least privilege to each integration:

* Each integration gets only the Capabilities that it needs.
* A compromised key can call only the Capabilities in its scope.

Common scope patterns:

* **Customer-specific keys.** Create one key per customer. For example, give Customer A
  the FAQ and Order lookup Capabilities, and give Customer B the FAQ Capability only.
* **Separate integrations.** Create one key per integration. Scope a mobile app key to
  the Capabilities that the app calls. Scope a public key to selected Capabilities and
  set key-level rate limits.
* **Third-party developers.** Give a partner selected Capabilities without exposing the
  entire Product.

Name each key after its integration and scope. For example, use
`Example Organization - FAQ only` instead of `API Key 3`. Descriptive names help you
identify the intended scope when you rotate or delete keys.

## Create a scoped key

The dashboard's **Generate API Key** dialog sets a key's name and key type. It does not
set scopes. To set scopes, use the API.

Chat Surfaces do not accept Surface API keys. They use client tokens, so the API returns
`400` if you create a key on a chat Surface.

1. Get the Surface's scope targets:

   **`Get the Surface`**

   ```bash title="Get the Surface"
   curl https://api.runtype.com/v1/products/YOUR_PRODUCT_ID/surfaces/YOUR_SURFACE_ID \
     -H "Authorization: Bearer YOUR_API_KEY"
   ```

   Each entry in the `scopeTargets` array has an `id` and a `name`. Copy the `id` of
   each Capability that the key needs.

2. Create the key with those IDs in `scopes`:

   **`Create a scoped API key`**

   ```bash title="Create a scoped API key"
   curl -X POST \
     https://api.runtype.com/v1/products/YOUR_PRODUCT_ID/surfaces/YOUR_SURFACE_ID/keys \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
     "name": "Example Organization - FAQ only",
     "scopes": ["YOUR_SCOPE_TARGET_ID"],
     "environment": "production",
     "rateLimitPerMinute": 60,
     "expiresAt": "2027-01-01T00:00:00Z"
   }'
   ```

   Replace the following:

   * `YOUR_PRODUCT_ID`: the ID of the Product that contains the Surface.
   * `YOUR_SURFACE_ID`: the ID of the Surface that accepts the key.
   * `YOUR_API_KEY`: an account API key with permission to create Surface API keys. See
     [Managing API keys](/user-guide/settings/managing-api-keys).
   * `YOUR_SCOPE_TARGET_ID`: an `id` from the Surface's `scopeTargets` array.

3. Store the `key` value from the response. A production key's full value appears only
   in this response.

4. Confirm the key's scope. The response's `scopeTargetIds` array lists the targets that
   the key can call. Compare it with the `id` values in the Surface's `scopeTargets`
   array.

### Request body fields

The request body accepts the following fields:

| Field                | Type             | Description                                                                                                                                                                                                  |
| -------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`               | string           | Optional. Up to 255 characters.                                                                                                                                                                              |
| `scopes`             | array of strings | Optional. Up to 100 entries. Omit it or send an empty array for an unscoped key.                                                                                                                             |
| `environment`        | string           | `test` (default) or `production`. A test key is limited to 50 executions per UTC day, and you can reveal it again in the dashboard. A production key has no daily cap, and its full value appears only once. |
| `rateLimitPerMinute` | integer          | Optional. Maximum requests per minute for this key.                                                                                                                                                          |
| `rateLimitPerDay`    | integer          | Optional. Maximum requests per day for this key.                                                                                                                                                             |
| `expiresAt`          | string           | Optional. A UTC date and time in ISO 8601 format, such as `2027-01-01T00:00:00Z`. The key stops working after this time.                                                                                     |

### Values accepted in scopes

Use scope target IDs. Each ID matches exactly one target, so the key gets exactly the
access that you intend. The API also accepts these values and converts each one to a
scope target ID when it creates the key:

* A Capability ID.
* A Capability name from the `name` field in `scopeTargets`. The name must match exactly
  one target.

A `scopes` array that contains `"*"` is unrestricted.

You can also create a scoped key through the
[Runtype MCP server](/developer-guides/integrations/runtype-mcp-server). The `create_surface_key` tool
accepts the same `scopes` values, and the `get_surface` tool returns `scopeTargets`. The
tool creates test keys only.

### Fix scope errors

If a scope value is not valid, the API returns `400` with one of these codes:

* `INVALID_SURFACE_KEY_SCOPE`: the value is blank, or it does not match a Capability on
  this Surface. Check the value against the Surface's `scopeTargets` array.
* `AMBIGUOUS_SURFACE_KEY_SCOPE`: the name matches more than one target. Use the scope
  target ID instead.

## How scopes apply on each Surface type

When a scoped key calls a Capability outside its scope, the response depends on the
Surface type. Handle these errors by status code or error code, not by message text.

| Surface type | Behavior for a scoped key                                                                                                                                                                                                                                |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| API, webhook | An out-of-scope call returns `403 Forbidden`.                                                                                                                                                                                                            |
| MCP          | The tool list shows only the tools in the key's scope. A call to another tool returns a tool error, `Access denied to tool`. The `name` of each scope target is the MCP tool name.                                                                       |
| A2A          | A request for a skill outside the key's scope returns a `Skill not found` JSON-RPC error that lists only the skills in scope. In Managed mode, the orchestrator skill is not available to a scoped key. The key sees only tasks for skills in its scope. |
| AG-UI        | A request for an out-of-scope Capability returns `403`. If no Capability on the Surface is in the key's scope, the request returns `404`.                                                                                                                |

The API `403` response has this form:

**`Out-of-scope response on an API Surface`**

```json title="Out-of-scope response on an API Surface"
{
  "error": "Forbidden",
  "message": "API key does not have access to capability 'FAQ'"
}
```

On API, MCP, and A2A Surfaces, each skill of an external agent is a separate scope
target. To grant one skill, use that skill's scope target ID. To grant every skill of
the agent, use its Capability ID.

## When the Surface changes

A scoped key keeps the scope targets it had when you created it:

* A Capability that you add to the Surface later is not available to existing scoped
  keys. Unscoped keys get it automatically.
* If you remove a Capability from the Surface and add it again, it gets a new scope
  target ID. Scoped keys lose access to it without an error at the time of the change.

After either change, create a new scoped key and make a test call to confirm access.

## Change a key's scope

You cannot update a key's scopes after you create it. Neither the API nor the dashboard
has a control for it. Delete the key, and then create another key with the scopes that
you need.

## Use unscoped keys

Use unscoped keys only for integrations that need every Capability exposed through the
Surface. A compromised unscoped key can invoke every Capability on that Surface. Rotate
unscoped keys on a regular schedule.

## Next steps

Continue with these guides:

* [Authenticating with product API keys](/user-guide/products-surfaces/authenticating-with-product-api-keys):
  configure authentication headers and manage keys
* [Calling your API endpoints](/user-guide/products-surfaces/calling-your-api-endpoints):
  send requests and process responses
* [Setting up an API Surface](/user-guide/products-surfaces/setting-up-an-api-surface):
  create and configure an API Surface
* [MCP authentication](/user-guide/products-surfaces/mcp-authentication): authenticate
  MCP clients with Surface API keys
* [How A2A works](/user-guide/products-surfaces/how-a2a-works): call Capabilities
  through an A2A Surface
* [Managing API keys](/user-guide/settings/managing-api-keys): create the account API
  key that manages Surface API keys