---
title: "Call keys.rotate"
method: POST
path: "/api/v1/tools/keys.rotate"
tags: ["keys"]
---

# Call keys.rotate

`POST /api/v1/tools/keys.rotate`

## Request

- Base URL: `https://app.goosybear.ai`
- URL: `https://app.goosybear.ai/api/v1/tools/keys.rotate`
- Auth: HTTP bearer

Propose, then confirm, replacing the API key making this call with a new one — 'rotate this key', 'swap in a fresh credential'. The current key keeps working for overlap_hours after the new key exists, then stops. The new key comes back once, on the confirm, so store it before anything else. Not for any other key — revoke or rotate those in Settings › API & MCP.

## Headers

- `x-request-id` string

## Request body

- object
  - `overlap_hours` integer: How many hours the current key keeps working after the new one exists, 0 to 168. Default 24.
  - `confirmation_id` string, uuid

## Response `200`

The call was admitted and dispatched. `ok` says whether the tool succeeded — a refusal the tool itself produced is still a 200, exactly as it is a successful JSON-RPC result over MCP.

- union
  - object: The tool ran and answered.
    - `ok` true, required
    - `state` 'confirmation_required' | 'rotated', required
    - `overlap_hours` integer, required
    - `previous_key_prefix` string, required
    - `confirmation_id` string, uuid
    - `expires_in_seconds` integer
    - `key` string
    - `key_prefix` string
    - `key_expires_at` string, nullable
    - `previous_key_expires_at` string
  - ToolRefusal: A call that was admitted and dispatched, and did not succeed.
    - `ok` false, required
    - `code` string, required: The typed reason — e.g. `workspace_ambiguous`, `invalid_arguments`, or the tool's own failure code.
    - `message` string, required: A sentence a person can act on.
    - `workspaces` string[]: On a workspace refusal: the slugs this key may pass as `workspace`.
    - `issues` object[]: On `invalid_arguments`: which fields failed, and why.
      - `path` string, required
      - `message` string, required
    - `path` string: On an authoring refusal: where in your call's input it points, e.g. `steps.<key>.params.<field>`.
    - `fix` string: On an authoring refusal: the change to your call that would pass.
    - `instruction` string: On an authoring refusal: what to do next with it.

## Other responses

- `400`: The request could not be used: the body is present but not a JSON object (`tenant_api.malformed_body`); it is a top-level array carrying more messages than this endpoint accepts (`tenant_mcp.batch_too_large`); or the `Idempotency-Key` header is malformed (`tenant_api.idempotency_key_invalid`), disagrees with the body's `idempotency_key` (`tenant_api.idempotency_key_conflict`), or was sent to an action that cannot replay a retry (`tenant_api.idempotency_unsupported`). Nothing ran.
- `401`: No bearer credential, or one that did not resolve. Deliberately ONE undifferentiated answer for absent / unknown / revoked / expired, so the endpoint is never an oracle over the key space.
- `403`: The credential is good but the surface is not open to it — the platform or account API switch is off, or the holder lacks the `api.access` capability. The `code` says which.
- `404`: No such tool. The body names no tool and is identical for every unrecognised name.
- `429`: Over this key's per-minute cap. Carries `Retry-After` and `error.retry_after_seconds`.

---

[API](https://skmtc.dev/goosybear/apis/goosy-bear-api.md) · [All operations](https://skmtc.dev/goosybear/apis/goosy-bear-api/llms.txt) · [OpenAPI document](https://skmtc.dev/goosybear/apis/goosy-bear-api/revisions/6d82fb124064?raw)
