Para agentes de IA: markdown desta página — /docs-content-en/partner-connect.md índice da documentação — /llms.txt
Os artigos da documentação estão disponíveis atualmente em inglês.
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
- When to use
- How it works
- Endpoint reference
- Full scenario — an Express handler end to end
GET /v1/connect/authorize— flow start, redirect to consentPOST /v1/connect/token— exchanging the code for an API key- Public client: the PKCE flow — an application with no server
- Signing in from a device without a browser — TV, set-top box, terminal
- Using the API key
- Available scopes
- Key lifetime and revocation
- Security
- Partner registration
- Client types — public or confidential, with examples
- See also
When to use
The scenario is 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] ───────────┘
- Redirect — you send the user to
/v1/connect/authorizewithclient_id,redirect_uri,state, and a list of scopes. - Consent — the user sees a Vibecode page, selects a Bitrix24 account, and confirms or declines the requested scopes.
- Code — after approval the user returns to
redirect_uriwith thecodeandstateparameters. On decline —redirect_uri?error=access_denied&state=.... - Exchange — the partner's server sends
POST /v1/connect/tokenand receives an API key.
Endpoint reference
| Method | Path | Description |
|---|---|---|
| GET | /v1/connect/authorize |
Flow start: redirect to the consent page |
| POST | /v1/connect/token |
Exchange a one-time code for a permanent API key |
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.
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 portal
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:
stateis generated on the server and verified on return — without this check an attacker could slip the user someone else's code.client_secretis 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_deniedthe user sees a clear message rather than an exchange error page.
GET /v1/connect/authorize
Redirects the user to the Alaio Vibecode consent page.
Query parameters:
| Parameter | Type | Req. | Description |
|---|---|---|---|
client_id |
string | yes | Partner identifier, received when you registered the client |
redirect_uri |
string | yes | Callback URL. Must match one of those registered for the client |
state |
string | yes | An arbitrary string that will be returned along with the code. CSRF protection |
scope |
string | no | Requested 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 |
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
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 |
|---|---|---|
| 400 | invalid_request |
One of client_id, redirect_uri, state was not passed |
| 400 | invalid_client |
client_id is unknown or the client is disabled |
| 400 | invalid_request |
redirect_uri matches none of the registered ones |
{
"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 |
|---|---|
invalid_scope |
A right outside the client's registered set was requested |
invalid_request |
The client requires PKCE and code_challenge is missing or its method is not S256 |
access_denied |
The user pressed "Deny" on the consent page |
access_denied |
The 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 |
temporarily_unavailable |
The 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 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. Treat such an authorization as unfinished on your own timeout rather than waiting for a callback. The Retry-After value names the minimum pause.
Refusal after the consent screen
A key issuance refusal happens 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 |
|---|---|
INT_TARIFF_REQUIRED |
The account's Bitrix24 plan does not grant access to Vibecode. Where access requires a Vibe+ plan, INT_VIBE_PLUS_REQUIRED arrives instead |
REGION_NOT_SUPPORTED |
The region of the Bitrix24 account is not supported |
KEY_LIMIT_REACHED |
The user has reached the key limit on this account — see Key limit |
BITRIX_UNAVAILABLE |
Bitrix24 did not respond while the key was being issued |
STATE_EXPIRED |
The consent link expired before confirmation — 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.
The user may stay on the consent page and upgrade the plan, in which case nothing arrives at redirect_uri. The one-time consent link is already spent by then, so after the upgrade the authorization starts over from /v1/connect/authorize.
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 | Req. | Description |
|---|---|---|---|
grant_type |
string | yes | Always authorization_code. Without it the endpoint responds with unsupported_grant_type |
client_id |
string | yes | Partner identifier |
client_secret |
string | yes | Partner secret. Pass only from the server side |
code |
string | yes | The code from the code parameter of the redirect |
redirect_uri |
string | yes | The same redirect_uri that was passed to /v1/connect/authorize |
Examples
curl
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
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 portal 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 |
|---|---|---|
api_key |
string | Vibecode API key in the vibe_api_... format. Pass it in the X-Api-Key header on subsequent calls |
portal.domain |
string | Bitrix24 portal domain, for example example.bitrix24.com |
portal.name |
string | Bitrix24 portal name |
scopes |
string[] | The rights the application requested. On the consent page the user confirms the set as a whole |
granted_scopes |
string[] | The rights the issued key actually carries. For a self-registered partner application they usually match scopes — they diverge if the key rights were changed after consent |
user.name |
string | Name of the user who granted access |
user.email |
string | The 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
{
"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:
{
"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. One exception is the refusal from the endpoint's own rate limiter, whose shape is described after the table.
| HTTP | error |
Condition |
|---|---|---|
| 400 | invalid_request |
One of client_id, code, redirect_uri was not passed |
| 400 | invalid_grant |
The code does not exist, has expired, has already been used, or redirect_uri differs from the one passed to /v1/connect/authorize |
| 400 | invalid_grant |
The PKCE check failed: code_verifier does not match the code_challenge sent earlier |
| 400 | invalid_client |
A public client sent a client_secret — a public client is issued no secret and must not send one |
| 400 | unsupported_grant_type |
grant_type was not passed or is not authorization_code |
| 401 | invalid_client |
client_id is unknown, the client is disabled, or client_secret does not match |
| 429 | slow_down |
The 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.
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).
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 |
|---|---|
code_challenge |
The hash from step 1 |
code_challenge_method |
Always 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.
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:
{
"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 it cannot be used.
Returning to localhost
A program with no site of its own has no public address to receive the code at. For those cases a return to itself 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.
The port is not part of the match — if it is taken, the application may listen on any other one without re-registering the address. 127.0.0.1, localhost and [::1] are accepted, and http is allowed for them. Every other address must be https.
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.
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"}'
{
"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.
This step is rate limited too — 30 requests per minute by caller address, with client_id not part of the 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; which one fired is visible from the presence of X-RateLimit-Limit.
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.
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 |
|---|---|
authorization_pending |
Wait and keep polling at the interval cadence |
slow_down |
Polling is faster than allowed. Add 5 seconds to the interval and continue |
access_denied |
The user declined. Stop polling |
expired_token |
The 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:
{ "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 15 minutes; when it runs out, show a new one rather than a dead screen.
- Respect
intervaland react toslow_down. Polling faster than allowed does not speed up delivery — it produces refusals. - Read
Retry-Afterand 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 asRetry-Aftersays, and still increase your own pause on every refusal. - Never display
device_code. Onlyuser_codegoes 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:
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 is not granted the platform scopes vibe:ai (AI Router) and vibe:search (web search) — it carries exactly the permissions the user confirmed on the consent page. Calls to the AI Router (/v1/chat/completions) and web search (/v1/search) through a partner key therefore return 403 by default.
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 rights (vibe:ai, vibe:search, vibe:infra, vibe:storage, vibe:feedback) cannot be requested through self-registration — see Partner registration.
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 will returninvalid_grant.The consent state (the internal state of the consent page) lives 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 four ways:
- The user revokes access themselves — the "Connected apps" section of their Vibecode profile. That kills every key they issued to your application for that Bitrix24 account, including keys from earlier authorizations. Access granted by other employees of the same account is not affected.
- The application revokes its own key —
POST /v1/connect/revoke, see below. Exactly one key is killed: the one you presented. - The application owner fully deletes the client — via
DELETE /api/connect/clients/:id?purge=true(the client has to be deactivated first); every key the client issued is revoked. The dashboard only offers deactivation, which leaves keys untouched. - 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/authorizerejects 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 — 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 kills 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_idortokenis missing.400 invalid_client— a public client presented aclient_secret.401 invalid_client— unknown client, or the secret did not match.429— rate limited, and there are two limiters here. The endpoint's own limiter: 60 requests per minute, counted by caller address alone —client_idis deliberately not part of the key, so rotating it yields no fresh budget, and every copy of an integration behind one address shares those 60; it does setX-RateLimit-*headers. The platform-edge limiter: 60 requests per second per address, a bucket shared by the whole of/v1/rather than by this endpoint; it sets noX-RateLimit-*headers and itsRetry-Afteris 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 ofX-RateLimit-Limitcan. The call is not terminal: the key was not revoked, so repeat it after the pause named inRetry-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, 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.
The key stops working immediately: requests with it start returning 401 KEY_INACTIVE, and its slot in the limit is freed at the same moment.
Re-authorization issues a new key
Every pass through the consent screen issues a new API key. The previous key of 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 kills 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 10 valid keys. That is the default, and a Bitrix24 account administrator can set another value between 1 and 100. The count covers every key the user holds on that account, not only the ones your application issued.
Once the limit is reached, issuance is refused with the code KEY_LIMIT_REACHED: the user sees the reason on the consent page, and the application receives it in error_description on the return to redirect_uri. 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.
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 connection string, do not log it.
redirect_uribinding. The value in/v1/connect/authorizeand/v1/connect/tokenmust match character for character — a mismatch will lead toinvalid_grant.
Partner registration
You register the client yourself in the Vibecode dashboard — the Connect apps section, sidebar, "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_urivalues —https://addresses or the local127.0.0.1andlocalhost, - 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 platform scopes vibe:ai, vibe:search, vibe:infra, vibe:storage and vibe:feedback are unavailable at self-registration — a request carrying them is rejected with SCOPE_NOT_ALLOWED. A platform administrator grants them to a verified application.
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.
What removes the "verified" mark
The mark rests on the shape the application had when it passed review, so editing any of these four fields returns it to "unverified":
- the name,
- the logo,
- the
redirect_urireturn addresses, - the set of rights you request.
The description and the site address do not affect the mark.
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 rights and an already-granted device-code sign-in all stay in place. The verification request is submitted 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.