
## Key self-description

The optional `data.deployment.standalone.reuseTarget` object contains the eligible existing server's `serverId`, `kind: "STANDALONE"`, `action: "deploy"` and `createRequired: false`. When it is present, the checklist targets that server instead of creating another; inspect any running code before replacing it. The signal can appear even with `capabilities.servers.create.available: false`, because no new VM is created. GET performs no repair: an empty personal application binding is filled only during create-reuse or an already authorized deploy to the exact id. Write paths revalidate the state.

`GET /v1/me`

Returns a self-description of the key that made the request: its type, the bound Bitrix24 account, that account's plan and the platform capabilities available on it. No scope is needed — the endpoint responds to any valid key and serves as an AI model's starting point for exploring the Bitrix24 account.

The response shape depends on the key type. How the key is passed in the request — [Passing the key](/docs/keys-auth#passing-the-key).

## Code instead of a key

If the owner gives the agent a one-time code and the self-description URL, follow this exchange procedure. This is a template, not a usable code:

```text
My code for the VibeCode platform: VIBE-… (swap for a key)

Documentation: https://vibecode.bitrix24.com/v1/me
```

The code lasts **60 minutes from issuance**. Renewing it never extends that deadline. It only allows delivery discovery and one delivery of an existing key; it is not an API key for other endpoints.

### Discover delivery and save the key

1. Request `GET /v1/me` with the code from the prompt in `X-Api-Key` or `Authorization: Bearer`: without credentials the server returns `401 MISSING_API_KEY`. Before the request, check the URL on the Documentation line: send the code only to the platform's HTTPS address `https://vibecode.bitrix24.com` and do not follow redirects carrying the code to another address. `exchange.url` and every URL in `exchange.save_steps` must use that same platform HTTPS address, otherwise stop and execute nothing.
2. Read the entire response. For a code this is a separate JSON object without a `success` / `data` envelope: `auth_kind: "key_claim_code"`, `protocol_version: 1` and `state`. It contains no key and carries `Cache-Control: no-store`.
3. With `state: "issued"`, check `expires_at`, `portal` and current permissions in `key`. The `exchange` object describes a single-use `POST /v1/key-claim` with the same code in either header above and **no request body**. Choose `Accept: text/x-dotenv`. The three commands are in `exchange.save_steps` (there is no top-level `save_steps`); storage rules are in `key_storage`.
4. Run all three `exchange.save_steps` in one dedicated POSIX shell in the project directory, substituting the code only where instructed. Before exchange, `.env` must be a regular file or absent, never a symlink, excluded from git and untracked, with mode `600`. Verify any existing `VIBE_API_KEY` first; replace it only with the owner's intent. Before removing a stale `.env.key-claim.lock`, check that no save process is running. If preparation fails, stop before POST. Other environments need equivalent private-file permissions, integrity checks and temporary-file cleanup.
5. A successful `text/x-dotenv` response contains exactly two lines: the `VIBE_API_KEY` assignment and the `# end` marker, each ending in a newline. The steps save it to a private temporary file, validate completeness and format, immediately append the key to the project's `.env`, then remove the temporary file. Never print the key in chat, tool output or logs; disable shell tracing. Do not replace these steps with a redirect of an unchecked response straight into `.env`. Without that `Accept`, the server returns JSON with `api_key`, `env_name`, `key_fingerprint` and `key`; that response is also secret.
6. Verify the saved key with `GET /v1/me`, passing the key in `X-Api-Key`: it returns the owner and scopes in the ordinary `success` / `data` envelope. For [MCP](/docs/mcp), forward `VIBE_API_KEY` through the parent client's environment and restart the client after saving `.env` and updating that environment. The MCP package does not load `.env` itself.

### States and recovery

| `state` | Action |
|---------|--------|
| `issued` | The key has not been delivered. Check the local file and perform the one-time exchange before `expires_at`. |
| `claimed` | The key was already delivered. The response includes `key_fingerprint`, `claimed_at` and `recovery`. If a local `VIBE_API_KEY` exists, compare the first 16 hexadecimal characters of its SHA-256 with `key_fingerprint`, without printing the key. Use it if it matches. If it is absent or different, ask the owner to revoke the old key and create a new key and code. |
| `expired` | The code has expired. Follow `recovery`: ask the owner to create a new key and code. |
| `key_not_active` | The key is inactive and cannot be delivered. Contact the owner. |

POST for an already claimed, expired or inactive code returns `409` with its current state; an unknown, replaced or malformed code returns `401 INVALID_CODE`. Conflicting credentials also return `INVALID_CODE`. Errors are JSON without a secret. After a failure, read the state through GET: **a lost successful POST response cannot be replayed**, and renewing the code cannot recover the key. Do not retry the exchange blindly.

`403 ACCESS_DENIED` means the owner or Bitrix24 account access no longer permits delivery; contact the owner. With `409 DELIVERY_BUSY`, another operation holds the delivery lock; `429 RATE_LIMITED` indicates a request limit. With `500 DELIVERY_FAILED`, also check the state through GET first: an error does not guarantee that delivery did not commit.

Manual fallback: before delivery, the owner can select **Show key** in the dashboard's keys section and privately save it to `.env` with the same permissions and git exclusion. This also consumes the one-time delivery; the agent will then receive `claimed`, not another copy of the key.

## Create an app following the instructions

When the user supplies an app creation prompt, follow these steps through publication.

1. Read the full successful `GET /v1/me` response using the supplied key in the
   `X-Api-Key` header. With MCP, call the full `get_me({})` without `sections`.
   Before choosing a design or asking about the UI target or brand, apply
   `data.uiDesign` with `policyVersion=2` (the `uiDesign` field in MCP).
2. For a standard new app with no explicit target and `context.status=portal_bound`,
   use the light Bitrix24 design without asking about the target or brand. Explicit
   user design preferences take priority; for an explicitly external page, clarify
   the brand. With `unresolved`, select a Bitrix24 account and repeat discovery with
   its app key, or clarify the target. A key binding neither proves embedding nor
   grants creation, embedding or publication rights: check them separately. Preserve
   the design of an existing UI; an app without a UI needs no styles. For a new UI,
   follow `uiDesign.instructions` and `uiDesign.css`, pinning the specified CSS version.
3. If discovery fails, `uiDesign` is missing or the policy version is unsupported,
   report that the design rule was not read and clarify the design.
4. If a blueprint spec URL is supplied, read it with the supplied key and briefly
   check the requirements with the user point by point. Without a spec, ask at most
   three short questions about what the app should do and which data it uses.
   Respect requirements already supplied and the design selected by the policy,
   then show a short plan.
5. Build the app and deploy it to the server using `data.deployment` and the
   [deployment instructions](/docs/infra/deploy). Use the server ID from the prompt
   when one is provided. For an app that is already built, only deploy it, without
   repeating the survey or changing its design.

## Parameters

| Parameter | Type | Required | Values | Description |
|-----------|------|:--------:|--------|-------------|
| `refresh` (query) | string | no | `tariff` | Forces a live re-check of the Bitrix24 account plan before the response is built. Works only for keys bound to a Bitrix24 account. Without the parameter the response is served from the server cache. |

## Examples

### curl — personal key

```bash
curl https://vibecode.bitrix24.com/v1/me \
  -H "X-Api-Key: YOUR_API_KEY"
```

### curl — OAuth application

```bash
curl https://vibecode.bitrix24.com/v1/me \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN"
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/me', {
  headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})
const { data } = await res.json()
console.log('Key type:', data.type, '· portal:', data.portal)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/me', {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})
const { data } = await res.json()
```

## Response fields

The response describes the key, not an entity on the Bitrix24 account. The top-level `data.*` blocks are a capability map: some blocks are common to all keys, some appear only for a key of a certain type. The "Key type" column shows which key types each block appears for.

If this is your first look at the key, four blocks are enough. `type` tells you which key you are working with. `scopes` — which data you have access to. `accessMode` — whether writing is allowed. `capabilities` — which operations are available on this Bitrix24 account and why the rest are denied. The other blocks are needed for a specific task: `deployment` and `infra` when working with servers, `ai` and `webSearch` when calling models, `storage` when uploading files.

| Field | Type | Key type | Description |
|------|-----|-----------|---------|
| `success` | boolean | all | Always `true` on success |
| `data.type` | string | all | Key type: `personal`, `oauth_app` or `management` |
| `data.portal` | string | `vibe_api_`, `vibe_app_` | Domain of the bound Bitrix24 portal |
| `data.portalId` | string | `vibe_api_`, `vibe_app_` | Stable identifier of the bound Bitrix24 portal. Unlike the domain, it survives a rename or a move of the portal — use it whenever local data is split per account. A key with no portal never reaches this response: it gets `401 NO_PORTAL`, so the field is never empty in a successful response |
| `data.features` | object | `vibe_api_`, `vibe_app_` | Platform features enabled for the Bitrix24 account |
| `data.features.appDb` | boolean | `vibe_api_`, `vibe_app_` | `true` — the [application database](/docs/infra/database) is enabled for the Bitrix24 account. With `false`, the section's endpoints return `403 APP_DB_DISABLED` |
| `data.tariff` | object | all | Bitrix24 account plan: `code`, `name`, `isCommercial`, `wasEverCommercial`, `checkedAt`, `kind` |
| `data.tariff.name` | string · null | all | Human-readable edition name — meant for display, not for comparison in code. May be `null`. Details below |
| `data.tariff.wasEverCommercial` | boolean | all | The account was seen on a commercial plan at some point. It is history, not access. The flag is **not one-way** — see below |
| `data.tariff.checkedAt` | string | all | Time of the last ATTEMPT to check the plan against Bitrix24, ISO 8601. Updated when called with `?refresh=tariff` — including when the check failed and the plan could not be read. A SUCCESSFUL check is marked by a separate response header, `X-Tariff-Checked-At` (see [Infrastructure](/docs/infra)), and that header may be absent from the very same response. If the field is present but the header is not, the check was attempted and the plan could not be read |
| `data.scopes` | array | all | Key scopes. Choosing a set — [Scopes](/docs/scopes) |
| `data.api.scopeRequirements` | object | all | Map of the key's scopes: what it lacks and what each missing scope would open. A pre-flight check instead of calling blind. Details below |
| `data.api.scopeRequirements.granted` | object | all | Granted scopes, split into `bitrix24` and `platform` — they are obtained differently |
| `data.api.scopeRequirements.coverage` | object | all | Map completeness: `entities` is always `complete`, `paths` is `complete` or `partial`. When `paths` is `partial`, the path list is incomplete, and the absence of a path from `unlocks` does NOT prove the path is ungated |
| `data.api.scopeRequirements.missing` | object | all | Missing scopes. Each carries `entities` — entities it would open, `unlocks` — paths (at most five, the rest in `unlocksRemaining` next to `truncated`), `howToObtain` — how to get it. A scope of a module enabled separately for each Bitrix24 account appears here only on an account where that module is enabled. Such a scope, once granted, stays in `granted` |
| `data.api.scopeRequirements.unobtainable` | object | all | Scopes this key cannot obtain, and why. Two reasons: a personal webhook key carries no app-context scopes, and some scopes are minted by the platform itself and are not available to a user on any surface |
| `data.api.scopeRequirements.aliases` | object | all | Spellings Bitrix24 treats as one scope (`task` and `tasks`) |
| `data.accessMode` | string | all | Key access mode: `READWRITE` or `READONLY`. More — [Access mode](/docs/keys-auth/access-mode) |
| `data.portalWriteRestriction` | object | `vibe_api_`, `vibe_app_` | The second restriction statement — about Bitrix24 data. All fields of the object: `restricted` (the code `WRITE_BLOCKED_READONLY_KEY`), `appliesTo` (what it covers — changes to Bitrix24 data), `routes` (closed routes, each entry with its own refusal condition), `note` and `docs` (a link to the access mode page). Present for a key whose access mode forbids changing Bitrix24 data, regardless of the emergency lever — unlike `writeRestriction`. More — [Access mode](/docs/keys-auth/access-mode#the-portalwriterestriction-field) |
| `data.capabilities` | object | all | Matrix of available operations. The top-level keys are groups, and each group holds operation slots. The shape of a slot is described below |
| `data.capabilities.apps` | object | all | Operation slots `create` — create an application, `publish` — publish it to the catalog, `bindPlacements` — bind placements. Plus `sourceStorage` — not an operation but a source-storage settings block with its own field set |
| `data.capabilities.apps.sourceStorage` | object | all | Source-storage settings: `enabled`, `requiredBeforeDeploy`, `automaticOnDeploy`, `limits.maxBlobBytes`, `endpoint`, `mcpToolName`, `contentTypes`, `docs`. The `freshnessWindowMinutes` field is added only when the platform checks the snapshot's age before publishing. This block carries no `available` or `reason` fields |
| `data.capabilities.servers` | object | all | Slots `create` — create a server, `deploy` — publish sources, `preview` — work with [access tokens](/docs/infra/access-tokens): a preview link and a Bearer token for an end-to-end (E2E) check, `wake` — wake the server |
| `data.capabilities.agents` | object | all | Slot `create` — create an AI agent |
| `data.capabilities.managedBots` | object | all | Slot `create` — create a managed bot |
| `data.capabilities.aiRouter` | object | all | Slots `chatCompletions` — model calls, `byok` — working with your own provider key |
| `data.capabilities.appUserSignIn` | object | all | Slot `setup` — the setup of end-user sign-in for an app through the platform SDK. While the feature is not enabled for the key owner, the slot comes back with `available: false` and `reason: "FEATURE_DISABLED"` |
| `data.capabilities.<group>.<slot>.available` | boolean | all | Whether this key can perform the operation on this Bitrix24 account right now |
| `data.capabilities.<group>.<slot>.reason` | string | all | State code of the slot. It arrives both on `available: true` (`COMMERCIAL`, `TRIAL_ACTIVE`) and on a denial — for example `SESSION_REQUIRED`, `WRITE_BLOCKED_READONLY_KEY`, `BILLING_EXHAUSTED`, `FEATURE_DISABLED`, `INFRA_NOT_PERMITTED`, `SERVER_CREATION_DISABLED`, `SERVER_CREATION_ADMINS_ONLY`, `INT_TARIFF_REQUIRED`, `COMMERCIAL_PLAN_REQUIRED`, `TRIAL_PORTAL_LIMIT`, `PLAN_NOT_ALLOWED_ON_TRIAL`. For accounts where access is narrowed to the Vibe+ plan line, infrastructure and key issuance require a Vibe+ plan, and an ordinary commercial plan is declined with `INT_VIBE_PLUS_REQUIRED`. The set grows — treat an unknown code as a denial and rely on `available` |
| `data.capabilities.<group>.<slot>.userMessage` | string | all | Ready-made text in the user's language. It arrives on a denial — show it as is |
| `data.capabilities.<group>.<slot>.note` | string | all | A condition that `available: true` does not cover: a Bitrix24-side prerequisite, how much of a limit is already used, or a trial-access restriction |
| `data.webResearch.promptForAgents` | string | all | A ready-made hint for an agent: how to call research and where to find the provider catalog |
| `data.capabilities.<group>.<slot>.limits` | object | all | Limits in force for the slot — for example `allowedPlans` and `maxPortalTotal` under trial access |
| `data.capabilities.<group>.<slot>.alternatives` | array | all | What to do instead of the blocked operation: items with the fields `type`, `description`, `url` or `endpoint` |
| `data.api` | object | all | API usage rules in the `_rules` array, the entities exposed by the entity API in `entityApi`, a link to the full reference |
| `data.rateLimit` | object | all | Call-rate limits: `requestsPerSecond` — the Bitrix24 account limit, shared across all of its keys, `edgeRequestsPerSecondPerIp` — the platform edge limit per client IP address. More — [Request limits](/docs/keys-auth#request-limits) |
| `data.ai` | object | all | AI Router access: default model, available models, catalog size. More — [AI Router](/docs/ai) |
| `data.ai.structuredOutputs.models` | array | all | Models that accept `response_format` of type `json_schema`. More — [Guaranteed JSON response](/docs/ai/chat/json) |
| `data.webSearch` | object | all | Web search providers and their cost. More — [Provider list](/docs/search/providers) |
| `data.webResearch` | object | all | Deep research: `available`, `endpoint`, `providers`, `defaultProvider`, `streaming`, `docs`. More — [Deep research](/docs/search/research) |
| `data.webResearch.providers[].cost` | object | all | Price of one research run with the provider: `research` — the price in Vibe credits (Ꝟ), `currency` — the billing unit. For a provider used with your own key, `research` equals `0` |
| `data.storage` | object | all | Object storage: usage, pricing, upload endpoints. More — [Storage](/docs/storage) |
| `data.deployment` | object | all | Application deployment contract — depends on the target server type. More — [Deploy API](/docs/infra/deploy) |
| `data.deployment.primary` | string | all | The placement model the platform treats as primary for this account: `galaxyApp` — an app inside a galaxy, `standalone` — a dedicated virtual machine |
| `data.deployment.galaxyApp` | object | all | The contract for deploying into a galaxy. During trial access, the block arrives only when stored state shows a suitable host. This is an advisory assessment: `POST /v1/infra/servers` makes the final live-capacity check |
| `data.deployment.placementNote` | string | all | Why the galaxy is unavailable and how to deploy in that state. During trial access, the field distinguishes the absence of a host from an existing but unsuitable host. The field is absent when Galaxy mode is disabled, see [Galaxy app](/docs/infra/galaxy) |
| `data.deployment.limits` | object | all | The publication limits in force: the body and archive ceilings, the per-step time budget, the number of log lines, the ceiling on concurrent inline-body publications |
| `data.deployment.limits.uploadInlineMax` | string | all | The HTTP body ceiling for a publication with an inline archive, as a string with its unit — for example `96MB`. A larger body is refused with `413 INLINE_SOURCE_TOO_LARGE`. See [Deploy](/docs/infra/deploy/deploy) |
| `data.deployment.limits.uploadInlineMaxArchive` | string | all | The same ceiling expressed as the size of the archive itself — for example `72MB`. It is about a quarter lower than `uploadInlineMax`: the contents travel as base64 and are heavier than the source bytes. The value comes from the same constant as the refusal, so you do not need to recompute the ceiling yourself |
| `data.deployment.limits.uploadUrlMax` | string | all | The ceiling for publishing by link and by a stored version — `500MB`. Those paths are streamed and do not fall under the inline body ceiling |
| `data.infra` | object | all | Infrastructure: providers, server limit, list of unhealthy servers, servers of other teams the key owner is on. More — [Infrastructure](/docs/infra) |
| `data.infra.collaboratorServers` | object | all | Servers where the key owner is on the development team rather than owning them: `total` — how many memberships there are in total, `count` — how many rows fit into the response, `items` — the rows themselves. These servers do not count towards `infra.limits` — the machines belong to someone else. The same servers arrive in [`GET /v1/infra/servers`](/docs/infra/servers) carrying the `access.via: "collaborator"` block |
| `data.infraState` | object | all | Whether infrastructure is stopped over an unpaid balance. The strings inside the block are machine-readable; the client writes the human-facing text |
| `data.infraState.frozen` | boolean | all | Whether servers, deploys and storage are stopped over an unpaid balance. When `true`, the `402 ACCOUNT_FROZEN` refusal arrives on calls paid for from the Vibe credits balance, while requests to your own Bitrix24, AI within the plan's monthly quota and a Cowork subscription for its paid period keep working. Until the narrowed refusal is rolled out to your Bitrix24 account, `true` means "almost everything is stopped": the self-description, the [guide](/docs/keys-auth/guide), the spec, four [Feedback](/docs/feedback) conversation calls — filing a ticket, listing tickets, reading one ticket and commenting on it — and the revocation of your own Cowork key keep working; everything else answers `402 ACCOUNT_FROZEN`. The full list is in [Error codes](/docs/errors) |
| `data.infraState.reason` | string or null | all | The stop reason code: `DEBT`, or `null` when nothing is stopped |
| `data.infraState.topupUrl` | string or null | all | The top-up address for your Vibecode account, or `null` when there is nothing to top up |
| `data.feedback` | object | all | Feedback endpoints and limits. More — [Feedback](/docs/feedback) |
| `data.auth` | object | all | How to pass the key in a request: headers, and for the authorization key — OAuth authorization steps |
| `data.quickstart` | object | all | A short list of first calls for exploring the API |
| `data.b24Credentials` | object | `vibe_api_` | Whether the key can call Bitrix24: `ready`. When `ready: false` it also carries `reason`, plus `paywallCode` and `upgradeUrl` for plan-related reasons. The `hint` field arrives when the access state is worth re-reading. The block is absent for a key that is not bound to a Bitrix24 account and for a key with no Bitrix24 scopes — the flag does not apply there. The `reason` values and the action for each — [Error codes](/docs/errors/auth#token_missing-401) |
| `data.b24Credentials.ready` | boolean | `vibe_api_` | `true` — the key carries credentials for calls to the Bitrix24 account, `false` — it does not, and any such call returns `401 TOKEN_MISSING` |
| `data.docs` | string | all | Link to the full API reference — `GET /v1/guide` |
| `data.errorCodes` | object | all | Error response format and a link to the full error code reference |
| `data.changelog` | object | all | Link to the API changelog |
| `data.expiresAt` | string or null | `vibe_api_` | Key expiration date. `null` — no expiration |
| `data.owner` | object | `vibe_api_` | Key owner: `name`, `userId`, optional `userIdSource` |
| `data.owner.userId` | string or null | `vibe_api_` | Employee ID from the webhook. A Cowork key without a webhook may receive a confirmed ID from Bitrix24 account membership; once assigned to the key, that ID stays fixed. `null` when no identity is available. Membership IDs with unclassified provenance (`bitrixUserIdSource: null`) are not returned. This field does not grant access to Bitrix24 data: with `b24Credentials.ready: false`, calls return `401 TOKEN_MISSING` |
| `data.owner.userIdSource` | string | `vibe_api_` | `webhook` or `portal-membership`. Present only when `userId` is available |
| `data.portalEmbedding` | object | `vibe_api_` | The embedding flow through `POST /v1/apps` → OAuth authorization → `POST /v1/apps/:id/publish`, plus the personal-key boundaries: it does not call `placements/bind` directly and is not itself a transparent-auth credential |
| `data.app` | object | `vibe_app_` | Bound application: `title`, `id` |
| `data.currentUser` | object or null | `vibe_app_` | The Bitrix24 user on whose behalf the request runs. Filled when a session token is passed. Without one it arrives as `null` |
| `data.placements` | object | `vibe_app_` | Embedding the application in the Bitrix24 account interface: available and registered placements, endpoints, how incoming requests are handled. More — [Embedding an app in the Bitrix24 account](/docs/keys-auth#embedding-an-app-in-the-bitrix24-account) |
| `data.placements.note` | string | `vibe_app_` | The embedding boundary for a key that may not write Bitrix24 data: which placements stay available to it. Absent for a key that may write that data |
| `data.placements.bindPrerequisite` | object | `vibe_app_` | The Bitrix24-side condition without which [binding a placement](/docs/apps/placements/bind) will not succeed. The contents of the block depend on the portal type |
| `data.placements.bindPrerequisite.subscriptionRequired` | boolean | `vibe_app_` | `false` — a commercial Bitrix24 plan is enough to bind a placement |
| `data.placements.bindPrerequisite.note` | string | `vibe_app_` | Text describing the condition and how to satisfy it |
| `data.placements.bindPrerequisite.errorCodes` | array | `vibe_app_` | Possible bind refusals, not the result of a check of current access. The set combines plan prerequisites, developer-key checks and shared application and placement refusals; it depends on the portal type and may grow. Read the current set from this field and the prerequisite explanation from `note`. Self-hosted portals add `SESSION_REQUIRES_ADMIN`. `BOX_WEBHOOK_NOT_DEVELOPER_KEY` can occur on both portal types: the `rest.developer` scope is fixed when the key is issued, so granting its owner administrator rights cannot repair the key |
| `data.oauth` | object | `vibe_app_` | OAuth authorization URL and required parameters |
| `data.oauthTutorial` | object | `vibe_app_` | Step-by-step OAuth authorization walkthrough |
| `data.eventDelivery` | object | `vibe_app_` | Receiving Bitrix24 account events on your server without polling |
| `data.eventDelivery.note` | string | `vibe_app_` | Explains which keys make Bitrix24 account events and automation rule calls arrive without `auth[access_token]` and `auth[refresh_token]`, what follows from that, and how to get the tokens back. Always present together with the `eventDelivery` block, whatever the mode of the key that calls `GET /v1/me`. More — [Application-side handler](/docs/infra/event-subscriptions/handler) |
| `data.schemaDiscovery` | object | `vibe_app_` | How to read the field schema without a session token |

The management key (`vibe_live_`) returns a different set of blocks — `portals`, `totalAppKeys`, a reduced `capabilities` — and carries no Bitrix24 account data. Description — [Management keys](/docs/management-keys).

## Response example

Personal key (`vibe_api_`) — main fields shown:

```json
{
  "success": true,
  "data": {
    "type": "personal",
    "portal": "mycompany.bitrix24.com",
    "portalId": "8c3d1e04-…",
    "tariff": {
      "code": "en_basic",
      "name": "Basic",
      "isCommercial": true,
      "wasEverCommercial": true,
      "checkedAt": "2026-07-08T08:57:58.270Z",
      "kind": "CLOUD"
    },
    "scopes": ["crm", "task", "tasks", "im", "imbot", "disk", "user"],
    "accessMode": "READWRITE",
    "capabilities": {
      "apps": {
        "create": { "available": true },
        "publish": { "available": true },
        "bindPlacements": { "available": true }
      },
      "servers": {
        "create": { "available": true, "reason": "COMMERCIAL" },
        "deploy": { "available": true, "reason": "COMMERCIAL" },
        "preview": { "available": true },
        "wake": { "available": true }
      },
      "agents": {
        "create": {
          "available": true,
          "reason": "COMMERCIAL",
          "note": "Agent servers count toward the portal's infrastructure limit (currently 2/10)."
        }
      },
      "managedBots": {
        "create": {
          "available": true,
          "reason": "COMMERCIAL",
          "note": "Managed bot servers count toward the portal infrastructure limit."
        }
      },
      "aiRouter": {
        "chatCompletions": { "available": true },
        "byok": { "available": true }
      },
      "appUserSignIn": {
        "setup": { "available": false, "reason": "FEATURE_DISABLED" }
      }
    },
    "owner": { "name": "John Brown", "userId": "1" },
    "expiresAt": null
  }
}
```

The `apps.publish` and `apps.bindPlacements` slots always carry a `note` — it is omitted from the example above, but the endpoint returns the full text.

Authorization key (`vibe_app_`) without a session token — showing the blocks a personal key does not have:

```json
{
  "success": true,
  "data": {
    "type": "oauth_app",
    "portal": "mycompany.bitrix24.com",
    "portalId": "8c3d1e04-…",
    "accessMode": "READWRITE",
    "app": { "title": "CRM Dashboard", "id": "f2342f7a-…" },
    "currentUser": null,
    "placements": {
      "available": true,
      "registered": ["LEFT_MENU"],
      "endpoints": [
        "POST https://vibecode.bitrix24.com/v1/placements/bind",
        "POST https://vibecode.bitrix24.com/v1/placements/unbind",
        "GET https://vibecode.bitrix24.com/v1/placements",
        "GET https://vibecode.bitrix24.com/v1/placements/available"
      ],
      "bindPrerequisite": {
        "subscriptionRequired": false,
        "note": "Binding a placement requires a Bitrix24-side prerequisite: the portal must be on a commercial Bitrix24 plan…",
        "errorCodes": [
          "INT_TARIFF_REQUIRED",
          "BITRIX_UNAVAILABLE",
          "BOX_WEBHOOK_NOT_DEVELOPER_KEY",
          "PLACEMENT_APP_GRANT_MISSING",
          "PLACEMENT_OPTIONS_REQUIRED",
          "PLACEMENT_NOT_REST_BINDABLE",
          "B24_EMBEDDING_APP_NOT_FOUND"
        ]
      }
    }
  }
}
```

> **`oauth.authorizeUrl` is a template, not a ready-made link.** The `/v1/oauth/authorize` endpoint requires the `state` parameter (16–512 characters) — a CSRF token per RFC 6749 §10.12 that the **client generates**: create a cryptographically random string, add it to the URL, and verify the value returned in the callback. The server cannot generate `state` for you — otherwise CSRF protection does not work. A request to `authorizeUrl` as is returns `400 INVALID_REQUEST "state: Required"`. Optionally add `redirect_uri` (your callback address — without it the built-in `/oauth/complete` page is used) and `scope`. Example of a full link:
>
> ```
> https://vibecode.bitrix24.com/v1/oauth/authorize?app_key=vibe_app_…&state=aAbBcCdDeEfFgGhH&redirect_uri=https://myapp.com/callback
> ```
>
> Only your application server requests this URL: it contains `app_key`, so do not pass it to the browser. Redirect the user's browser to the address in the `Location` header of the response — [step 1 of authorization](/docs/keys-auth/oauth#step-1-send-the-user-to-authorization).

## Error response example

401 — invalid key:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_API_KEY",
    "message": "Invalid API key"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | The key is not recognized — no such key exists on the platform |
| 401 | `KEY_INACTIVE` | The key is revoked |
| 401 | `KEY_EXPIRED` | The key has expired |
| 401 | `KEY_NOT_FOUND` | The key was deleted while the request was being processed |
| 403 | `IP_NOT_ALLOWED` | Request from an address outside the allowed IP list |

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

## Known specifics

**`?refresh=tariff` checks the plan no more than once per minute.** The parameter forces a live check of the Bitrix24 account plan and resets the response cache. If the previous check was less than a minute ago, the request returns the already known value without a new check. For a management key the parameter does nothing — such a key is not bound to a Bitrix24 account.

**The response is cached server-side for about 30 seconds.** A change of scopes or access mode is reflected in the response immediately. The plan, balance and infrastructure state are updated on the first read after the cache expires. A request with `?refresh=tariff` resets the cache, and the next read without this parameter returns an up-to-date plan.

**For a key in read-only mode, some `capabilities` slots come back closed.** They are `apps.publish`, `apps.bindPlacements`, `servers.create`, `servers.wake`, `agents.create`, `managedBots.create` and both `aiRouter` operations — all of them return `available: false` and `reason: "WRITE_BLOCKED_READONLY_KEY"`. The `servers.deploy` slot is not on that list: delivery to a server the key already owns is allowed for such a key. For a read-only key the response root also carries `writeRestriction` — the `WRITE_BLOCKED_READONLY_KEY` code, what it covers, and a link to the access-mode page. The field is present only while the restriction is in force: a platform administrator may lift it, and then the field disappears from the response along with the restriction itself. Inside the field an `exceptions` list names the routes the central restriction does not touch. Read the list from the response rather than from this page's prose; the printed set, broken down by group, is on the [Access mode](/docs/keys-auth/access-mode) page, under "Exceptions to the write block". A management key carries the field too, but nothing lifts it there: the write block for such keys is unconditional. The field exists because the response names write endpoints in some thirty places (storage, feedback, source-storage and Cowork hints): those are addresses, not permissions. Per-operation availability is in `capabilities`.

**There are two restriction statements, and they cover different things.** Next to `writeRestriction` the same key receives `portalWriteRestriction` — about changing Bitrix24 data: the code, what it covers, a list of routes where each entry has its own refusal condition, and a link to the access mode page. `writeRestriction` covers platform writes and disappears if a platform administrator switches this block off with the emergency lever. `portalWriteRestriction` covers Bitrix24 data and does not depend on that lever: delegated writes to Bitrix24 are refused even when the platform write block is off. Read the set of routes from the response — the section "[The portalWriteRestriction field](/docs/keys-auth/access-mode#the-portalwriterestriction-field)" on the access mode page explains the groups.

**`apps.create` does NOT come back closed:** such a key can create an application in `READONLY` mode, and the ban on `READWRITE` is spelled out in that slot's `note` field. Reads are not affected by the mode — `servers.preview`, for one. More — [Access mode](/docs/keys-auth/access-mode).

**The plan name is for display, not for comparison.** The `data.tariff.name` field is assembled at read time and can change without the plan itself changing. The platform picks it in this order. If the plan code is known to the edition catalog — from there, in English. Otherwise the license name as Bitrix24 reported it, in the account's language. Otherwise the previously stored name. Otherwise `null`. Three things follow. First, `null` is a normal value, not an error. Second, the name does not depend on who asks — neither `Accept-Language` nor the key owner's interface language changes it. Third, the plan code is never substituted for the name — when there is no name, you get `null`, not `pro100`. Branch your logic on `data.tariff.code`, `isCommercial`, and `kind`. The labels of known editions have also been refined — three of them: the `demo` edition is now `Trial period`, `Project` is now `Project — legacy free`, and `NFR` is now `NFR — partner licence`.

**The `wasEverCommercial` flag is history, not access.** A `true` value only means the account was seen on a commercial plan at some point. The flag grants neither infrastructure access nor [`OPEN` mode](/docs/infra/access/mode), and it does not lift a refusal of either: access is decided by the account's current Bitrix24 plan only. Base decisions on `capabilities` in the same response, not on the flag.

**The `wasEverCommercial` flag is not one-way.** A plan downgrade does not clear the value, but the platform may change it from `true` to `false` when no grounds for it are found. Do not build logic on this field that assumes the transition can only go one way.

**Determine the placement model from the `deployment` block, not from the account plan.** During trial access, a new galaxy is not created. When Galaxy mode is enabled but there is no host, `deployment.galaxyApp` is absent, `deployment.primary` is `standalone`, and `deployment.placementNote` describes the two-step path. It is available only while `capabilities.servers.create.available` in the same response is `true`; select an allowed plan ID from `capabilities.servers.create.limits.allowedPlans`. When Galaxy mode is disabled, `placementNote` is absent, a one-shot request carrying `source` returns `400 SOURCE_AT_CREATE_GALAXY_ONLY`, and the same `capabilities` slot decides whether the two-step path is available. When an existing host passes the advisory stored-state check, the `galaxyApp` block is present and a one-shot create carrying `source` is worth trying. This is not a guarantee: only `POST /v1/infra/servers` checks live capacity.

When an existing host fails the advisory check, the `galaxyApp` block is absent. While `capabilities.servers.create.available` is `false`, `placementNote` rules out creating a dedicated machine in two steps: with trial enforcement active, the occupied slot returns `402 TRIAL_PORTAL_LIMIT`. To retry one-shot creation, wait for the host to recover, ask an administrator to repair it, or upgrade the plan. After removing the host, re-read `GET /v1/me` and use the two-step path advertised there. Follow the hint in the actual `POST` response. The call sequence — [Create a server](/docs/infra/servers/create) and [Deploy API](/docs/infra/deploy).

**Without a key in a browser the endpoint returns HTML.** A `GET /v1/me` request without the `X-Api-Key` header and with the `Accept: text/html` header returns a stub page with status `200`, not JSON. A request with a key or without `text/html` in the `Accept` header always gets the JSON self-description.

## Scope requirements map

Until now the only way to learn a key's boundary was to make the call: the request went out and came back with a `403 SCOPE_DENIED` refusal naming the missing scope. The list of available entities showed only what was open, while what was closed was simply absent — and absence alone could not distinguish "no such entity in the product" from "it exists but your key cannot reach it".

The `data.api.scopeRequirements` block answers that before the call:

```json
{
  "granted": { "bitrix24": ["imbot"], "platform": ["vibe:ai", "vibe:search"] },
  "missing": {
    "bitrix24": {
      "crm": {
        "entities": ["deals", "contacts", "companies", "leads"],
        "unlocks": ["/v1/duplicates/find", "/v1/addresses"],
        "unlocksRemaining": 41,
        "truncated": true,
        "howToObtain": "reissue-key"
      }
    },
    "platform": {}
  }
}
```

Bitrix24 scopes are bound to a key when the key is issued, so their `howToObtain` is `reissue-key`: adding a scope in the settings of an already-issued key is not enough; the key has to be reissued. Vibecode scopes are edited in your Vibecode account.

> **The map describes the platform-side scope check only.** It does not promise the call will succeed. A scope granted in your Vibecode account after the key was issued shows up in the map, but Bitrix24 does not honour it and answers `BITRIX_ACCESS_DENIED`. Refusals by key kind, ownership, read-only mode, account freeze and plan are separate axes and are not described in the map. The `_note` field inside the block carries the same warning.

> **Path coverage is not complete yet, and the map says so itself.** A `coverage.paths` value of `partial` means some custom routes still lack a machine-checked verdict, so `unlocks` and `unlocksRemaining` under-report. Absence of a path from the list does NOT prove the path is ungated: if a call still answers with a scope refusal, trust the refusal, not the map. The entity list is always complete.

The refusal has not changed: a call without the required scope still returns `403 SCOPE_DENIED` naming that scope. The map is a pre-flight check, not a replacement for the refusal.

## See also

- [Keys and authorization](/docs/keys-auth)
- [API reference for the model](/docs/keys-auth/guide)
- [Access mode](/docs/keys-auth/access-mode)
- [Scopes](/docs/scopes)
- [Management keys](/docs/management-keys)
- [Errors](/docs/errors)
