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

Application user authorization

OAuth authorization flow for applications that act on behalf of different account users. The user signs in through Bitrix24, the application receives a session token, and then calls the API on their behalf. An AI agent that is given the authorization key completes this whole flow on its own — following the steps below.

Scope: not required | Base URL: https://vibecode.bitrix24.com/v1 | Authorization: X-Api-Key: YOUR_APP_KEY

When user authorization is needed

The vibe_app_ authorization key acts on behalf of the specific user who installed the application. So that the platform knows on whose behalf to call Bitrix24, the request carries a session token in the Authorization: Bearer header. The token is issued at the end of this flow.

The personal vibe_api_ key acts on behalf of its own owner and does not use a session token. For background scenarios without a user at the screen, use it instead of the authorization key — Passing the key.

All /v1/oauth/* endpoints respond to a single X-Api-Key header holding the authorization key, without Authorization: Bearer.

Step 1. Send the user to authorization

Generate a state — a cryptographically random string of 16–512 characters. This protects against cross-site request forgery per RFC 6749 §10.12: the client generates the value, and on return checks that the same value came back. The server cannot generate state for you.

Build a link to GET /v1/oauth/authorize and redirect the user's browser to it. This is a top-level navigation, not a background request — the Bitrix24 consent page sends an X-Frame-Options header and does not open inside an iframe.

javascript
const state = crypto.randomUUID()
// Save state in the user's session to check it on return
const url = new URL('https://vibecode.bitrix24.com/v1/oauth/authorize')
url.searchParams.set('app_key', 'YOUR_APP_KEY')
url.searchParams.set('state', state)
url.searchParams.set('redirect_uri', 'https://myapp.example.com/callback')
window.location.href = url.toString()

The platform checks app_key and redirect_uri and redirects the user to the Bitrix24 authorization page. After authorization, Bitrix24 returns the user to the application's registered handler, and the platform to your redirect_uri.

Step 2. Handle the return to `redirect_uri`

After authorization, the platform redirects the browser to your redirect_uri with a one-time code and your state:

https://myapp.example.com/callback?code=VIBE_AUTH_CODE&state=YOUR_STATE

Compare state with the value you saved in step 1. If the values do not match, abort authorization. Parsing the errors in this address — What arrives at redirect_uri.

Step 3. Exchange the code for a session token

From the application server, exchange code for a session token. The redirect_uri in the body is exactly equal to the one from step 1.

cURL

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "app_key": "YOUR_APP_KEY",
    "code": "VIBE_AUTH_CODE",
    "redirect_uri": "https://myapp.example.com/callback"
  }'

JavaScript

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    app_key: 'YOUR_APP_KEY',
    code: 'VIBE_AUTH_CODE',
    redirect_uri: 'https://myapp.example.com/callback',
  }),
})
const { access_token, user, expires_in } = await res.json()
// Save access_token — it is valid for 24 hours
JSON
{
  "success": true,
  "access_token": "vibe_session_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "user": { "id": "42", "name": "John Smith", "email": "user@example.com" },
  "expires_in": 86400
}

The code is one-time and valid for 5 minutes.

Step 4. Call the API with the session token

Every API request carries two headers — the authorization key in X-Api-Key and the session token in Authorization: Bearer:

cURL

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

JavaScript

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

The session inherits the application's rights on the account. Without the Authorization: Bearer header, endpoints that require a user context return 401 TOKEN_MISSING.

What arrives at `redirect_uri`

The platform returns the user to redirect_uri with the result in query parameters. Your state always comes back — check it first.

Parameter When it arrives Value
code Authorization succeeded One-time code to exchange for a token. Valid for 5 minutes
state Always The state value passed in step 1
error Authorization failed Reason for the denial — one of the values below

error values:

error What happened
token_exchange_failed Bitrix24 did not issue tokens for the authorization code
invalid_domain The portal domain failed validation
profile_fetch_failed Failed to read the user profile on the portal
app_unavailable The application is no longer available: its author erased their data

Some failures at the return step arrive not in the address but as a JSON response with status 400 or 500 — when the platform does not yet know redirect_uri, it cannot return to it. These are MISSING_PARAMS, INVALID_STATE, and APP_CONFIG_ERROR from the Errors table.

`redirect_uri` rule

redirect_uri must exactly match one of the addresses registered for the application — scheme, host, port, and path all match. You can add a return address in the application card in the Authorization Keys section — the "Redirect URI (OAuth)" field.

A new application has the address http://localhost registered by default — without a port and with the path /. The match is exact, so http://localhost:3000/callback does not match it. For the return to go to your address — http://localhost:3000/callback or https://myapp.example.com/callback — first add it in the application card. Otherwise GET /v1/oauth/authorize returns 400 INVALID_REDIRECT_URI.

If redirect_uri is omitted, the platform uses the built-in return page — Authorization without your own redirect_uri.

Authorization without your own `redirect_uri`

When redirect_uri is omitted, the platform returns the user to the built-in /oauth/complete page and creates the session token on its side. The application retrieves the result by polling GET /v1/oauth/poll with the same state.

cURL

Terminal
curl "https://vibecode.bitrix24.com/v1/oauth/poll?app_key=YOUR_APP_KEY&state=YOUR_STATE"

JavaScript

javascript
const url = `https://vibecode.bitrix24.com/v1/oauth/poll?app_key=YOUR_APP_KEY&state=${state}`
const res = await fetch(url)
const result = await res.json()
if (result.status === 'complete') {
  // result.access_token, result.user, result.expires_in
}

Until the user completes sign-in, status pending arrives:

JSON
{ "success": true, "status": "pending" }

After sign-in, the same request returns the session token. The result is one-time — the next poll with this same state returns pending again:

JSON
{
  "success": true,
  "status": "complete",
  "access_token": "vibe_session_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "user": { "id": "42", "name": "John Smith", "email": "user@example.com" },
  "expires_in": 86400
}

The result is kept for 10 minutes. Retrieve it right after the user returns to the sign-in page.

Session token: lifetime and revocation

The session token is valid for 24 hours and is not refreshed — the expires_in field in the response equals 86400. There is no renewal mechanism. After expiry, calls on behalf of the user return 401 INVALID_SESSION — this signals to go through authorization again: GET /v1/oauth/authorizePOST /v1/oauth/token.

To revoke a token early — POST /v1/oauth/revoke with the session token in the Authorization: Bearer header. Revocation deletes the user's session, and further calls with this token are rejected.

cURL

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/oauth/revoke \
  -H "Authorization: Bearer USER_SESSION_TOKEN"

JavaScript

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/oauth/revoke', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer USER_SESSION_TOKEN' },
})
const { revoked } = await res.json()
JSON
{ "success": true, "revoked": true }

The revoked field equals false if there was no longer an active session with such a token. The response arrives with status 200 in both cases.

Application on your own server

A separate tab for sign-in is not always available — for example, the application opens as a placement inside Bitrix24, and the consent page does not open inside an iframe. If the application is hosted on your own server rather than behind BlackHole, the current user's session token is obtained in one of two ways — depending on whose handler receives the placement.

Vibecode application, appUrl — your server. The placement arrives at the Vibecode handler, and the platform redirects the browser to <appUrl>/?code=<one-time code>&placement=...&member_id=.... Your server exchanges the code for a session token with the same POST /v1/oauth/token request and body { app_key, code, redirect_uri }, where redirect_uri exactly equals the configured appUrl. The session token does not reach the browser — the address holds only a one-time code, useless without app_key. The appUrl field is edited in the application card in the Authorization Keys section.

Your own Bitrix24 application with its own handler. The placement arrives directly at your server with a Bitrix24 user token. Exchange it for a session token with a server request — POST /v1/oauth/placement-session.

cURL

Terminal
curl -X POST https://vibecode.bitrix24.com/v1/oauth/placement-session \
  -H "Content-Type: application/json" \
  -d '{
    "app_key": "YOUR_APP_KEY",
    "access_token": "BITRIX24_USER_TOKEN",
    "member_id": "MEMBER_ID",
    "domain": "mycompany.bitrix24.com"
  }'

JavaScript

javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/oauth/placement-session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    app_key: 'YOUR_APP_KEY',
    access_token: 'BITRIX24_USER_TOKEN',
    member_id: 'MEMBER_ID',
    domain: 'mycompany.bitrix24.com',
  }),
})
const { access_token, user, expires_in } = await res.json()
JSON
{
  "success": true,
  "access_token": "vibe_session_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "user": { "id": "42", "name": "John Smith", "email": "user@example.com" },
  "expires_in": 86400
}

domain must match the application's account. The refresh_token and expires_in fields in the body are optional and describe the Bitrix24 user token: expires_in — how long it is valid, 3600 seconds by default. Without an active refresh_token, the Bitrix24 token is not refreshed automatically — after it expires, reopen the placement. The session token itself, as in the main flow, is valid for 24 hours.

The full lifecycle of an embedded application and handler examples in Node, Python, and Go — Authorization in a BlackHole application.

Endpoint reference

Method Path Description
GET /v1/oauth/authorize Start authorization — redirect to the Bitrix24 sign-in page
GET /v1/oauth/callback Receive the Bitrix24 response. Called by Bitrix24, not the application
POST /v1/oauth/token Exchange the code for a session token
GET /v1/oauth/poll Retrieve the authorization result without your own redirect_uri
POST /v1/oauth/placement-session Session token for an application on your own server
POST /v1/oauth/revoke Revoke the session token

Errors

The authorization key is checked on entry to GET /v1/oauth/authorize, POST /v1/oauth/token, and POST /v1/oauth/placement-session:

HTTP Code Description
401 INVALID_APP_KEY app_key not found or it is not an authorization key
401 KEY_INACTIVE Authorization key revoked
401 KEY_EXPIRED Authorization key has expired
403 OWNER_BLOCKED The key owner is blocked by the platform

GET /v1/oauth/authorize:

HTTP Code Description
400 INVALID_REQUEST Missing required state or it is outside the 16–512 character range
400 INVALID_REDIRECT_URI redirect_uri is not registered for the application
500 NO_PORTAL The application is not linked to a portal

GET /v1/oauth/callback (called by Bitrix24):

HTTP Code Description
400 MISSING_PARAMS Bitrix24 returned without code or state
400 INVALID_STATE state not found or expired after 20 minutes. error.details.reason holds NOT_FOUND or EXPIRED
500 APP_CONFIG_ERROR The application has no OAuth credentials set

POST /v1/oauth/token:

HTTP Code Description
400 INVALID_REQUEST The body has no code or redirect_uri
400 INVALID_CODE Code not found or belongs to another application
400 CODE_EXPIRED The code has expired — 5 minutes
400 CODE_ALREADY_USED The code has already been exchanged for a token
400 REDIRECT_URI_MISMATCH redirect_uri does not match the one passed to GET /v1/oauth/authorize
500 TOKEN_NOT_FOUND User token not found

GET /v1/oauth/poll:

HTTP Code Description
400 INVALID_REQUEST state shorter than 16 characters or no app_key
401 INVALID_APP_KEY app_key not found or it is not an authorization key

POST /v1/oauth/placement-session:

HTTP Code Description
400 INVALID_REQUEST The body has no access_token, member_id, or domain
400 DOMAIN_MISMATCH domain does not match the application's portal
401 USER_AUTH_REQUIRED The Bitrix24 user token did not confirm authorization on the portal
403 APP_UNAVAILABLE The application is no longer available: its author erased their data
500 NO_PORTAL The application is not linked to a portal

POST /v1/oauth/revoke:

HTTP Code Description
400 MISSING_TOKEN No Authorization: Bearer with a session token was passed

Full list of common API errors — Errors.

Error response example

400 — required state not passed:

JSON
{
  "success": false,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "state: Required"
  }
}

See also