For AI agents: markdown of this page — /docs-content-en/cowork/coupon-redeem.md documentation index — /llms.txt
Redeem a coupon
POST /v1/cowork/coupon/redeem
Redeems a Cowork/Code coupon and grants the user a complimentary tier for the term set by the coupon at the platform's expense, with no charge to the Bitrix24 account.
The user and the account come from the key binding; there are no path parameters. The operation is irreversible: a redemption cannot be undone.
Request fields (body)
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes | Coupon in the form PREFIX-BODY-CHECKSUM, from 1 to 64 characters. Case, whitespace and dash variants are normalized to a canonical form on the server |
action |
string | no | Action selected by the user: apply, extend, force or resume-and-apply. Take the value from decision returned by the coupon check: for kind apply or extend, use that value; for kind: choose, use the kind of the selected item in options. If the field is omitted, it defaults to apply, preserving the behavior from before the choice was introduced |
Examples
This endpoint supports only one authorization method: a Cowork/Code desktop key. Personal keys and application keys receive 403 INSUFFICIENT_SCOPE or 403 COWORK_DESKTOP_KEY_REQUIRED.
curl — Cowork/Code desktop key
curl -X POST https://vibecode.bitrix24.com/v1/cowork/coupon/redeem \
-H "X-Api-Key: YOUR_COWORK_KEY" \
-H "Content-Type: application/json" \
-d '{"code": "PRAKTIKUM-7HKM4TQZ2RVW-45C80C", "action": "force"}'
JavaScript — Cowork/Code desktop key
const res = await fetch('https://vibecode.bitrix24.com/v1/cowork/coupon/redeem', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_COWORK_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
code: 'PRAKTIKUM-7HKM4TQZ2RVW-45C80C',
action: 'force',
}),
})
if (!res.ok) {
const { error } = await res.json()
if (error.code === 'CONCURRENT_REDEMPTION') {
// The only error code to retry automatically.
}
console.error(error.code)
} else {
const { data } = await res.json()
if (data.accessGranted) {
const months = data.grantedTermMonths
console.log(`${data.grantedTier} for ${months} ${months === 1 ? 'month' : 'months'}`)
} else {
// The tier is granted, but there is no access yet — an access request has been sent to the account administrator.
console.log(`${data.grantedTier} activated, waiting for the administrator's decision`)
}
}
Response fields
| Field | Type | Description |
|---|---|---|
success |
boolean | true on a successful redemption |
data.grantedTier |
string | The granted tier: PRO, MAX or ULTRA |
data.grantedTermMonths |
number | Number of months for which the tier is granted |
data.accessGranted |
boolean | false means the tier is granted but the Bitrix24 account administrator has not yet granted access to Cowork/Code |
Response example
{
"success": true,
"data": {
"grantedTier": "PRO",
"grantedTermMonths": 3,
"accessGranted": true
}
}
The tier is granted, but Cowork/Code access has not yet been enabled for the Bitrix24 account:
{
"success": true,
"data": {
"grantedTier": "PRO",
"grantedTermMonths": 3,
"accessGranted": false
}
}
Error response example
409 — this tier is already assigned to the user's seat:
{
"success": false,
"error": {
"code": "COUPON_SEAT_ALREADY_ON_TIER",
"message": "The coupon cannot be redeemed"
}
}
Errors
Coupon-specific refusals return status 409, with error.code identifying the reason. The message text is the same for all such refusals; do not parse it — branch on the code.
| HTTP | Code | Description |
|---|---|---|
| 400 | INVALID_CODE |
The body has no string field code; the field is empty or longer than 64 characters; or the body contains a field the endpoint does not accept |
| 400 | INVALID_ACTION |
The action field is present, but its value is not one of apply, extend, force, resume-and-apply |
| 400 | INVALID_JSON_BODY |
A body was sent, but it does not parse as JSON |
| 401 | MISSING_API_KEY |
The X-Api-Key header is missing |
| 401 | INVALID_API_KEY |
The authorization key is invalid |
| 403 | INSUFFICIENT_SCOPE |
The key lacks the vibe:cowork scope |
| 403 | COWORK_DESKTOP_KEY_REQUIRED |
The key belongs to another class: personal key, application key, agent seat key or project deploy key |
| 403 | COUPON_FEATURE_DISABLED |
Coupons are not enabled on the account |
| 403 | KEY_NOT_BOUND_TO_USER |
The key is not bound to an account user |
| 403 | WRITE_BLOCKED_READONLY_KEY |
The key was issued as read-only |
| 409 | COUPON_INVALID |
The coupon does not exist, is revoked, already redeemed or expired, the campaign has ended, its overall redemption cap is exhausted, or the per-user or per-account limit is exhausted. Show a single message — "This code cannot be used" |
| 409 | COUPON_TOO_MANY_ATTEMPTS |
Ten failures within an hour. Further attempts are blocked for thirty minutes |
| 409 | COUPON_ALREADY_REDEEMED_BY_YOU |
This user has already activated this coupon |
| 409 | COUPON_NOT_ASSIGNED_TO_YOU |
The coupon was issued to a different email address |
| 409 | COUPON_PORTAL_ACCESS_GATED |
Cowork/Code access is disabled for the account |
| 409 | COUPON_TARIFF_NOT_ELIGIBLE |
The campaign is limited to a list of Bitrix24 plans, and the account plan is not on it. The refusal is terminal: a repeated call returns the same result |
| 409 | COUPON_TARIFF_UNKNOWN |
The campaign is limited to a list of Bitrix24 plans, and the platform has not read the account plan yet. The coupon is intact and the attempt is not counted against the limit — repeat the call later |
| 409 | COUPON_SEAT_PAUSED |
The seat is paused because of insufficient funds. The gift does not resolve the cause of the pause — top up the balance first |
| 409 | COUPON_SEAT_CANCELLATION_SCHEDULED |
The seat is scheduled for cancellation, and the redemption request omitted action: resume-and-apply. When the gifted tier is not below the current tier, the check includes this action in options — send it to resume the seat and apply the gift. When the gifted tier is lower, the action is unavailable and the refusal is terminal |
| 409 | COUPON_TIER_DOWNGRADE_BLOCKED |
The coupon grants a tier below the current tier, and the user did not select force |
| 409 | COUPON_SEAT_ALREADY_ON_TIER |
This tier is already assigned to the seat, and the redemption request omitted action: extend. If the campaign does not allow extending the term or the purchased term is longer than one month, the check returns the decision refuse rather than extend |
| 409 | COUPON_SEAT_PAID_TERM_ACTIVE |
The seat has an active paid term with no recurring charge. The gift would overwrite that term, so the refusal is terminal — direct the user to support |
| 409 | COUPON_SEAT_ALREADY_PAID_LONGER |
The seat's paid term extends beyond the term offered by the coupon, and the user did not select force |
| 409 | COUPON_SELF_PORTAL |
The portal is backed by a platform employee holding the coupon-issuing right — redemption on such a portal is closed |
| 409 | COUPON_SEAT_IS_PAID |
The seat has an active recurring charge, and the user did not select force |
| 409 | COUPON_ACTION_NOT_AVAILABLE |
The submitted action is stale: the seat state changed between the check and redemption. The coupon remains valid and unspent |
| 409 | CONCURRENT_REDEMPTION |
Another operation is competing for the same seat |
| 429 | RATE_LIMITED |
The platform-wide limit is 10 requests per minute. The effective value for your key is returned in the x-ratelimit-limit header. It is lower than the platform-wide limit because that limit is divided across replicas |
See Errors for the full list of common API errors.
Known specifics
Using a generic success screen for accessGranted: false creates the costliest category of support requests. The operation looks successful to the user, yet they cannot launch Cowork/Code, which looks like a malfunction. This state needs a dedicated screen — "The tier is activated, and an access request has been sent to the administrator" — rather than a generic "Done".
The submitted action expresses intent, not permission. The seat may have changed between the check and redemption: the user may have paid for a tier in another tab, the administrator may have downgraded the seat, or a scheduled cancellation may have taken effect. The decision is recomputed from the current state. If the selected action is no longer available, the endpoint returns COUPON_ACTION_NOT_AVAILABLE — repeat the check and show the actions it currently returns.
Retry CONCURRENT_REDEMPTION; never retry any other coupon refusal. The coupon remains valid, the rules were satisfied, and the gift was not consumed: by the time the response arrives, the coupon is available again. Retry up to three times with a 300–800 ms delay and random jitter — without jitter, dozens of clients retry in lockstep and reproduce the same contention. Show the user a waiting state during that time, not an error. Automatically retrying any other coupon refusal exhausts the failure counter in seconds. 429 RATE_LIMITED is separate: it is not a coupon refusal, does not affect the failure counter, and may be retried after a delay.
The force action forfeits the remainder of the paid term with no refund. The coupon check returns the exact number of forfeited days in losesDays; do not recompute it on the client. The button label must include that number before the user clicks it — after redemption, the term cannot be restored.
A coupon is single-use per user, but not necessarily overall. Some coupons allow several activations by different people. The number of users who share a coupon is not disclosed and must not be mentioned in user-facing text.
The failed-attempt counter is shared with the coupon check. The coupon check describes the pair used as the counter key and its reset condition.
The platform-wide limit is 10 requests per minute for redemption and 20 requests per minute for the check. The effective values for your key are returned in the x-ratelimit-limit response header. They are lower than the platform-wide limits because those limits are divided across replicas.
Coupon issuance is intentionally unavailable to this key. The desktop key is stored on the user's machine, so granting it issuance rights would make the campaign cap ineffective. Programmatic issuance requires a different key class and is performed by the partner's server, not the application.