---
title: "Update a case"
method: PUT
path: "/v2/{tenant_slug}/cases/{case_id}"
tags: ["Cases"]
---

# Update a case

`PUT /v2/{tenant_slug}/cases/{case_id}`

## Request

- Base URL: `https://api.demo-retail.urbanfox.io`
- URL: `https://api.demo-retail.urbanfox.io/v2/{tenant_slug}/cases/{case_id}`
- Auth: OAuth 2
  - Client credentials
  - Token URL: `https://api.demo-retail.urbanfox.io/v2/oauth/token` scopes: `update:case`
- TypeScript: <https://skmtc.dev/urbanfox/apis/urbanfox-customer-api/docs/v2/:tenant_slug/cases/:case_id/put.ts> (a function with typed inputs; its response is checked against the schema)

Update the status of a fraud case.

## Path parameters

- `tenant_slug` string, required: Tenant DNS slug (lowercase letters, digits, and hyphens; 3-100 characters).
- `case_id` string, required: Unique case identifier returned by `GET /cases`.

## Headers

- `idempotency-key` string, required: Required. Names this update so a retry of it is answered with the recorded outcome instead of being applied again. Generate one value per user action or logical operation, a UUID v7 is recommended, and send the same value on every retry of that action. 1 to 255 characters from A-Z a-z 0-9 . _ : ~ + -. A request without the header is refused with 400.

## Request body

- CaseUpdateRequest
  - `status` 'OPEN' | 'CLOSED', required
  - `case_version` integer, required: The `case_version` this update was based on, as returned by the last read of the case. The update is applied only while the stored version still matches; if the case has been written since, the request is refused with `409` carrying the current version, and nothing is overwritten. Send `0` for a case that has never carried a version. Required: a request that omits it is refused with `428`.

## Response `200`

Successful Response

- APIResponseCaseSingleResponse
  - `data` CaseSingleResponse, required
    - `case_id` string, required: Unique case identifier used in `/cases/{case_id}` paths
    - `tenant_id` string, uuid, required: Identifier of the tenant that owns this case
    - `website_account_id` string: Identifier of the end user's website account; defaults to `UNKNOWN` when the account cannot be resolved
    - `session_id` string, required: Identifier of the user session this case originated from
    - `unique_id` string, uuid, required: Opaque record identifier; clients should use `case_id` for all references
    - `end_user_is_blocked` boolean, required: True when the end user account associated with this case is blocked from accessing services
    - `status` 'OPEN' | 'CLOSED', required
    - `created_at` string, date-time, required: Time the case was opened (RFC 3339 / ISO 8601 UTC)
    - `updated_at` string, date-time, nullable: Time of the most recent case update (RFC 3339 / ISO 8601 UTC); null when the case has never been updated
    - `case_version` integer: Current version of the case. An update advances it by one; newly created and legacy cases read as 0 until their first update.
    - `current_state` CaseState
      - `state_id` string, uuid, required: Stable identifier for this state snapshot, unique within the case
      - `case_status` 'OPEN' | 'CLOSED', required
      - `session_status` 'ACTIVE' | 'FINISHED', required
      - `score` integer, required: Risk score captured by this snapshot, on a 1-5 scale (1=lowest risk, 5=highest risk)
      - `changed_at` string, date-time, required: Time this state change was recorded (RFC 3339 / ISO 8601 UTC)
      - `changed_by` string: Identifier of the actor that triggered this state change. Use `system` for automated transitions; otherwise the user_id of the operator.
      - `reason` string, required: Human-readable explanation of why the state changed
    - `state_history` CaseState[], nullable: All prior state snapshots for this case ordered chronologically (oldest first)
      - `state_id` string, uuid, required: Stable identifier for this state snapshot, unique within the case
      - `case_status` 'OPEN' | 'CLOSED', required
      - `session_status` 'ACTIVE' | 'FINISHED', required
      - `score` integer, required: Risk score captured by this snapshot, on a 1-5 scale (1=lowest risk, 5=highest risk)
      - `changed_at` string, date-time, required: Time this state change was recorded (RFC 3339 / ISO 8601 UTC)
      - `changed_by` string: Identifier of the actor that triggered this state change. Use `system` for automated transitions; otherwise the user_id of the operator.
      - `reason` string, required: Human-readable explanation of why the state changed
    - `details` CaseDetails, required
      - `overall_risk_score` integer, required: Aggregate risk score across all fraud signals, on a 1-5 scale (1=lowest risk, 5=highest risk)
      - `fraud_labels` FraudLabels[], required: All fraud categories detected for this case; must contain at least the `primary_fraud_label`
      - `primary_fraud_label` 'Bot' | 'Synthetic' | 'Account Takeover' | 'Organic', required
      - `summary` string, nullable, required: Natural-language summary of the detected fraud signals; null when no summary is available
      - `highlights` string[]: Short bullet points highlighting the strongest fraud signals contributing to the score
      - `recommended_action` 'MONITOR' | 'CHALLENGE' | 'BLOCK' | 'ESCALATE'
    - `fraud_occurrences` object: Fraud occurrences for this case, keyed by `occurrence_id`. Entries follow the detailed fraud occurrence shape.

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not Found
- `409`: Conflict
- `422`: Request Validation Failed
- `428`: Precondition Required
- `500`: Internal Server Error

## Changes

- **2026-09-09** `62f04976cbf5`: 2 breaking, 1 info
  - the `header` request parameter `idempotency-key` became required
  - `header` request parameter `idempotency-key` list-of-types was narrowed by removing types `null`
  - for the `header` request parameter `idempotency-key`, the type was generalized from no type to `string`
- **2026-09-09** `9dd568e86298`: 2 breaking, 1 info
  - the response property `current_version` became optional for the status `409`
  - response property `current_version` list-of-types was widened by adding types `null` to media type `application/json` of response `409`
  - added the new optional `header` request parameter `idempotency-key`
- **2026-09-07** `ec70a1fc77c3`: 1 breaking, 1 info
  - the request property `case_version` became required
  - added the non-success response with the status `428`
- **2026-09-05** `bb2118aaf1af`: 3 info
  - added the new optional request property `case_version`
  - added the non-success response with the status `409`
  - added the optional property `data/case_version` to the response with the `200` status

[Operation history](https://skmtc.dev/urbanfox/apis/urbanfox-customer-api/changes/v2/:tenant_slug/cases/:case_id/put.md)

---

[API](https://skmtc.dev/urbanfox/apis/urbanfox-customer-api.md) - [All operations](https://skmtc.dev/urbanfox/apis/urbanfox-customer-api/llms.txt) - [OpenAPI document](https://skmtc.dev/urbanfox/apis/urbanfox-customer-api/revisions/2850fb315db8?raw)
