---
title: Secrets | Runware Docs
url: https://runware.ai/docs/serverless/api-secrets
description: Manage organization secrets and the attachments that expose them to an app.
relatedDocuments:
  - https://runware.ai/docs/serverless/api
  - https://runware.ai/docs/serverless/secrets
  - https://runware.ai/docs/serverless/environment-variables
---
## 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.

> [!NOTE]
> 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](https://runware.ai/docs/serverless/secrets).

## List secrets

GETapi.serverless.runware.ai/v1/secrets

Returns secret metadata only. Encrypted values are never returned.

### [Request](#list-secrets-request)

Query

### [limit](#list-secrets-query-limit)

- **Type**: `integer`
- **Format**: `int32`
- **Min**: `1`
- **Max**: `100`
- **Default**: `20`

Maximum number of items to return.

### [cursor](#list-secrets-query-cursor)

- **Type**: `string`

Opaque pagination cursor returned by a previous call, as `nextCursor` or, on the operations that offer one, `prevCursor`.

### [Response](#list-secrets-response)

### [nextCursor](#list-secrets-response-nextcursor)

- **Type**: `string (nullable)`

Cursor for the next page. Null when there are no more items.

### [data](#list-secrets-response-data)

- **Type**: `object[] (6 properties)`

#### [id](#list-secrets-response-data-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

#### [name](#list-secrets-response-data-name)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Organization-scoped secret name. The shape matches `EnvironmentVariableName` and the `secrets.name` / `deployment_secrets.env_var_name` column 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 with `422`: 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](#list-secrets-response-data-type)

- **Type**: `string`
- **Required**: true

Kind 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 values**: `generic`

#### [metadata](#list-secrets-response-data-metadata)

- **Type**: `object (nullable)`

Optional opaque metadata associated with the secret.

#### [createdAt](#list-secrets-response-data-createdat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

#### [updatedAt](#list-secrets-response-data-updatedat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

### [Errors](#list-secrets-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">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 <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Create a secret

POSTapi.serverless.runware.ai/v1/secrets

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](#create-secret-request)

Body

### [name](#create-secret-body-name)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Organization-scoped secret name. The shape matches `EnvironmentVariableName` and the `secrets.name` / `deployment_secrets.env_var_name` column 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 with `422`: 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](#create-secret-body-type)

- **Type**: `string`
- **Required**: true

Kind 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 values**: `generic`

### [value](#create-secret-body-value)

- **Type**: `string`
- **Required**: true
- **Max**: `65536`

Plain-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). `maxLength` is 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 with `422`, a rule this schema cannot express alone (multi-byte characters can pass `maxLength` and still overrun the KMS limit).

### [metadata](#create-secret-body-metadata)

- **Type**: `object (nullable)`

### [Response](#create-secret-response)

### [id](#create-secret-response-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

### [name](#create-secret-response-name)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Organization-scoped secret name. The shape matches `EnvironmentVariableName` and the `secrets.name` / `deployment_secrets.env_var_name` column 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 with `422`: 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](#create-secret-response-type)

- **Type**: `string`
- **Required**: true

Kind 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 values**: `generic`

### [metadata](#create-secret-response-metadata)

- **Type**: `object (nullable)`

Optional opaque metadata associated with the secret.

### [createdAt](#create-secret-response-createdat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

### [updatedAt](#create-secret-response-updatedat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

### [Errors](#create-secret-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">403</code></td><td data-rw-cid-pz7tphoj="true">Authenticated but not permitted to access this resource</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">409</code></td><td data-rw-cid-pz7tphoj="true">Resource already exists or the request conflicts with its current state</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">413</code></td><td data-rw-cid-pz7tphoj="true">The request body exceeds its size limit: 10 MiB on <code class="component-Code">invoke-sync</code> and <code class="component-Code">invoke-async</code>, whose body carries the endpoint's payload, and 1 MiB on the other operations that answer with this response. <code class="component-Code">detail</code> names the limit in bytes.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">415</code></td><td data-rw-cid-pz7tphoj="true">The request body arrived with a Content-Type this operation does not accept. The body was not decoded, so this is not a schema failure. A body of <code class="component-Code">application/json</code> that cannot be parsed returns <code class="component-Code">400</code>. One that parses and breaks the schema returns <code class="component-Code">422</code>. A body sent with no Content-Type is the same refusal.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">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 <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Create many secrets

POSTapi.serverless.runware.ai/v1/secret-batches

Creates organization-scoped secrets. The batch is one write: every secret is created, or none are. Returns `409` when any name is already in use, including when a secret of that name is `pending_destroy`, and `errors[]` names each conflicting item. 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. Duplicate names in the batch are `422`. This route does not attach a secret to an app. Attach each one with `POST /v1/apps/{appId}/secrets`. 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](#create-secret-batch-request)

Body

### [secrets](#create-secret-batch-body-secrets)

- **Type**: `object[] (4 properties)`
- **Required**: true
- **Min items**: `1`
- **Max items**: `100`

Secrets to create. The response lists them in this order. At most 100. The request body is still capped at 1 MiB, which is what limits how many large values fit. Each item has the same fields and the same 64 KiB value ceiling as `SecretCreate`.

#### [name](#create-secret-batch-body-secrets-name)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Organization-scoped secret name. The shape matches `EnvironmentVariableName` and the `secrets.name` / `deployment_secrets.env_var_name` column 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 with `422`: 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](#create-secret-batch-body-secrets-type)

- **Type**: `string`
- **Required**: true

Kind 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 values**: `generic`

#### [value](#create-secret-batch-body-secrets-value)

- **Type**: `string`
- **Required**: true
- **Max**: `65536`

Plain-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). `maxLength` is 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 with `422`, a rule this schema cannot express alone (multi-byte characters can pass `maxLength` and still overrun the KMS limit).

#### [metadata](#create-secret-batch-body-secrets-metadata)

- **Type**: `object (nullable)`

### [Response](#create-secret-batch-response)

### [data](#create-secret-batch-response-data)

- **Type**: `object[] (6 properties)`
- **Required**: true

#### [id](#create-secret-batch-response-data-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

#### [name](#create-secret-batch-response-data-name)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Organization-scoped secret name. The shape matches `EnvironmentVariableName` and the `secrets.name` / `deployment_secrets.env_var_name` column 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 with `422`: 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](#create-secret-batch-response-data-type)

- **Type**: `string`
- **Required**: true

Kind 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 values**: `generic`

#### [metadata](#create-secret-batch-response-data-metadata)

- **Type**: `object (nullable)`

Optional opaque metadata associated with the secret.

#### [createdAt](#create-secret-batch-response-data-createdat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

#### [updatedAt](#create-secret-batch-response-data-updatedat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

### [Errors](#create-secret-batch-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">403</code></td><td data-rw-cid-pz7tphoj="true">Authenticated but not permitted to access this resource</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">409</code></td><td data-rw-cid-pz7tphoj="true">Resource already exists or the request conflicts with its current state</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">413</code></td><td data-rw-cid-pz7tphoj="true">The request body exceeds its size limit: 10 MiB on <code class="component-Code">invoke-sync</code> and <code class="component-Code">invoke-async</code>, whose body carries the endpoint's payload, and 1 MiB on the other operations that answer with this response. <code class="component-Code">detail</code> names the limit in bytes.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">415</code></td><td data-rw-cid-pz7tphoj="true">The request body arrived with a Content-Type this operation does not accept. The body was not decoded, so this is not a schema failure. A body of <code class="component-Code">application/json</code> that cannot be parsed returns <code class="component-Code">400</code>. One that parses and breaks the schema returns <code class="component-Code">422</code>. A body sent with no Content-Type is the same refusal.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">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 <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Update a secret

PUTapi.serverless.runware.ai/v1/secrets/{secretName}

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](#update-secret-request)

Path

### [secretName](#update-secret-path-secretname)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Secret name, unique within the authenticated organization.

Body

### [value](#update-secret-body-value)

- **Type**: `string`
- **Required**: true
- **Max**: `65536`

Plain-text value. Encrypted at rest by the platform. Same 64 KiB Cloud KMS plaintext ceiling and UTF-8 byte check as `SecretCreate.value`.

### [metadata](#update-secret-body-metadata)

- **Type**: `object`

Omit to leave the stored metadata unchanged. Send an object (including `{}`) to replace it.

### [Response](#update-secret-response)

### [id](#update-secret-response-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

### [name](#update-secret-response-name)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Organization-scoped secret name. The shape matches `EnvironmentVariableName` and the `secrets.name` / `deployment_secrets.env_var_name` column 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 with `422`: 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](#update-secret-response-type)

- **Type**: `string`
- **Required**: true

Kind 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 values**: `generic`

### [metadata](#update-secret-response-metadata)

- **Type**: `object (nullable)`

Optional opaque metadata associated with the secret.

### [createdAt](#update-secret-response-createdat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

### [updatedAt](#update-secret-response-updatedat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

### [Errors](#update-secret-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">403</code></td><td data-rw-cid-pz7tphoj="true">Authenticated but not permitted to access this resource</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">413</code></td><td data-rw-cid-pz7tphoj="true">The request body exceeds its size limit: 10 MiB on <code class="component-Code">invoke-sync</code> and <code class="component-Code">invoke-async</code>, whose body carries the endpoint's payload, and 1 MiB on the other operations that answer with this response. <code class="component-Code">detail</code> names the limit in bytes.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">415</code></td><td data-rw-cid-pz7tphoj="true">The request body arrived with a Content-Type this operation does not accept. The body was not decoded, so this is not a schema failure. A body of <code class="component-Code">application/json</code> that cannot be parsed returns <code class="component-Code">400</code>. One that parses and breaks the schema returns <code class="component-Code">422</code>. A body sent with no Content-Type is the same refusal.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">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 <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Delete a secret

DELETEapi.serverless.runware.ai/v1/secrets/{secretName}

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](#delete-secret-request)

Path

### [secretName](#delete-secret-path-secretname)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Secret name, unique within the authenticated organization.

### [Errors](#delete-secret-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">403</code></td><td data-rw-cid-pz7tphoj="true">Authenticated but not permitted to access this resource</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">409</code></td><td data-rw-cid-pz7tphoj="true">Resource already exists or the request conflicts with its current state</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">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 <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## List an app's secrets

GETapi.serverless.runware.ai/v1/apps/{appId}/secrets

### [Request](#list-app-secrets-request)

Path

### [appId](#list-app-secrets-path-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier, unique among the authenticated organization's live apps.

Query

### [limit](#list-app-secrets-query-limit)

- **Type**: `integer`
- **Format**: `int32`
- **Min**: `1`
- **Max**: `100`
- **Default**: `20`

Maximum number of items to return.

### [cursor](#list-app-secrets-query-cursor)

- **Type**: `string`

Opaque pagination cursor returned by a previous call, as `nextCursor` or, on the operations that offer one, `prevCursor`.

### [Response](#list-app-secrets-response)

### [nextCursor](#list-app-secrets-response-nextcursor)

- **Type**: `string (nullable)`

Cursor for the next page. Null when there are no more items.

### [data](#list-app-secrets-response-data)

- **Type**: `object[] (7 properties)`

#### [id](#list-app-secrets-response-data-id)

- **Type**: `string (uuid)`
- **Required**: true
- **Format**: `UUID v4`

#### [name](#list-app-secrets-response-data-name)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Organization-scoped secret name. The shape matches `EnvironmentVariableName` and the `secrets.name` / `deployment_secrets.env_var_name` column 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 with `422`: 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](#list-app-secrets-response-data-type)

- **Type**: `string`
- **Required**: true

Kind 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 values**: `generic`

#### [metadata](#list-app-secrets-response-data-metadata)

- **Type**: `object (nullable)`

Optional opaque metadata associated with the secret.

#### [createdAt](#list-app-secrets-response-data-createdat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

#### [updatedAt](#list-app-secrets-response-data-updatedat)

- **Type**: `string (date-time)`
- **Format**: `date-time`

#### [envVarName](#list-app-secrets-response-data-envvarname)

- **Type**: `string (nullable)`
- **Min**: `1`
- **Max**: `128`

Resolved environment variable name when it differs from `name`. Omitted when the secret name is used. Same reserved-name rules as `SecretName` when set.

### [Errors](#list-app-secrets-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">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 <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Attach a secret to an app

POSTapi.serverless.runware.ai/v1/apps/{appId}/secrets

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](#attach-app-secret-request)

Path

### [appId](#attach-app-secret-path-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier, unique among the authenticated organization's live apps.

Body

### [secretName](#attach-app-secret-body-secretname)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Organization-scoped secret name. The shape matches `EnvironmentVariableName` and the `secrets.name` / `deployment_secrets.env_var_name` column 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 with `422`: 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](#attach-app-secret-body-envvarname)

- **Type**: `string (nullable)`
- **Min**: `1`
- **Max**: `128`

Environment 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 as `SecretName` (`422` if reserved). The resolved name must also not collide with a plain environment variable key on this app (`422`).

### [Errors](#attach-app-secret-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">400</code></td><td data-rw-cid-pz7tphoj="true">The request was malformed and could not be parsed (e.g. invalid JSON). A well-formed request that fails validation returns <code class="component-Code">422</code> instead.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">403</code></td><td data-rw-cid-pz7tphoj="true">Authenticated but not permitted to access this resource</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">409</code></td><td data-rw-cid-pz7tphoj="true">Resource already exists or the request conflicts with its current state</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">413</code></td><td data-rw-cid-pz7tphoj="true">The request body exceeds its size limit: 10 MiB on <code class="component-Code">invoke-sync</code> and <code class="component-Code">invoke-async</code>, whose body carries the endpoint's payload, and 1 MiB on the other operations that answer with this response. <code class="component-Code">detail</code> names the limit in bytes.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">415</code></td><td data-rw-cid-pz7tphoj="true">The request body arrived with a Content-Type this operation does not accept. The body was not decoded, so this is not a schema failure. A body of <code class="component-Code">application/json</code> that cannot be parsed returns <code class="component-Code">400</code>. One that parses and breaks the schema returns <code class="component-Code">422</code>. A body sent with no Content-Type is the same refusal.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">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 <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>

## Detach a secret from an app

DELETEapi.serverless.runware.ai/v1/apps/{appId}/secrets/{secretName}

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](#detach-app-secret-request)

Path

### [appId](#detach-app-secret-path-appid)

- **Type**: `string`
- **Required**: true
- **Min**: `6`
- **Max**: `30`

Immutable app identifier, unique among the authenticated organization's live apps.

### [secretName](#detach-app-secret-path-secretname)

- **Type**: `string`
- **Required**: true
- **Min**: `1`
- **Max**: `128`

Secret name, unique within the authenticated organization.

### [Errors](#detach-app-secret-errors)

<table><tbody><tr><td data-rw-cid-pz7tphoj="true">Status</td><td data-rw-cid-pz7tphoj="true">When</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">401</code></td><td data-rw-cid-pz7tphoj="true">Missing or invalid credentials</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">403</code></td><td data-rw-cid-pz7tphoj="true">Authenticated but not permitted to access this resource</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">404</code></td><td data-rw-cid-pz7tphoj="true">Resource not found</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">422</code></td><td data-rw-cid-pz7tphoj="true">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 <code class="component-Code">400</code>. The <code class="component-Code">errors</code> array carries one entry per offending field.</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">500</code></td><td data-rw-cid-pz7tphoj="true">Unexpected server error</td></tr><tr><td data-rw-cid-pz7tphoj="true"><code data-rw-cid-pz7tphoj="">503</code></td><td data-rw-cid-pz7tphoj="true">A required service is temporarily unavailable</td></tr></tbody></table>