
## Release a stuck lock

`DELETE /v1/infra/servers/:id/lock`

Forcibly releases a stuck operation lock on the server. Use it when [`/exec`](./exec.md) or [`/deploy`](./deploy.md) was aborted from the client side (lost connection, local timeout) but the lock is still active on the server and subsequent calls return `EXEC_BUSY`. The endpoint always returns 200: if a lock was held on this replica — it is released, the response carries `released: true` and `localLock: true`; if not — `released: false` (also a success).

The release is broadcast to **all** platform replicas (`broadcast: true`), so the endpoint releases a stuck lock even when it is held on a different replica (a common case — which is exactly why `released` can be `false` while the lock really was held and released on the holder replica). `released`/`localLock` describe only the current replica and are **not proof of a fleet-wide release** — do NOT poll the endpoint in a loop until `released: true`; instead retry the operation (`/exec`/`/deploy`) or, if `EXEC_BUSY` persists, call [`/unstick`](./exec.md). The broadcast is best-effort (a no-op at N=1 or if the bus is down); a guaranteed release of a stuck `exec` lock comes from the server-side TTL auto-sweep (below).

> **Use with care.** Releasing the lock of an active operation can lead to an unpredictable server state — `/exec` and `/deploy` running at the same time may compete for files and systemd units.

## Parameters

| Parameter | In | Type | Required | Description |
|----------|---|-----|:-----:|----------|
| `id` | path | string (UUID) | yes | Server ID |

The request body is empty.

## Examples

### curl — personal key

```bash
curl -X DELETE -H "X-Api-Key: YOUR_API_KEY" \
  https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/lock
```

### curl — OAuth application

```bash
curl -X DELETE -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/lock
```

### JavaScript — personal key

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/lock`,
  {
    method: 'DELETE',
    headers: { 'X-Api-Key': 'YOUR_API_KEY' },
  }
)
const { data } = await res.json()

if (data.released) {
  console.log(`Released lock ${data.operation}, held for ${data.ageMs}ms, ${data.expiresInMs}ms left before expiry`)
} else {
  // No local lock, but the release was broadcast to other replicas (data.broadcast === true).
  console.log(data.message)
}
```

### JavaScript — OAuth application

```javascript
await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/lock`,
  {
    method: 'DELETE',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
    },
  }
)
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | Always `true` when called on an existing server |
| `data.released` | boolean | `true` — a lock was held on this replica and it was released. `false` — no local lock. **Describes only the current replica**, not the whole fleet |
| `data.localLock` | boolean | Whether the lock was held on this very replica (`true` when `released: true`) |
| `data.broadcast` | boolean | `true` — the release was broadcast to the other replicas (best-effort — requested, delivery not confirmed; a no-op at N=1 or if the bus is down) |
| `data.operation` | string | Type of the released operation: `"exec"` or `"deploy"`. Present only when `released: true` |
| `data.ageMs` | number | How many milliseconds the lock had been active. Present only when `released: true` |
| `data.expiresInMs` | number | In how many milliseconds the lock would have expired on its own. Present only when `released: true` |
| `data.message` | string | A note that there was no local lock and the release was broadcast to the replicas. Present only when `released: false` |

## Response example

A lock was held on this replica — released:

```json
{
  "success": true,
  "data": {
    "released": true,
    "localLock": true,
    "broadcast": true,
    "operation": "deploy",
    "ageMs": 320000,
    "expiresInMs": 580000
  }
}
```

No local lock (the release was broadcast to other replicas):

```json
{
  "success": true,
  "data": {
    "released": false,
    "localLock": false,
    "broadcast": true,
    "message": "No lock on this replica; a fleet-wide EXEC-lock release was broadcast to peers"
  }
}
```

## Error response example

404 — server not found:

```json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Server not found"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not provided |
| 401 | `INVALID_API_KEY` | Invalid or expired API key |
| 404 | `NOT_FOUND` | The server does not exist or belongs to another API key |
| 429 | `RATE_LIMITED` | The platform's overall request limit was exceeded |

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

## Known specifics

- **One lock per server.** Protection against races between `/exec` and `/deploy` — they share one common lock. It is impossible to lock an individual command.
- **Auto-release on normal completion.** Success or failure of `/exec`/`/deploy` releases the lock itself. `DELETE /lock` is only needed when the client aborted the connection before completion or the agent timed out without sending a final event.
- **Lock TTL — auto-expiry without a call.** `/exec` sets the lock for `timeout + 30 + 60` seconds. `/deploy` — for 15 minutes. Even without `DELETE /lock` a stuck lock will expire on its own.
- **The release is broadcast to all replicas.** The platform scales horizontally, and a stuck lock is often held on a replica other than the one serving your `DELETE /lock`. So the release is always broadcast fleet-wide (`broadcast: true`) — that is how the endpoint releases the lock on the holder replica. That is also why `released` can be `false` on a lock that really was released: the local replica did not hold it.
- **Guaranteed auto-release of a stuck `exec` lock.** Even without `DELETE /lock`, a stuck `exec` lock is guaranteed to be released by a server-side background sweep shortly after its TTL expires (on the order of a few minutes) — for the case where the handler could not release it itself. `DELETE /lock` is the fast path; the sweep is the safety net. (There is no sweep for `/deploy`: a long deploy may legitimately exceed its TTL; a stuck deploy lock is released by `DELETE /lock` or by the operation finishing.)
- **`released: false` is a success**, not an error. The endpoint does not return 404 when there is no lock, because the idea is to "guarantee there is no lock". The client does not care whether there was one or not.
- **Works in any server mode.** Unlike most Deploy API endpoints, `DELETE /lock` does not require BLACKHOLE — the lock is held in the platform's memory and does not depend on the agent.
- **The lock outlives server deletion.** A stuck lock often survives its server: if the previous server was deleted but its in-memory lock remained, the next deploy fails with `EXEC_BUSY`. `DELETE /lock` releases such a lock even on a deleted server — as long as it still belongs to your API key (ownership is the only check; the lock itself holds no data and no cloud resources).

## See also

- [Run a command](./exec.md) — the main source of `EXEC_BUSY`.
- [Full deploy](./deploy.md) — a long operation, lock for 15 minutes.
- [Tunnel metrics](./metrics.md) — `available: true` + `httpConnections: 0` shows whether real work is in progress.
