Secrets
Manage organization secrets and the attachments that expose them to an app.
Introduction
A secret is stored once for your organization and attached to the apps that need it, so the two halves of this page are separate: the store is org-scoped, and an attachment binds one secret to one app.
An attachment resolves to the environment variable name the value is injected as, which defaults to the secret's own name. That indirection is what lets the same code read HF_TOKEN against a production and a staging credential.
Secrets and plain environment variables share one pod environment and one ceiling of 100 bindings per app. A name already taken by either is rejected with 422, and attaching a secret the app already has returns 409. See Secrets.
List secrets
Returns secret metadata only. Encrypted values are never returned.
Request
Response
nextCursor
stringnullableCursor for the next page. Null when there are no more items.
data
object[]Array items6 properties each
id
string (uuid)requiredUUID v4
name
stringrequiredmin: 1max: 128Organization-scoped secret name. The shape matches
EnvironmentVariableNameand thesecrets.name/deployment_secrets.env_var_namecolumn CHECKs, one rule for the contract and the schema, because attached secrets are intended to be injected as environment variables once ADR-019 in-pod unseal lands. Names the platform sets on the serving container (RUNTIME,DISABLE_NGINX,MLFLOW_MODELS_WORKERS,UVICORN_HOST) are rejected with422: when injection exists, the deployer appends customer env after its own and kubelet resolves duplicates last-wins, so an accepted collision would silently replace a platform value. Same guard as plain environment variables. Enforced by the server (not expressible as a pattern here).
type
stringrequiredKind of secret. Only the environment-variable variant (
generic) is supported. Image-pull (registry) credentials are consumed by the kubelet before any container starts, so they cannot use the in-pod unseal path (ADR-019) and await their own decision.Possible values1 value
metadata
objectnullableOptional opaque metadata associated with the secret.
createdAt
string (date-time)date-time
updatedAt
string (date-time)date-time
Errors
| Status | When |
400 | The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns 422 instead.
|
401 | Missing or invalid credentials |
422 | The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns 400. The errors array carries one entry per offending field.
|
500 | Unexpected server error |
503 | A required service is temporarily unavailable |
Create a secret
Creates an organization-scoped secret. Returns 409 if the name is already in use, including when a secret of that name is pending_destroy. List only shows active secrets, so a name can appear free while create still conflicts for as long as that row remains. A pending_destroy row is removed, and its name released, by a background sweep once no running worker can still hold the value. There is no deadline on that wait, so a continuously busy app can hold a name for as long as it runs. Recreate-while-deleting may later reuse the pending row with the new value (same name, new ciphertext). An organization whose serverless tenancy is revoked, or has no tenancy receipt at all, returns 403 Forbidden: the per-organization CryptoKey that seals the value is only minted when the tenancy is active. An organization whose tenancy is still being provisioned returns 503 Service Unavailable instead. That state is retryable, not a revocation, and clears once provisioning finishes.
Request
name
stringrequiredmin: 1max: 128Organization-scoped secret name. The shape matches
EnvironmentVariableNameand thesecrets.name/deployment_secrets.env_var_namecolumn CHECKs, one rule for the contract and the schema, because attached secrets are intended to be injected as environment variables once ADR-019 in-pod unseal lands. Names the platform sets on the serving container (RUNTIME,DISABLE_NGINX,MLFLOW_MODELS_WORKERS,UVICORN_HOST) are rejected with422: when injection exists, the deployer appends customer env after its own and kubelet resolves duplicates last-wins, so an accepted collision would silently replace a platform value. Same guard as plain environment variables. Enforced by the server (not expressible as a pattern here).
type
stringrequiredKind of secret. Only the environment-variable variant (
generic) is supported. Image-pull (registry) credentials are consumed by the kubelet before any container starts, so they cannot use the in-pod unseal path (ADR-019) and await their own decision.Allowed values1 value
value
stringrequiredmax: 65536Plain-text value. Encrypted at rest by the platform. Capped at 64 KiB, Cloud KMS Encrypt's plaintext ceiling, and the reason the design needs no envelope encryption (ADR-019).
maxLengthis OpenAPI's string-length bound (character count), not a byte limit. Cloud KMS measures UTF-8 bytes, so the server also rejects a value whose UTF-8 encoding exceeds 65536 bytes with422, a rule this schema cannot express alone (multi-byte characters can passmaxLengthand still overrun the KMS limit).
metadata
objectnullable
Response
id
string (uuid)requiredUUID v4
name
stringrequiredmin: 1max: 128Organization-scoped secret name. The shape matches
EnvironmentVariableNameand thesecrets.name/deployment_secrets.env_var_namecolumn CHECKs, one rule for the contract and the schema, because attached secrets are intended to be injected as environment variables once ADR-019 in-pod unseal lands. Names the platform sets on the serving container (RUNTIME,DISABLE_NGINX,MLFLOW_MODELS_WORKERS,UVICORN_HOST) are rejected with422: when injection exists, the deployer appends customer env after its own and kubelet resolves duplicates last-wins, so an accepted collision would silently replace a platform value. Same guard as plain environment variables. Enforced by the server (not expressible as a pattern here).
type
stringrequiredKind of secret. Only the environment-variable variant (
generic) is supported. Image-pull (registry) credentials are consumed by the kubelet before any container starts, so they cannot use the in-pod unseal path (ADR-019) and await their own decision.Possible values1 value
metadata
objectnullableOptional opaque metadata associated with the secret.
createdAt
string (date-time)date-time
updatedAt
string (date-time)date-time
Errors
| Status | When |
400 | The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns 422 instead.
|
401 | Missing or invalid credentials |
403 | Authenticated but not permitted to access this resource |
409 | Resource already exists or the request conflicts with its current state |
413 | The request body exceeds its size limit: 10 MiB on invoke-sync and invoke-async, whose body carries the endpoint's payload, and 1 MiB on the other operations that answer with this response. detail names the limit in bytes.
|
422 | The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns 400. The errors array carries one entry per offending field.
|
500 | Unexpected server error |
503 | A required service is temporarily unavailable |
Update a secret
Re-encrypts the value under the same name (no rename). Rolls every live deployment that attaches this secret in place so a running worker picks up the new value. If a rollout is already in progress when this commits, this change is not guaranteed to land on it. It reaches the worker on a later redeploy instead. A deployment that is not live picks it up on its next deploy for another reason. An organization whose serverless tenancy is revoked, or has no tenancy receipt at all, returns 403 Forbidden: the per-organization CryptoKey that seals the value is only minted when the tenancy is active. An organization whose tenancy is still being provisioned returns 503 Service Unavailable instead. That state is retryable, not a revocation, and clears once provisioning finishes.
Request
secretName
stringrequiredmin: 1max: 128Secret name, unique within the authenticated organization.
Response
id
string (uuid)requiredUUID v4
name
stringrequiredmin: 1max: 128Organization-scoped secret name. The shape matches
EnvironmentVariableNameand thesecrets.name/deployment_secrets.env_var_namecolumn CHECKs, one rule for the contract and the schema, because attached secrets are intended to be injected as environment variables once ADR-019 in-pod unseal lands. Names the platform sets on the serving container (RUNTIME,DISABLE_NGINX,MLFLOW_MODELS_WORKERS,UVICORN_HOST) are rejected with422: when injection exists, the deployer appends customer env after its own and kubelet resolves duplicates last-wins, so an accepted collision would silently replace a platform value. Same guard as plain environment variables. Enforced by the server (not expressible as a pattern here).
type
stringrequiredKind of secret. Only the environment-variable variant (
generic) is supported. Image-pull (registry) credentials are consumed by the kubelet before any container starts, so they cannot use the in-pod unseal path (ADR-019) and await their own decision.Possible values1 value
metadata
objectnullableOptional opaque metadata associated with the secret.
createdAt
string (date-time)date-time
updatedAt
string (date-time)date-time
Errors
| Status | When |
400 | The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns 422 instead.
|
401 | Missing or invalid credentials |
403 | Authenticated but not permitted to access this resource |
404 | Resource not found |
413 | The request body exceeds its size limit: 10 MiB on invoke-sync and invoke-async, whose body carries the endpoint's payload, and 1 MiB on the other operations that answer with this response. detail names the limit in bytes.
|
422 | The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns 400. The errors array carries one entry per offending field.
|
500 | Unexpected server error |
503 | A required service is temporarily unavailable |
Delete a secret
Soft-deletes a secret: marks the row pending_destroy and bumps revision. This API does not hard-delete the row. Returns 409 while any app still attaches it. Cascade-detach is not performed here. Detach each holder with DELETE .../apps/{id}/secrets/{name} first, which rolls the deployments it names in place. A background sweep removes the row and releases the name once no running worker can still hold the value, rather than assuming every roll a detach started actually landed. A deployment that was not live when detached has nothing to roll until it resumes, so the sweep is the actual backstop, not the detach. While the row remains pending_destroy the name stays reserved, so create may return 409 even though list no longer shows the secret. Retries on an already-pending name are safe when no attachments remain (204). They still return 409 while attached.
Request
secretName
stringrequiredmin: 1max: 128Secret name, unique within the authenticated organization.
Errors
| Status | When |
401 | Missing or invalid credentials |
404 | Resource not found |
409 | Resource already exists or the request conflicts with its current state |
422 | The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns 400. The errors array carries one entry per offending field.
|
500 | Unexpected server error |
503 | A required service is temporarily unavailable |
List an app's secrets
Request
appId
stringrequiredmin: 6max: 30Immutable app identifier, unique among the authenticated organization's live apps.
Response
nextCursor
stringnullableCursor for the next page. Null when there are no more items.
data
object[]Array items7 properties each
id
string (uuid)requiredUUID v4
name
stringrequiredmin: 1max: 128Organization-scoped secret name. The shape matches
EnvironmentVariableNameand thesecrets.name/deployment_secrets.env_var_namecolumn CHECKs, one rule for the contract and the schema, because attached secrets are intended to be injected as environment variables once ADR-019 in-pod unseal lands. Names the platform sets on the serving container (RUNTIME,DISABLE_NGINX,MLFLOW_MODELS_WORKERS,UVICORN_HOST) are rejected with422: when injection exists, the deployer appends customer env after its own and kubelet resolves duplicates last-wins, so an accepted collision would silently replace a platform value. Same guard as plain environment variables. Enforced by the server (not expressible as a pattern here).
type
stringrequiredKind of secret. Only the environment-variable variant (
generic) is supported. Image-pull (registry) credentials are consumed by the kubelet before any container starts, so they cannot use the in-pod unseal path (ADR-019) and await their own decision.Possible values1 value
metadata
objectnullableOptional opaque metadata associated with the secret.
createdAt
string (date-time)date-time
updatedAt
string (date-time)date-time
envVarName
stringnullablemin: 1max: 128Resolved environment variable name when it differs from
name. Omitted when the secret name is used. Same reserved-name rules asSecretNamewhen set.
Errors
| Status | When |
400 | The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns 422 instead.
|
401 | Missing or invalid credentials |
404 | Resource not found |
422 | The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns 400. The errors array carries one entry per offending field.
|
500 | Unexpected server error |
503 | A required service is temporarily unavailable |
Attach a secret to an app
Records that an organization secret is attached to an app under a resolved env-var name, and rolls the app's live deployment in place so a running worker picks up the value without waiting for an unrelated deploy. If a rollout is already in progress when this commits, this attach is not guaranteed to land on it. It reaches the worker on a later redeploy instead. A deployment that is not live records the attachment only. The next resume reads the attach set fresh. Returns 409 if the secret is already attached, or if another attach would use the same env-var name.
The resolved name (envVarName, or secretName when omitted) must not already exist as a plain environment variable on this app (deployment_configs.key). Both sources use the same pod env namespace, so the server rejects the collision with 422 instead of allowing a last-wins override later. The reverse check applies when setting a plain environment variable.
An app holds at most 100 environment bindings in total, plain environment variables plus attached secrets, the same combined ceiling as create and the single-key env-var route. Attaching when the app is already at that limit returns 422.
A secret can be attached to at most 25 deployments. Rolling every attached deployment is what an update or detach costs, so the ceiling bounds that cost rather than the app side of the binding. Attaching past it returns 422.
Request
appId
stringrequiredmin: 6max: 30Immutable app identifier, unique among the authenticated organization's live apps.
secretName
stringrequiredmin: 1max: 128Organization-scoped secret name. The shape matches
EnvironmentVariableNameand thesecrets.name/deployment_secrets.env_var_namecolumn CHECKs, one rule for the contract and the schema, because attached secrets are intended to be injected as environment variables once ADR-019 in-pod unseal lands. Names the platform sets on the serving container (RUNTIME,DISABLE_NGINX,MLFLOW_MODELS_WORKERS,UVICORN_HOST) are rejected with422: when injection exists, the deployer appends customer env after its own and kubelet resolves duplicates last-wins, so an accepted collision would silently replace a platform value. Same guard as plain environment variables. Enforced by the server (not expressible as a pattern here).
envVarName
stringnullablemin: 1max: 128Environment variable name the secret is injected as. Omit or null to use
secretName. The server resolves and stores the final name. Same reserved-name rules asSecretName(422if reserved). The resolved name must also not collide with a plain environment variable key on this app (422).
Errors
| Status | When |
400 | The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns 422 instead.
|
401 | Missing or invalid credentials |
404 | Resource not found |
409 | Resource already exists or the request conflicts with its current state |
413 | The request body exceeds its size limit: 10 MiB on invoke-sync and invoke-async, whose body carries the endpoint's payload, and 1 MiB on the other operations that answer with this response. detail names the limit in bytes.
|
422 | The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns 400. The errors array carries one entry per offending field.
|
500 | Unexpected server error |
503 | A required service is temporarily unavailable |
Detach a secret from an app
Removes the attachment and rolls the app's live deployment in place so a running worker stops receiving the value. If a rollout is already in progress when this commits, this detach is not guaranteed to land on it. The worker stops receiving the value on a later redeploy instead. A deployment that is not live has the removal recorded only. There is nothing to roll until it resumes.
Request
appId
stringrequiredmin: 6max: 30Immutable app identifier, unique among the authenticated organization's live apps.
secretName
stringrequiredmin: 1max: 128Secret name, unique within the authenticated organization.
Errors
| Status | When |
401 | Missing or invalid credentials |
404 | Resource not found |
422 | The request was well-formed but semantically invalid (e.g. a missing or out-of-range field). A request that could not be parsed returns 400. The errors array carries one entry per offending field.
|
500 | Unexpected server error |
503 | A required service is temporarily unavailable |