لوكلاء AI: ملف markdown لهذه الصفحة — /docs-content-en/partner-connect.md فهرس التوثيق — /llms.txt

مقالات التوثيق متاحة حاليًا باللغة الإنجليزية فقط.

Partner Connect

Connecting external services to Bitrix24: your application asks the user for permission to access their Bitrix24 account and receives a Vibecode API key for subsequent calls. The flow is built on the Authorization Code scheme — the same one used by OAuth providers like Google or GitHub.

Base URL: https://vibecode.bitrix24.com/v1 | Authorization: the partner's client_id + client_secret | Key scopes: requested on the consent page

Contents

Connection mode

New clients use PERSONAL: consent and the key belong to the user. COMPANY prepares a shared company connection to a Bitrix24 cloud account; only a platform administrator can set this mode. It requires a CONFIDENTIAL client, a client secret and PKCE S256. Device sign-in and COMPANY are mutually exclusive: enabling one while the other is on is rejected. A mode change is possible until the client issues its first key; attempts without issuance do not prevent switching. The first issuance fixes the mode permanently, even after key revocation; a mode change returns HTTP 409 CONNECT_COMPANY_MODE_LOCKED. Register a new client for Company mode. Unfinished attempts are cancelled when an allowed mode change succeeds. An existing company connection prevents switching back to PERSONAL.

Issuance for COMPANY is currently closed. GET /v1/connect/authorize redirects to the registered redirect_uri with error=access_denied, the original state and error_description=CONNECT_COMPANY_NOT_READY regardless of preparation availability. No authorization code is created. POST /v1/connect/token with grant_type=authorization_code and valid credentials for this client returns HTTP 403 with the same code in code and error_description.

Starting or exchanging a device code for COMPANY returns HTTP 400, error=unauthorized_client and code=CONNECT_COMPANY_DEVICE_FLOW_FORBIDDEN. Consent from a support session acting as another user is rejected with CONNECT_COMPANY_IMPERSONATION_FORBIDDEN, including on an account that permits support access. Retrying or selecting another account cannot remove these restrictions. The issuance scenarios below apply to PERSONAL.

A COMPANY credential cannot access storage: HTTP 403 CONNECT_COMPANY_STORAGE_NOT_READY. Creating derived tokens or keys and managing external model keys are forbidden: HTTP 403 CONNECT_COMPANY_DERIVED_TOKEN_FORBIDDEN. Unknown routes are rejected with HTTP 403 CONNECT_COMPANY_ROUTE_FORBIDDEN. Selecting another Bitrix24 account cannot remove these restrictions. Production issuance remains closed regardless of the rollout flag.

Company connection polling

GET /v1/connect/company/status lets the Product server inspect its own connection using an already issued COMPANY credential. Send it only in the X-Api-Key header. A client secret, cookie or Bearer credential cannot authorize this method. Keep the connection credential on your server, out of browsers and logs. The credential determines the account and Product pair. The method accepts no query parameters, including identifiers of another pair.

Request

Terminal
curl --max-time 20 "$VIBE_URL/v1/connect/company/status" \
  -H "X-Api-Key: $VIBE_COMPANY_SECRET"
javascript
const response = await fetch(`${process.env.VIBE_URL}/v1/connect/company/status`, {
  headers: { 'X-Api-Key': process.env.VIBE_COMPANY_SECRET },
  cache: 'no-store',
  signal: AbortSignal.timeout(20000),
})
const result = await response.json()
if (!response.ok) throw new Error(result.error.code)
console.log(result.data.status, result.data.revision, result.data.action)

Responses and errors include Cache-Control: no-store. Example response for a connection waiting for an administrator:

JSON
{
  "success": true,
  "data": {
    "connection_id": "connection-id",
    "connection_mode": "COMPANY",
    "status": "NEEDS_ADMIN",
    "generation": 0,
    "revision": 1,
    "credential_version": 1,
    "bitrix_ready": false,
    "reason_code": "CONNECTION_ADMIN_REQUIRED",
    "webhook_cleanup": "none",
    "capabilities": { "bitrix": false, "ai": false, "storage": false },
    "poll_after_seconds": 30,
    "action": "AUTHORIZE_ADMIN"
  }
}

Response fields

Field Type Meaning
Fieldsuccess Typeboolean Meaningtrue for successful polling
Fielddata Typeobject MeaningDiagnostics for your own connection
Fielddata.connection_id Typestring MeaningConnection identifier returned by this method
Fielddata.connection_mode Typestring MeaningAlways COMPANY
Fielddata.status Typestring MeaningACTIVE, NEEDS_ADMIN, SUSPENDED, DISCONNECTED, DELETION_PENDING
Fielddata.generation Typeinteger MeaningConnection consent generation
Fielddata.revision Typeinteger MeaningStored connection state revision
Fielddata.credential_version Typeinteger MeaningCurrent connection credential version
Fielddata.bitrix_ready Typeboolean MeaningCurrently always false
Fielddata.reason_code Typestring MeaningReason access is closed, see the table below
Fielddata.webhook_cleanup Typestring Meaningnone: no unfinished attempts, pending: cleanup or creation is unfinished, attention_required: webhook creation outcome is unknown
Fielddata.capabilities.bitrix Typeboolean MeaningCurrently always false
Fielddata.capabilities.ai Typeboolean MeaningCurrently always false
Fielddata.capabilities.storage Typeboolean MeaningCurrently always false
Fielddata.poll_after_seconds Typeinteger MeaningRecommended polling interval: 30 seconds
Fielddata.action Typestring MeaningWAIT, AUTHORIZE_ADMIN, CONTACT_SUPPORT, RECONNECT, WAIT_FOR_DELETION
State or condition reason_code action
State or conditionACTIVE, administrator proof is valid reason_codeCONNECT_COMPANY_NOT_READY actionWAIT
State or conditionACTIVE, proof expired, was invalidated or is missing reason_codeADMIN_CHECK_REQUIRED actionAUTHORIZE_ADMIN
State or conditionNEEDS_ADMIN reason_codeCONNECTION_ADMIN_REQUIRED actionAUTHORIZE_ADMIN
State or conditionSUSPENDED reason_codeCONNECTION_SUSPENDED actionCONTACT_SUPPORT
State or conditionDISCONNECTED reason_codeCONNECTION_DISCONNECTED actionRECONNECT
State or conditionDELETION_PENDING reason_codeCONNECTION_DELETION_PENDING actionWAIT_FOR_DELETION

Polling does not renew administrator proof. ACTIVE alone does not open access: Bitrix24 and AI currently return CONNECT_COMPANY_NOT_READY, files return CONNECT_COMPANY_STORAGE_NOT_READY. The response contains no personal data, people lists, secrets or URLs.

Errors and polling retries

HTTP Code Action
HTTP401 CodeINVALID_API_KEY ActionCheck the current credential. Unknown, revoked, pre-rotation credentials and requests targeting another pair receive the same refusal
HTTP403 CodeCLIENT_BLOCKED ActionThe Product is blocked, contact support
HTTP403 CodePORTAL_DELETED ActionThe account is deleted, stop polling
HTTP403 CodePORTAL_BLOCKED ActionThe account is blocked, contact support
HTTP403 CodePORTAL_SUSPENDED ActionThe account is suspended, contact support
HTTP429 CodeRATE_LIMITED ActionIP limit reached. Wait at least Retry-After seconds
HTTP429 CodeCALLER_RATE_LIMITED ActionShared connection or account limit reached. Wait at least Retry-After seconds

Example of the uniform refusal for an invalid credential:

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

Poll no more often than once every poll_after_seconds seconds and increase the pause on 429. The baseline IP limit is 120 requests per minute. The effective limit is available in x-ratelimit-limit. Successful polling also consumes the shared connection and account limits alongside other API requests. Credential rotation does not reset these limits.

A STATUS_ONLY credential permits diagnostics but cannot call business methods. REVOKED credentials cannot even poll. Use the new credential after rotation. Protection for existing credentials and polling work regardless of new issuance availability. An account administrator reconnects through separate consent. The general codes from the table are also described in the error reference, while CLIENT_BLOCKED occurs only on this method.

Prepared Company handover protocol (issuance remains closed)

The existing authorization-code endpoints have a prepared Company branch. The production gate still returns CONNECT_COMPANY_NOT_READY; the following contract does not enable issuance.

Authorize accepts operation=CREATE (default), RECONNECT, or ROTATE_SECRET. Rotation also requires credential_version equal to the current version. All operations require confidential-client authentication and PKCE S256. Approval issues a code only after the restricted candidate webhook and encrypted cleanup obligation are saved. Rotation replaces the local secret without minting a webhook. Additional reconnect scopes require a separate explicit consent on the consent screen; the session approve request carries approvedAdditionalScopes.

  • HTTP 409 CONNECTION_GENERATION_CONFLICT: another attempt won. No raw secret is returned; the losing candidate is scheduled for revocation. Start a new authorization.
  • HTTP 409 CONNECTION_WEBHOOK_CLEANUP_PENDING: the cleanup cap is reached. Wait for cleanup before reconnecting.
  • HTTP 403 CONNECTION_SCOPE_CONSENT_REQUIRED: new scopes have not been explicitly approved.
  • HTTP 403 CONNECTION_SUSPENDED: security suspension cannot be lifted by ordinary reconnect.
  • HTTP 503 CONNECTION_ADMIN_CHECK_UNAVAILABLE: administrator verification is inconclusive; the existing connection is preserved.

The first exchange and a successful rotation return the secret once. Reconnect keeps the secret unchanged. Bitrix24 use remains blocked until terminal proof that previous webhooks were deleted. An uncertain mint keeps a durable cleanup obligation; automatic lookup by marker is not enabled. Company AI consent is not enabled by these flows.

Holder departure

A Company connection is held by the account administrator who completed consent. When that person stops being the holder, the connection moves to NEEDS_ADMIN. This happens when they are dismissed, deactivated or deleted in Bitrix24, become a guest, lose administrator rights or withdraw their consent, and also when their platform account is transferred or erased. The platform detects a lost administrator right during its scheduled re-check, not at the moment the change is made in Bitrix24.

After the holder departs, the status poll shows:

  • status is NEEDS_ADMIN, reason_code is CONNECTION_ADMIN_REQUIRED, action is AUTHORIZE_ADMIN.
  • generation and revision have each increased by 1.
  • webhook_cleanup is pending while the platform deletes the previous holder's webhook in Bitrix24, or attention_required if the outcome of creating the webhook is unknown.

The connection secret stays valid, and you do not need to change it. Unfinished authorization attempts are cancelled, and unused authorization codes can no longer be exchanged. The person's return to work does not restore the connection: an account administrator restores access by completing consent with operation=RECONNECT.

When the owner deactivates the application or the platform blocks it, all of its Company connections move to SUSPENDED and their secrets are revoked.

When to use

This flow is for an external SaaS product that integrates with your clients' Bitrix24 accounts: CRM analytics, ERP synchronization, a chat assistant, a landing page builder. A "Connect Bitrix24" button on your site launches the flow: the user selects a Bitrix24 account and confirms the scopes; your server receives a permanent API key and works with the Bitrix24 account on behalf of the user.

If you are building an application that is installed inside a Bitrix24 account and works only with it, use a regular API key — see Keys and authorization.

How it works

Your application → [1. Redirect] → Vibecode Consent Page → [2. Consent]
    ↑                                                          ↓
    └──── [4. API key] ←── [3. Code → redirect_uri] ───────────┘
  1. Redirect — you send the user to /v1/connect/authorize with client_id, redirect_uri, state, and a list of scopes.
  2. Consent — the user sees a Vibecode page, selects a Bitrix24 account, and confirms or declines the requested scopes.
  3. Code — after approval the user returns to redirect_uri with the code and state parameters. On decline — redirect_uri?error=access_denied&state=....
  4. Exchange — the partner's server sends POST /v1/connect/token and receives an API key.

Endpoint reference

Method Path Description
MethodGET Path/v1/connect/authorize DescriptionFlow start: redirect to the consent page
MethodPOST Path/v1/connect/token DescriptionExchange a one-time code for a permanent API key and poll a device sign-in
MethodPOST Path/v1/connect/device/authorize DescriptionConfirmation code for signing in from a device without a browser
MethodPOST Path/v1/connect/revoke DescriptionRevoke a single key from the application itself
MethodGET Path/v1/connect/company/status DescriptionDiagnostics for your own Company connection

Full scenario

An example Express handler that walks the user through both endpoints end to end. Before running it, put the client_id and client_secret from the client registration into environment variables, and register the redirect_uri for that client.

javascript
import express from 'express'
import crypto from 'node:crypto'

const app = express()
const sessions = new Map() // in production — Redis or a DB

const CLIENT_ID = process.env.PARTNER_CLIENT_ID
const CLIENT_SECRET = process.env.PARTNER_CLIENT_SECRET
const REDIRECT_URI = 'https://yourapp.com/callback'

// 1. The "Connect Bitrix24" button leads here
app.get('/connect', (req, res) => {
  const state = crypto.randomBytes(16).toString('hex')
  sessions.set(state, { userId: req.user.id, createdAt: Date.now() })

  const params = new URLSearchParams({
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    scope: 'crm task',
    state,
  })
  res.redirect(`https://vibecode.bitrix24.com/v1/connect/authorize?${params}`)
})

// 2. Vibecode returns the user here after consent or decline
app.get('/callback', async (req, res) => {
  const { code, state, error } = req.query

  const session = sessions.get(state)
  if (!session) return res.status(400).send('Unknown state')
  sessions.delete(state)

  if (error === 'access_denied') {
    return res.redirect('/dashboard?connect=denied')
  }

  // 3. Exchange the code for an API key — a server-side request with client_secret
  const tokenRes = await fetch('https://vibecode.bitrix24.com/v1/connect/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code,
      redirect_uri: REDIRECT_URI,
    }),
  })
  const data = await tokenRes.json()

  if (!tokenRes.ok) {
    // Errors arrive in RFC form: { error, error_description }
    return res.status(400).send(`Exchange error: ${data.error}`)
  }

  // 4. Store the binding: our service user ↔ Bitrix24 account
  await db.connections.upsert({
    userId: session.userId,
    portalDomain: data.portal.domain,
    portalName: data.portal.name,
    apiKey: encrypt(data.api_key), // secret, encrypt before storing
    scopes: data.scopes,
  })

  res.redirect('/dashboard?connect=ok')
})

// 5. From here on we use the stored key for calls on behalf of the user
async function listDeals(userId) {
  const connection = await db.connections.findByUserId(userId)
  const apiKey = decrypt(connection.apiKey)

  const res = await fetch('https://vibecode.bitrix24.com/v1/deals?limit=10', {
    headers: { 'X-Api-Key': apiKey },
  })
  return res.json()
}

Key points of the scenario:

  • state is generated on the server and verified on return — without this check an attacker could slip the user someone else's code.
  • client_secret is kept only on the server and never reaches the browser.
  • The API key is encrypted before being written to the DB and decrypted only at the moment of a call.
  • On error=access_denied the user sees a clear message rather than an exchange error page.

Connecting AI from an application inside Bitrix24

An application that opens in a frame inside a Bitrix24 account cannot show the consent page in that frame: the consent page blocks embedding in other sites with the X-Frame-Options: SAMEORIGIN and Content-Security-Policy: frame-ancestors 'self' headers. This way it cannot be spoofed or hidden under someone else's interface. So the "Connect AI" button opens the consent page in a separate browser window, and the frame waits for the result from your server.

The steps are:

  1. The button in the frame opens a window at your server's address and passes a one-time attempt identifier, ticket, in the URL.
  2. The server remembers the attempt and redirects the window to /v1/connect/authorize with the vibe:ai scope.
  3. The user picks a Bitrix24 account and approves access. Vibecode returns the window to your redirect_uri.
  4. The server exchanges the code for a key, marks the attempt as finished and closes the window.
  5. The frame polls the server using the ticket and shows the result.

Code in the application frame:

html
<button id="connect-ai" type="button">Connect AI</button>
<p id="connect-ai-status" role="status"></p>

<script>
  const button = document.getElementById('connect-ai')
  const statusLine = document.getElementById('connect-ai-status')

  button.addEventListener('click', () => {
    // Open the window right in the click handler: the browser blocks a window opened after await
    const ticket = crypto.randomUUID()
    const popup = window.open(
      `/connect/ai/start?ticket=${ticket}`,
      'vibe-connect-ai',
      'popup,width=640,height=760',
    )
    if (!popup) {
      statusLine.textContent = 'Allow pop-up windows for this application and press the button again'
      return
    }
    statusLine.textContent = 'Approve access in the window that opened'
    waitForResult(ticket)
  })

  async function waitForResult(ticket) {
    const deadline = Date.now() + 10 * 60 * 1000 // 10 minutes for consent
    while (Date.now() < deadline) {
      await new Promise((resolve) => setTimeout(resolve, 2000))
      const res = await fetch(`/connect/ai/status?ticket=${ticket}`)
      const result = await res.json()
      if (result.status === 'ok') {
        statusLine.textContent = 'AI connected'
        return
      }
      if (result.status === 'denied') {
        statusLine.textContent = `AI not connected: ${result.reason}`
        return
      }
    }
    statusLine.textContent = 'Connection not finished. Press the button again'
  }
</script>

The application server does the same code exchange as in the full scenario and keeps the attempt state for the frame. db and encrypt are your storage and encryption. Register https://yourapp.com/connect/ai/callback with the client as a redirect_uri, and the vibe:ai scope must be among the client's scopes — see Platform scopes.

javascript
import express from 'express'
import crypto from 'node:crypto'

const app = express()
const attempts = new Map() // ticket → attempt result; use Redis or a database in production
const states = new Map() // state → ticket

const CLIENT_ID = process.env.PARTNER_CLIENT_ID
const CLIENT_SECRET = process.env.PARTNER_CLIENT_SECRET
const REDIRECT_URI = 'https://yourapp.com/connect/ai/callback'
const TICKET_RE = /^[0-9a-f-]{36}$/

// 1. The window opened by the button comes here and goes on to the consent page
app.get('/connect/ai/start', (req, res) => {
  const { ticket } = req.query
  if (typeof ticket !== 'string' || !TICKET_RE.test(ticket) || attempts.has(ticket)) {
    return res.status(400).send('Invalid connection attempt')
  }
  attempts.set(ticket, { status: 'pending' })

  const state = crypto.randomBytes(16).toString('hex')
  states.set(state, ticket)

  const params = new URLSearchParams({
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    scope: 'vibe:ai',
    state,
  })
  res.redirect(`https://vibecode.bitrix24.com/v1/connect/authorize?${params}`)
})

// 2. Vibecode returns the window here after consent or refusal
app.get('/connect/ai/callback', async (req, res) => {
  const { code, state, error, error_description: reason } = req.query
  const ticket = states.get(state)
  if (!ticket) return res.status(400).send('Unknown state')
  states.delete(state)

  if (error) {
    // error_description carries the machine-readable reason code — see "Refusal after the consent screen"
    attempts.set(ticket, { status: 'denied', reason: reason ?? error })
    return res.send(closeWindowPage('Access was not granted. You can close this window'))
  }

  const tokenRes = await fetch('https://vibecode.bitrix24.com/v1/connect/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'authorization_code',
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code,
      redirect_uri: REDIRECT_URI,
    }),
  })
  const data = await tokenRes.json()
  if (!tokenRes.ok) {
    attempts.set(ticket, { status: 'denied', reason: data.error })
    return res.send(closeWindowPage('The key was not received. You can close this window'))
  }

  await db.connections.upsert({
    portalDomain: data.portal.domain,
    apiKey: encrypt(data.api_key), // a secret, encrypt it before storing
    scopes: data.scopes,
  })
  attempts.set(ticket, { status: 'ok' })
  res.send(closeWindowPage('AI connected. You can close this window'))
})

// 3. The frame asks for the attempt result
app.get('/connect/ai/status', (req, res) => {
  res.json(attempts.get(req.query.ticket) ?? { status: 'unknown' })
})

function closeWindowPage(text) {
  return `<!doctype html><meta charset="utf-8"><p>${text}</p><script>window.close()</script>`
}

Key points:

  • The window opens synchronously in the click handler. The browser blocks a window opened after await as a pop-up.
  • The frame gets the result from its own server using the ticket, not from the window: the window goes to other sites, and polling your own server does not depend on what happens to the window along the way.
  • ticket is not a secret: it only shows the attempt state. The key stays on the server and never reaches the frame.
  • Polling is time-limited: the user may close the window without answering, and then nothing arrives at redirect_uri.
  • A refusal arrives in error and error_description, and the frame shows the reason code as is. The codes are covered in Refusal after the consent screen.

GET /v1/connect/authorize

Redirects the user to the Alaio Vibecode consent page.

Query parameters:

Parameter Type Required Description
Parameterclient_id Typestring Requiredyes DescriptionPartner identifier, received when you registered the client
Parameterredirect_uri Typestring Requiredyes DescriptionCallback URL. Must match one of those registered for the client
Parameterstate Typestring Requiredyes DescriptionAn arbitrary string that will be returned along with the code. CSRF protection
Parameterscope Typestring Requiredno DescriptionRequested rights, space- or comma-separated: crm task im. If not passed, the rights registered for the client apply. A request for a right outside the registered set is rejected
Parametercode_challenge Typestring Requiredconditional DescriptionSHA-256 hash of code_verifier in base64url without =. Required for public clients or when PKCE is mandatory, otherwise optional
Parametercode_challenge_method Typestring Requiredconditional DescriptionS256, required together with code_challenge. Other methods, empty values and a method without a challenge are rejected

For a confidential client, PKCE is optional unless required in the client settings. If you send code_challenge, the platform stores it and requires the matching code_verifier when exchanging the code. PKCE supplements client_secret, rather than replacing it. Public clients and clients with mandatory PKCE always need a challenge.

Example URL:

https://vibecode.bitrix24.com/v1/connect/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.com/callback&scope=crm%20task&state=abc123random

Server code that builds this link and checks state on return is in the full scenario. You can also build the link for your application without code: use the "Build a link" helper on the application card, see Partner registration.

Successful redirect after approval:

https://yourapp.com/callback?code=AUTH_CODE_HERE&state=abc123random

Redirect on decline by the user on the consent page:

https://yourapp.com/callback?error=access_denied&state=abc123random

Errors

Errors from this endpoint arrive by three different routes, and all three need handling.

Before redirect_uri is verified — a plain 400 with an RFC 6749 body. There is nowhere to send the user back to: the address is not yet confirmed, and redirecting to an unverified URL would be a security hole.

HTTP error Condition
HTTP400 errorinvalid_request ConditionOne of client_id, redirect_uri, state was not passed
HTTP400 errorinvalid_client Conditionclient_id is unknown or the client is disabled
HTTP400 errorinvalid_request Conditionredirect_uri is malformed: it does not parse as a URL, or it carries a # fragment, a username or a password. error_description is Redirect URI is malformed
HTTP400 errorinvalid_request ConditionThe redirect_uri scheme is not allowed for its host: not https on a non-localhost address, neither http nor https on localhost. error_description is Redirect URI scheme is not allowed for this host
HTTP400 errorinvalid_request Conditionredirect_uri matches none of the registered ones. error_description is Redirect URI not registered
JSON
{
  "error": "invalid_client",
  "error_description": "Unknown or inactive client"
}

After redirect_uri is verified — a redirect back to it carrying error and state (RFC 6749 §4.1.2.1). Your redirect_uri handler must parse this.

error in the redirect Condition
error in the redirectinvalid_scope ConditionA right outside the client's registered set was requested
error in the redirectinvalid_request ConditionRequired PKCE is missing, or the supplied code_challenge or code_challenge_method is invalid. Only S256 is supported
error in the redirectaccess_denied ConditionThe user pressed "Deny" on the consent page
error in the redirectaccess_denied ConditionThe user approved access, but no key could be issued: the Bitrix24 plan does not grant access to Vibecode, the region is unsupported, or the key limit has been reached. The machine-readable reason arrives in error_description
error in the redirecttemporarily_unavailable ConditionThe user approved access, but key issuance did not finish because Bitrix24 was temporarily unavailable. The attempt can be repeated
https://yourapp.com/callback?error=invalid_scope&state=abc123random

Rate limited — a 429 raised before any parameter is checked, so there is no redirect and no state. The limit is 30 requests per minute, counted for each client_id and caller-address pair. The exact value arrives in the x-ratelimit-limit header (the cap is divided across replicas). The body arrives in the general API envelope — { "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } } — rather than in RFC 6749 form, and the X-RateLimit-* headers are present.

This route needs handling of its own because authorize is opened by a browser navigation: the user lands on a page of JSON instead of the consent screen, redirect_uri receives nothing, and your application never gets its state back. Once your own timeout expires, treat such an authorization as unfinished rather than waiting for a callback. The Retry-After value gives the minimum pause.

The refusal to issue a key comes after the user has already approved access. They see the reason on the Vibecode consent page and return to your application via a button — at which point redirect_uri receives error, error_description and the original state:

https://yourapp.com/callback?error=access_denied&error_description=INT_TARIFF_REQUIRED&state=abc123random

error_description carries the machine-readable reason, so the application can render its own screen:

Code in error_description What happened
Code in error_descriptionINT_TARIFF_REQUIRED What happenedThe account's Bitrix24 plan does not grant access to Vibecode. Where access requires a Vibe+ plan, INT_VIBE_PLUS_REQUIRED arrives instead
Code in error_descriptionKEY_LIMIT_REACHED What happenedA key cannot be issued: the user's key limit or the platform's per-account key cap has been reached — see Key limit
Code in error_descriptionBITRIX_UNAVAILABLE What happenedBitrix24 did not respond while the key was being issued
Code in error_descriptionSTATE_EXPIRED What happenedThe consent link expired before confirmation — the authorization starts over from /v1/connect/authorize
Code in error_descriptionadmin_approval_requested What happenedThe user is not an administrator of the Bitrix24 account and asked the administrators to allow AI for the app — the request reached them in the bot chat. Arrives when the platform has enabled AI approval by account administrators. Once an administrator allows AI, the authorization starts over from /v1/connect/authorize

The list of codes is open-ended: treat any error_description as a refusal reason instead of relying on the table above being exhaustive.

When access is refused for a payment-related reason, the user sees an inline payment card on the consent page. Its actions apply to the selected Bitrix24 account. For INT_TARIFF_REQUIRED, it offers to select a Bitrix24 plan; for INT_VIBE_PLUS_REQUIRED, a Vibe+ plan. Depending on the selected account, the INT_TARIFF_REQUIRED card may also offer Vibe+. If a Vibe+ trial is available for that account, the card offers to activate it. A Bitrix24 trial plan is activated in Bitrix24 itself, if one is available there.

Nothing reaches redirect_uri until the user chooses to return to your application. Returning after this refusal sends error=access_denied, the machine-readable reason in error_description, and the original state; no code or API key is issued. After a purchase or trial activation, return to your application and start authorization again from /v1/connect/authorize: the previous link's one-time state has already been consumed.

The full reference of general API errors — Errors.

POST /v1/connect/token

Exchanges a one-time authorization code for a permanent API key. Accepts application/json and application/x-www-form-urlencoded.

Request fields (body):

Field Type Required Description
Fieldgrant_type Typestring Requiredyes Descriptionauthorization_code for this flow. Without it the endpoint responds with unsupported_grant_type. The same endpoint also serves urn:ietf:params:oauth:grant-type:device_code (see Signing in from a device without a browser)
Fieldclient_id Typestring Requiredyes DescriptionPartner identifier
Fieldclient_secret Typestring Requiredyes DescriptionPartner secret. Pass only from the server side
Fieldcode Typestring Requiredyes DescriptionThe code from the code parameter of the redirect
Fieldredirect_uri Typestring Requiredyes DescriptionThe same redirect_uri that was passed to /v1/connect/authorize
Fieldcode_verifier Typestring Requiredconditional DescriptionOriginal PKCE string. Required if code_challenge was sent during authorization, including for confidential clients

Examples

curl

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d code=RECEIVED_CODE \
  -d redirect_uri=https://yourapp.com/callback

JavaScript

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/connect/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'authorization_code',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    code: receivedCode,
    redirect_uri: 'https://yourapp.com/callback',
  }),
})

const data = await res.json()
// data.api_key — permanent Vibecode API key
// data.portal — the Bitrix24 account selected by the user
// data.scopes — the requested scopes, data.granted_scopes — the key's rights
// data.user — the user who granted access

Response fields

Field Type Description
Fieldapi_key Typestring DescriptionVibecode API key in the vibe_api_... format. Pass it in the X-Api-Key header on subsequent calls
Fieldportal.domain Typestring DescriptionBitrix24 account domain, for example example.bitrix24.com
Fieldportal.name Typestring DescriptionBitrix24 account name
Fieldscopes Typestring[] DescriptionThe rights the application requested. On the consent page the user confirms the set as a whole
Fieldgranted_scopes Typestring[] DescriptionThe rights the issued key actually carries. For a self-registered partner application they usually match scopes — they diverge if the key's rights were changed after consent
Fielduser.name Typestring DescriptionName of the user who granted access
Fielduser.email Typestring DescriptionThe user's email

The two sets diverge only for applications whose scope set is assigned by the platform itself — the Cowork desktop application, for example. Such an application may request nothing at all, in which case scopes arrives empty while granted_scopes lists the key's full set. Check access against granted_scopes.

The granted_scopes field may be absent from the response if the platform could not read the issued key's rights. It never arrives empty, so a missing field means the set is unknown, not that there are no rights — in such a response, go by scopes.

The set in granted_scopes reflects what is written into the key. An individual right may not be confirmed on the Bitrix24 side, so diagnose a refusal by the Bitrix24 response code rather than by the presence of a right in the list.

Response example

JSON
{
  "api_key": "vibe_api_...",
  "portal": {
    "domain": "example.bitrix24.com",
    "name": "My company"
  },
  "scopes": ["crm", "task"],
  "granted_scopes": ["crm", "task"],
  "user": {
    "name": "John Smith",
    "email": "john@example.com"
  }
}

Error response example

400 — a required parameter is missing:

JSON
{
  "error": "invalid_request",
  "error_description": "Missing client_id, code or redirect_uri"
}

Errors

The endpoint responds in RFC 6749 form — { error, error_description }, with no success wrapper. This is deliberate, for compatibility with off-the-shelf OAuth libraries. The only exception is the refusal from the endpoint's own rate limiter, whose shape is described after the table.

HTTP error Condition
HTTP400 errorinvalid_request ConditionOne of client_id, code, redirect_uri was not passed
HTTP400 errorinvalid_grant ConditionThe code does not exist, has expired, has already been used, or redirect_uri differs from the one passed to /v1/connect/authorize
HTTP400 errorinvalid_grant ConditionThe PKCE check failed: code_verifier is missing or does not match the code_challenge sent earlier
HTTP400 errorinvalid_client ConditionA public client sent a client_secret — a public client is issued no secret and must not send one
HTTP400 errorunsupported_grant_type Conditiongrant_type was not passed or is not one of the modes listed in the grant_type field above
HTTP401 errorinvalid_client Conditionclient_id is unknown, the client is disabled, or client_secret does not match
HTTP403 erroraccess_denied ConditionThe client is in COMPANY mode, and Company key issuance is closed. code and error_description are CONNECT_COMPANY_NOT_READY; see Connection mode
HTTP400 errorunauthorized_client ConditionA device sign-in poll from a client in COMPANY mode. code and error_description are CONNECT_COMPANY_DEVICE_FLOW_FORBIDDEN
HTTP429 errorslow_down ConditionThe rate limit at the platform edge was exceeded. The limit is counted per caller address and is shared by the code exchange and the device poll. The response carries Retry-After with the minimum pause in seconds and carries no X-RateLimit-* headers

There are two rate limiters, and both answer 429. Tell them apart by the X-RateLimit-Limit header. The edge refusal from the table above does not carry that header and arrives in RFC 6749 form. The endpoint's own refusal does carry it, and its body arrives in the general API envelope — { "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } }. The action is the same in both cases: wait and retry with a longer pause.

Refusals of the prepared Company handover arrive with HTTP 403, 409 and 503 and a code field; they are listed in Prepared Company handover protocol.

The full reference of general API errors — Errors.

Public client: the PKCE flow

Everything above describes a confidential client — one that has a server and a client_secret. If there is no server and nowhere to hide a secret (a mobile application, a desktop program, a single-page web application), register a public client. It is issued no secret; authenticity is proven by PKCE instead.

Three things differ from the main flow; everything else is identical.

1. The application generates a pair on every run of the flow. A random string (code_verifier) and its SHA-256 hash in base64url without the = padding (code_challenge).

javascript
import crypto from 'node:crypto'

const b64url = (buf) => buf.toString('base64url')
const verifier  = b64url(crypto.randomBytes(32))
const challenge = b64url(crypto.createHash('sha256').update(verifier).digest())

Keep verifier until the flow ends; challenge goes into the first request.

2. Two parameters are added to /v1/connect/authorize.

Parameter Value
Parametercode_challenge ValueThe hash from step 1
Parametercode_challenge_method ValueAlways S256. Other values are rejected

For a public client they are mandatory: without them the flow ends in a ?error=invalid_request redirect.

3. /v1/connect/token takes code_verifier instead of client_secret.

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/connect/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "client_id": "YOUR_CLIENT_ID",
    "code": "RECEIVED_CODE",
    "redirect_uri": "http://127.0.0.1:9999/callback",
    "code_verifier": "CODE_VERIFIER"
  }'

The response is the same as for a confidential client:

JSON
{
  "api_key": "vibe_api_...",
  "scopes": ["crm"],
  "granted_scopes": ["crm"],
  "portal": { "domain": "example.bitrix24.com", "name": "My company" },
  "user": { "name": "John Smith", "email": "john@example.com" }
}

A public client must not send client_secret — the platform responds with 400 invalid_client. This is not pedantry: a secret shipped inside a distributed application is not a secret, and the flow is built so that such a secret cannot be used.

Returning to localhost

A program with no site of its own has no public address to receive the code at. For such cases a loopback redirect is allowed: register a redirect_uri such as http://127.0.0.1:9999/callback, start a temporary listener on that port for the duration of the flow, and close it as soon as the code arrives.

127.0.0.1, localhost and [::1] are accepted, with either http or https. Every other address must be https. The scheme and the path are always matched; whether the port and the localhost address itself are matched depends on the client type:

Client type How a localhost address is matched
Client typePublic How a localhost address is matchedThe port is not part of the match — if it is taken, the application may listen on any other one without re-registering the address. The addresses 127.0.0.1, localhost and [::1] are interchangeable: a registered http://127.0.0.1/callback also accepts http://localhost:5123/callback
Client typeConfidential How a localhost address is matchedThe whole address is matched, port included. This is how a server application is debugged on your own machine: register the address with the port your server listens on, for example http://localhost:3030/callback

The name localhost can point somewhere other than this machine if the network settings override it, so 127.0.0.1 is the more reliable choice.

Signing in from a device without a browser

A TV, a set-top box, a terminal utility — anywhere typing a login and password is awkward. The user confirms the sign-in on a phone or computer, while the device receives access. The flow is defined by RFC 8628.

The device needs device mode enabled: a platform administrator grants it, and only to verified applications. A self-registered client gets 400 unauthorized_client — this protects users from having a confirmation code shown to them by an unverified application.

Step 1. The device requests a code.

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/connect/device/authorize \
  -H "Content-Type: application/json" \
  -d '{"client_id":"YOUR_CLIENT_ID","scope":"crm","code_challenge":"HASH","code_challenge_method":"S256"}'
JSON
{
  "device_code": "MYQnuWB3H0tR...",
  "user_code": "54BV-XT8F",
  "verification_uri": "https://vibecode.bitrix24.com/connect/device",
  "verification_uri_complete": "https://vibecode.bitrix24.com/connect/device?user_code=54BV-XT8F",
  "expires_in": 899,
  "interval": 5
}

device_code is the device secret and is never shown on screen. user_code is displayed to the user together with the address. expires_in is how many seconds the user has to confirm.

The request accepts application/json and application/x-www-form-urlencoded.

Field Type Required Description
Fieldclient_id Typestring Requiredyes DescriptionIdentifier of an application for which a platform administrator has enabled device sign-in
Fieldscope Typestring Requiredno DescriptionRequested scopes, separated by spaces or commas. If omitted, the scopes registered for the client apply
Fieldcode_challenge Typestring Requiredconditional DescriptionSHA-256 hash of code_verifier in base64url without =. Required if PKCE is enabled in the client settings. The platform stores a supplied hash and checks it against code_verifier during polling
Fieldcode_challenge_method Typestring Requiredconditional DescriptionS256, required together with code_challenge
Fielddevice_id Typestring Requiredno DescriptionA stable identifier of the application installation: up to 128 characters, Latin letters, digits and the characters ., :, -, _. The platform adds it to verification_uri_complete as the device_id parameter so the steps of one sign-in stay linked even if the link is opened in another browser. A value of any other shape is dropped without an error: the response stays 200 and the link arrives without the parameter. It does not affect key issuance for a third-party application
Fieldlogin_source Typestring Requiredno DescriptionYour analytics label for the sign-in button: a lowercase Latin letter followed by lowercase Latin letters, digits and _, up to 64 characters in total. Surrounding spaces are trimmed. An invalid label does not interrupt sign-in and does not affect permissions or key issuance

Refusals of this step arrive in RFC 6749 form — { error, error_description }:

HTTP error Condition
HTTP400 errorinvalid_request Conditionclient_id was not passed. error_description is Missing client_id
HTTP401 errorinvalid_client Conditionclient_id is unknown or the client is disabled
HTTP400 errorunauthorized_client ConditionDevice sign-in is not enabled for this client. error_description is Client is not enabled for the device flow
HTTP400 errorunauthorized_client ConditionThe client is in COMPANY mode. code and error_description are CONNECT_COMPANY_DEVICE_FLOW_FORBIDDEN
HTTP400 errorinvalid_request ConditionThe client requires PKCE and code_challenge was not passed, or the method is not S256 or the hash is malformed
HTTP400 errorinvalid_scope ConditionAn unknown scope was requested, or a scope outside the client's registered set

This step is rate limited too — 30 requests per minute by caller address, with client_id not part of the rate-limit key. On top of that the platform-edge limiter applies, shared by the whole of /v1/. Exceeding either returns 429 in the general API envelope with a Retry-After header; the presence of X-RateLimit-Limit shows which one fired.

Step 2. The user opens the address and confirms. The confirmation screen shows the application card, the requested rights, the Bitrix24 account picker, and a phishing warning: confirm only a sign-in you started yourself.

Step 3. The device polls /v1/connect/token.

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/connect/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "client_id": "YOUR_CLIENT_ID",
    "device_code": "DEVICE_CODE",
    "code_verifier": "CODE_VERIFIER"
  }'

Until the user confirms, the answer is 400 with one of these states:

error What to do
errorauthorization_pending What to doWait and keep polling at the interval cadence
errorslow_down What to doPolling is faster than allowed. Add 5 seconds to the interval and continue
erroraccess_denied What to doThe user declined. Stop polling
erroraccess_denied with error_description=admin_approval_requested What to doThe user asked an administrator of the Bitrix24 account to allow AI for the app. Arrives when the platform has enabled AI approval by account administrators. Stop polling and, after the administrator decides, request a new code from step 1
errorexpired_token What to doThe code expired. Request a new one from step 1

A poll can also receive 429 — that is a rate-limiter refusal, not an authorization state, and it comes in two forms, because this route has two limiters. The platform-edge refusal arrives in RFC 6749 form (error set to slow_down) and carries no X-RateLimit-* headers. The endpoint's own limiter refuses in the general API envelope — { "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } } — and does carry X-RateLimit-*. A branch on error === "slow_down" alone is therefore not enough: it misses half the refusals.

Handle both the same way: wait at least as long as Retry-After says and keep polling, without resetting your own growing pause. If you need to tell them apart, check for X-RateLimit-Limit.

After confirmation the same request returns 200 with the key and two scope sets:

JSON
{ "api_key": "vibe_api_...", "scopes": ["crm"], "granted_scopes": ["crm"] }

Note that this object is narrower than in the main flow — no portal and no user blocks.

The key is delivered once. A repeat poll with the same device_code returns expired_token, so store the key immediately.

What the device must handle

  • Show a countdown. The code lives for 15 minutes; when it runs out, show a new one rather than a dead screen.
  • Respect interval and react to slow_down. Polling faster than allowed does not speed up delivery — it produces refusals.
  • Read Retry-After and grow your own pause. The header value is a minimum pause, not a guarantee that the next request will be accepted: every running copy of the application competes for the same per-address limit. Wait at least as long as Retry-After says, and still increase your own pause on every refusal.
  • Never display device_code. Only user_code goes on screen.

Using the API key

The received key is of type APP and works like a standard Vibecode API key. Pass it in the X-Api-Key header:

Terminal
curl -H "X-Api-Key: vibe_api_..." \
  https://vibecode.bitrix24.com/v1/deals

The same key can be used with any Vibecode API endpoint within the confirmed scopes: deal lists, batch requests, aggregation, and so on.

A partner key carries exactly the permissions the user confirmed on the consent page: platform scopes are never added to the set on their own. If you need the AI Router (/v1/chat/completions), request vibe:ai along with the rest (here is how). Skip it and the call returns 403. Web search (/v1/search), vibe:infra and vibe:feedback are granted by a platform administrator, and vibe:storage is not available to a Connect application — the same section covers both.

When the platform has enabled AI approval by account administrators, an administrator of the Bitrix24 account grants AI access to an app — on the consent page or at an employee's request. Until it is enabled, any employee who confirms access grants AI access to the app, and the restrictions in this paragraph do not apply. If the app requests vibe:ai, the user confirming consent is not an administrator, and AI access has not been granted to the app on that account, the consent page refuses with CONNECT_AI_ADMIN_REQUIRED and offers to ask an administrator; after the request, redirect_uri receives error=access_denied with error_description=admin_approval_requested. Until AI access is granted, or after an administrator revokes it, the model routes (/v1/chat/completions, /v1/embeddings, /v1/models and others) return 403 with the code CONNECT_AI_NOT_PERMITTED. The key's other permissions keep working and the key does not need to be reissued: once an administrator grants AI access, the same key can access the models again.

The partner key works with a single X-Api-Key header and does not require Authorization: Bearer <session_token>. The prefix tells the two apart at a glance: a partner key starts with vibe_api_, an OAuth application key from the Vibecode catalog with vibe_app_, and the latter does require a user session.

Available scopes

When calling /v1/connect/authorize, pass the same scopes you would when creating a standard API key in a Bitrix24 account. On the consent page the user sees the list of requested rights and confirms it as a whole — an individual item cannot be removed; only "Allow" and "Deny" are available. The final set arrives in the granted_scopes field of the /v1/connect/token response, with the requested one in the neighbouring scopes field.

The current list of supported values is described in the Keys and authorization section — it grows as new Bitrix24 modules appear, so it is not duplicated here.

The Vibecode platform scopes fall into three groups: an application requests vibe:ai itself; vibe:search, vibe:infra and vibe:feedback are granted by a platform administrator; and vibe:storage is not available to a Connect application. The procedure is in Platform scopes.

A machine-readable list of what an application may request on its own is served in the scopes_supported field of the /.well-known/oauth-authorization-server document (RFC 8414). Scopes an administrator granted to one specific application never appear in that anonymous document.

Key lifetime and revocation

  • The authorization code (code) is valid for 5 minutes and is single-use. After the exchange via /v1/connect/token, a repeat call with the same code returns invalid_grant.

  • The consent state (the internal state of the consent page) lives for 10 minutes — if the user does not confirm within that time, the link has to be issued again.

  • The API key has no expiration. It works until it is revoked. Revocation happens in one of three ways:

    1. The user revokes access themselves — the "Connected apps" section of their Vibecode profile. That revokes every key they issued to your application for that Bitrix24 account, including keys from earlier authorizations. Access granted by other employees on the same account is not affected.
    2. The application revokes its own key — POST /v1/connect/revoke, see below. Exactly one key is revoked: the one you presented.
    3. A platform administrator revokes the keys the client has issued.

    Deactivating a client does NOT revoke keys — it only stops new issuance: with an inactive client /v1/connect/authorize rejects the request, while keys already issued keep working.

When a key is revoked, all requests with it return 401 KEY_INACTIVE. If the key is deleted or unknown, the response is 401 INVALID_API_KEY. The partner must handle both codes correctly and prompt the user to re-authorize.

Revoking a key from the application: `POST /v1/connect/revoke`

The endpoint follows RFC 7009 and revokes one key — the one you present in it. Call it when the user disables your integration on your side: otherwise the earlier keys keep working and keep occupying slots in the key limit (see below).

POST /v1/connect/revoke
Content-Type: application/x-www-form-urlencoded

client_id=your-app&client_secret=your-secret&token=<api_key>

JSON with the same fields is accepted too. Public (PUBLIC) clients send no client_secret — they have none; confidential clients must send it.

Responses:

  • 200 {} — either the key was revoked, or there was nothing to revoke. An unknown token, a key issued to another application and an already revoked key all get the same answer, so you cannot probe whether someone else's keys are alive. That also makes the call idempotent: repeating it is safe.
  • 400 invalid_request — client_id or token is missing.
  • 400 invalid_client — a public client presented a client_secret.
  • 401 invalid_client — unknown client, or the secret did not match.
  • 409 KEY_MANAGES_FOREIGN_SERVER — the key manages a live server owned by another user, a server without an owner, or a server owned by the same user in another portal. Set the server's owner and intended portal, rebind it to a key owned by that user in that portal, then retry revocation.
  • 409 KEY_OPERATION_IN_PROGRESS — another operation is changing this key. Retry when it finishes. Both refusal bodies contain error, error_description and code.
  • 429 — rate limited, and there are two limiters here. The endpoint's own limiter: 60 requests per minute. The exact value arrives in the x-ratelimit-limit header (the cap is divided across replicas). Counted by caller address alone — client_id is deliberately not part of the rate-limit key, so rotating it yields no fresh budget, and every copy of an integration behind one address shares those 60; it does set X-RateLimit-* headers. The platform-edge limiter: 60 requests per second per address (counted at the edge, so not divided across replicas), a bucket shared by the whole of /v1/ rather than by this endpoint; it sets no X-RateLimit-* headers and its Retry-After is always 1. In both cases the body arrives in the general API envelope — { "success": false, "error": { "code": "RATE_LIMITED", "message": "..." } } — which differs from the RFC 7009 shape of this endpoint's other answers, so the body cannot tell the two apart, only the presence of X-RateLimit-Limit can. The call is not terminal: the key was not revoked, so repeat it after the pause named in Retry-After.

A key issued before your client was deactivated can be revoked as well: deactivation stops new issuance but does not block revoking the old keys.

What the endpoint does not do:

  • it does not remove the webhook on the Bitrix24 account — the user deletes that on their side, and revoking a key is no substitute;
  • it does not bring a lost key back — to revoke a key you have to present it, and it cannot be recovered;
  • it does not fire by itself — if the user simply stops using the integration, the key lives on; calling the endpoint is your responsibility.

If the presented key was issued to your client and was active, a successful revocation stops it immediately: requests with it return 401 KEY_INACTIVE, and its slot in the limit is freed. A 200 {} response alone does not prove revocation: an unknown key, a key issued to another client, and an already revoked key receive the same response.

Re-authorization issues a new key

Every pass through the consent screen issues a new API key. The previous key for the same user and Bitrix24 account keeps working: authorization does not revoke it. The user's profile shows both issuances as one connection, and revoking access invalidates them together.

What this means for your application:

  • store the key you received last and replace the previous one with it, otherwise you accumulate working keys you no longer track,
  • every re-authorization takes a slot in the key limit — see below,
  • a key that has lost its validity cannot be restored: obtain a new one through /v1/connect/authorize.

Key limit

One user on one Bitrix24 account holds at most 100 valid keys — this is both the default and the highest value the setting accepts. A Bitrix24 account administrator can set another value, from 1 to 100. The count covers every key the user holds on that account, not only the ones your application issued.

In addition to the per-user limit, there is the platform's per-account key cap. It is counted across all live keys on the account and cannot be raised by a Bitrix24 account administrator. Once either limit is reached, issuance is refused with the code KEY_LIMIT_REACHED: the user sees the reason on the consent page, while the application receives only that code in error_description on the return to redirect_uri. For the per-user limit, a slot is freed by deleting unneeded keys in the "API Keys" section of the Vibecode dashboard, or by revoking the application's access in the profile. For the per-account cap, the consent screen asks the user to choose a plan with a higher key limit. Deleting keys also frees capacity once the total number of valid keys on the account falls below the cap.

Managing apps in a Bitrix24 account

An administrator of the selected Bitrix24 account sees every employee's apps in the profile, on the "All apps in the account" tab: connections, AI requests and each app's share of the quota. The "AI Models" page adds a "By app" breakdown.

"Allow AI" approves a pending request or grants permission directly. Permission does not create a key: the employee has to connect again. "Block AI" stops model calls while the key keeps its other scopes. "Disconnect for everyone" revokes every key of the app in this Bitrix24 account and the tokens derived from them. Usage for the current period stays in the reports.

These controls are available when the platform has enabled AI approval by account administrators. Keys in other Bitrix24 accounts are not affected.

Security

  • State parameter — generate a cryptographically strong random string for each request and verify it on return to redirect_uri. Protects against CSRF.
  • client_secret — server only. Never pass the secret to a browser, mobile app, or client-side code. The code-for-key exchange is performed only by a server-side request.
  • Key storage. The API key is a secret that grants access to the user's data. Store it encrypted, restrict access to the stored connection record, do not log it.
  • redirect_uri binding. The value in /v1/connect/authorize and /v1/connect/token must match character for character — a mismatch results in invalid_grant.

Partner registration

You register the client yourself in the Vibecode dashboard — the Connect apps section in the sidebar, under the "Access" group. Sign in with your Vibecode account and click "Register app" — the client_id is issued immediately, with no request to file and no upfront review.

The form asks for:

  • the name and a short description of the application,
  • an identifier — lowercase Latin letters, digits and hyphens,
  • one or more redirect_uri values — https:// addresses or the local 127.0.0.1, localhost and [::1] over http or https,
  • the access rights you request.

The set of rights cannot be empty: registration without a single right returns EMPTY_SCOPES. An application with no rights cannot request anything from the user.

The client type is chosen at creation and cannot be changed later — see Client types for a walkthrough with examples.

The client_secret is shown once, at creation. You cannot read it again, only rotate it with the "Rotate secret" action.

The ⋮ menu on the application card carries two helpers: "Build a link" — an authorization-link builder (pick the redirect_uri and the scopes, get a ready URL and the curl for the code exchange; for a client without a secret, a one-time PKCE pair of strings), and "Client's view" — a preview of the consent page exactly as the user will see it, verification badge included.

The number of active clients per user is limited. Once the limit is reached, registration returns CLIENT_LIMIT_REACHED and the current value arrives in the limit field. Deactivating a client frees a slot.

A new application gets the "unverified" status — the user sees that mark on the consent page. A platform administrator can raise the status, and the request is sent with the "Request verification" action on the application card. A request can be declined with a reason — the reason reaches the owner of the application, and once the fixes are made, the request is submitted again.

Platform scopes

Vibecode scopes grant access to sections of the platform itself rather than to Bitrix24 data. For a Connect application they fall into three groups.

An application requests vibe:ai itself. Tick it among the scopes at registration — in the same place as the Bitrix24 scopes — and from there it travels the usual path: it is passed in the scope parameter of /v1/connect/authorize, shown to the user on the consent page as an "AI models" line, and lands on the issued key. No request form and no review are needed for this. Consumption is covered by the monthly AI quota of the account the application connects to.

The scope is never added automatically: an application that did not tick it works without AI Router access, and previously issued keys keep their scope sets. To enable AI for an existing connection, tick the scope on the application card and walk the user through consent again — they will receive a new key that carries it.

vibe:search, vibe:infra and vibe:feedback are granted by a platform administrator. At self-registration a request carrying them is rejected with SCOPE_NOT_ALLOWED. The procedure takes three steps, and the owner of the application performs the first two:

  1. Submit a request with the "Request verification" action on the application card.
  2. In the "Discussion with the platform" thread on that same card, write which scope you need and for which scenario. The request carries no dedicated "requesting this scope" field, so without a message the reviewer only sees a request for the badge.
  3. The administrator grants the scope to a verified application. Verification on its own grants no scopes — these are two separate decisions, and the first one is a precondition for the second.

Once granted, the scope behaves like vibe:ai: passed in scope, shown on the consent page, landing on the key.

vibe:storage is not available to a Connect application. A key issued through Connect lands in the same Storage area as the personal keys of the connecting user in that Bitrix24 account. With this scope, the application would see every file uploaded with that user's personal keys. For this reason, not even a platform administrator grants this scope, and a request carrying it at self-registration is rejected with SCOPE_NOT_ALLOWED.

What removes the "verified" mark

The mark rests on the shape the application had when it passed review, so editing any of these five fields returns it to "unverified":

  • the name,
  • the description,
  • the logo,
  • the redirect_uri return addresses,
  • the set of rights you request.

The site address does not affect the mark. Saving the same description again does not remove the mark; a missing description and an empty string are equivalent.

The dashboard warns you before saving: the form shows a confirmation listing the fields that will remove the mark. Without that confirmation the edit is not saved, and the response is VERIFICATION_RESET_NOT_CONFIRMED carrying the same list in its body.

Populating a previously empty set of rights does not remove the mark when the chosen rights fit inside the set granted by the administrator.

The reset touches the mark alone. Issued keys, connected users, platform scopes and an already-granted device-code sign-in all stay in place. Submit the verification request again.

Client types

The type answers one question: does the application have a place the user cannot reach, where a secret can live? The answer decides whether a client_secret is issued.

Confidential

The application has a server side. The client_secret lives there and never reaches the browser or the user's device.

Examples: a SaaS analytics service that visits the customer's Bitrix24 account on a schedule. An ERP sync running on your own server. A chat assistant that receives webhooks and answers from its backend.

This is the type the flow on this page needs: the code is exchanged for a key by a server-side request carrying the client_secret.

Public

There is nowhere to hide a secret — the code runs entirely on the user's device. No client_secret is issued. Authenticity is proven by PKCE instead: on every run of the flow the application generates a random string, sends its hash to /v1/connect/authorize, and the string itself to /v1/connect/token. An intercepted code cannot be exchanged without it.

Examples: a mobile application. A desktop program. A single-page web application with no backend of its own.

How to choose

If the application has a server you control, pick confidential. If all the code ships to the user, pick public.

The type is set once, at registration. To change it, register a new client.

See also