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.
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 { data } = await res.json()
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 { data } = await res.json()
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 |
DescriptionThe X-Api-Key header was not provided |
| HTTP401 | CodeINVALID_API_KEY |
DescriptionThe key is not recognized — no such key exists on the platform |
| 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/Code key — such a key works with data only and cannot perform write operations. To issue a key that can, see Project key for deploy |
| HTTP404 | CodeNOT_FOUND |
DescriptionToken not found on this server |
| 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 |
| HTTP503 | CodeFEATURE_DISABLED |
DescriptionThe access-tokens section is disabled on the platform. The pre-call signal and the fallback are described in Availability |
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 { data } = await r.json()
jwt = data.token // same tokenId, fresh JWT
res = await fetch(`${appUrl}/api/health`, {
headers: { Authorization: `Bearer ${jwt}` },
})
}
return res
}