> 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.

# Authenticating with product API keys

An API Surface that requires authentication accepts only requests that carry one of its API keys. Send the key with every request.

## Before you begin

Complete these steps on the **Keys** tab of your API Surface:

1. In **Authentication Mode**, select **Keys Only**. In **Public (No Auth)** mode, the API accepts requests without a key, and the **Keys** tab doesn't list or create keys.
2. Click **Generate Key**. The **Generate API Key** dialog opens.
3. In **Key Type**, select a type. A **Test** key is capped at 50 capability calls per day, and you can reveal it again later. A **Production** key has no daily cap, and the dashboard shows it only once.
4. Optional: Enter a name in **Name (optional)** and a per-minute limit in **Rate Limit (requests/minute)**.
5. Click **Generate Key**. A dialog shows the new key.
6. Copy the key and store it in your server-side configuration.

API Surface keys start with `papi_`. For more information about the surface settings, see [Setting up an API Surface](/user-guide/products-surfaces/setting-up-an-api-surface).

## Choose an authentication header

The API accepts the key as a Bearer token in the `Authorization` header:

**`Authorization header`**

```http title="Authorization header"
Authorization: Bearer papi_YOUR_PRODUCT_KEY
```

Replace `papi_YOUR_PRODUCT_KEY` with the full API key for your API Surface. Or send the key in the `X-API-Key` header:

**`X-API-Key header`**

```http title="X-API-Key header"
X-API-Key: papi_YOUR_PRODUCT_KEY
```

The API reads only values that start with `papi_`. If you send any other key, such as an account API key, the API responds as if you sent no key.

## Verify your key

To confirm that a key works, send a `GET` request to the surface's capabilities endpoint. The request runs no capability:

**`List the capabilities a key can call`**

```bash title="List the capabilities a key can call"
curl https://api.runtype.com/v1/products/YOUR_PRODUCT_ID/surfaces/YOUR_SURFACE_ID/api/capabilities \
  -H "Authorization: Bearer papi_YOUR_PRODUCT_KEY"
```

Replace the following:

* `YOUR_PRODUCT_ID`: the ID of the Product that contains the API Surface
* `YOUR_SURFACE_ID`: the ID of the API Surface
* `papi_YOUR_PRODUCT_KEY`: the full API key

A `200 OK` response lists each capability the key can call, with its name, description, and endpoint path. If a capability is missing from the list, the key's scopes exclude it or the capability is disabled.

## Make authenticated requests

To call a capability, send a `POST` request to its endpoint on the API Surface. Include the `Authorization` or `X-API-Key` header in every request:

**`Call a capability`**

```bash title="Call a capability"
curl -X POST https://api.runtype.com/v1/products/YOUR_PRODUCT_ID/surfaces/YOUR_SURFACE_ID/api/YOUR_CAPABILITY_SLUG \
  -H "Authorization: Bearer papi_YOUR_PRODUCT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"topic": "machine learning"}'
```

Replace `YOUR_CAPABILITY_SLUG` with the capability's endpoint slug, and replace the other placeholders as in the previous example. Find exact endpoint URLs on the **Endpoints** or **Ship** tab of your API Surface.

The request body passes its top-level fields to the capability as input. The same key also authenticates the surface's `/api/dispatch` and `/api/chat` endpoints. For the full request and response contract, including streaming and response formats, see [Calling your API endpoints](/user-guide/products-surfaces/calling-your-api-endpoints).

## Handle authentication errors

A request without a key returns `401 Unauthorized` with this body:

**`Missing API key response`**

```json title="Missing API key response"
{
  "error": "Authentication required",
  "message": "Provide API key via Authorization: Bearer papi_xxx or X-API-Key header"
}
```

When the API rejects a key, the body has `error` and `code` fields. For example, an expired key returns this body:

**`Expired API key response`**

```json title="Expired API key response"
{
  "error": "API key has expired",
  "code": "EXPIRED_KEY"
}
```

Branch on the `code` field in your error handling. The following table lists the errors a key check can return:

| Status | `code`                          | `error`                     | Cause and fix                                                                                                                                                       |
| ------ | ------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`  | `INVALID_KEY`                   | `Invalid API key`           | The key is mistyped or deleted, or it belongs to a different API Surface or Product. Each key works only on the surface that created it.                            |
| `401`  | `EXPIRED_KEY`                   | `API key has expired`       | The key passed its expiry time. Generate a replacement key.                                                                                                         |
| `401`  | `INVALID_SURFACE`               | `API surface is not active` | The API Surface is in **Draft** or **Paused** status. [Activate the API Surface](/user-guide/products-surfaces/setting-up-an-api-surface#activate-the-api-surface). |
| `404`  | None                            | `API surface not found`     | The Product ID or Surface ID in the URL is wrong. The API returns this error before it checks the key.                                                              |
| `429`  | `RATE_LIMITED`                  | `Rate limit exceeded`       | The key exceeded its rate limit. Wait the number of seconds in the `Retry-After` header or the `retryAfter` field, then retry.                                      |
| `429`  | `TEST_KEY_DAILY_LIMIT_EXCEEDED` | `Test Key Limit Exceeded`   | A Test key reached its limit of 50 capability calls per day. The limit resets at midnight UTC. For uncapped access, generate a Production key.                      |

Successful capability calls that use a Test key include an `X-Test-Key-Remaining-Daily` header with the number of calls the key has left today.

A request with a valid key that lacks access to an endpoint returns `403 Forbidden`. The key's scopes exclude the endpoint:

**`Insufficient capability scope response`**

```json title="Insufficient capability scope response"
{
  "error": "Forbidden",
  "message": "API key does not have access to this endpoint"
}
```

## Protect your API keys

Follow these practices to protect your API keys:

* Store API keys in environment variables instead of source code.
* Use a Test key for development and a Production key for production.
* Rotate an API key after exposure and on a regular schedule.
* Scope each API key to the capabilities it needs.

Don't expose API keys in client-side JavaScript, mobile apps, or public repositories. Send requests from server-side code or through a proxy that keeps the key private.

The **Generate API Key** dialog doesn't set an expiry time or a daily request limit. To set them, create the key with the API and include the `expiresAt` field (an ISO 8601 date and time) or the `rateLimitPerDay` field. The request also accepts `name`, `scopes`, `rateLimitPerMinute`, and `environment` (`test`, the default, or `production`). For the request format, see [Scoping API keys to capabilities](/user-guide/products-surfaces/scoping-api-keys-to-capabilities).

## Rotate a compromised API key

To replace a compromised API key, follow these steps:

1. On the **Products** page, open the Product that contains the API Surface.
2. Open the API Surface.
3. On the **Keys** tab, find the compromised API key.
4. Next to the compromised key, click the delete button.
5. In the **Delete API Key?** dialog, click **Delete Key**. Requests that use the deleted key fail authentication.
6. Generate a replacement key, as in [Before you begin](#before-you-begin).
7. Update your server-side configuration with the replacement key.

This order stops the compromised key first. For a scheduled rotation, generate and deploy the replacement key first, then delete the old key, so live traffic isn't interrupted.

### Rotate a key with the API

To rotate keys from a script, use an account API key. Create the replacement key:

**`Create a replacement key`**

```bash title="Create a replacement 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": "backend-production", "environment": "production"}'
```

For a Production key, only this create response contains the full key, in the `key` field. Store it before you continue. The response `id` field identifies the new key.

Then delete the old key:

**`Delete the old key`**

```bash title="Delete the old key"
curl -X DELETE \
  https://api.runtype.com/v1/products/YOUR_PRODUCT_ID/surfaces/YOUR_SURFACE_ID/keys/YOUR_OLD_KEY_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Replace the following:

* `YOUR_API_KEY`: an account API key with the `PRODUCTS:SURFACES:WRITE` permission. For more information, see [Managing API keys](/user-guide/settings/managing-api-keys).
* `YOUR_OLD_KEY_ID`: the ID of the key to delete.

The Runtype MCP server provides the same operations as the `create_surface_key` and `delete_surface_key` tools.

## Next steps

Continue with these guides:

* [Calling your API endpoints](/user-guide/products-surfaces/calling-your-api-endpoints): send requests and process responses
* [Scoping API keys to capabilities](/user-guide/products-surfaces/scoping-api-keys-to-capabilities): limit each key to selected capabilities
* [Setting up an API Surface](/user-guide/products-surfaces/setting-up-an-api-surface): create and configure an API Surface