> ## Documentation Index
> Fetch the complete documentation index at: https://kosli-kosli-capture-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Rotating API keys

> Learn how to rotate Kosli service account API keys with zero downtime.

Rotating API keys regularly is a security best practice that limits the blast radius of a leaked or compromised credential. This tutorial walks you through rotating a Kosli service account API key with zero downtime, using the Kosli web app, the CLI, or the API directly.

<Tip>
  Kosli never stores your API token in plain text. Only a cryptographic hash of the token is stored, so the original token cannot be retrieved from our systems — make sure to copy a new key immediately after creating or rotating it.
</Tip>

## Prerequisites

* A Kosli shared organization with at least one [service account](/administration/authentication/service_accounts) and an existing API key.
* Administrator access to the organization that owns the service account.
* An inventory of every system (CI pipelines, runtime reporters, scripts, secrets managers, etc.) that uses the API key you plan to rotate.

## How rotation works

When you rotate a service account API key, Kosli:

1. Generates a **new** API key immediately and returns its value **once**.
2. Sets the new key's expiry to the **rotated key's current expiry** unless you pass `--expires-at` (CLI) or `expires_at` (API), bounded by the server-side **maximum lifetime of 365 days from creation**.
3. Keeps the **old** key valid for a configurable grace period (default: **24 hours**).
4. Automatically revokes the old key when the grace period expires.

The grace period lets you roll the new key out to all consumers without an interruption in service. Choose a window that matches your deployment cadence — short enough to limit exposure, long enough to update every dependent system.

<Warning>
  Rotation on its own does not extend the credential. If the rotated key is already close to its expiry (for example, most of the way through the 365-day cap), the new key inherits that expiry and dies at the same moment — the exact failure rotation is supposed to prevent. Pass `--expires-at` to reset the clock, up to the 365-day cap.
</Warning>

## Rotate a key

Choose the interface that best fits your workflow. All three trigger the same rotation flow described above.

<Tabs>
  <Tab title="Web UI">
    1. Log in to Kosli and select the organization that owns the service account.
    2. Go to **Settings** → **Service accounts** in the left navigation.
    3. Open the service account whose key you want to rotate.
    4. Find the key in the **API Keys** list and click **Regenerate**.
    5. Choose a grace period for the old key, then confirm.
    6. Copy the new key value immediately and store it in your secrets manager — it will not be shown again.
  </Tab>

  <Tab title="CLI">
    Use the [`kosli rotate api-key`](/client_reference/kosli_rotate_api-key) command to rotate one or more keys from your terminal or a CI job:

    ```shell theme={"theme":"dracula","languages":{"custom":["/languages/rego.json"]}}
    kosli rotate api-key <<key-id>> \
      --service-account <<service-account-name>> \
      --grace-period-hours 24 \
      --expires-at 2026-12-31 \
      --api-token "$KOSLI_ADMIN_TOKEN" \
      --org "$ORG"
    ```

    `--expires-at` accepts an epoch timestamp, `YYYY-MM-DD`, `YYYY-MM-DD HH:MM:SS`, or an RFC3339 timestamp, and is capped at 365 days from creation. Omit it to inherit the rotated key's expiry — combine that with a rotation cadence well inside the 365-day cap, or pass `--expires-at` to reset the clock.

    Rotate multiple keys for the same service account in one call by passing additional key IDs. When `--grace-period-hours` is omitted, the server-side default grace period applies:

    ```shell theme={"theme":"dracula","languages":{"custom":["/languages/rego.json"]}}
    kosli rotate api-key keyID1 keyID2 \
      --service-account <<service-account-name>> \
      --api-token "$KOSLI_ADMIN_TOKEN" \
      --org "$ORG"
    ```

    Use `--output json` to capture the new key value programmatically:

    ```shell theme={"theme":"dracula","languages":{"custom":["/languages/rego.json"]}}
    NEW_KEY=$(kosli rotate api-key <<key-id>> \
      --service-account <<service-account-name>> \
      --api-token "$KOSLI_ADMIN_TOKEN" \
      --org "$ORG" \
      --output json \
      | jq -r '.api_key')
    ```
  </Tab>

  <Tab title="API">
    Call the rotate endpoint directly — useful when integrating with a secrets manager or another automation system:

    ```shell theme={"theme":"dracula","languages":{"custom":["/languages/rego.json"]}}
    curl -X POST \
      -H "Authorization: Bearer <<your-admin-api-key>>" \
      -H "Content-Type: application/json" \
      -d '{"grace_period_hours": 24, "expires_at": 1798761600}' \
      https://app.kosli.com/api/v2/service-accounts/<<your-org>>/<<service-account-name>>/api-keys/<<key-id>>/rotate
    ```

    `expires_at` is an epoch timestamp for the new key's expiry. Omit it to inherit the rotated key's expiry. The value is capped at 365 days from creation.

    The response contains the new API key value. Capture it directly into your secrets store:

    ```shell theme={"theme":"dracula","languages":{"custom":["/languages/rego.json"]}}
    NEW_KEY=$(curl -s -X POST \
      -H "Authorization: Bearer $KOSLI_ADMIN_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"grace_period_hours": 24}' \
      https://app.kosli.com/api/v2/service-accounts/$ORG/$SA_NAME/api-keys/$KEY_ID/rotate \
      | jq -r '.api_key')
    ```
  </Tab>
</Tabs>

<Tip>
  You can list a service account's keys (including the rotation status of the old key) with `GET /service-accounts/{org}/{name}/api-keys`. See the [API reference](/api-reference/service-accounts/list-api-keys-for-a-service-account) for details.
</Tip>

## Roll the new key out

While the old key is still valid, update every consumer to use the new key:

* **CI/CD pipelines**: Update the `KOSLI_API_TOKEN` secret in GitHub Actions, GitLab CI, Jenkins, CircleCI, etc.
* **Runtime reporters**: Update Kubernetes secrets used by the [Kosli Kubernetes reporter](/helm/k8s_reporter), and roll the relevant pods.
* **Local config files**: Update any [Kosli CLI config files](/getting_started/install#assigning-flags-via-config-files) that hard-code the token.
* **Secrets managers**: Update the value in AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, Azure Key Vault, or wherever you store the token.

Verify the rollout by triggering a job (or running a Kosli CLI command) that uses the new key and confirming it succeeds:

```shell theme={"theme":"dracula","languages":{"custom":["/languages/rego.json"]}}
kosli list environments --api-token "$NEW_KEY" --org "$ORG"
```

## Verify the old key is decommissioned

Once every consumer is on the new key, you can either wait for the grace period to elapse or revoke the old key immediately:

```shell theme={"theme":"dracula","languages":{"custom":["/languages/rego.json"]}}
curl -X DELETE \
  -H "Authorization: Bearer <<your-admin-api-key>>" \
  https://app.kosli.com/api/v2/service-accounts/<<your-org>>/<<service-account-name>>/api-keys/<<old-key-id>>
```

See [Revoke an API key for a service account](/api-reference/service-accounts/revoke-an-api-key-for-a-service-account) for details.

After revocation (or grace-period expiry), confirm the old key no longer works:

```shell theme={"theme":"dracula","languages":{"custom":["/languages/rego.json"]}}
curl -i -H "Authorization: Bearer $OLD_KEY" \
  https://app.kosli.com/api/v2/environments/$ORG
# Expect: HTTP/1.1 401 Unauthorized
```

## Recommended rotation cadence

* **Service accounts**: rotate at least every 90 days, and immediately if you suspect a leak.
* **After offboarding**: rotate any key an offboarded user could have accessed.
* **After incidents**: rotate any key potentially exposed by a security incident, regardless of cadence.

Automating rotation from your secrets manager — using the rotate endpoint above — is the most reliable way to keep within your target cadence.

## Related

* [Service accounts](/administration/authentication/service_accounts)
* [Rotate an API key for a service account (API reference)](/api-reference/service-accounts/rotate-an-api-key-for-a-service-account)
* [Revoke an API key for a service account (API reference)](/api-reference/service-accounts/revoke-an-api-key-for-a-service-account)
* [List API keys for a service account (API reference)](/api-reference/service-accounts/list-api-keys-for-a-service-account)
