For AI agents: markdown of this page — /docs-content-en/infra/access-tokens/refresh.md documentation index — /llms.txt
Refresh an access token
POST /v1/infra/servers/:id/access-tokens/:tokenId/refresh
Mints a fresh JWT for an existing api-bearer token — without creating a new record. A long-running client (CI, AI agent) calls refresh before jwtExpiresAt is reached instead of minting a new token: refresh counts against neither the active-token limit nor the hourly mint limit.
No request body is required.
Refresh is available only with the key that currently manages the server and issued this token. A development-team key cannot call this endpoint; issuing replacement tokens requires separate approval. Switching to the managing key does not make a team-issued token refreshable.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Parameterid |
Inpath | Typestring (UUID) | Requiredyes | DescriptionBLACKHOLE server ID. List: GET /v1/infra/servers |
ParametertokenId |
Inpath | Typestring (UUID) | Requiredyes | Descriptionapi-bearer token ID — the data.id field from a mint response or an item from the token list |
Examples
curl — personal key
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/access-tokens/TOKEN_ID/refresh" \
-H "X-Api-Key: YOUR_API_KEY"
curl — OAuth application
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/access-tokens/TOKEN_ID/refresh" \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN"
JavaScript — personal key
const res = await fetch(
`https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/access-tokens/${tokenId}/refresh`,
{
method: 'POST',
headers: { 'X-Api-Key': 'YOUR_API_KEY' },
}
)
const result = await res.json()
if (!res.ok || result.success !== true) {
throw new Error(result.error?.code ?? `HTTP_${res.status}`)
}
const { data } = result
console.log(data.token, data.jwtExpiresAt)
JavaScript — OAuth application
const res = await fetch(
`https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/access-tokens/${tokenId}/refresh`,
{
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
}
)
const result = await res.json()
if (!res.ok || result.success !== true) {
throw new Error(result.error?.code ?? `HTTP_${res.status}`)
}
const { data } = result
console.log(data.token, data.jwtExpiresAt)
Response fields
| Field | Type | Description |
|---|---|---|
Fieldsuccess |
Typeboolean | DescriptionAlways true on success |
Fielddata.id |
Typestring | DescriptionToken ID — the same as at mint time (no new record is created) |
Fielddata.mode |
Typestring | Description"api-bearer" |
Fielddata.token |
Typestring | DescriptionFresh JWT for the Authorization: Bearer header |
Fielddata.expiresAt |
Typestring (ISO 8601) | DescriptionRetention period of the token record — unchanged by refresh |
Fielddata.jwtExpiresAt |
Typestring (ISO 8601) | DescriptionActual expiry of the new JWT. Capped at 10 minutes (or the record's expiresAt, whichever is sooner) |
Fielddata.subdomain |
Typestring | DescriptionServer subdomain |
Fielddata.appUrl |
Typestring | DescriptionFull HTTPS address of the app |
Fielddata.curlExample |
Typestring | DescriptionReady-to-use curl example with the new token |
Fielddata.note |
Typestring | DescriptionNote about the validity periods and when to call refresh again |
Response example
{
"success": true,
"data": {
"id": "9f1c4b7e-3d52-4a18-9c0e-7b2a1f6d84c3",
"mode": "api-bearer",
"token": "eyJhbGciOiJFUzI1NiJ9...",
"expiresAt": "2026-06-25T10:50:00.000Z",
"jwtExpiresAt": "2026-06-25T10:40:10.000Z",
"subdomain": "app-91306a4c",
"appUrl": "https://app-91306a4c.vibecode.bitrix24.com",
"curlExample": "curl -H \"Authorization: Bearer eyJhbGciOiJFUzI1NiJ9...\" https://app-91306a4c.vibecode.bitrix24.com/api/health",
"note": "Refreshed the Gateway session JWT for this api-bearer token (same token id, no new row). The JWT is valid for up to 10 minutes (or until the row's expiresAt, whichever is sooner). Call this endpoint again before jwtExpiresAt to keep a long-running client authenticated."
}
}
Error response example
410 — the record's retention period has elapsed:
{
"success": false,
"error": {
"code": "TOKEN_EXPIRED",
"message": "The long-lived token row has expired; mint a new token."
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| HTTP400 | CodeWRONG_TOKEN_MODE |
DescriptionThe token is in share-url mode. Refresh applies only to api-bearer; share-url links auto-refresh on each visit |
| HTTP400 | CodeSERVER_NO_SUBDOMAIN |
DescriptionThe server has no subdomain |
| HTTP401 | CodeMISSING_API_KEY |
DescriptionNo API key was supplied through X-Api-Key or a supported Authorization: Bearer scheme |
| HTTP401 | CodeINVALID_API_KEY |
DescriptionThe authentication key is not recognized |
| HTTP401 | CodeKEY_INACTIVE |
DescriptionThe requesting API key is no longer active |
| HTTP401 | CodeKEY_EXPIRED |
DescriptionThe requesting API key has expired; this is distinct from resource URL expiry |
| HTTP401 | CodeNO_PORTAL |
DescriptionA non-management key has no portal binding |
| HTTP401 | CodeCOWORK_CHAT_KEY_INACTIVE |
DescriptionThe parent device key behind a Cowork chat key is no longer usable |
| HTTP401 | CodeWRONG_AUTH_SCHEME |
DescriptionAn OAuth application key was sent as a Bearer session: put the key in X-Api-Key and the session in Authorization: Bearer |
| HTTP401 | CodeINVALID_SESSION |
DescriptionThe supplied OAuth session is invalid or expired |
| HTTP401 | CodeTOKEN_EXPIRED |
DescriptionThe OAuth user token expired and its refresh failed; re-authorization is required |
| HTTP401 | CodeTOKEN_INVALID |
DescriptionWhen the partner AI plane is enabled, its supplied token is unknown or expired |
| HTTP401 | CodeKEY_REVOKED |
DescriptionThe partner credential behind the AI plane token is revoked |
| HTTP402 | CodeACCOUNT_FROZEN |
DescriptionThe key owner’s account is frozen. With wallet-debt scoping disabled, the shared API-key guard refuses this call before its handler. Follow error.topupUrl to resolve the balance; this status alone does not authorize repeating a prior write. |
| HTTP403 | CodeKEY_BLOCKED |
DescriptionThe current or a recorded previous token issuer is blocked |
| HTTP403 | CodeTOKEN_OWNER_MISMATCH |
DescriptionThe token belongs to a different key, or the server is no longer bound to your key |
| HTTP403 | CodeINFRA_FORBIDDEN_FOR_COWORK_KEY |
DescriptionThe call was made with a Cowork key — such a key works with data only and cannot perform write operations. To issue a key that can, see Project key for deploy |
| HTTP403 | CodeWRITE_BLOCKED_READONLY_KEY |
DescriptionA read-only key cannot perform this write. |
| HTTP403 | CodePORTAL_COLLABORATOR_KEY_OUT_OF_SCOPE |
DescriptionThis operation is outside the development-team key route list. Use the key that manages the server |
| HTTP403 | CodeEXTERNAL_COLLABORATOR_KEY_OUT_OF_SCOPE |
DescriptionThis operation is outside the external-collaborator key route list. Use the key that manages the server |
| HTTP403 | CodeIP_NOT_ALLOWED |
DescriptionThe caller IP is outside the key allowlist. |
| HTTP403 | CodeOWNER_BLOCKED |
DescriptionThe key owner is blocked. |
| HTTP403 | CodeOWNER_DELETED |
DescriptionThe key owner was deleted. |
| HTTP403 | CodePORTAL_SUSPENDED |
DescriptionThe bound portal is suspended. |
| HTTP403 | CodePORTAL_DELETED |
DescriptionThe bound portal was deleted. |
| HTTP403 | CodePORTAL_BLOCKED |
DescriptionThe bound portal is blocked. |
| HTTP403 | CodeMANAGEMENT_KEY_NO_ENTITY_ACCESS |
DescriptionThe management key lacks the portal binding or entity authority required by this call. |
| HTTP403 | CodeAGENT_MAINTENANCE_KEY_OUT_OF_SCOPE |
DescriptionThe maintenance key route list does not include this operation. |
| HTTP403 | CodeAPP_API_KEY_OUT_OF_SCOPE |
DescriptionThe application key route list does not include this operation. |
| HTTP403 | CodeWAITLIST_PENDING |
DescriptionThe key owner has not been activated. |
| HTTP403 | CodeSESSION_APP_MISMATCH |
DescriptionThe OAuth session belongs to another application. |
| HTTP403 | CodePLANE_FORBIDDEN |
DescriptionThe partner key does not allow this AI plane. |
| HTTP403 | CodeSYSTEM_FORBIDDEN |
DescriptionThe partner key does not allow this AI system. |
| HTTP403 | CodeSCOPE_FORBIDDEN |
DescriptionThe partner AI plane forbids this route. |
| HTTP403 | CodeCONNECT_COMPANY_DERIVED_TOKEN_FORBIDDEN |
DescriptionA company connection key cannot manage derived tokens. |
| HTTP403 | CodeCOWORK_READONLY |
DescriptionThe Cowork seat is read-only. |
| HTTP403 | CodeGALAXY_DISABLED |
DescriptionGalaxy operations are disabled for this portal. |
| HTTP404 | CodeNOT_FOUND |
DescriptionThe token was not found on this server or the server's portal has been deactivated |
| HTTP404 | CodeSERVER_NOT_FOUND |
DescriptionServer not found or deleted |
| HTTP410 | CodeALREADY_REVOKED |
DescriptionThe token was revoked — mint a new one |
| HTTP410 | CodeTOKEN_EXPIRED |
DescriptionThe record's retention period has expired — mint a new token via POST /access-tokens |
| HTTP429 | CodeQUOTA_EXCEEDED |
DescriptionAPI billing is enabled with a nonzero overage price: the daily free quota is exhausted and the PREPAY balance is not positive. Top up the balance. error.quota and error.used report counts; X-RateLimit-Quota, X-RateLimit-Used and X-RateLimit-Remaining report the quota, usage and zero remainder. This refusal does not promise Retry-After or X-RateLimit-Scope |
| HTTP429 | CodeCALLER_RATE_LIMITED |
DescriptionThe shared key or user request limit. Wait for the number of seconds specified in Retry-After; X-RateLimit-Scope identifies the refusing layer |
| HTTP503 | CodeFEATURE_DISABLED |
DescriptionThe access-tokens section is disabled on the platform. The pre-call signal and the fallback are described in Availability |
| HTTP503 | CodeACCOUNT_PENDING_ERASURE |
DescriptionThe key owner is pending erasure or being erased. Header Retry-After: 3600. |
| HTTP503 | CodeSERVICE_DISABLED |
DescriptionThe partner AI plane or its quota-readiness service is disabled. No Retry-After is promised for this refusal. |
| HTTP503 | Codeuser_self_deletion_pending |
DescriptionThe API key owner scheduled self-deletion. The token plugin refuses the call before its handler. Header Retry-After: 604800. |
Full list of common API errors — Errors.
Diagnosing a Gateway rejection
If a request to the app with the Authorization: Bearer header returns 401 with code BH_LOGIN_REQUIRED, the response body carries a reason field with the specific Gateway rejection cause:
reason |
What happened | What to do |
|---|---|---|
reasonexpired |
What happenedThe JWT expired (the 10-minute window elapsed) | What to doRefresh the token via this endpoint, or mint a new one |
reasonsignature |
What happenedThe signature did not match | What to doUse a token minted for this server; do not edit it |
reasonsubdomain |
What happenedThe token is bound to a different subdomain | What to doSend the request to the server's own app-* subdomain |
reasonrevoked |
What happenedThe token was revoked | What to doMint a new token |
reasontype |
What happenedA non-api-bearer token was sent |
What to doUse an api-bearer token, not a cookie session |
reasonmalformed / invalid |
What happenedThe string is not a valid JWT | What to doCheck that the token is intact |
Known specifics
- A single revocation invalidates every JWT issued for the token. A
DELETEon the record stops both the original JWT and every JWT issued by refresh. share-urllinks refresh themselves. Every visit goes through/auth/bh-login, so calling refresh for them is unnecessary.- A ready-made "use → on 401 refresh → retry" loop. Instead of watching a timer, the client reacts to a rejection:
async function callWithRefresh(serverId, tokenId, appUrl, jwt) {
let res = await fetch(`${appUrl}/api/health`, {
headers: { Authorization: `Bearer ${jwt}` },
})
if (res.status === 401) {
const r = await fetch(
`https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/access-tokens/${tokenId}/refresh`,
{ method: 'POST', headers: { 'X-Api-Key': 'YOUR_API_KEY' } },
)
const result = await r.json()
if (!r.ok || result.success !== true) {
throw new Error(result.error?.code ?? `HTTP_${r.status}`)
}
const { data } = result
jwt = data.token // same tokenId, fresh JWT
res = await fetch(`${appUrl}/api/health`, {
headers: { Authorization: `Bearer ${jwt}` },
})
}
return res
}