## Update a ticket

`PATCH /v1/feedback/:id`

Changes a ticket's status and resolution. A full update is available to a key with the `vibe:feedback` scope. An author with a regular key can only withdraw their own ticket — with the body `{ "status": "WITHDRAWN" }`.

## Parameters

| Parameter | Type | Required | Description |
|-----------|------|:--------:|-------------|
| `id` (path) | string | yes | Ticket UUID. List: [`GET /v1/feedback`](/docs/feedback/list) |

## Request fields (body)

| Field | Type | Required | Description |
|-------|------|:--------:|-------------|
| `status` | string | no | New status — values in the [Statuses](#statuses) table |
| `resolution` | string · null | no | Resolution text. Recommended when moving to `RESOLVED`. An explicit `null` clears the field |

## Statuses

| Status | Meaning |
|--------|---------|
| `NEW` | The ticket was just created |
| `REVIEWING` | The ticket is being triaged |
| `AWAITING_USER` | A reply or clarification from the author is needed |
| `NEEDS_REVIEW` | Requires another round of triage |
| `RESOLVED` | The ticket is closed with a resolution |
| `ARCHIVED` | The ticket is closed without a resolution — a duplicate or out of scope |
| `WITHDRAWN` | The ticket was withdrawn by the author |

## Examples

### curl — personal key

```bash
curl -X PATCH https://vibecode.bitrix24.com/v1/feedback/a1b2c3d4-1111-2222-3333-444455556666 \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "RESOLVED",
    "resolution": "Fixed in release 2026-04-19. Empty filter now returns 200 with an empty array."
  }'
```

### curl — OAuth application

```bash
curl -X PATCH https://vibecode.bitrix24.com/v1/feedback/a1b2c3d4-1111-2222-3333-444455556666 \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "RESOLVED",
    "resolution": "Fixed in release 2026-04-19. Empty filter now returns 200 with an empty array."
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch(
  'https://vibecode.bitrix24.com/v1/feedback/a1b2c3d4-1111-2222-3333-444455556666',
  {
    method: 'PATCH',
    headers: { 'X-Api-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' },
    body: JSON.stringify({
      status: 'RESOLVED',
      resolution: 'Fixed in release 2026-04-19. Empty filter now returns 200 with an empty array.',
    }),
  },
)
const { data } = await res.json()
```

### JavaScript — OAuth application

```javascript
const res = await fetch(
  'https://vibecode.bitrix24.com/v1/feedback/a1b2c3d4-1111-2222-3333-444455556666',
  {
    method: 'PATCH',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      status: 'RESOLVED',
      resolution: 'Fixed in release 2026-04-19. Empty filter now returns 200 with an empty array.',
    }),
  },
)
const { data } = await res.json()
```

## Withdrawing a ticket

The ticket author can withdraw it using the same regular key it was created with — the body must be exactly `{ "status": "WITHDRAWN" }`. Withdrawal is available as long as the ticket is not closed.

```bash
curl -X PATCH https://vibecode.bitrix24.com/v1/feedback/a1b2c3d4-1111-2222-3333-444455556666 \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "WITHDRAWN" }'
```

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Always `true` on success |
| `data.id` | string | Ticket UUID |
| `data.category` | string | Category |
| `data.title` | string | Title |
| `data.status` | string | Updated status |
| `data.resolution` | string · null | Current resolution |
| `data.resolvedAt` | string · null | Closing date. Set on transition to `RESOLVED` or `WITHDRAWN` |

## Response example

```json
{
  "success": true,
  "data": {
    "id": "a1b2c3d4-1111-2222-3333-444455556666",
    "category": "BUG",
    "title": "POST /v1/deals/search returns 500 on empty filter",
    "status": "RESOLVED",
    "resolution": "Fixed in release 2026-04-19. Empty filter now returns 200 with an empty array.",
    "resolvedAt": "2026-04-19T12:00:00.000Z"
  }
}
```

## Error response example

403 — update without the `vibe:feedback` scope:

```json
{
  "success": false,
  "error": {
    "code": "FEEDBACK_SCOPE_REQUIRED",
    "message": "Requires management key or vibe:feedback scope to update feedback"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|------|-------------|
| 400 | `VALIDATION_ERROR` | `resolution` is neither a string nor `null` |
| 403 | `FEEDBACK_SCOPE_REQUIRED` | Updating status or resolution without the `vibe:feedback` scope |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is read-only — see [access rights](/docs/access-rights) |
| 404 | `NOT_FOUND` | The ticket does not exist or is not accessible to the key |
| 409 | `FEEDBACK_CLOSED` | The author tries to withdraw an already closed ticket (`RESOLVED`, `ARCHIVED`, or `WITHDRAWN`) |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header was not passed |

For the full list of common API errors, see [Errors](/docs/errors).

## Known specifics

**Moving to `RESOLVED` notifies the author.** On closing, `resolvedAt` and `resolvedBy` are set, and the author receives an email with the resolution text if their profile has an email address. Move to `RESOLVED` only after the fix is available — otherwise the author gets a "fixed" email before the fix itself.

**Returning to an active status clears the closing mark, but not the resolution text.** A transition from any closed status — `RESOLVED`, `WITHDRAWN`, or `ARCHIVED` — to one of the active ones (`NEW`, `REVIEWING`, `AWAITING_USER`, `NEEDS_REVIEW`) clears `resolvedAt` and `resolvedBy`. The `resolution` field does not change: if you did not pass it, the previous text stays. A ticket returning from `ARCHIVED` keeps its archive reason as well.

The rule does not apply in the opposite direction: a transition **to** `ARCHIVED` keeps the closing mark — archiving preserves the resolution history. A transition between active statuses does not touch it either.

**A comment that reopens a ticket behaves the same way.** [Adding a comment](./comments.md) with an active status clears `resolvedAt` and `resolvedBy` and leaves `resolution` alone — exactly like an update. The difference lies elsewhere: a comment also lands in the ticket thread, and it overwrites `resolution` only when the comment itself closes the ticket.

**The case of `status` does not matter, and an unknown value is ignored.** `resolved` and `RESOLVED` are equivalent. A value outside the status list does not change the status and raises no error: the response is `200` with the previous status.

**Terminal states depend on the action.** For an author with a regular key, `WITHDRAWN` and `ARCHIVED` are terminal states: such a ticket can be neither withdrawn nor commented on. `RESOLVED` is terminal for withdrawal only — [an author comment](./comments.md) brings a resolved ticket back into the queue. A key with the `vibe:feedback` scope can change the status of any closed ticket.

## See also

- [Get a ticket](/docs/feedback/get)
- [Add a comment](/docs/feedback/comments)
- [Feedback](/docs/feedback)
- [Errors](/docs/errors)
