# Application catalog

This section provides the foundation for a "my applications" screen in your product: a single request returns both a person's own applications and other applications they can access. The catalog itself has two read operations: listing applications and retrieving a single application card. Separately, the owner can replace an application's server: the two methods for that are described under "Operations".

There is no other way to assemble that list: [`GET /v1/infra/servers`](/docs/infra/servers/list) is scoped to the calling key, so an application created in the Vibecode dashboard is not visible there. Every row also carries the state you would otherwise have to fetch separately: the server, the saved source versions, whether an operation is in flight, and a ready-made address for opening the application. The list and card operations are read-only: they do not create, edit or delete applications.

**Base URL:** `https://vibecode.bitrix24.com/v1` | **Authorization:** `X-Api-Key` | **Key:** an API key or an authorization key

## Documentation sections

- [Application secrets](/docs/applications/app-secrets) — secret names through the API, values in the application card
- [Application external API](/docs/applications/external-api) — requests from an external system or another application to the application's HTTP surface, authorized with the external API key
- [A long-running method in a business process](/docs/applications/long-method) — recipe: the application accepts a task from a business process action, answers at once and delivers the result later
- [Deliver the result of a long-running method](/docs/applications/solution-call-result) — delivering the outcome of a solution's long-running method to the Bitrix24 account
- [Send a solution event](/docs/applications/solution-event) — starting business processes on the Bitrix24 account from a solution event

[What the selection contains](#what-the-selection-contains) | [What a card carries](#what-a-card-carries) | [Identifiers](#identifiers-what-matches-what) | [Ordering and pagination](#ordering-and-pagination) | [Rate limiting](#rate-limiting) | [Quick start](#quick-start) | [Full example](#full-example) | [Endpoint reference](#endpoint-reference) | [Error codes](#error-codes)

## What the selection contains

The selection is scoped to the **key owner**, not to the key itself. That is the main difference from [Servers](/docs/infra/servers): there the output is limited to the servers of the current key, so an application created in the Vibecode dashboard is not visible there — its server is bound to another key belonging to the same person. That application does appear here, and issuing a new key does not reset the list.

This section accepts a key bound to a Bitrix24 account: either an API key `vibe_api_…` or an authorization key `vibe_app_…`. It does not require a session token. With an authorization key, the selection belongs to the key owner — whoever issued the key — not the employee on whose behalf the application acts. The section requires no dedicated scope. A key that is not bound to a Bitrix24 account is rejected before reaching this section with `401 NO_PORTAL`; a management key gets `403 MANAGEMENT_KEY_NO_ENTITY_ACCESS`. See "Error codes" below for a full breakdown.

The `scope` parameter determines what is returned:

| `scope` | What it covers |
|---------|----------------|
| `feed` | The combined list of your own applications and all other applications you can access. This is the default value |
| `mine` | Your own applications only |
| `shared` | Other applications that you can access. Besides applications shared with you personally, this includes applications without a server that are open to the whole Bitrix24 account |

Deleted applications are never returned in any `scope`.

The `feed` selection has an upper limit because its order is computed by the platform rather than the database. Therefore, `total` is the size of the selection, not the number of applications on the Bitrix24 account, and the list envelope indicates this with `truncated`. The `mine` and `shared` selections have no upper limit.

The `viewerState` field describes the viewer's relationship to each row: `owner` — the application is yours, `shared` — access was granted to you personally or through the server, `usable` — the application has no server and is open to the whole Bitrix24 account, `requestable` — no access. When an application has a server, its access is determined by the server policy, so an application open to everyone is returned as `shared` rather than `usable`. The catalog returns no separate "may I manage this" flag — it is read-only. `viewerState` itself distinguishes your own applications from other people's.

## What a card carries

A list row and a single application card have the same shape, so a client does not need to know about two different objects. Beyond the name, description and icon, that shape carries the opening address and three blocks of state.

**`openUrl` and `openTarget`** together answer the question "where do I open this application?" If the application has a server with an address, `openUrl` returns that address and `openTarget` is `app`. In every other case, both fields are `null`; this is an expected result, not an error.

Both fields are also `null` for an application registered in one of the [placements](/docs/apps/placements) on the Bitrix24 account, meaning that it opens inside the Bitrix24 interface. The platform does not yet know the URL for opening such an application and returns `null`. Do not substitute `server.url` yourself; the [Application card](./applications/get.md) explains why.

**`isEmbedded`** distinguishes those two cases. Both opening fields are `null` for an embedded application and for an application that has not yet been published, but users need different messages: "opens inside Bitrix24" versus "nowhere to open it yet". Do NOT infer embedding from the presence of a server: an embedded application with no server of its own is a valid state — it was embedded before the code was deployed — and the flag does not depend on the server. The field is returned to everyone who can see the card, not only the owner.

**`server`** is a summary of the application server: ID, state, address, resource type, and the `reachable` flag. It is `null` when there is no server or the server has been deleted. The `reachable` field does not answer the same question as `status`: `status` says the container is up, while `reachable` says it also responds over the network. The values can differ in practice: a container can be up even if its tunnel has never come up, and the application appears healthy based on `status` alone.

The flag **already implies** `status === "RUNNING"`: it is never true in any other state, so clients do not need to combine it with `status`.

**For an application on a shared galaxy host (`server.kind: "GALAXY_APP"`), the second half of the flag is determined by the HOST, not the container itself.** The host provides connectivity; by design, the container has no tunnel of its own. Therefore, `true` means three things at once: the container is running, the host is running, and the host's tunnel is up. A newly created container that has not yet reached `RUNNING` (it does so only after the first source upload) is returned with `reachable: false` even when the host is healthy. This means "the container is not up yet", not "the host is unreachable"; this flag alone cannot distinguish the two cases, so check `status`.

**`iconUrl`** is a relative path without a hostname, for example `/api/app-icons/7f3…?v=1755500000000`, or `null` when the card has no icon. The icon endpoint is anonymous: the URL requires no key, has no signature, and does not expire. Join the path to your API base URL and use it directly in `<img src>`; there is no need to proxy it through your own server. The endpoint returns a 256×256 PNG with `Cache-Control: public, max-age=300, must-revalidate` and an ETag, so card caching keeps working: after five minutes, the client revalidates the icon with an inexpensive `304` response. The client must provide its own placeholder for `null`; the platform does not generate one.

**`sources`** is a summary of the saved source versions. The `latestVersionId` field uses the `v<N>` format accepted by [`GET /v1/infra/servers/:id/sources/:versionId/download`](/docs/source-storage). It is not the record's primary key: use it in the download URL to get the latest version without a separate request for the version list.

**`activeOperation`** describes the operation in flight on the server: its kind, step, and start time. **`null` means "there is no operation with a stored record", not "nothing is happening to this application"**. The field tracks deployment, repair, server plan changes, and moves between galaxies. The platform does not log other actions, so they do not appear in this field. A third state is `status: "unknown"`: the operation started, but its outcome is unknown, and there is no need to wait for it to finish.

Only users who can manage the application and members of its server development team receive data in those last two blocks. A viewer with whom the application was merely shared gets an empty `sources` summary (`hasVersions: false`, both fields `null`) and `activeOperation: null`. The response shape does not change, so an empty summary alone does not mean "there are no versions". The three possible causes look identical from the outside: the application has no server, no versions exist, or the data is hidden because you have no access to it: you neither manage the application nor belong to its server development team.

## Identifiers: what matches what

The section returns two identifiers, and both lead into other API sections — just not where the field name suggests.

**`server.id` is the identifier used by the [Servers](/docs/infra/servers) endpoints.** Use it directly in `GET /v1/infra/servers/:id` and in the source download path. One caveat: the format matches, but the access scope does not. That endpoint is scoped to the calling key's own servers, so a server returned by this section (which selects by key owner, not by key) may return `404` there.

**The application `id` and the `id` from [`GET /v1/apps`](/docs/apps) identify DIFFERENT entities.** The former identifies an application card; the latter identifies an OAuth application registration on the Bitrix24 account, and even their formats differ. The platform links them internally, but this section deliberately does not expose that link. As a result, using an `id` from this section in a `/v1/apps/...` path returns `404`, not somebody else's data.

**Same concept, different names.** The application name is returned here as `name`, while in `GET /v1/apps` the registration title is called `title`. These are independent fields on two entities: they are not synchronized and may differ, so do not copy one into the other.

**Sources use two paths, and `:appId` in the first identifies the OAuth application.** Use `POST /v1/apps/:appId/sources` to save a version, where `:appId` identifies the OAuth application — NOT the application in this section or the server. Reading and downloading versions use the server ID: pass `server.id` from this section together with `sources.latestVersionId` to `GET /v1/infra/servers/:id/sources/:versionId/download`. Both paths are current and serve different purposes; neither replaces the other.

## Ordering and pagination

The complete ordering is specified here because it determines whether paginating through the list returns every application exactly once.

| `scope` | Order |
|---------|-------|
| `feed` | Applications you pinned → your own applications → other users' applications. Within each group, `updatedAt` newest first, with ties broken by `id` |
| `mine`, `shared` | `createdAt` newest first, with ties broken by `id` |

**`updatedAt` changes only when the card itself is edited; a deployment does not update it.** Therefore, "freshness" in the feed means "when the card was last changed", not "when the application was last deployed": an application deployed a minute ago stays exactly where it was. To determine how recent the code is, read `sources.latestSavedAt`.

The trailing `id` key is not a formality. Applications created in a batch have identical timestamps, and without a secondary key two pages from the same traversal could overlap or skip an application: the database does not guarantee order within a group of equal timestamps. The secondary key makes pagination deterministic.

## Rate limiting

| Operation | Platform-wide limit |
|-----------|---------------------|
| [`GET /v1/applications`](/docs/applications/list) | 60 requests per minute |
| [`GET /v1/applications/:id`](/docs/applications/get) | 120 requests per minute |

The effective limit for your key is returned in the `x-ratelimit-limit` header.

**The counter is shared by all keys belonging to the same owner within a Bitrix24 account — it is not per IP address or per individual key.** This matters in both directions: several keys belonging to one person share a single counter (issuing a new key does not reset the limit), while employees of the same Bitrix24 account use separate counters even when all their requests reach the internet through a single address. The list and card operations also use separate counters.

Exceeding the rate limit returns `429` with the code `RATE_LIMITED` and a `Retry-After` header containing the whole number of seconds until the window closes.

**`429` does not always indicate rate limiting, so branch on `error.code` rather than the status.** Exhausting the key's daily free-call quota returns the same status with the code `QUOTA_EXCEEDED`, and waiting will not help — a paid plan is required. A client that presents every `429` as "too fast, please wait" will wait indefinitely when the quota is exhausted; one that presents every `429` as "quota exhausted" will mislead users during an ordinary rate spike.

## Quick start

Your own applications in a single request:

```bash
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://vibecode.bitrix24.com/v1/applications?scope=mine&limit=20"
```

```json
{
  "success": true,
  "data": [
    {
      "id": "cmsw806qi0000tdskiw2aegii",
      "name": "Deal report",
      "type": "PERSONAL",
      "viewerState": "owner",
      "pinned": false,
      "author": { "name": "Application author" },
      "isEmbedded": false,
      "openUrl": "https://app-05b67cf7.vibecode.bitrix24.com",
      "openTarget": "app",
      "server": {
        "id": "5cbb50f9-f95f-4ddf-ba3b-b771209cb6fb",
        "status": "RUNNING",
        "url": "https://app-05b67cf7.vibecode.bitrix24.com",
        "kind": "STANDALONE",
        "reachable": true,
        "lastDeployedAt": "2026-08-16T19:50:41.302Z"
      },
      "sources": { "hasVersions": true, "latestVersionId": "v2", "latestSavedAt": "2026-08-16T19:41:18.818Z" },
      "activeOperation": { "kind": "deploy", "status": "running", "step": "build", "startedAt": "2026-08-16T19:50:33.822Z" }
    }
  ],
  "total": 2,
  "page": 1,
  "limit": 20,
  "truncated": false
}
```

The example shows one application and its main fields. See [Application card](./applications/get.md) for a full breakdown.

## Full example

The script collects all your own applications page by page, shows the ones with an operation in flight, and takes the address of the latest source version from one of them.

```javascript
const BASE = 'https://vibecode.bitrix24.com/v1'
const API_KEY = process.env.VIBE_API_KEY

async function api(path) {
  const res = await fetch(`${BASE}${path}`, { headers: { 'X-Api-Key': API_KEY } })
  const body = await res.json()
  if (!res.ok) throw new Error(`${path}: ${body.error.code} — ${body.error.message}`)
  return body
}

// 1. Your own catalog: walk the pages until every application is collected.
const applications = []
let page = 1
let total = 0
do {
  const chunk = await api(`/applications?scope=mine&page=${page}&limit=100`)
  applications.push(...chunk.data)
  total = chunk.total
  page += 1
} while (applications.length < total && page <= 100)

console.log(`Applications: ${applications.length} of ${total}`)

// 2. Applications with an operation in flight right now.
for (const app of applications.filter(a => a.activeOperation !== null)) {
  const { kind, status, step, startedAt } = app.activeOperation
  console.log(`${app.name}: ${kind} — ${status}, step ${step ?? 'not reported'}, started ${startedAt}`)
}

// 3. One application card: where to open it and the address of its latest source version.
const target = applications.find(a => a.server !== null && a.sources.hasVersions)
if (target) {
  const { data: card } = await api(`/applications/${target.id}`)
  console.log(`${card.name}: open at ${card.openUrl ?? 'nowhere'} (${card.openTarget ?? 'no address'})`)
  console.log(`Server ${card.server.status}, answers over the network: ${card.server.reachable}`)
  console.log(`Version ${card.sources.latestVersionId} saved ${card.sources.latestSavedAt}`)
  console.log(`Download: ${BASE}/infra/servers/${card.server.id}/sources/${card.sources.latestVersionId}/download`)
}
```

Output for the Bitrix24 account used in the example:

```
Applications: 2 of 2
Deal report: deploy — running, step build, started 2026-08-16T19:50:33.822Z
Deal report: open at https://app-05b67cf7.vibecode.bitrix24.com (app)
Server RUNNING, answers over the network: true
Version v2 saved 2026-08-16T19:41:18.818Z
Download: https://vibecode.bitrix24.com/v1/infra/servers/5cbb50f9-f95f-4ddf-ba3b-b771209cb6fb/sources/v2/download
```

## Endpoint reference

| Method | Path | Description |
|--------|------|-------------|
| GET | [/v1/applications](/docs/applications/list) | Applications of the key owner: their own, shared by colleagues, or everything together |
| GET | [/v1/applications/:id](/docs/applications/get) | One application: server, saved sources, operation in flight |
| GET | [/v1/applications/:id/available-servers](/docs/applications/available-servers) | Which of your own servers the application can move to, and whether a new one can be created |
| POST | [/v1/applications/:id/replace-server](/docs/applications/replace-server) | Move the application to your own existing server or a new one |
| GET | [/v1/applications/:id/secrets](/docs/applications/app-secrets/list) | Application secret names and the state of applying them |
| POST | [/v1/applications/:id/secrets](/docs/applications/app-secrets/create) | Create a secret name without a value |
| DELETE | [/v1/applications/:id/secrets/:name](/docs/applications/app-secrets/delete) | Delete a secret name together with its value |
| GET, POST, PUT, PATCH, DELETE, HEAD | [/v1/applications/:applicationId/api/*](/docs/applications/external-api) | A request to the application's external API |
| POST | [/v1/solution-calls/:callId/result](/docs/applications/solution-call-result) | Deliver the result of a long-running method |
| POST | [/v1/solution-events](/docs/applications/solution-event) | Send a solution event |

## Error codes

| HTTP | Code | Description |
|------|------|-------------|
| 400 | `INVALID_SCOPE` | The `scope` value is not one of `mine`, `shared`, `feed` |
| 401 | `NO_PORTAL` | The key is not bound to a portal. This is the response for "a key with no portal" — such a request never reaches the handler |
| 403 | `MANAGEMENT_KEY_NO_ENTITY_ACCESS` | A management key cannot access entity sections; use a personal key or an authorization key |
| 403 | `PORTAL_KEY_REQUIRED` | The section's own safety net. It is returned only for a damaged key row with no owner; an ordinary "key with no portal" returns `401 NO_PORTAL` above |
| 403 | `FORBIDDEN` | The application exists, but the viewer has no access to it |
| 404 | `APPLICATION_NOT_FOUND` | No application with that `id` on the key's portal |
| 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 |
| 429 | `RATE_LIMITED` | The platform-wide limits are 60 requests per minute for the list and 120 for the card. The effective limit for your key is returned in the `x-ratelimit-limit` header. A `Retry-After` header is present — retrying after the specified delay is expected |
| 429 | `QUOTA_EXCEEDED` | The key's daily free-call quota is exhausted. It uses the same status, but waiting will not help — this is not about rate limiting |

See [Errors](/docs/errors) for the full list of common API errors.

## See also

- [Servers](/docs/infra/servers)
- [Source storage](/docs/source-storage)
- [Keys and authorization](/docs/keys-auth)
- [Limits and optimization](/docs/optimization)
- [Errors](/docs/errors)

## Operations

- [List applications](./applications/list.md) — `GET /v1/applications`
- [Application card](./applications/get.md) — `GET /v1/applications/:id`
- [Servers for moving an application](./applications/available-servers.md) — `GET /v1/applications/:id/available-servers`
- [Replace an application server](./applications/replace-server.md) — `POST /v1/applications/:id/replace-server`
