---
title: "Add a secret version"
method: POST
path: "/api/secrets/{id}/versions"
---

# Add a secret version

`POST /api/secrets/{id}/versions`

## Request

- Base URL: `https://api.hyperbrowser.ai`
- URL: `https://api.hyperbrowser.ai/api/secrets/{id}/versions`
- Auth: API key in header `x-api-key`
- TypeScript: <https://skmtc.dev/hyperbrowser/apis/hyperbrowser-api/docs/api/secrets/:id/versions/post.ts> (a function with typed inputs; its response is checked against the schema)

Uploads a new value as the secret's next version. Encrypt the value on your side as a compact JWE (RSA-OAEP-256 and A256GCM) to the key from GET /api/secrets/public-key, with the protected header `{alg, enc, kid, ctx}`, where `ctx` is the secret's `encryptionContext`. The SDKs and CLI do this for you. A secret keeps at most 10 versions. Secrets are enabled for teams on request; please contact support to enable them (otherwise 403 SECRETS_FEATURE_DISABLED). Restricted API keys need the secrets.manage scope. When you're signed in rather than using an API key, you must be a team owner or admin (otherwise 403 SECRETS_ADMIN_REQUIRED).

## Path parameters

- `id` string, uuid, required

## Request body

- AddSecretVersionParams
  - `envelope` string, required: The value, encrypted on your side as a compact JWE: five canonical, unpadded base64url segments. Wrap the content key with RSA-OAEP-256 using the key from GET /api/secrets/public-key, and encrypt the content with A256GCM. The protected header must hold exactly `alg` (`RSA-OAEP-256`), `enc` (`A256GCM`), `kid` (the public key's `kid`) and `ctx` (the secret's `encryptionContext`, copied verbatim). The plaintext is the value as UTF-8: any string for `secret`; a base32 seed (spaces, hyphens and line breaks are ignored, `=` padding only at the end) or an `otpauth://totp/` URI (with `secret` and optionally `algorithm` SHA1, SHA256 or SHA512, `digits` 6 or 8, and `period` 1-300) for `totp`; a 1Password service account token for `op_token`. The value can be at most 32 KiB, without NUL bytes, and the envelope at most 64 KiB. The API checks the envelope's structure but can't decrypt it, so it can't check the value: an invalid TOTP seed returns 422 SECRET_VALUE_INVALID when it's used. The Hyperbrowser SDKs and CLI validate the value and build the envelope for you, so we recommend using them rather than encrypting by hand. For a customer-protected secret, `envelope` is that inner envelope wrapped with your own key as another compact JWE: `RSA-OAEP-256`, `ECDH-ES` or `ECDH-ES+A256KW` key management with `A256GCM`, and a protected header holding exactly `alg`, `enc`, `kid` (your key's ID, 1-256 printable ASCII characters) and `ctx` (the secret's `encryptionContext`), plus `epk`, and optionally `apu` and `apv`, with ECDH-ES.
  - `innerDigest` string: Customer-protected secrets only, and required for them: the unpadded base64url SHA-256 of the inner envelope's compact form.
  - `innerKid` string: Customer-protected secrets only, and required for them: the `kid` of the platform public key the inner envelope was encrypted to. It must be the current key (otherwise 409 SECRET_KEY_OUTDATED).

## Response `200`

Success

- SecretVersion
  - `version` integer, required
  - `kid` string, required: ID of the platform public key the envelope (for a customer-protected secret, the inner envelope) was encrypted to.
  - `createdAt` string, date-time, required
  - `outerKid` string: Customer-protected secrets only: the ID of your key that wrapped this version.
  - `retiresAt` string, date-time, nullable: When the platform key this version was encrypted to is retired. Uses after then fail with 409 SECRET_KEY_RETIRED; add a new version. Null when no retirement is scheduled.

## Other responses

- `400`: Invalid request body, or SECRET_ENVELOPE_INVALID: the envelope isn't a valid compact JWE for this secret (for example, a different `ctx`) or the value exceeds 32 KiB. SECRET_PROTECTION_MISMATCH: the version was uploaded in the other protection's form (a customer-protected secret needs `innerDigest` and `innerKid`; other secrets don't take them).
- `401`: Authentication required.
- `403`: API_KEY_SCOPE_DENIED (the API key lacks a required scope), SECRETS_FEATURE_DISABLED, or SECRETS_ADMIN_REQUIRED.
- `404`: SECRET_NOT_FOUND: no secret with this ID in your team.
- `409`: SECRET_KEY_OUTDATED: the envelope was encrypted to an old public key; fetch the current key and encrypt again. SECRET_LIMIT_REACHED: the secret has 10 versions; delete an old one first. SECRET_VERSION_DUPLICATE: another version already holds this inner envelope; replace that version's wrapping instead.
- `500`: Internal server error.
- `503`: SECRETS_NOT_CONFIGURED: secrets aren't available on this deployment.

## Changes

- **2026-11-10** `94dc06948680`: 4 info
  - added the new optional request property `innerDigest`
  - added the new optional request property `innerKid`
  - added the optional property `outerKid` to the response with the `200` status
  - added the optional property `retiresAt` to the response with the `200` status
- **2026-10-15** `6ca0b00ee564`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/hyperbrowser/apis/hyperbrowser-api/changes/api/secrets/:id/versions/post.md)

---

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