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.
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
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
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
{
"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
curl https://vibecode.bitrix24.com/v1/deals \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN"
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
curl "https://vibecode.bitrix24.com/v1/oauth/poll?app_key=YOUR_APP_KEY&state=YOUR_STATE"
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:
{ "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:
{
"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/authorize → POST /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
curl -X POST https://vibecode.bitrix24.com/v1/oauth/revoke \
-H "Authorization: Bearer USER_SESSION_TOKEN"
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()
{ "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
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
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()
{
"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:
{
"success": false,
"error": {
"code": "INVALID_REQUEST",
"message": "state: Required"
}
}