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

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] ───────────┘
  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
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.

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 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:

  • 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.

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
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
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.

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

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 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

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. 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).

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
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.

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 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.

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.

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.

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
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:

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 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 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 return invalid_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:

    1. 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.
    2. The application revokes its own keyPOST /v1/connect/revoke, see below. Exactly one key is killed: the one you presented.
    3. 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.
    4. 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 — 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_requestclient_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.
  • 429 — rate limited, and there are two limiters here. The endpoint's own limiter: 60 requests per minute, counted by caller address aloneclient_id is 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 set X-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 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, 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_uri binding. The value in /v1/connect/authorize and /v1/connect/token must match character for character — a mismatch will lead to invalid_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_uri values — https:// addresses or the local 127.0.0.1 and localhost,
  • 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_uri return 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.

See also