# Approve a held payment

> The risk.reviews.approve operation: POST /api/v1/risk/reviews/{id}/approve in the REST API and the risk_reviews_approve tool in the MCP server.

Releases a payment the risk rules held: the customer's wallets the hold froze are unfrozen (unless another
review of the customer is still pending, or someone changed the wallet since), the hold's reason leaves the
customer's risk (back to `normal` when nothing else elevated it), and `risk.review.approved` goes to your
webhook endpoints, so your integration can deliver what it held back. A review is decided once.

## At a glance

- **REST:** `POST /api/v1/risk/reviews/{id}/approve`
- **MCP tool:** `risk_reviews_approve`
- **Operation ID:** `risk.reviews.approve`
- **Scope:** Brand
- **Permission:** `customers:write`
- **Risk tier:** `write`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** API key with the `customers:write` scope
- **Since:** M9

## Request

### Path parameters

| Field | Type       | Description |
| ----- | ---------- | ----------- |
| `id`  | `rsk_…` ID |             |

### Body

A JSON object (`Content-Type: application/json`):

| Field             | Type                 | Required | Description |
| ----------------- | -------------------- | -------- | ----------- |
| `note`            | string               | No       |             |
| `expectedVersion` | integer (at least 1) | No       |             |

## Response

`200 OK` with a JSON object:

| Field                       | Type                                                                                                            | Description |
| --------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------- |
| `id`                        | `rsk_…` ID                                                                                                      |             |
| `object`                    | `"risk_evaluation"`                                                                                             |             |
| `livemode`                  | boolean                                                                                                         |             |
| `brandId`                   | `brd_…` ID                                                                                                      |             |
| `subject`                   | object                                                                                                          |             |
| `subject.type`              | one of `checkout_session`, `auto_reload`                                                                        |             |
| `subject.checkoutSessionId` | `cs_…` ID or null                                                                                               |             |
| `subject.walletId`          | `wal_…` ID or null                                                                                              |             |
| `customerId`                | `cus_…` ID or null                                                                                              |             |
| `paymentId`                 | `pay_…` ID or null                                                                                              |             |
| `stage`                     | one of `intent`, `payment_method`, `confirm`, `off_session`                                                     |             |
| `decision`                  | one of `allow`, `require_3ds`, `review`, `block`                                                                |             |
| `score`                     | integer                                                                                                         |             |
| `reasons`                   | array of object                                                                                                 |             |
| `reasons[].code`            | one of 21 values, e.g. `customer_blocked`, `customer_elevated`, `blocklist_email` (all in the OpenAPI document) |             |
| `reasons[].action`          | one of `block`, `review`, `require_3ds`, `score`                                                                |             |
| `reasons[].points`          | integer or null                                                                                                 |             |
| `reasons[].detail`          | string                                                                                                          |             |
| `signals`                   | object                                                                                                          |             |
| `signals.emailDomain`       | string or null                                                                                                  |             |
| `signals.ipNetwork`         | string or null                                                                                                  |             |
| `signals.ipCountry`         | string or null                                                                                                  |             |
| `signals.billingCountry`    | string or null                                                                                                  |             |
| `signals.cardCountry`       | string or null                                                                                                  |             |
| `signals.cardFingerprint`   | string or null                                                                                                  |             |
| `signals.amount`            | money or null                                                                                                   |             |
| `signals.topup`             | boolean                                                                                                         |             |
| `signals.newCustomer`       | boolean                                                                                                         |             |
| `signals.firstPayment`      | boolean                                                                                                         |             |
| `signals.disposableEmail`   | boolean                                                                                                         |             |
| `history`                   | array of object                                                                                                 |             |
| `history[].at`              | date-time                                                                                                       |             |
| `history[].stage`           | one of `intent`, `payment_method`, `confirm`, `off_session`                                                     |             |
| `history[].decision`        | one of `allow`, `require_3ds`, `review`, `block`                                                                |             |
| `history[].score`           | integer                                                                                                         |             |
| `review`                    | object or null                                                                                                  |             |
| `createdAt`                 | date-time                                                                                                       |             |
| `updatedAt`                 | date-time                                                                                                       |             |

## Examples

### Approve after calling the customer

```http
POST /api/v1/risk/reviews/rsk_01m4ttwd3veh9apbewxheq5xr1/approve
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "note": "Called the customer; the card is a company card."
}
```

Response (`200`):

```json
{
  "id": "rsk_01m4ttwd3veh9apbewxheq5xr1",
  "object": "risk_evaluation",
  "livemode": true,
  "brandId": "brd_01m4ttwd3veh9apbewxheq5xh5",
  "subject": {
    "type": "checkout_session",
    "checkoutSessionId": "cs_01m4ttwd3veh9apbewxheq5xr4",
    "walletId": null
  },
  "customerId": "cus_01m4ttwd3veh9apbewxheq5xr5",
  "paymentId": "pay_01m4ttwd3veh9apbewxheq5xr6",
  "stage": "confirm",
  "decision": "review",
  "score": 70,
  "reasons": [
    {
      "code": "first_payment",
      "action": "require_3ds",
      "points": null,
      "detail": "The customer's first payment."
    },
    {
      "code": "new_customer",
      "action": "score",
      "points": 10,
      "detail": "A new customer."
    },
    {
      "code": "country_mismatch_ip_billing",
      "action": "score",
      "points": 20,
      "detail": "IP country DE isn't the billing country HR."
    },
    {
      "code": "country_mismatch_card_billing",
      "action": "score",
      "points": 25,
      "detail": "Card country US isn't the billing country HR."
    },
    {
      "code": "country_mismatch_ip_card",
      "action": "score",
      "points": 15,
      "detail": "IP country DE isn't the card country US."
    },
    {
      "code": "score_review",
      "action": "review",
      "points": null,
      "detail": "Score 70 ≥ 70."
    }
  ],
  "signals": {
    "emailDomain": "corp.example",
    "ipNetwork": "203.0.113.0",
    "ipCountry": "DE",
    "billingCountry": "HR",
    "cardCountry": "US",
    "cardFingerprint": "Xo1abC9dEf2GhI3j",
    "amount": {
      "amount": "90000000",
      "currency": "EUR"
    },
    "topup": true,
    "newCustomer": true,
    "firstPayment": true,
    "disposableEmail": false
  },
  "history": [
    {
      "at": "2026-10-20T09:00:00.000Z",
      "stage": "intent",
      "decision": "require_3ds",
      "score": 30
    },
    {
      "at": "2026-10-20T09:01:00.000Z",
      "stage": "payment_method",
      "decision": "review",
      "score": 70
    },
    {
      "at": "2026-10-20T09:01:30.000Z",
      "stage": "confirm",
      "decision": "review",
      "score": 70
    }
  ],
  "review": {
    "status": "approved",
    "openedAt": "2026-10-20T09:02:00.000Z",
    "decidedAt": "2026-10-20T11:30:00.000Z",
    "decidedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
    "note": "Called the customer; the card is a company card.",
    "refundId": null,
    "blocklistEntryIds": [],
    "frozenWalletIds": ["wal_01m4ttwd3veh9apbewxheq5xr7"]
  },
  "createdAt": "2026-10-20T09:00:00.000Z",
  "updatedAt": "2026-10-20T11:30:00.000Z"
}
```

## Errors

| Code                                                                                                 | HTTP status | When                                                               | What to do                                                                                                                                                          |
| ---------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)             | 404         | No risk evaluation with this ID in this brand and mode.            | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                                                |
| [`risk_evaluation_not_held`](https://ownrecord.io/docs/reference/errors.md#risk_evaluation_not_held) | 400         | The evaluation held no payment for review.                         | Only payments the risk rules held are in the review queue (`risk.reviews.list`). Refund other payments with `refunds.create`.                                       |
| [`risk_review_not_pending`](https://ownrecord.io/docs/reference/errors.md#risk_review_not_pending)   | 409         | The review was already approved or rejected.                       | Reload the review: it was approved or rejected (`details.status`). A decision is final; refund the payment or unfreeze the wallet by hand if something must change. |
| [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict)                 | 409         | The review changed since `expectedVersion`, or at the same moment. | Fetch the object again and retry the change.                                                                                                                        |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)               | 403         | You can't change this brand's customers.                           | Ask an organization owner or admin for a role or scope that allows this action.                                                                                     |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed), [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required), [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected), [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired), [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
