
## Mint an access token

`POST /v1/infra/servers/:id/access-tokens`

Mints a short-lived token for external access to a deployed application. Two modes: `api-bearer` — JWT for the `Authorization` HTTP header; `share-url` — a link that sets a cookie when visited.

## Parameters

| Parameter | In | Type | Required | Description |
|----------|---|-----|:-----:|----------|
| `id` | path | string (UUID) | yes | BLACKHOLE server ID |

## Request body fields

| Field | Type | Required | Default | Description |
|------|-----|:-----:|-----------|----------|
| `mode` | string | yes | — | `"api-bearer"` or `"share-url"` |
| `ttlSeconds` | number | no | `86400` | Token lifetime in seconds. Range: 300–315 360 000 (from 5 minutes to 10 years). The value 315 360 000 is shown in the interface as "Never expires" |
| `identityBound` | boolean | no | `true` | For `share-url` only. When `true` — login via Bitrix24 is required, and the real user identifier ends up in the log. When `false` — anonymous visit, a synthetic identifier |
| `name` | string | no | — | Token label for display in the list (up to 100 characters) |

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/access-tokens" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "api-bearer",
    "ttlSeconds": 600,
    "name": "ci-smoke"
  }'
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/access-tokens" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "share-url",
    "ttlSeconds": 2592000,
    "identityBound": false,
    "name": "preview"
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/access-tokens`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ mode: 'api-bearer', ttlSeconds: 600 }),
  }
)
const { data } = await res.json()

// E2E check via the public path
const check = await fetch(`${data.appUrl}/api/health`, {
  headers: { Authorization: `Bearer ${data.token}` },
})
console.log(check.status) // 200 — the application responds through the Gateway
```

### JavaScript — OAuth application

```javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/access-tokens`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      mode: 'share-url',
      ttlSeconds: 2592000,
      name: 'preview',
    }),
  }
)
const { data } = await res.json()
console.log(data.url) // https://app-xxxx.vibecode.bitrix24.com/?s=R8k3Zm2P
```

## Response fields

The set of fields depends on the mode.

**Mode `api-bearer`:**

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | Always `true` on success |
| `data.id` | string | Token ID for later revocation |
| `data.mode` | string | `"api-bearer"` |
| `data.token` | string | JWT for the `Authorization: Bearer` header. Returned **once** — at mint time |
| `data.expiresAt` | string (ISO 8601) | Retention period of the token record — for listing and revocation |
| `data.jwtExpiresAt` | string (ISO 8601) | The real validity period of the Bearer token. Capped at 10 minutes regardless of `ttlSeconds`. After it expires, mint a new token |
| `data.note` | string | Note about the difference between `expiresAt` and `jwtExpiresAt` |
| `data.subdomain` | string | Server subdomain |
| `data.appUrl` | string | Full HTTPS address of the application |
| `data.curlExample` | string | Ready-to-use `curl` example with the token for a quick check |

**Mode `share-url`:**

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | Always `true` on success |
| `data.id` | string | Token ID for later revocation |
| `data.mode` | string | `"share-url"` |
| `data.shortcode` | string | Code inserted into the URL as `?s=<shortcode>` |
| `data.url` | string | Full distributable link |
| `data.identityBound` | boolean | Whether a visit requires login via Bitrix24 |
| `data.expiresAt` | string (ISO 8601) | Token expiration moment |
| `data.name` | string \| null | The label passed at mint time |

## Response example

**Mode `api-bearer`:**

```json
{
  "success": true,
  "data": {
    "id": "tk_7d3fa2b1",
    "mode": "api-bearer",
    "token": "eyJhbGciOiJFUzI1NiJ9...",
    "expiresAt": "2026-05-18T10:50:00.000Z",
    "jwtExpiresAt": "2026-05-18T10: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": "JWT is a 10-minute Gateway session token. Bearer stops working at jwtExpiresAt. POST .../:tokenId/refresh to renew the same token, or POST /access-tokens for a new one."
  }
}
```

**Mode `share-url`:**

```json
{
  "success": true,
  "data": {
    "id": "tk_8e4gb3c2",
    "mode": "share-url",
    "shortcode": "R8k3Zm2P",
    "url": "https://app-91306a4c.vibecode.bitrix24.com/?s=R8k3Zm2P",
    "identityBound": false,
    "expiresAt": "2026-06-17T08:44:00.000Z",
    "name": "preview"
  }
}
```

## Error response example

429 — token mint rate limit exceeded:

```json
{
  "success": false,
  "error": {
    "code": "TOKEN_MINT_RATE_LIMIT",
    "message": "Rate limit: 50 mints/hour per API key"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_MODE` | An invalid `mode` was passed |
| 400 | `INVALID_TTL` | `ttlSeconds` is outside the allowed range [300, 315 360 000] |
| 400 | `NAME_TOO_LONG` | `name` exceeds 100 characters |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not provided |
| 401 | `INVALID_API_KEY` | Invalid or expired API key |
| 403 | `TOKEN_OWNER_MISMATCH` | The server belongs to a different API key |
| 404 | `SERVER_NOT_FOUND` | Server not found or deleted |
| 409 | `ACTIVE_TOKEN_LIMIT` | The limit of 100 active tokens per server has been reached |
| 429 | `TOKEN_MINT_RATE_LIMIT` | The limit of 50 mints per hour per API key has been exceeded. Header `Retry-After: 3600` |
| 503 | `FEATURE_DISABLED` | Access tokens are disabled on the platform |

Full list of common API errors — [Errors](/docs/errors).

## Known specifics

- **`token` is valid for 10 minutes regardless of `ttlSeconds`.** Save `token` and `jwtExpiresAt` immediately: the JWT stops working at `jwtExpiresAt`, not `expiresAt`. `expiresAt` is the retention period of the record (for listing and revocation). To extend access, call [token refresh](./refresh.md) (same token, fresh JWT, no new record) or mint a new one via `POST /access-tokens`.
- **`identityBound` is ignored for `api-bearer`.** The identifier in the log is always the UUID of the API key owner.
- **Limits:** 100 active tokens per server; 50 mints per hour per API key; `ttlSeconds` from 300 to 315 360 000.

## See also

- [Refresh a token](./refresh.md)
- [List tokens](./list.md)
- [Revoke a token](./delete.md)
- [Access tokens](/docs/infra/access-tokens)
- [Deploy API](/docs/infra/deploy)
