---
title: "Replace an API key's scopes."
method: PUT
path: "/api/api-keys/{keyId}/scopes"
tags: ["API Keys"]
---

# Replace an API key's scopes.

`PUT /api/api-keys/{keyId}/scopes`

## Request

- Base URL: `/` (relative, and the document was published on a file host, not at the API’s origin)
- URL: `/api/api-keys/{keyId}/scopes`
- Auth: HTTP bearer

Replaces the scopes on a key. The key value does not change. The new scopes must be a subset of those the calling key holds, and the calling key cannot change a key holding a scope it does not hold. The key making the request cannot change its own scopes. A caller can change its own user's keys; an administrator can change any user's. Recorded as an api_key_scopes_changed audit event with the scopes before and after and the calling user and API key. Requires the `api-keys:write` scope.

## Path parameters

- `keyId` string, required

## Headers

- `Authorization` string, required

## Request body

- SetApiKeyScopesRequest
  - `scopes` string[]

## Response `200`

The scopes were replaced.

- ApiKeyResponse
  - `bootstrap` boolean
  - `clientAddress` string: Session keys only: the address of the client that signed in, so a person can tell their sessions apart. Null for a long-lived key.
  - `created` string, date-time
  - `current` boolean: Whether this is the key making the request.
  - `expiresAt` string, date-time: Session keys only: when the key's maximum lifetime ends. Null for a long-lived key.
  - `id` string
  - `idleExpiresAt` string, date-time: Session keys only: when the key expires unless it is used first. Null for a long-lived key.
  - `lastUsedAt` string, date-time: Session keys only: when the key was last used. Null for a long-lived key.
  - `prefix` string
  - `scopes` string[]
  - `session` boolean: Whether this is a session key, issued when a person signed in, rather than a long-lived key.
  - `userAgent` string: Session keys only: the user agent of the client that signed in, such as a browser. Null for a long-lived key, or when the client sent none.

## Other responses

- `400`: No scopes were given, or one of them is not a scope.
- `401`: The Authorization header is absent or the API key is not recognized.
- `403`: The key does not hold api-keys:write, a requested scope is not held by the calling key, the key being changed holds a scope the calling key does not, or the calling key is a session key and the change adds a scope.
- `404`: There is no active key with that ID that the caller may manage.
- `406`: No representation matches Accept.
- `409`: The key is the one making the request.
- `413`: Content Too Large
- `415`: Unsupported Media Type
- `500`: Internal Server Error
- `503`: Service Unavailable

## Changes

- **2026-10-10** `c4f85daa9208`: 16 info
  - added the optional property `field` to the response with the `400` status
  - added the optional property `field` to the response with the `401` status
  - added the optional property `field` to the response with the `403` status
  - added the optional property `field` to the response with the `409` status
  - …12 more
- **2026-10-10** `bcd644347f8e`: 3 info
  - added the optional property `clientAddress` to the response with the `200` status
  - added the optional property `current` to the response with the `200` status
  - added the optional property `userAgent` to the response with the `200` status
- **2026-10-06** `0390610d039d`: 1 info
  - added the non-success response with the status `409`
- **2026-10-05** `4082c17721d8`: 6 breaking, 6 info
  - removed the media type `*/*` for the response with the status `400`
  - removed the media type `*/*` for the response with the status `401`
  - removed the media type `*/*` for the response with the status `413`
  - removed the media type `*/*` for the response with the status `415`
  - …8 more
- **2026-10-05** `b877962e047e`: 4 info
  - added the optional property `expiresAt` to the response with the `200` status
  - added the optional property `idleExpiresAt` to the response with the `200` status
  - added the optional property `lastUsedAt` to the response with the `200` status
  - added the optional property `session` to the response with the `200` status

[Operation history](https://skmtc.dev/philterd/apis/philter-api/changes/api/api-keys/:keyId/scopes/put.md)

---

[API](https://skmtc.dev/philterd/apis/philter-api.md) - [All operations](https://skmtc.dev/philterd/apis/philter-api/llms.txt) - [OpenAPI document](https://skmtc.dev/philterd/apis/philter-api/revisions/aa3d7ad1aab4?raw)
