For AI agents: markdown of this page — /docs-content-en/applications.md documentation index — /llms.txt

Application catalog

The section gives you the basis for a "my applications" screen inside your own product: one request returns both the person's own applications and other people's applications that this person can access. It has two operations: a list and a card for a single application.

There is no other way to assemble that list: GET /v1/infra/servers 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 section is read-only — creating, editing and deleting applications are not part of it.

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

What the selection contains | What a card carries | Identifiers | Ordering and walking pages | Rate limiting | Quick start | Full example | Endpoint reference | 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: 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 of the same person. Here such an application is present, and issuing a new key does not reset the list.

The section works with a key bound to a Bitrix24 account: either an API key vibe_api_… or an authorization key vibe_app_… will do. This section needs no session token. Under an authorization key the selection belongs to the key owner — whoever issued the key — and not to the employee on whose behalf the application acts. The section requires no dedicated scope. A key that is not bound to a Bitrix24 account never reaches this section — the key check refuses it with 401 NO_PORTAL; a management key gets 403 MANAGEMENT_KEY_NO_ENTITY_ACCESS. The full breakdown is in "Error codes" below.

What exactly comes back is set by the scope parameter:

scope What it covers
feed The combined list: your own applications and all other people's applications you can access. The default value
mine Your own applications only
shared Other people's applications that you can access. Besides the ones shared with you personally, this also covers applications without a server that are open to the whole Bitrix24 account

Deleted applications are never returned in any scope.

On feed the selection is capped from above, because the order for it is computed by the platform rather than by the database. So total there is the size of the selection, not the number of applications on the Bitrix24 account, and the list envelope says so through truncated. On mine and shared there is no ceiling.

The viewer's relation to each row arrives in the viewerState field: 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 decided by the server policy, so an application open to everyone arrives as shared rather than usable. The section returns no separate "may I manage this" flag — it only reads. viewerState itself is what 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 — one answer to "where do I open this application". There is a server with an address — openUrl returns that address and openTarget is app. In every other case both fields arrive null, and an empty slot here is the normal answer rather than a failure.

Both fields are also null for an application registered at one of the Bitrix24 account's placements, that is, one that opens inside the Bitrix24 interface: the platform does not yet know the address such an application is opened at, and it will not invent one in place of an honest null. Substituting server.url there yourself is not an option either — the Application card explains why.

isEmbedded is the flag that tells those two cases apart. An empty open pair arrives both for an embedded application and for one that has not been published yet, and a person must be shown different things: "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 an ordinary state — it was embedded before the code was ever deployed — and the flag does not depend on the server. The field is disclosed to everyone who can see the card, not only to the owner.

server — a summary of the application server: id, state, address, resource type and the reachable flag. null when there is no server or it was deleted. The reachable field does not answer the same question as status: status says the container is up, reachable says it also answers over the network. They diverge in practice, not in theory — a container can be up while the tunnel to it never came up at all, and by status alone the application looks fine.

The flag already implies status === "RUNNING": it never arrives true for any other state, so there is nothing to conjoin it with on the client.

⚠️ For an application on a shared galaxy host (server.kind: "GALAXY_APP") the second half of the flag is taken from the HOST, not from the container itself. The connectivity there is held by the host; the container has none of its own by design. So true means three things at once: the container runs, the host runs, and the host's tunnel is up. Consequence worth knowing: a just-created container that has not reached RUNNING yet (it only does so after its first source upload) arrives with reachable: false even on a perfectly healthy host. That is "the container is not up yet", not "the host is unreachable", and this flag alone does not tell the two apart — read status for that.

iconUrl — a host-less relative path, e.g. /api/app-icons/7f3…?v=1755500000000, or null when the card holds no icon pointer. The serve endpoint is anonymous: the link needs no key, carries no signature and never expires, so it is enough to join the path with your API base URL and drop it straight into <img src> — no need to proxy it through a server of your own. A 256×256 PNG is returned with Cache-Control: public, max-age=300, must-revalidate and an ETag, so card caching is not broken: after five minutes the client gets a cheap 304. The placeholder for null is the client's to draw — the platform does not invent an icon.

sources — a summary of the saved source versions. The latestVersionId field arrives in the v<N> shape — exactly the shape GET /v1/infra/servers/:id/sources/:versionId/download accepts. It is not the record's primary key: put it into the download address and you get the latest version, with no separate call for the version list.

activeOperation — the operation in flight on the server: kind, step and start time. And here is the caveat that saves hours of debugging: null means "there is no operation with a stored record", not "nothing is happening to this application". The field is reliable for deployment, repair, a server plan change and moving a container between galaxies — the platform does not log anything else, so no other action appears in this field. The third state is status: "unknown": the operation did start, but its outcome is unknown, so waiting for it to finish is pointless.

Both of those last blocks are filled in only for whoever manages the application. A viewer the application was merely shared with gets an empty sources (hasVersions: false, both fields null) and activeOperation: null. The response shape does not change, so an empty summary on its own does not mean "there are no versions". It has three causes, and from the outside they look identical: the application has no server, there really are no versions, or the data is not disclosed to you because the application is someone else's.

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 same identifier the Servers paths take. Put it straight into GET /v1/infra/servers/:id and into the source-download path. One caveat: the format matches, the access right does not — that endpoint is scoped to the calling key's own servers, so a server this section showed you (it selects by key OWNER, not by key) may answer 404 there.

The application id and the id from GET /v1/apps are DIFFERENT values of different entities. Here it is an application card; there it is the registration of an OAuth application on the Bitrix24 account, and even their formats differ. A bridge between them exists inside the platform, but this section deliberately does not expose it. Practical consequence: putting an id from this section into /v1/apps/... paths yields a 404, not somebody else's data.

⚠️ Two names for one meaning. The application name arrives here as name, while in GET /v1/apps the registration heading is called title. Those are two independent fields of two entities: they are not synchronized and may diverge, so do not carry one over into the other.

⚠️ Sources: two paths, and :appId in the first is not what it looks like. A version is written with POST /v1/apps/:appId/sources, where :appId is the OAuth application's identifier — NOT the application from this section and not the server. Reading and downloading a version go by server: GET /v1/infra/servers/:id/sources/:versionId/download, and that is where server.id from here goes, together with sources.latestVersionId. Both paths are live; neither replaces the other.

Ordering and walking pages

The order is stated here in full, because whether you can walk the list page by page and get every application exactly once depends on it.

scope Order
feed Pinned by you → your own → other people's. Within a group, by updatedAt newest first, ties broken by id
mine, shared By createdAt newest first, ties broken by id

⚠️ updatedAt only moves when the card itself is edited — a deployment does not touch it. So "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. When you need the freshness of the code, read sources.latestSavedAt.

The trailing id key is not a formality. Applications created in a batch carry identical timestamps, and without a secondary key two pages of one walk could overlap or skip an application: the database promises nothing about the order within a group of equal timestamps. With it, the walk is reproducible.

Rate limiting

Operation Fleet-wide limit One replica's share today
GET /v1/applications 60 requests per minute 20
GET /v1/applications/:id 120 requests per minute 40

⚠️ The left-hand numbers are the platform-wide limit, not the one your client will see. Requests are served by several backend replicas; each holds its own share of the limit and its own counter, independent of its neighbours: there are three replicas today, so a single replica passes 20 list requests per minute and 40 card requests. The full 60 and 120 are reached only when requests spread evenly across replicas; a client whose connection sticks to one replica will hit that replica's share sooner.

So plan from the header, not from the table: X-RateLimit-Limit carries the actual limit of the replica that served you, X-RateLimit-Reset the number of seconds until the window closes. The replica count may change without an entry in the changelog; the header never lies.

The counter is per key OWNER within a portal — not per IP address and not per individual key. That matters in both directions: several keys belonging to one person share a single counter (issuing a new key does not reset the limit), and employees of one portal never get in each other's way — even when all their requests leave for the internet through a single address. The list and the card keep separate counters.

Exceeding the rate arrives as 429 with the code RATE_LIMITED and a Retry-After header (whole seconds until the window closes).

⚠️ 429 is not always about rate, so branch on error.code rather than on the status. The key's daily free-call quota answers with the same status but the code QUOTA_EXCEEDED, and waiting does not cure it — a paid plan does. A client that shows every 429 as "too fast, please wait" will wait forever on an exhausted quota; a client that shows every 429 as "quota exhausted" will frighten a person during an ordinary rate spike.

Quick start

Your own applications in a single request:

Terminal
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. The full breakdown — Application card.

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 — unknown, 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

List applications

GET /v1/applications

Returns the applications of the Bitrix24 account that are visible to the key owner — their own, the ones colleagues shared, or everything together. The selection is paged, and a row carries the same field set as an application card.

Parameters

Parameter Type Req. Default Description
scope (query) string no feed Which applications to return: mine — your own, shared — other people's applications you can access, feed — both together. The values are case-sensitive, and an empty scope= answers 400 INVALID_SCOPE just like an unknown one: either omit the parameter or send one of the three values
page (query) number no 1 Page number, counting from one. A value below one and a non-numeric value are both read as 1
limit (query) number no 50 How many applications per page, at most 100. A larger value is clamped to 100; 0 and a non-numeric value fall back to 50; a negative value becomes 1

Paging is driven by total, page and limit from the response: request pages one after another until the number of rows collected reaches total. A page past the end of the selection answers 200 with an empty data array rather than an error.

Counting pages as total / limit is valid only when truncated is false. On true, total stops being a full count, there are no pages beyond it, and the remainder is reached through mine and shared, which have no ceiling.

Examples

curl — personal key

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

curl — OAuth application

Terminal
curl -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  "https://vibecode.bitrix24.com/v1/applications?scope=mine&limit=20"

JavaScript — personal key

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/applications?scope=mine&limit=20', {
  headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})
const { data: applications, total } = await res.json()

applications.forEach(app => {
  const state = app.activeOperation
    ? `${app.activeOperation.kind} — ${app.activeOperation.status}`
    : app.server?.status ?? 'no server'
  console.log(`${app.name}: ${state}`)
})
console.log(`Showing ${applications.length} of ${total}`)

JavaScript — OAuth application

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/applications?scope=mine&limit=20', {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})
const { data: applications, total } = await res.json()

Response fields

Field Type Description
success boolean Always true on success
data array Array of applications. An array element and the data of a single card have the same shape, and the fields of that shape are described on the Application card page
total number How many applications matched the requested scope. On feed this is the size of the selection rather than the number of applications on the Bitrix24 account — read truncated before counting pages from it
page number The number of the page served, already clamped to an allowed value
limit number The page size, already clamped to an allowed value
truncated boolean Whether the output was cut short. Always present, in every scope. falsetotal is exact. truetotal is the size of the selection, not how many exist, and there are no pages beyond it

Response example

JSON
{
  "success": true,
  "data": [
    {
      "id": "cmsw806qp0001tdskzf6mitka",
      "name": "Draft with no server",
      "description": null,
      "type": "PERSONAL",
      "iconUrl": null,
      "createdAt": "2026-08-16T19:51:18.817Z",
      "updatedAt": "2026-08-16T19:51:18.817Z",
      "viewerState": "owner",
      "pinned": false,
      "author": { "name": "Application author" },
      "isEmbedded": true,
      "openUrl": null,
      "openTarget": null,
      "server": null,
      "sources": { "hasVersions": false, "latestVersionId": null, "latestSavedAt": null },
      "activeOperation": null
    },
    {
      "id": "cmsw806qi0000tdskiw2aegii",
      "name": "Deal report",
      "description": "Pipeline summary for the period",
      "type": "PERSONAL",
      "iconUrl": null,
      "createdAt": "2026-08-16T19:51:18.810Z",
      "updatedAt": "2026-08-16T19:51:18.810Z",
      "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": 50,
  "truncated": false
}

Error response example

400 — the scope value is outside the allowed set:

JSON
{
  "success": false,
  "error": {
    "code": "INVALID_SCOPE",
    "message": "scope must be one of: mine, shared, feed"
  }
}

Errors

HTTP Code Description
400 INVALID_SCOPE The scope value is not one of mine, shared, feed. An empty value and a different letter case land here too
401 NO_PORTAL The key is not bound to an account — the actual answer for this case
403 MANAGEMENT_KEY_NO_ENTITY_ACCESS A management key: an API key or an authorization key is required
403 PORTAL_KEY_REQUIRED The section's safety net: a damaged key row with no owner. The ordinary "key with no account" is 401 NO_PORTAL above
401 MISSING_API_KEY The X-Api-Key header was not sent
401 INVALID_API_KEY Invalid or expired API key
429 RATE_LIMITED Rate exceeded. 60 requests per minute is the platform-wide limit; one replica's share today is 20 — read X-RateLimit-Limit from the response. The counter is per key owner. The limit is stricter than the one for the application card: the list is meant for refreshing a screen, not for polling in a loop. A Retry-After header carries the pause in whole seconds
429 QUOTA_EXCEEDED The key's daily free-call quota is exhausted. Same status, different cause: waiting does not help

The error.message text is always English and meant for logs: build your interface texts from error.code. The full list of common API errors — Errors.

Known specifics

  • The selection follows the key owner, not the key itself. Applications created in the Vibecode dashboard are included too: their servers are bound to other keys of the same person, so GET /v1/infra/servers cannot see them while this list can. The reverse holds as well — swap the key for a fresh one and the same list comes back.
  • Row order depends on scope, and it is total. On feed pinned applications come first, then your own, then the ones shared with you, and within each group, applications with a newer updatedAt come first. On mine and shared there is a single order — by createdAt, newest first. In both cases ties on the timestamp are broken by id, and that matters for walking pages: applications created in a batch carry identical timestamps, and without the secondary key two pages of one walk could overlap or skip an application.
  • On feed the selection is capped from above, and truncated says so. The order for that list is computed in the service rather than by the database query, so the page is cut after sorting and total reports the size of that selection, not the number of applications on the whole Bitrix24 account. Read the flag carefully: it is raised when the selection reached the cap, and reaching the cap cannot be told from "that was exactly all" in one query, so the platform errs toward admitting incompleteness. A false true you can diagnose yourself — ask for the next page and get an empty one. A false false could not be diagnosed at all, which is why it is not allowed to happen. On mine and shared there is no ceiling, total is the full number of rows matching the filter and truncated is always false.
  • updatedAt does not track how fresh the code is. The field moves when the card itself is updated: a rename, a description change, unlinking a deleted server. A deployment does not move it, so an application deployed ten times today sinks to the tail of feed. To see how fresh the code is, read sources.latestSavedAt.
  • On shared and feed the source summary is almost always empty. Both enrichment blocks are filled in only for whoever manages the application, so on other people's rows sources arrives empty and activeOperation is null. The shape of the row does not change, and from the row alone "the data is not disclosed" cannot be told apart from "there are no versions". A screen that renders code status for the whole output has to account for that.
  • An empty shared is a working state, not a refusal. A 200 with an empty data array and total: 0 means there are no other people's applications you can access. There is no error to look for in that case.
  • requestable is never returned in the list — in any scope. The selection only admits applications that are yours, reachable through the server's access policy, opened to you personally by a grant, bound to a server whose development team you belong to, or serverless and open to the whole Bitrix24 account. So this section needs no "request access" affordance — and there would be nowhere to point one: there are no write operations here. The requestable value exists for completeness and only surfaces where the card refuses: 403 FORBIDDEN on a direct request for someone else's closed application.
  • scope and viewerState draw different lines. Besides the applications shared with you personally, shared also covers applications with no server that are open to the whole Bitrix24 account — and such a row arrives with viewerState: "usable", not "shared". A client that filters rows by viewerState === "shared" will drop part of the output.

See also