How-to guides

How to investigate and close a fraud case

Find open fraud cases, review risk details, and close resolved cases

Before you start

UrbanFox creates fraud cases automatically when it detects suspicious activity. When the activity includes an identified account, UrbanFox links the case to an end-user account. This guide shows you how to list, review, update the linked account when needed, and close cases programmatically.

  • A valid access token (see How to authenticate)
  • Your tenant_slug, the tenant DNS slug used in the API host and path (for example, demo-retail)
  • Required scopes: read:cases to list cases, read:case to review a case, update:case to close it, and read:enduseraccount to inspect the linked account; add update:enduseraccount if you need to block, quarantine, or mark the linked account

List open cases

curl 'https://api.YOUR_TENANT_SLUG.urbanfox.io/v2/YOUR_TENANT_SLUG/cases?status=OPEN' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Narrow by end-user account

If you know which account is under investigation:

curl 'https://api.YOUR_TENANT_SLUG.urbanfox.io/v2/YOUR_TENANT_SLUG/cases?website_account_id=ACC_123&status=OPEN' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Filter by blocked accounts

List cases only for accounts that are currently blocked:

curl 'https://api.YOUR_TENANT_SLUG.urbanfox.io/v2/YOUR_TENANT_SLUG/cases?user_is_blocked=true' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Review a specific case

Fetch full details including the risk score:

curl 'https://api.YOUR_TENANT_SLUG.urbanfox.io/v2/YOUR_TENANT_SLUG/cases/CASE_ID' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

The response includes the linked end-user account when UrbanFox can resolve one:

{
  "data": {
    "case_id": "case-abc123",
    "website_account_id": "account-abc123",
    "end_user_is_blocked": false,
    "status": "OPEN",
    "case_version": 4,
    "created_at": "2024-01-15T10:30:00Z",
    "current_state": {
      "case_status": "OPEN",
      "changed_at": "2024-01-15T10:35:00Z",
      "changed_by": "system",
      "reason": "Case created from model prediction",
      "score": 4
    },
    "details": {
      "overall_risk_score": 3,
      "primary_fraud_label": "Bot",
      "summary": "Account takeover attempt detected from suspicious IP",
      "recommended_action": "BLOCK"
    }
  }
}

This example shows only the fields used in this workflow. See the API Reference for the full case response schema.

Use data.details.recommended_action as one signal in the investigation. Copy data.website_account_id before you update account state, and check data.end_user_is_blocked so you know whether the account is already blocked. If website_account_id is UNKNOWN, continue with the case review only; UrbanFox could not resolve a website account identifier for that activity.

Keep data.case_version as well. Every update to the case must send the version you read, so the API can refuse an update based on a stale view instead of overwriting a change made after you looked.

Inspect the linked end-user account

Use the case's website_account_id to inspect the current account flags:

curl 'https://api.YOUR_TENANT_SLUG.urbanfox.io/v2/YOUR_TENANT_SLUG/enduseraccounts/WEBSITE_ACCOUNT_ID' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Response:

{
  "data": {
    "tenant_id": "00000000-0000-4000-8000-000000000000",
    "website_account_id": "account-abc123",
    "crm_account_id": "crm-abc456",
    "is_blocked": false,
    "is_quarantined": false,
    "is_fraudulent": false,
    "last_session_updated_on": "2024-01-15T10:30:00Z"
  }
}

Update the linked account

If the investigation confirms fraud, update only the flags that should change. For example, block the account and mark it as fraudulent:

curl -X PUT 'https://api.YOUR_TENANT_SLUG.urbanfox.io/v2/YOUR_TENANT_SLUG/enduseraccounts/WEBSITE_ACCOUNT_ID' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "is_blocked": true,
    "is_fraudulent": true
  }'

Use only the account flags that match the investigation outcome. Omit any field you do not want to change.

Close the case

After you resolve the review and confirm any linked account state, update the case status. Send the case_version you read with the case, and label the update with an Idempotency-Key so a retry is safe:

curl -X PUT 'https://api.YOUR_TENANT_SLUG.urbanfox.io/v2/YOUR_TENANT_SLUG/cases/CASE_ID' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 0198f4c2-8a1e-7b3d-9f5a-2c6e1d0b4a77' \
  -d '{ "status": "CLOSED", "case_version": 4 }'

The request must carry case_version. If the case changed after you read it, the API answers 409 with type set to item-version-conflict and the current version in current_version. Read the case again, decide whether your update still applies, and send it with the new version under a new Idempotency-Key, because a request with a different case_version is a different update.

Every case update requires an Idempotency-Key; the API refuses a request without one with 400. Generate one value for each update you intend. The API accepts any 1 to 255 characters from letters, digits, ., _, :, ~, + and -; use a UUID v7, which is unique without coordination and sorts by time; the API also takes any other UUID version. Send the same value if you retry that update after a timeout or a lost response. The API answers the retry with the result of the first attempt instead of applying the update again. Use a new value for each new update. If you send the same key with a different body, the API refuses it with 409 and type set to operation-idempotency-conflict.

If it's not working

SymptomAction
403 on case list or getConfirm your token has read:cases or read:case permission
403 on account getConfirm your token has read:enduseraccount permission
403 on account updateConfirm your token has update:enduseraccount permission
403 on case updateConfirm your token has update:case permission
428 on case updateSend the case_version you read with the case in the request body
409 item-version-conflict on case updateThe case changed after you read it. Read it again, decide whether your update still applies, and only then resend it with the current case_version under a new key
409 operation-idempotency-conflict on case updateYou reused an Idempotency-Key with a different body. Use a new key for a new update
400 invalid-operation-id on case updateSend an Idempotency-Key header: 1 to 255 characters from letters, digits, ., _, :, ~, + and -
404 on account getVerify the website_account_id from the case exists for your tenant
404 on case updateVerify the case ID exists for your tenant

See also