External Secrets Operator
Point the External Secrets Operator webhook provider at GET /api/v1/bulk-read-secrets to read a whole environment in one request.
Not verified yet
The operator's webhook provider reads one of our routes and writes a Kubernetes Secret in your cluster. We ship no Kubernetes sync integration because this path exists.
We have not checked a ClusterSecretStore manifest against a running operator, so we show none. Take the provider schema from the External Secrets Operator documentation for your version and map it onto the request and response below.
Request
GET /api/v1/bulk-read-secrets
Authorization: Bearer pck_...| Part | Value |
|---|---|
| Permission | secret:reveal (not secret:read, which only lists names) |
| Body, query string | None. We take workspace, project, and environment from the credential. |
Operation reference: bulk read secrets.
Response
{
"values": {
"DATABASE_URL": "...",
"redis/password": "..."
},
"skipped": ["stripe/rotating-key"]
}values is flat: a root key is its bare name, a key under a path is path/name, the same naming an export sync uses. We send skipped only when it is non-empty.
In skipped | Reason |
|---|---|
| A dynamic address | Minted on request; no stored value. See dynamic values. |
| A key with no version | Created, never written. Absent, not empty. |
Check skipped in your tooling. A needed key in skipped is missing from the Secret, and a missing variable at pod boot is harder to diagnose than a refusal here.
Audit and rate cost
We open each value through the single-read path and record one secret.read entry with the credential (the audit log). A one-minute refresh over 40 keys is 40 entries a minute; set your refresh interval accordingly.
We admit a bulk read at a cost of one, then take one rate-limit token per value opened (a dynamic address costs nothing). See rate limits.
Refusals
| Status | error | Cause |
|---|---|---|
401 | unauthorized | Credential unknown to us. |
401 | expired | Credential past its expiry. |
403 | forbidden | The credential's role lacks secret:reveal. |
429 | rate_limited | IP ceiling (1000 requests a minute, checked before the credential) or the plan's per-identity bucket. retry-after gives seconds. |
Full list: errors.
Setup
Create a machine identity in the console (machine identities) bound to the project and environment the operator reads, with a role carrying secret:reveal, and issue a pck_ credential. To avoid manual rotation, give the operator's service account a trust and exchange its projected token instead, as on Kubernetes.
Store the credential in a Kubernetes Secret in the operator's namespace.
Configure the webhook provider for GET /api/v1/bulk-read-secrets, sending the credential in the Authorization header.
Map values onto the keys the ExternalSecret requests.
Set the refresh interval, apply, and check the first sync.
Serverless functions
Deliver your values to Lambda, Cloud Run, Workers, Vercel, and Deno Deploy with an export sync, or run penv inside Lambda through the layer.
Upgrade from 0.16
Move from penv 0.16 to 1.0: export values, remove 0.16, install 1.0, push each environment, and the mapping from every 0.16 file, command and variable.