---
title: "Screen a transaction of your end customer"
method: POST
path: "/transaction-screenings"
tags: ["Screenings"]
---

# Screen a transaction of your end customer

`POST /transaction-screenings`

## Request

- Base URL: `https://developers.ozav.io/v2`
- URL: `https://developers.ozav.io/v2/transaction-screenings`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Screens one transaction of one of your end customers and returns `allow`, `review` or `deny`
with the rules that fired. The rules look at this transaction and at the history you sent
for the same `customer_reference` (in the same environment):

| rule | fires when | effect | score |
|---|---|---|---|
| `high_risk_country` | `counterparty_country` is KP, IR, SY or CU | deny | 100 |
| `large_amount` | `amount_usd` is 10,000 or more | review | 40 |
| `velocity_24h` | more than 10 transactions of the customer in the 24 hours ending at `occurred_at` | review | 30 |
| `structuring_24h` | this transaction is between 9,000 and 10,000 USD and is the 3rd or more of the customer in that band within those 24 hours | review | 50 |
| `volume_spike` | the customer's 24-hour volume (at least 1,000 USD) is more than 5 times its daily average of the 29 days before, with at least 5 transactions in those days | review | 30 |

| `prior_confirmed_risk` | you reported a transaction of this customer as `confirmed_risk` in the last 90 days | review | 50 |

`effect` is the worst rule (`deny` over `review` over `allow`); `score` is the sum, capped at
100. `rule_version` names the rule set. The result is advice: OZAV never blocks the transaction.

**It learns from your outcomes.** Report how a transaction ended with
`POST /transaction-screenings/{transaction_screening_id}/outcome`. A `false_positive` clears the
rules that fired on it (`large_amount`, `velocity_24h`, `volume_spike`) for that customer for 90
days, up to the size you labeled (the amount, the 24-hour count or the 24-hour volume of that
transaction): above it the rule counts again. A cleared rule still shows in `reasons` with
`cleared_by` and score 0, and no longer counts; a cleared rule never clears the next one. A
`confirmed_risk` flags every next transaction of that customer for 90 days. A sanctioned country
and structuring are never cleared, and your outcomes never affect another organization. Your
outcomes tune the advice you receive; each one is your statement, kept with the screening.

`customer_reference` is your own id for the end customer. OZAV never stores it in clear,
only a keyed hash of it with your organization, so do not send a document number or an
email there: any stable id of yours works.

Send `external_id` (your transaction id) to make each transaction count once: a second call
with the same `external_id` is `409 DUPLICATE_TRANSACTION` and is not charged. Send an
`Idempotency-Key` to retry the same call safely.

`occurred_at` (default: now) must be within the last 30 days and not in the future. A
transaction is judged against the ones already sent with an earlier or equal `occurred_at`,
so send each customer's transactions in chronological order.

## Headers

- `Idempotency-Key` string

## Request body

- TransactionScreeningRequest
  - `customer_reference` string, required: Your own stable id for the end customer. Stored only as a keyed hash; do not send a document number or an email.
  - `external_id` string: Your transaction id. A second call with the same one is 409 and not charged.
  - `direction` 'inbound' | 'outbound', required
  - `amount_usd` string, required: The value in US dollars, as a decimal string greater than zero. The rules use this value.
  - `currency` string: The original currency or token, for your record. Optional.
  - `amount` string: The value in the original currency, for your record. Optional.
  - `counterparty_country` string: The country of the other side (ISO 3166-1 alpha-2). Optional.
  - `occurred_at` string, date-time: When the transaction happened. Default: now. Within the last 30 days, not in the future.

## Response `201`

The screened transaction.

- TransactionScreening
  - `object` 'transaction_screening', required
  - `id` string, required: The transaction screening id (`ozav_txs_...`).
  - `livemode` boolean, required
  - `external_id` string, nullable
  - `direction` 'inbound' | 'outbound', required
  - `amount_usd` string, required
  - `currency` string, nullable
  - `amount` string, nullable
  - `counterparty_country` string, nullable
  - `occurred_at` string, date-time, required
  - `effect` 'allow' | 'review' | 'deny', required: The worst rule that fired; `allow` when none did. Advice only: OZAV never blocks the transaction, the decision is yours.
  - `score` integer, required: The sum of the rules' scores, capped at 100.
  - `reasons` object[], required
    - `code` 'high_risk_country' | 'large_amount' | 'velocity_24h' | 'structuring_24h' | 'volume_spike' | 'prior_confirmed_risk', required
    - `effect` 'review' | 'deny', required
    - `score` integer, required: 0 when the rule is cleared by your own outcome (see `cleared_by`).
    - `severity` 'low' | 'medium' | 'high', required
    - `detail` string, required
    - `cleared_by` string, nullable: The screening you reported as `false_positive` for this customer in the last 90 days, which clears this rule for this customer up to the size you labeled. A cleared rule still shows here, with score 0, and does not count toward `effect`. Only `large_amount`, `velocity_24h` and `volume_spike` can be cleared.
    - `related_screening` string, nullable: For `prior_confirmed_risk`, the screening you reported as `confirmed_risk`.
  - `rule_version` string, required
  - `outcome` 'confirmed_risk' | 'false_positive' | 'completed' | 'reversed' | 'chargeback' | 'null', nullable: How the transaction ended, as you reported it with `POST /transaction-screenings/{transaction_screening_id}/outcome`; `null` until then.
  - `outcome_note` string, nullable
  - `outcome_at` string, date-time, nullable
  - `created_at` string, date-time, required

## Other responses

- `202`: Accepted - a concurrent request with the same `Idempotency-Key` is still executing; the body is `{"status": "processing"}`. Retry with the same key once the original completes: a completed request replays its stored response verbatim with the original status code and the `Idempotent-Replayed: true` header. This is not the resource - check the status code before deserializing. If this `202` repeats instead of resolving, the original request's outcome is unknown and no stored result will arrive - the key stays claimed when a request fails after the resource may already have been written. Stop retrying, reconcile with a `GET` to see whether the resource exists, and use a new key if it does not.
- `400`: The request is invalid.
- `401`: Missing or invalid API key.
- `402`: OZAV Services only (prepaid): your prepaid credit does not cover this call (code INSUFFICIENT_CREDIT). The call did not run and nothing was charged. `details` carries `balance_usd` and `price_usd`; top up in the portal (Billing) and retry. Organizations on the OZAV platform are invoiced monthly and never receive this. On `POST /decisions` and `POST /screenings`, where OZAV accepts x402 payment, the response also carries an x402 v2 challenge: the `PAYMENT-REQUIRED` header (base64 `PaymentRequired`) and the same object in `details.x402` - the price of this one call in native USDC, paid to OZAV. `accepts` lists one entry per network OZAV receives on, Polygon (`eip155:137`) first; where enabled, a second entry offers Base (`eip155:8453`). Pay on any one of them, signing for that network's USDC (the entry's `asset`); a payment whose `accepted.asset` is not the USDC of its `network` is refused with `network_or_asset_not_accepted`. Retry with the `PAYMENT-SIGNATURE` header to pay it. A payment OZAV does not accept answers `402 X402_PAYMENT_REJECTED` with `details.reason` (`amount_below_price`, `wrong_pay_to`, `network_or_asset_not_accepted`, `authorization_not_yet_valid`, `authorization_expires_too_soon`, `authorization_window_too_long`, `signature_invalid`, `authorization_already_used`, `insufficient_balance`, `settlement_refused`, `payment_malformed` or `payment_not_accepted`) and a new challenge; nothing was charged. If OZAV could not confirm the settlement, `503 X402_PAYMENT_UNAVAILABLE`: retry with the same header, it is never charged twice. A retried payment is credited only once OZAV reads, on-chain, the transaction that used the authorization moving the USDC to OZAV; when that cannot be established automatically, the `503` says the payment needs a manual check (contact support with the request id). Where OZAV also accepts MPP, the response carries an MPP challenge in `WWW-Authenticate: Payment` (method `evm`, intent `charge`, credential type `authorization`, valid for five minutes) and in `details.mpp.www_authenticate`. A credential OZAV does not accept answers `402 MPP_PAYMENT_REJECTED` with `details.reason` (the reasons above, plus `challenge_not_issued_by_ozav`, `challenge_expired` and `nonce_not_bound_to_challenge`); `503 MPP_PAYMENT_UNAVAILABLE` means retry with the same credential.
- `403`: The organization lacks the entitlement (code ENTITLEMENT_REQUIRED) or the key lacks the scope (code INSUFFICIENT_SCOPE). On money-moving requests (POST /transfers, POST /wallets/{wallet_id}/transfers) a compliance hold on the customer answers with code COMPLIANCE_HOLD: the hold is reviewed and lifted on OZAV's side; retrying does not clear it. When the only hold is a pause until pending onboarding documents are received, a withdrawal answers CUSTOMER_DOCUMENTS_PENDING instead: send what `requirements` on GET /customers/{id} lists; OZAV lifts the pause after reviewing it. On OZAV Services (live without the platform's KYB), any API outside decisions, screenings, lists and self-custody wallets answers with code PRODUCTION_NOT_ACTIVATED.
- `409`: Conflict (e.g. IDEMPOTENCY_KEY_REUSED).
- `503`: A dependency OZAV needs (storage, a resource lookup) could not be reached, so OZAV fails closed rather than guess. Safe to retry with the same `Idempotency-Key` where the operation accepts one.

---

[API](https://skmtc.dev/ozav/apis/ozav-api.md) - [All operations](https://skmtc.dev/ozav/apis/ozav-api/llms.txt) - [OpenAPI document](https://skmtc.dev/ozav/apis/ozav-api/revisions/4133dc2109d7?raw)
