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 — the card is named by a body field.

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. Without this field the request saves the card leaving the answers unchanged
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 limit 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 previous write on that card. On a mismatch the request is rejected with code 409

Apart from relationId no field is required: a body of { "relationId": 4 } saves a draft with no answers and returns a fresh stateHash. 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

Terminal
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

Terminal
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

javascript
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

javascript
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 — 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 chosen option of 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 portal 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 stayed new.

JSON
{
  "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:

JSON
{
  "success": false,
  "error": {
    "code": "PERFORMAN_STATE_CONFLICT",
    "message": "The review changed since it was read. Re-read the relation and retry with the stateHash from the fresh response."
  }
}

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 an empty string
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 account. The answer is indistinguishable from the answer to any address that does not exist
404 ENTITY_NOT_FOUND The section is enabled, but the account has no Performance Review module: Bitrix24 answers that the performan.review.* method was not found
409 PERFORMAN_SCOPE_JUST_GRANTED A Cowork key was granted the performan scope by this very request. Repeat it and it goes through
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_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, 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 behaves 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 following 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 nowhere to get 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 was not filled in the Bitrix24 interface returns 422 BITRIX_ERROR with a message about unanswered mandatory fields.

The selectedOptions field has a different shape in the request and in the response. In the request body it is an array of numbers, the option ids. In the response it is an array of objects with the id, text and value fields. Sending a response back without converting it will not work.

The card is named by the body's relationId alone. Its id is not in the path: the method address is the same for every card, so a request without relationId is refused up front and never reaches Bitrix24.

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 behind a presence check.

See also