
## Update a CRM payment

`PATCH /v1/crm-payments/:id`

Changes a CRM payment's payment system or its "paid" mark: sets the mark or clears it.

Fields are passed flat at the JSON root, without a `fields` wrapper.

## Parameters

| Parameter | Type | Required | Description |
|----------|-----|:-----:|---------|
| `id` (path) | number | yes | Payment ID. List of an item's payments: [`GET /v1/crm-payments`](/docs/entities/crm-payments/list) |

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|---------|
| `paid` | boolean | no | `true` sets the "paid" mark, `false` clears it |
| `paySystemId` | number | no | ID of an existing payment system. List: [`GET /v1/pay-systems`](/docs/entities/pay-systems/list). The value `0` is rejected, as is the ID of a nonexistent system |

Pass at least one of the two fields. The payment amount is the sum of its [line items](/docs/entities/crm-payments/products-add) and cannot be set directly.

## Examples

### curl — personal key

```bash
curl -X PATCH "https://vibecode.bitrix24.com/v1/crm-payments/565" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "paySystemId": 3 }'
```

### curl — OAuth application

```bash
curl -X PATCH "https://vibecode.bitrix24.com/v1/crm-payments/565" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "paySystemId": 3 }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/crm-payments/565', {
  method: 'PATCH',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ paySystemId: 3 }),
})

const { data } = await res.json()
console.log('Payment system:', data.paySystemName)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/crm-payments/565', {
  method: 'PATCH',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ paySystemId: 3 }),
})

const { data } = await res.json()
```

## Response fields

Returns the payment after the change.

| Field | Type | Description |
|------|-----|---------|
| `success` | boolean | `true` on success |
| `data.id` | number | Payment ID |
| `data.accountNumber` | string | Payment number: the linked order number and the payment's sequence number separated by `/` |
| `data.paid` | string | Paid mark: `Y` for paid, `N` for not paid |
| `data.datePaid` | string \| null | Time of the latest "paid" mark, ISO 8601 with a time zone offset. `null` if the payment was never marked |
| `data.empPaidId` | number \| null | ID of the employee who last marked the payment. List: [`GET /v1/users`](/docs/entities/users/list) |
| `data.paySystemId` | number | Payment system ID. List: [`GET /v1/pay-systems`](/docs/entities/pay-systems/list) |
| `data.sum` | number | Payment amount |
| `data.currency` | string | Currency code. List: [`GET /v1/currencies`](/docs/entities/currencies/list) |
| `data.paySystemName` | string | Payment system name |
| `meta.readbackUnavailable` | boolean | Returned only with the value `true` when the change was applied but the payment could not be read back. In that case `data` contains only `id`. Do not repeat the request; fetch the payment by this `id` |

## Response example

```json
{
  "success": true,
  "data": {
    "id": 565,
    "accountNumber": "1029/2",
    "paid": "N",
    "datePaid": null,
    "empPaidId": null,
    "paySystemId": 3,
    "sum": 0,
    "currency": "USD",
    "paySystemName": "Bank transfer (Contacts)"
  }
}
```

## Error response example

400 — `paid` is not a boolean:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "paid must be a boolean."
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `MISSING_REQUIRED_FIELDS` | The body is missing, is not an object, or is an empty object `{}` |
| 400 | `READONLY_FIELD` | The body contains a field other than `paid` and `paySystemId` |
| 400 | `INVALID_PARAMS` | `paid` is not a boolean, for example the string `"Y"`, or `paySystemId` is negative or not an integer |
| 400 | `INVALID_ID` | `id` is not a positive integer |
| 404 | `ENTITY_NOT_FOUND` | No payment with this `id` exists |
| 422 | `BITRIX_ERROR` | No payment system with this `paySystemId` exists, including `0`. Message: `Payment system has not been found` |
| 422 | `BITRIX_ERROR` | Bitrix24 rejected the request; the reason is in `error.message` |
| 422 | `BITRIX_NO_EFFECT` | Bitrix24 did not confirm the change |
| 403 | `BITRIX_ACCESS_DENIED` | The key's user has no access to the CRM item or payment in Bitrix24 |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is not allowed to write |
| 403 | `SCOPE_DENIED` | The API key does not have the `crm` scope |
| 401 | `TOKEN_MISSING` | The API key has no configured tokens |
| 401 | `MISSING_API_KEY` | `X-Api-Key` is missing |

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

## Known specifics

**`paid` has different types in the request and the response.** In the request body `paid` is a boolean, `true` or `false`; in the response it is the string `Y` or `N`.

**`paid` behaves like the pay and unpay endpoints.** `paid: true` sets the mark, like [`POST /v1/crm-payments/:id/pay`](/docs/entities/crm-payments/pay), and `paid: false` clears it, like [`POST /v1/crm-payments/:id/unpay`](/docs/entities/crm-payments/unpay). Repeating `paid: true` on a payment that is already marked paid does not change `datePaid`. The mark does not transfer money, but it can trigger automation rules, an email or an online receipt.

## See also

- [Get a CRM payment](/docs/entities/crm-payments/get)
- [Mark a payment as paid](/docs/entities/crm-payments/pay)
- [Clear the paid mark](/docs/entities/crm-payments/unpay)
- [Pay systems](/docs/entities/pay-systems)
- [CRM payments](/docs/entities/crm-payments)
