# Application secrets

Secrets store an application's tokens and passwords: the value is set on the application card, and the platform passes it to the running application as an environment variable.

A value set on the card never passes through a conversation with an AI agent and is not sent in a deploy call. If the feature is not enabled for the Bitrix24 account, the card has no "Secrets" block and the methods below return `404 APPLICATION_NOT_FOUND`.

**Authorization:** `X-Api-Key`. The methods can be called by the application owner with any of their keys, by the application's own key, and by members of the linked server's development team. Through the API and MCP you can list names, create names and delete them. Only the owner and the Bitrix24 account administrator set and replace values, on the `/applications` card. The API and MCP never accept or return a value. The dashboard does not show it after saving either: a value can only be replaced. The card's secrets history records when and by whom a name was created or a value was set, replaced or deleted, and how applying the change ended. The history contains no values.

## How secrets reach the application

The application receives secrets as environment variables of its process and reads them the same way as variables from `env`: moving a value from `env` to a secret needs no change to the application code. A name without a value is not passed to the application.

On a standalone server, the platform writes the values to `/var/lib/vibe/app-secrets/<service>.env` and attaches the file to the application's systemd service. In Galaxy, the values are added to the container's environment when the container starts, not when the image is built. Details, including `systemd: false`, are in the "Application secrets" section of [Full application deploy](/docs/infra/deploy/deploy).

### On deploy

Every deploy passes the current set of secrets to the application.

- A secret with a value takes precedence over a variable with the same name in the deploy's `env`. The application receives the secret's value, and `warnings` in the deploy response reports that the `env` value was not applied.
- A name without a value does not override `env`: if the variable is passed in `env`, the application receives it. `warnings` asks for the value to be set, with a link to the card.
- The platform sets `PORT`. The names `PORT`, `VIBE_*` and `HERMES_EVENT_INGEST_*` cannot be secrets.
- The `install` and `preStart` commands, and in Galaxy the image build, do not receive secrets: the values exist only in the running application's environment. If a migration needs a secret, run it from the application itself at startup.
- In Galaxy, a name that was previously delivered as a secret and then deleted reaches the container empty, even if the deploy's `env` passes it. This keeps the old value from coming back from an image. To pass that variable through `env` again, create the name without a value.

### Without a deploy

Saving a value on the card and deleting a secret take effect on their own, with no deploy. After "Save" or a deletion, the platform restarts the running application in the background with the new environment. On a standalone server the application's service restarts; in Galaxy the container is recreated from the current image. The application does not respond while it restarts. Creating a name without a value applies nothing.

On a standalone server, the platform records the application's service on every successful deploy, even if the application has no secrets yet. An application last deployed before the feature was enabled for the Bitrix24 account does not receive the first change: the card shows "Could not apply: the application needs to be redeployed" with a hint. Deploy once: the change applies during that deploy, and later changes apply without one.

### Sleeping machine

A change does not wake the machine. While it sleeps, the card shows "Will apply when the machine wakes up", and `status` in the API is `PENDING`. After the machine wakes up, the platform applies the current set on its own. In Galaxy, the container is recreated with the new environment on wake. A standalone server first starts the application with the old values and restarts it once the server is back in contact with the platform.

### Failure and retry

If applying fails, the card shows "Could not apply" with the reason, `status` in the API is `FAILED`, and the reason is in `failureCode`. While the machine is running, the platform retries on its own, at most once a minute. The "Retry" button on the card starts an attempt right away. It is available to the owner and the Bitrix24 account administrator. The API has no separate retry method. The exception is the reason `SECRET_SERVICE_UNKNOWN`: a retry cannot fix it without a deploy, so the platform does not retry on its own and the card has no "Retry" button.

| `failureCode` | Card label | What happened |
| --- | --- | --- |
| `SECRET_SERVICE_UNKNOWN` | the application needs to be redeployed | Standalone server: the platform does not know the application's service yet because the application has not been deployed since secrets were enabled. Deploy the application, and the change applies during the deploy. |
| `SECRET_INVALID_SERVICE` | application service is not configured | Standalone server: the stored service name is not valid. Deploy the application. |
| `SECRET_RESTART_FAILED` | application restart failed | Standalone server: the service did not start with the new environment. |
| `SECRET_CONTAINER_UNAVAILABLE` | container is unavailable | Galaxy: the platform could not prepare to recreate the container. |
| `SECRET_RECREATE_FAILED` | application restart failed | Galaxy: the new container did not start. |
| `SECRET_DELIVERY_FAILED` | delivery failed | The values could not be delivered to the machine, including when the deploy that was delivering them failed. |

### Migration and restore

Secrets belong to the application card, not to the machine. A redeploy and a migration of the application to another galaxy keep them: the application receives the current set again.

A restore from a backup takes the current values from the card, not the values as of the backup date. A restored machine receives the set again. When a container is restored into a new application, secrets are copied from the source application's card if that application still exists and both applications have the same owner and the same Bitrix24 account. Otherwise, set the secrets on the new application's card.

## Protection boundaries

The dashboard, the API and MCP never return a value. It is not in the secrets history, the audit log or the deploy step output. However, the running application's environment and the secrets file can be read by anyone who runs commands on the machine: a development team member, an AI agent calling [`POST /exec`](/docs/infra/deploy/exec), the `install` and `preStart` commands. The application's own code sees its environment variables. Secrets keep a value out of conversations and out of the API, but they do not hide it from anyone with access to the machine. Grant that access only to people allowed to know the values.

A secret does not revoke access in the external system. If a value leaks, change it in the external system, then replace the secret on the card. A deleted value can remain in older backups of the machine or the container.

## Operations

### GET /v1/applications/:id/secrets

`GET /v1/applications/:id/secrets`

Returns up to 100 names in alphabetical order and the apply state of the set. Values and ciphertext are never returned.

### POST /v1/applications/:id/secrets

`POST /v1/applications/:id/secrets`

Creates a name without a value and applies nothing. Body: `{ "name": "PAYMENT_TOKEN" }`. Success: 201. Any `value` field, including an empty one, produces 400 `SECRET_VALUE_NOT_ACCEPTED`, and `error.applicationUrl` links to `/applications?app=<id>`, where the owner sets the value. Duplicate name: 409.

### DELETE /v1/applications/:id/secrets/:name

`DELETE /v1/applications/:id/secrets/:name`

Deletes the name together with its stored value. Success: 200. The running application restarts and no longer receives this value, as after a change on the card — see "Without a deploy". A deleted value cannot be recovered: it has to be set again.

## Parameters

| Field | Description |
| --- | --- |
| `id` | Card ID from [GET /v1/applications](/docs/applications). Not an OAuth application or server ID. |
| `name` | Up to 128 characters, `^[A-Za-z_][A-Za-z0-9_]*$`. `PORT` and the prefixes `VIBE_` and `HERMES_EVENT_INGEST_` are reserved. Maximum 100 names per application. |
| `value` | Application card only: a single line of up to 16 KiB, counted in UTF-8 bytes. LF and CR line breaks and the NUL character are not allowed. Encode multiline data, such as a PEM key, as base64 first and decode it in the application: the platform does not do this. The limit applies to the encoded string. |

## Example

```bash
curl -X POST "$BASE_URL/v1/applications/$APPLICATION_ID/secrets" \
  -H "X-Api-Key: $API_KEY" -H 'Content-Type: application/json' \
  -d '{"name":"PAYMENT_TOKEN"}'
```

```javascript
const response = await fetch(`${baseUrl}/v1/applications/${applicationId}/secrets`, {
  method: 'POST',
  headers: { 'X-Api-Key': apiKey, 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'PAYMENT_TOKEN' }),
})
const result = await response.json()
```

```json
{
  "success": true,
  "data": {
    "secret": {
      "name": "PAYMENT_TOKEN",
      "hasValue": false,
      "createdAt": "2026-10-09T09:00:00.000Z",
      "updatedAt": "2026-10-09T09:00:00.000Z",
      "createdBy": "user-id",
      "updatedBy": "user-id",
      "updatedByName": "Owner"
    },
    "state": {
      "desiredRevision": 0,
      "appliedRevision": 0,
      "appliedServerId": null,
      "status": "PENDING",
      "failureCode": null,
      "deliveredNames": [],
      "serviceName": null,
      "updatedAt": "2026-10-09T09:00:00.000Z"
    }
  }
}
```

GET returns `data.secrets` and `data.state`, POST returns `data.secret` and `data.state`, DELETE returns `data.secret: null` and `data.state`. Before the first change, `state` can be `null`. Secret fields are the same in `data.secrets[]` and `data.secret`.

| Field | Description |
| --- | --- |
| `name` | Secret name. |
| `hasValue` | Whether a value is set. `false` means the name is waiting for a value. |
| `createdAt`, `createdBy` | When and by whom the name was created. |
| `updatedAt`, `updatedBy`, `updatedByName` | The last change and its author. |
| `state.desiredRevision` | Number of the current set. Increases when a value is saved and when a secret is deleted. Creating a name does not change it. |
| `state.appliedRevision` | Number of the set the application has confirmed receiving. Equals `desiredRevision` when everything is applied. |
| `state.appliedServerId` | The machine where the set is applied. |
| `state.status` | `PENDING` — waiting to be applied, including while the machine sleeps. `APPLYING` — being applied. `APPLIED` — applied. `FAILED` — failed, the reason is in `failureCode`. |
| `state.failureCode` | Failure reason when `FAILED`. The codes are in "Failure and retry". |
| `state.deliveredNames` | Names the platform has already passed to the application, including deleted ones. |
| `state.serviceName` | The application's service on a standalone server, recorded by the platform during a deploy. `null` in Galaxy and before such a deploy. |
| `state.updatedAt` | Time of the last state change. |

The MCP tool `manage_app_secrets` accepts `applicationId`, `action` — `list`, `create` or `delete` — and `name` for `create` and `delete`. It does not accept a value.

## Errors

| HTTP | Code | Meaning |
| --- | --- | --- |
| 400 | `SECRET_VALUE_NOT_ACCEPTED` | Set the value on the application card. |
| 400 | `SECRET_INVALID_NAME` | Invalid or reserved name. |
| 400 | `SECRET_INVALID_REQUEST` | Invalid request body. |
| 400 | `SECRET_LIMIT_REACHED` | The limit of 100 names has been reached. |
| 401 | `MISSING_API_KEY`, `INVALID_API_KEY` | No valid key. |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | A read-only key cannot create or delete names. |
| 403 | `EXTERNAL_COLLABORATOR_KEY_SERVER_MISMATCH` | The key is bound to a different server. |
| 403 | `EXTERNAL_COLLABORATOR_NOT_A_MEMBER` | The caller is no longer a development team member. |
| 404 | `APPLICATION_NOT_FOUND` | Application or access missing, or the feature is not enabled for the portal. |
| 404 | `SECRET_NOT_FOUND` | Name not found. |
| 409 | `SECRET_ALREADY_EXISTS` | Name already exists. |
| 413 | `SECRET_BODY_TOO_LARGE` | Request body exceeds 128 KiB. |
| 429 | `RATE_LIMIT_EXCEEDED` | Platform-wide cap of 60 requests per minute per caller within a portal, divided across replicas. `x-ratelimit-limit` gives the effective limit for the key. |
| 500 | `SECRET_OPERATION_FAILED` | The operation failed, and the underlying error is not returned. |

Full list of common API errors — [Errors](/docs/errors).

## See also

- [Application catalog](/docs/applications)
- [Full application deploy](/docs/infra/deploy/deploy)
- [MCP](/docs/mcp)
