## Upload an app icon

`POST /v1/infra/servers/:id/icon`

Uploads the icon shown in the Bitrix24 app catalog and used as the browser-tab favicon. You can replace the image without rebuilding the application.

**Scope:** `vibe:infra`.

## Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` (path) | string | yes | Server ID from [`GET /v1/infra/servers`](/docs/infra/servers/list). |

## Request body fields

Body: `multipart/form-data`. Supply one file under either field name.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `file` | file | no | Icon file. Required unless `icon` is supplied. Limits are listed under [Format](#format). |
| `icon` | file | no | Alternative field name for the same file. |

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/icon" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -F "file=@./icon.svg"
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/icon" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -F "file=@./icon.svg"
```

### JavaScript — personal key

```javascript
const form = new FormData();
form.append("file", fileInput.files[0]);

const response = await fetch("https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/icon", {
  method: "POST",
  headers: {
    "X-Api-Key": "YOUR_API_KEY",
  },
  body: form,
});
const body = await response.json();
```

### JavaScript — OAuth application

```javascript
const form = new FormData();
form.append("file", fileInput.files[0]);

const response = await fetch("https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/icon", {
  method: "POST",
  headers: {
    "X-Api-Key": "YOUR_APP_KEY",
    "Authorization": "Bearer USER_SESSION_TOKEN",
  },
  body: form,
});
const body = await response.json();
```

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Whether the request succeeded. |
| `data` | object | Upload result. |
| `data.icon` | object | Stored icon. |
| `data.icon.url` | string | Relative icon URL with a cache version query parameter. |

## Response example

```json
{
  "success": true,
  "data": {
    "icon": {
      "url": "/api/server-icons/SERVER_ID?v=1793318400000"
    }
  }
}
```

## Error response example

`404 SERVER_NOT_FOUND` — no server with this ID exists, or it is not available to this key.

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

## Errors

| HTTP | Code | Description |
|------|------|-------------|
| 400 | `ICON_NO_FILE` | The body carries no file part: it is not `multipart/form-data`, or the field is named neither `file` nor `icon` |
| 400 | `ICON_EMPTY` | The file is empty |
| 400 | `ICON_TOO_LARGE` | The file is larger than the limit for its format — the limits are in the "Format" section above |
| 400 | `ICON_TOO_MANY_PIXELS` | Too many pixels — this code is returned for both formats: for a raster image once it exceeds 4 megapixels, for an SVG once its rasterized canvas exceeds 4096×4096 |
| 400 | `ICON_UNSUPPORTED_FORMAT` | The format was not recognized from the file contents |
| 400 | `ICON_NOT_SVG` | The contents are an SVG, but the `Content-Type` does not start with `image/svg` and the file name does not end in `.svg`. The same code is returned when the markup is not a valid SVG container |
| 400 | `ICON_RASTERIZE_FAILED` | The format was recognized, but the image could not be converted to a 256×256 PNG |
| 400 | `ICON_SVG_SCRIPT`, `ICON_SVG_EVENT_HANDLER`, `ICON_SVG_EXTERNAL_REF` and the other codes of the `ICON_SVG_*` family | The SVG failed sanitization: it carries a script, an event handler, an external reference or another forbidden construct. The code names the check that fired |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not sent |
| 401 | `INVALID_API_KEY` | The key is not recognized — no such key exists on the platform |
| 403 | `INFRA_FORBIDDEN_FOR_COWORK_KEY` | The call was made with a Cowork key — such a key works with data only and cannot perform write operations. To issue a key that can, see [Project key for deploy](/docs/cowork/deploy-key) |
| 403 | `WRONG_KEY` | The server exists on your portal but is bound to a different API key. Uploading an icon updates the catalog card and requires the managing key. A key for an application bound to the server can deploy, run `exec`, upload files and read logs, but cannot upload the icon. The body carries `error.hint` with the recovery order — [Server access recovery](/docs/infra/server-access-recovery) |
| 404 | `SERVER_NOT_FOUND` | There is no server with that `id`, it was deleted, or it belongs to another portal. Being on the server's development team does not grant access to this operation — it requires the managing key regardless of your role. |
| 429 | `RATE_LIMITED` | The limit was exceeded: 10 requests per minute per API-key-and-server pair. The current limit is returned in the `x-ratelimit-limit` header |
| 502 | `ICON_STORAGE_FAILED` | The icon passed validation, but the object storage did not accept it. Retry the upload |

For the full list of common API errors, see [Errors](/docs/errors).

## Known specifics

### Format

- **SVG** (`image/svg+xml`) — **up to 256 KB**, with a rasterized canvas of **up to 4096×4096**: the platform converts the SVG to a PNG, so an oversized canvas is rejected with `ICON_TOO_MANY_PIXELS` even for a file of a few hundred bytes. No scripts, event handlers or external references: no `<script>`, `on*` attributes, `<foreignObject>`, or links to external resources. The icon is sanitized on upload; unsafe content is rejected.
- **Raster formats** — PNG, JPG, GIF, WEBP, **up to 5 MB and up to 4 megapixels** (2000×2000, for example). The format is detected from the file contents, not from its name or `Content-Type`. SVG is the one exception: the contents are read too, but they must come with a matching type marker — a `Content-Type` starting with `image/svg`, or a file name ending in `.svg` — otherwise the upload is rejected with `ICON_NOT_SVG`.
- **Whatever you upload, you get back a 256×256 PNG.** The serve URL always returns the icon as a PNG (`Content-Type: image/png`): the platform scales the image into a 256×256 square, keeping its proportions and padding the rest with transparency. If the file cannot be read, the upload is rejected with `ICON_RASTERIZE_FAILED`; if it exceeds the size limit, with `ICON_TOO_LARGE`; if it has too many pixels, with `ICON_TOO_MANY_PIXELS`; if the format is unknown, with `ICON_UNSUPPORTED_FORMAT`.

### Upload the icon — the mandatory step (no source code needed)

This is the **only mandatory step**, and it **does not require the app source code**: the icon immediately appears in the Bitrix24 app catalog and is served at the stable anonymous URL `…/api/server-icons/:id`. To re-upload or regenerate it later, repeat the same `POST`.

### Browser-tab favicon — one line in your HTML

To also make the icon the browser-tab favicon, add a single line to your app's HTML:

```html
<link rel="icon" href="/_gw/icon">
```

`/_gw/icon` is a platform path on your app's own domain (served by the platform, not by your app) that always returns the **current** uploaded icon. The line carries no server id and is identical for every app — you can bake it in at build time in a single pass (including when you create the app in a single request).

You do not need to ship your own static icon file (`/icon.svg` and the like) — a static file cannot be changed through the Vibecode dashboard. `/_gw/icon` updates on its own: re-upload the icon via `POST …/icon` and the favicon refreshes within ~5 minutes, with no rebuild.

### Changing the icon — no rebuild

The catalog icon and the browser-tab favicon are the **same image**, both driven by one upload:

- Upload a new file with the same `POST …/icon` — the Bitrix24 catalog card updates, and so does the browser-tab favicon (`/_gw/icon`, within ~5 minutes).
- No app source code is needed and no rebuild/redeploy is required — as long as the `<link rel="icon" href="/_gw/icon">` line was added at build time.
- If the `<link>` line was never added, there is no tab favicon (expected), but the Bitrix24 catalog icon still works. You can add the tab favicon at the next rebuild by including the `<link>` line (no id needed).

## See also

- [Create a server](/docs/infra/servers/create)
- [Deploy API](/docs/infra/deploy)
- [Infrastructure](/docs/infra)
