For AI agents: markdown of this page — /docs-content-en/performan/manager-answer.md documentation index — /llms.txt
Manager answer
POST /v1/performan/review/manager/answers
Saves a manager's answers on a review card. By default this is a draft — the card status stays as it was. An isAutosave value of false finalises the review, and that cannot be undone.
Request body fields
The method has no path or query parameters — a body field identifies the card.
| Field | Type | Required | Description |
|---|---|---|---|
relationId |
number | yes | Manager review card ID. Taken from the id field of the GET /v1/performan/review/manager/reviews response |
answers |
array | no | Answers to the stage questions. List every answer that has to stay on the card |
answers[].questionId |
number | yes | Question ID. The list — GET /v1/performan/review/manager/questions |
answers[].answerText |
string | no | Answer text for a question of type 3 |
answers[].selectedOptions |
number[] | no | IDs of the chosen options for a question of type 1 or 2. The restriction on obtaining them is described under "Known specifics" |
answers[].selectedOptionsText |
object | no | Extra text for the chosen options. The key is an option ID, the value is a string |
isAutosave |
boolean | no | true — save a draft, the card status does not change. false — finalise the review. Defaults to true |
expectedStateHash |
string | no | The stateHash value from the response to the previous write on this card. On a mismatch the request is rejected with code 409 |
Apart from relationId, no field is required. The answers field reaches Bitrix24 on every write — a body without it sends an empty set of answers, so list in answers every answer that has to stay on the card. Fields not listed in the table are dropped and never reach Bitrix24.
Examples
A draft of two text answers with an optimistic lock on the state hash.
curl — personal key
curl -X POST https://vibecode.bitrix24.com/v1/performan/review/manager/answers \
-H "X-Api-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"relationId": 4,
"answers": [
{ "questionId": 15, "answerText": "Met the quarterly goals" },
{ "questionId": 16, "answerText": "Strong engineering fundamentals" }
],
"isAutosave": true,
"expectedStateHash": "23fb2c21af87e3f692789a9082d89b89be1333492c40ddb13245cb2d9d6ec790"
}'
curl — OAuth application
curl -X POST https://vibecode.bitrix24.com/v1/performan/review/manager/answers \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"relationId": 4,
"answers": [
{ "questionId": 15, "answerText": "Met the quarterly goals" },
{ "questionId": 16, "answerText": "Strong engineering fundamentals" }
],
"isAutosave": true,
"expectedStateHash": "23fb2c21af87e3f692789a9082d89b89be1333492c40ddb13245cb2d9d6ec790"
}'
JavaScript — personal key
const res = await fetch('https://vibecode.bitrix24.com/v1/performan/review/manager/answers', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
relationId: 4,
answers: [
{ questionId: 15, answerText: 'Met the quarterly goals' },
{ questionId: 16, answerText: 'Strong engineering fundamentals' },
],
isAutosave: true,
expectedStateHash: '23fb2c21af87e3f692789a9082d89b89be1333492c40ddb13245cb2d9d6ec790',
}),
})
const body = await res.json()
if (res.status === 409) {
// The card was changed in parallel: re-read it and retry with the fresh hash
throw new Error(body.error.message)
}
if (!body.success) {
throw new Error(`${body.error.code}: ${body.error.message}`)
}
// Put the hash from the response into expectedStateHash of the next write on this card
console.log(body.data.status, body.data.stateHash)
JavaScript — OAuth application
const res = await fetch('https://vibecode.bitrix24.com/v1/performan/review/manager/answers', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
relationId: 4,
answers: [
{ questionId: 15, answerText: 'Met the quarterly goals' },
{ questionId: 16, answerText: 'Strong engineering fundamentals' },
],
isAutosave: true,
expectedStateHash: '23fb2c21af87e3f692789a9082d89b89be1333492c40ddb13245cb2d9d6ec790',
}),
})
const body = await res.json()
if (!body.success) {
throw new Error(`${body.error.code}: ${body.error.message}`)
}
console.log(body.data.status, body.data.stateHash)
Response fields
| Field | Type | Description |
|---|---|---|
success |
boolean | Always true on success |
data |
object | The card after the write |
data.id |
number | Card ID |
data.campaignId |
number | The card's campaign |
data.campaignStageId |
number | The campaign stage the card belongs to |
data.stageType |
string | Stage type. Here it is managersReview |
data.revieweeUserId |
number | The employee being reviewed. Employee card — GET /v1/users/:id |
data.revieweeUserName |
string | Name of the employee being reviewed |
data.reviewerUserId |
number | The reviewing employee |
data.reviewerUserName |
string | Name of the reviewing employee |
data.managerUserId |
number | The reviewee's manager |
data.managerUserName |
string | Manager name |
data.status |
string | Card state after the write: new — the draft is saved, completed — the review is finalised |
data.rate |
number | The final rate. Derived from the value of the option chosen for the rating question |
data.comment |
string | Card comment |
data.answers |
array | The card answers after the write |
data.answers[].questionId |
number | Question ID |
data.answers[].questionTitle |
string | Question wording |
data.answers[].questionDescription |
string | Question note |
data.answers[].questionType |
string | Question type as a string: 1 — single choice, 2 — multiple choice, 3 — text answer |
data.answers[].questionTypeLabel |
string | Question type name in the Bitrix24 account language |
data.answers[].isRating |
boolean | Whether the answer forms the card's final rate |
data.answers[].answerText |
string | Answer text |
data.answers[].selectedOptions |
array | The chosen options as objects. In a request the same field takes an array of numbers |
data.answers[].selectedOptions[].id |
number | Option ID |
data.answers[].selectedOptions[].text |
string | Option text |
data.answers[].selectedOptions[].value |
number | Numeric option value |
data.ratingScale |
array | The rating scale of the stage |
data.ratingScale[].value |
number | Scale value |
data.ratingScale[].label |
string | Value caption |
data.stateHash |
string | The card state hash. Goes into the expectedStateHash field of the next write |
Response example
The draft is saved and the card status stays new.
{
"success": true,
"data": {
"id": 4,
"campaignId": 1,
"campaignStageId": 5,
"stageType": "managersReview",
"revieweeUserId": 42,
"revieweeUserName": "Mary Jones",
"reviewerUserId": 7,
"reviewerUserName": "John Smith",
"managerUserId": 7,
"managerUserName": "John Smith",
"status": "new",
"rate": 0,
"comment": "",
"answers": [
{
"questionId": 15,
"questionTitle": "Key results and goal delivery",
"questionDescription": "",
"questionType": "3",
"questionTypeLabel": "Text answer",
"isRating": false,
"answerText": "Met the quarterly goals",
"selectedOptions": []
}
],
"ratingScale": [
{ "value": 1, "label": "Well below expectations" },
{ "value": 2, "label": "Below expectations" },
{ "value": 3, "label": "Meets expectations" },
{ "value": 4, "label": "Above expectations" },
{ "value": 5, "label": "Well above expectations" }
],
"stateHash": "23fb2c21af87e3f692789a9082d89b89be1333492c40ddb13245cb2d9d6ec790"
}
}
Error response example
409 — the card changed since it was read:
{
"success": false,
"error": {
"code": "PERFORMAN_STATE_CONFLICT",
"message": "The relation changed since your `expectedStateHash` — re-read it and retry with the fresh stateHash"
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 400 | INVALID_PARAMS |
The body carries no relationId, or it is not a positive integer |
| 400 | INVALID_PARAMS |
The body shape is wrong: answers is not an array, an element is not an object, questionId is not a positive integer, selectedOptions is not an array of IDs, answerText is not a string, selectedOptionsText is not a map of an ID to a string, isAutosave is not a boolean, expectedStateHash is not a string or is empty, including a string of spaces. A null in expectedStateHash is equivalent to omitting the field |
| 401 | MISSING_API_KEY |
The X-Api-Key header was not sent |
| 401 | TOKEN_MISSING |
The key has no portal tokens. An OAuth application key requires the Authorization: Bearer header |
| 403 | SCOPE_DENIED |
The key has no performan scope |
| 404 | ROUTE_NOT_FOUND |
The Performance Review section is not enabled for the portal. The answer is indistinguishable from the answer to any address that does not exist |
| 404 | ENTITY_NOT_FOUND |
The section is enabled, but the portal has no Performance Review module: Bitrix24 answers that the performan.review.* method was not found |
| 403 | WRITE_BLOCKED_READONLY_KEY |
The key is in read-only mode |
| 403 | BITRIX_ACCESS_DENIED |
The card belongs to another reviewer, or no card with that relationId exists. Bitrix24 answers both cases the same way |
| 409 | PERFORMAN_SCOPE_JUST_GRANTED |
A Cowork key was granted the performan scope by this very request. Repeat it and it goes through |
| 409 | PERFORMAN_STATE_CONFLICT |
The expectedStateHash sent does not match the current card state |
| 422 | BITRIX_ERROR |
Bitrix24 rejected the request: the question does not belong to the card's stage, or mandatory answers are missing on finalisation. The per-field breakdown is in the error.validation array, the machine code from Bitrix24 in error.b24Code |
| 429 | RATE_LIMITED |
The request rate on the Bitrix24 side was exceeded |
| 429 | QUEUE_OVERFLOW, QUEUE_TIMEOUT |
The portal request queue is full or the request did not get through the queue in time. The Retry-After header suggests the delay |
| 503 | BITRIX_TIMEOUT |
Bitrix24 accepted the request but did not answer in time. Re-read the card before retrying — the write may have been applied |
| 502 | BITRIX_UNAVAILABLE |
Bitrix24 is unavailable |
The full list of common API errors — Errors.
Known specifics
The default is a draft, not a finalised review. A request without the isAutosave field is treated as isAutosave: true: the card status does not change and no finalisation side effects run. A value of false moves the card to completed, sends a notification and starts the next campaign stages. The API cannot bring a card back to the draft state.
A choice question cannot be answered through the API. The answers[].selectedOptions field takes option IDs, but there is no way to obtain them: the question list arrives without its options, and a card's answers[].selectedOptions carries only the options already chosen. So a call with isAutosave: false on a card whose mandatory rating question has not been answered in the Bitrix24 interface returns 422 BITRIX_ERROR with a message about unanswered mandatory fields.
An element of the error.validation array does not always carry a field key. A refusal on a specific field arrives with the message and field keys, while a refusal on finalisation because of unanswered mandatory questions carries message alone. Read field only after checking that it is present.