
## Задать порт приложения

`PATCH /v1/infra/servers/:id/port`

Задаёт TCP-порт, на который агент туннеля проксирует входящие HTTPS-запросы с субдомена. По умолчанию туннель идёт на `:3000` — это стандарт платформы, большинство приложений должны слушать именно его. Этот эндпоинт нужен в редких случаях: приложение по историческим причинам работает на другом порту или вы хотите `0` (автоопределение), чтобы агент сам нашёл слушающий порт. Порты 1–1023 запрещены — это системные (SSH, HTTP, HTTPS, SQL), их использование создало бы риск перенаправления трафика на служебные процессы.

## Параметры

| Параметр | В | Тип | Обяз. | Описание |
|----------|---|-----|:-----:|----------|
| `id` | path | string (UUID) | да | ID BLACKHOLE-сервера, `status: running`, `blackholeStatus: CONNECTED` |

## Поля запроса (body)

| Поле | Тип | Обяз. | Описание |
|------|-----|:-----:|----------|
| `port` | number | **да** | `0` (автоопределение) или `1024–65535`. Порты 1–1023 отклоняются с `PORT_RESTRICTED` |

## Примеры

### curl — личный ключ

```bash
# Установить порт 8080
curl -X PATCH https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/port \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port": 8080}'

# Автоматическое определение
curl -X PATCH https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/port \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"port": 0}'
```

### curl — OAuth-приложение

```bash
curl -X PATCH https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/port \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"port": 8080}'
```

### JavaScript — личный ключ

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/port`,
  {
    method: 'PATCH',
    headers: {
      'X-Api-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ port: 8080 }),
  }
)
const { data } = await res.json()
console.log(`Порт приложения: ${data.port}`)
```

### JavaScript — OAuth-приложение

```javascript
await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/port`,
  {
    method: 'PATCH',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ port: 0 }),
  }
)
```

## Поля ответа

| Поле | Тип | Описание |
|------|-----|----------|
| `success` | boolean | Всегда `true` при успехе |
| `data.port` | number | Итоговый порт. При `port: 0` вернётся `3000` (значение по умолчанию при автоопределении) |
| `data.mode` | string | `manual` (задан конкретный порт) или `auto` (`port: 0`) |
| `data.verified` | boolean | `true` — платформа подтвердила, что агент проксирует на этот порт (опросом агента, а у закреплённого сервера — чтением настроек на машине плюс опросом после перезапуска). `false` — порт сохранён, но маршрутизация ещё не подтверждена (см. `data.warning` и раздел «Известные особенности») |
| `data.warning` | string | Присутствует только при `verified: false`. Три состояния с разными действиями: агент остался в режиме автоопределения (закрепление не применилось — работает сканер), агент порт принял, а подтверждение не пришло (повторите запрос), либо порт записан в настройки закреплённого сервера, но агент ещё не подтвердил перезапуск. В первых двух случаях `/repair` не помогает и вредит, в третьем — наоборот, применит сохранённый порт. Текст предупреждения говорит, что делать |
| `data.pinned` | boolean | Закреплён ли порт в настройках агента на машине. `true` — переживёт пробуждение и ремонт, `false` — живёт только в памяти агента до ближайшего перезапуска |

## Пример ответа

```json
{
  "success": true,
  "data": { "port": 8080, "mode": "manual", "verified": true, "pinned": true }
}
```

## Пример ответа при ошибке

409 — агент отклонил смену порта:

```json
{
  "success": false,
  "error": {
    "code": "PORT_NOT_APPLIED",
    "message": "The server agent runs in fixed-port mode and cannot change its port at runtime. Repair the server (POST /v1/infra/servers/:id/repair) to switch the agent to port auto-detection, then retry.",
    "agentError": "NO_SCANNER"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|----------|
| 400 | `VALIDATION_ERROR` | `port` не целое число (дробные значения отклоняются) или вне диапазона 0–65535 |
| 400 | `PORT_RESTRICTED` | Порт в диапазоне 1–1023 (системные порты) |
| 400 | `GALAXY_APP_USE_GALAXY_ROUTE` | Сервер — galaxy-приложение. Порт его контейнера закреплён за хостом и задаётся полем `port` при [загрузке кода](./deploy.md) — [Galaxy-приложение](/docs/infra/galaxy) |
| 401 | `MISSING_API_KEY` | Не передан заголовок `X-Api-Key` |
| 401 | `INVALID_API_KEY` | Неверный или просроченный API-ключ |
| 403 | `INFRA_FORBIDDEN_FOR_COWORK_KEY` | Вызов сделан ключом Cowork/Code — такой ключ работает только с данными, изменяющие операции ему закрыты. Что делать — [Проектный ключ для деплоя](/docs/cowork/deploy-key) |
| 403 | `SERVER_ROLE_FORBIDDEN` | Вы состоите в команде разработки этого сервера с ролью «Разработчик», а операция открыта роли «Администратор». В `error.hint` придут ваша роль, требуемый порог и перечень открытых вам вызовов. Разбор ролей — [Список серверов](/docs/infra/servers/list) |
| 404 | `NOT_FOUND` | Сервер не `BLACKHOLE + RUNNING`, удалён или привязан к другому API-ключу, и вы не состоите в его команде разработки |
| 409 | `SERVER_NOT_READY` | Агент туннеля не в статусе `CONNECTED` |
| 409 | `PORT_NOT_APPLIED` | Агент отклонил смену порта. `agentError: NO_SCANNER` — сервер в режиме фиксированного порта (см. ниже). Другой `agentError` — агент устарел. В обоих случаях помогает [`/repair`](/docs/infra/lifecycle/repair). На закреплённом сервере тот же код приходит без `agentError`, если запрошенный порт на машине никто не слушает: закрепить его значит оставить публичный адрес без ответа, поэтому запись отклоняется — сообщение перечисляет порты, которые слушают на самой машине |
| 409 | `SERVER_BUSY` | На сервере уже идёт выкладка, выполнение команды или другая смена порта. Повторите через несколько секунд |
| 429 | `RATE_LIMITED` | Превышен лимит: 10 запросов в минуту на этот эндпоинт |
| 502 | `AGENT_CONFIG_WRITE_FAILED` | Только для закреплённого сервера: машина отклонила запись настроек агента. Помогает [`/repair`](/docs/infra/lifecycle/repair) — ремонт переустановит агента вместе с настройками |
| 502 | `GATEWAY_ERROR` | Gateway не смог доставить команду агенту |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

- **Системные порты (1–1023) запрещены не случайно.** SSH (22), HTTP (80), HTTPS (443), DNS (53), PostgreSQL (5432), MySQL (3306) — открытие туннеля на них создало бы риск перенаправления внешнего трафика на служебные процессы внутри виртуальной машины.
- **Как работает `port: 0`.** Агент читает список слушающих сокетов машины из `/proc/net/tcp` и `/proc/net/tcp6`, проверяет каждый порт HTTP-запросом и направляет туннель на тот, который ответил. Из ответивших берётся наименьший номер, кроме `80` и `443` — эти два выбираются, только если больше не ответил никто. Ограничение на системные порты `1–1023` относится к явному заданию порта в этом вызове, автоопределение оно не затрагивает. Пять портов в автоопределении не участвуют: `22`, `2375`, `2376`, `4243` и `9200`. На них отвечают служебные интерфейсы машины, и публикация такого интерфейса на субдомене открыла бы к нему доступ из интернета. Фильтр действует только на автоопределение — явно заданный порт агент принимает любой из разрешённого диапазона, поэтому служебные интерфейсы оставляйте вне туннеля. Автоопределение подходит приложениям, которые выбирают порт динамически при запуске.
- **В автоопределении участвуют и процессы, слушающие только `127.0.0.1`.** Для приложения Black Hole такой запуск штатный: агент обращается к приложению по локальному интерфейсу, а снаружи машина закрыта. Отсюда следствие — служебный процесс, поднятый на машине только для локальных обращений, при автоопределении неотличим от приложения и может получить туннель. Когда рядом с приложением на машине работают другие процессы, отвечающие по HTTP, задайте порт приложения явно этим вызовом. Тогда выбор не зависит от того, кто ещё слушает.
- **Автоопределение удерживает прежний порт, пока на нём кто-то отвечает.** Сканер не переключается на новый порт по первому же наблюдению: пока на прежнем порту живёт отвечающий процесс, цель остаётся за ним — так короткая заминка приложения не роняет туннель на заглушку. Практическое следствие: если приложение переехало на другой порт, а на старом остался живой процесс, автоопределение само не переключится. Выход — задать порт явно этим вызовом либо остановить процесс на прежнем порту. Если прежний порт просто освободился, переключение происходит само в течение примерно минуты. Удержание не распространяется на `80` и `443`: когда цель стоит на одном из них, автоопределение переключается сразу, как только ответит прикладной порт.
- **Закреплённый порт переживает пробуждение и ремонт.** Закрепление хранится на самой машине — строкой `local_http` в настройках агента `/etc/vibe-agent/config.yml`. Значение `auto` означает автоопределение, значение вида `127.0.0.1:8080` — фиксированный порт. Строку пишет платформа. У закреплённого сервера её переписывает этот вызов. Незакреплённый сервер становится закреплённым, когда порт подтверждён, — успешной выкладкой, прошедшей проверку работоспособности, либо подтверждённой сменой порта этим вызовом, если закрепление включено для портала. Закреплённый сервер отвечает `portPinned: true` в [`GET /v1/infra/servers/:id`](/docs/infra/servers/get), и после закрепления пробуждение сервера и [`/repair`](/docs/infra/lifecycle/repair) возвращают агента на тот же порт. У НЕзакреплённого обычного сервера ремонт ставит автоопределение, и порт выбирается заново. Агентам, ботам и галактик-хостам ремонт пишет фиксированный порт из базы.
- **`port: 0` в ответе возвращается как `3000`.** Это не ошибка: «при автоопределении возвращаем 3000, если ничего подходящего не нашли». Реальный слушающий порт проверяйте через [`/exec`](./exec.md) с командой `ss -tlnp`.
- **Платформа подтверждает смену порта (`data.verified`).** У НЕзакреплённого сервера после `set_port` платформа опрашивает агента и проверяет, что туннель действительно проксирует на запрошенный порт. `verified: true` — маршрутизация готова. `verified: false` + `data.warning` — агент команду принял, но подтверждение не пришло. Повторите запрос через несколько секунд. **`/repair` в этом случае не нужен и вредит:** порт, заданный командой, живёт в памяти агента, поэтому перезапуск при ремонте его сбросит и вернёт автоопределение. `409 PORT_NOT_APPLIED` — агент в принципе не принял смену порта: при `agentError: NO_SCANNER` сервер работает в режиме фиксированного порта (запустите приложение на текущем порту, либо `/repair` для перехода в автоопределение), при ином `agentError` — агент устарел (`/repair` обновит его).
- **У ЗАКРЕПЛЁННОГО сервера этот вызов работает иначе — но не сразу.** Порт применяется перезаписью настроек агента и его перезапуском, `verified: true` означает, что настройки несут запрошенный порт и агент вернулся на связь. Туннель при этом на несколько секунд обрывается. `port: 0` снимает закрепление и возвращает агента к автоопределению. Если агент не успел вернуться, ответ несёт `verified: false` — это не отказ: порт уже записан и применится, когда агент поднимется. Отдельные отказы: `502 AGENT_CONFIG_WRITE_FAILED` — машина отклонила запись настроек (нужен [`/repair`](/docs/infra/lifecycle/repair)), `502 GATEWAY_ERROR` — команда не дошла до машины (повторите позже), `409 PORT_NOT_APPLIED` без `agentError` — запрошенный порт никто не слушает.
- **Описанное выше наступает после первого перезапуска агента, а не в момент появления `portPinned: true`.** Путь выбирается по состоянию МАШИНЫ, а не по полю ответа: пока агент не перезапускался, он ещё держит сканер, и вызов ведёт себя как на незакреплённом сервере — туннель не обрывается, а `409 PORT_NOT_APPLIED` с `agentError` приходить может. Перезапуск случается при пробуждении сервера, выкладке или ремонте. Поэтому обработчик, написанный по признаку `portPinned`, обязан быть готов к обоим режимам.

## Смотрите также

- [Полный деплой](./deploy.md)
- [Метрики туннеля](./metrics.md)
- [Выполнить команду](./exec.md)
