---
title: "Change an API key"
method: PATCH
path: "/keys/{id}"
tags: ["Credentials"]
---

# Change an API key

`PATCH /keys/{id}`

## Request

- Base URL: `https://api.primitive.dev/v1`
- URL: `https://api.primitive.dev/v1/keys/{id}`
- Auth: HTTP bearer

Rename a key, change its expiry, its grant or its conditions, or suspend it. Any change needs the person's own access to cover the key's access now (an owner covers every key); otherwise 403 grant_forbidden, details.reason target_not_covered. A grant change is a new version of the key and is held to the same rules as creating one (a custom grant that does not say which mail it reaches is refused with details.reason mail_scope_required), so it takes the same step-up. The Primitive CLI's token is refused, even with a step-up. Needs the organization's credential features to be enabled; while they are not, this answers 404 feature_disabled.

Person only: a dashboard session, or the person's own OAuth token with a step-up token for purpose `keys.create` in the Primitive-Step-Up header. Without one an OAuth token is answered 403 step_up_required, whose details carry the dashboard URL that issues it (or 403 grant_forbidden when the person's role could not do it anyway). The person's role must hold keys:manage. API keys and agent connection keys are refused. An agent acting for the person is refused unless it carries a step-up token the person issued in the dashboard.

## Path parameters

- `id` string, required

## Headers

- `Primitive-Step-Up` string

## Request body

- CredentialsUpdateKeyInput
  - `name` string
  - `expires_at` string, date-time, nullable
  - `grant` union
    - 'super_admin' | 'send_only' | 'read_only' | 'own_addresses' | 'own_addresses_read_only' | 'custom'
    - object
      - `preset` 'super_admin' | 'send_only' | 'read_only' | 'own_addresses' | 'own_addresses_read_only' | 'custom', required
      - `matrix` object
      - `permissions` string[]
      - `addresses` string[]
      - `domains` string[]
      - `rules` string[]
      - `depth` 'metadata' | 'content' | 'raw'
      - `resources` object
      - `mail_scope` 'org'
  - `conditions` object, nullable
    - `ip_allowlist` string[], nullable
    - `rate_limit_per_minute` integer, nullable
  - `suspended` boolean

## Response `200`

Success

- object
  - `success` true, required
  - `data` CredentialsUpdateKeyResult, required
    - `id` string, required
    - `changed` string[], required
    - `warnings` object[], required
      - `permission` string, required
      - `adds` string[], required
      - `why` string, required
      - `description` string, required
    - `address_warnings` object[]
      - `address` string, required
      - `reason` string, required
      - `description` string, required

## Other responses

- `400`: Invalid request parameters
- `401`: Invalid or missing API key
- `403`: Authenticated caller lacks permission for the operation
- `404`: Resource not found
- `409`: The request conflicts with the current state of the resource
- `429`: Rate limit exceeded. The `ratelimit-*` headers describe the limiter that rejected this request, with its own limit and window, not the API budget that successful responses report. A limiter that reports only a wait sends `Retry-After` alone.

---

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