
# On-Demand Wallet Screening API

`POST /api/v1/wallet-screenings` screens one address. Use a secret `sk_test_` or `sk_live_` key on your backend. The key selects the environment. Use a new `Idempotency-Key` for each intended screening and keep that key for exact retries. A replay returns the same screening without another screening call or charge.

```bash
curl -X POST "https://sandbox.trust.myaza.app/api/v1/wallet-screenings" \
  -H "Authorization: Bearer $MYAZA_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: wallet-customer-42-20260930" \
  -d '{"network":"ethereum","address":"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","asset":"USDT","sandboxScenario":"wallet.sanctions.direct"}'
```

`network` is the chain, such as `ethereum` or `TRX`; `asset` is separate, so USDT and USDC are never network values. Network identifiers may contain letters, numbers, periods, underscores, colons and hyphens, up to 40 characters. Myaza translates known aliases and forwards other well-formed identifiers as supplied, without a fixed allowlist. The configured provider determines network and address acceptance. `sandboxScenario` works only with a test key. Optionally add `subject: {"externalUserId":"customer_42"}` to link an existing customer and enable linked alerts and cases.

With the Sandbox scenario above and the default direct-sanctions rule, these
selected response fields show what to inspect first. IDs and timestamps vary:

```json
{
  "screening": {
    "id": "wsc_test_example",
    "environment": "sandbox",
    "kind": "address",
    "status": "screened",
    "wallet": {
      "address": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "blockchain": "ETH",
      "asset": "USDT"
    },
    "risk": { "score": 92, "level": "critical" },
    "decision": "block",
    "exposures": [
      { "category": "SANCTIONS", "direction": "source", "type": "direct", "hops": 0, "share": 65 },
      { "category": "EXCHANGE", "direction": "source", "type": "direct", "hops": 0, "share": 35 }
    ],
    "fundFlow": {
      "kind": "exposure_map",
      "totalExposures": 2,
      "truncated": false,
      "nodes": [
        { "id": "exposure-0", "exposurePosition": 0, "category": "SANCTIONS", "direction": "source", "type": "direct", "hops": 0, "share": 65, "entityName": "OFAC SDN wallet", "isHighRisk": true },
        { "id": "exposure-1", "exposurePosition": 1, "category": "EXCHANGE", "direction": "source", "type": "direct", "hops": 0, "share": 35, "entityName": "Sandbox Exchange", "isHighRisk": false }
      ]
    },
    "rulesTriggered": [
      { "name": "Direct sanctions exposure", "version": 1, "action": "BLOCK" }
    ]
  }
}
```

The response also contains the full permitted profile, exposure details, rule
conditions, case links and timestamps. `block` is Myaza Trust's API value for a
decline decision. The `rulesTriggered` array records the matched rule version.

```ts
const response = await fetch(`${process.env.MYAZA_API_BASE_URL}/wallet-screenings`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MYAZA_SECRET_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'wallet-customer-42-20260930',
  },
  body: JSON.stringify({ network: 'ethereum', address: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' }),
});
const { screening } = await response.json();
console.log(screening.id, screening.decision, screening.rulesTriggered);
```

Responses use the Myaza Trust contract: `screening.id`, `environment`, `wallet`, `risk`, `decision`, `exposures`, `rulesTriggered`, `provider`, `screenedAt` and `createdAt`. The provider section contains only identity and references, never raw screening data. `decision` is `allow`, `review` or `block`. A new result returns `201`, an idempotent replay `200` and an in-progress request `202`.

## Wallet verification & screening

The address overview uses the saved screening evidence. These fields are also
returned for each counterparty wallet in a [transaction assessment](https://trust.myaza.co/documentation/monitoring-events/markdown#transaction-assessment-response).

| Address overview | Response field |
|---|---|
| Screening target | `kind` (`address` for a wallet address) |
| Wallet address | `wallet.address` |
| Provider | `provider.upstreamProvider` when returned, such as `merklescience` (Merkle Science); `provider.name` identifies the integration, such as `didit` |
| Score | `risk.score`, out of 100 |
| Risk level | `risk.level`, the Myaza severity band |
| Blockchain | `wallet.blockchain` |
| Owner | `profile.owner` |
| Owner type | `profile.ownerType` and optional `profile.ownerSubtype` |
| User | `profile.user` |
| User type | `profile.userType` and optional `profile.userSubtype` |
| First transaction time | `profile.firstTransactionAt` |
| Latest transaction time | `profile.latestTransactionAt` |
| Date added | `profile.providerDateAdded` |

Owner and user labels are separate provider attributions, not proof of wallet
control. Missing fields are `null`; Myaza does not copy owner into user or
substitute the screening date for a missing transaction date. Existing
`profile.entityLabel` and `profile.entityType` fields remain available.
`provider.riskLevel` preserves the provider's severity separately from Myaza's
risk band. An empty profile does not mean the wallet is clean.

Use `GET /api/v1/wallet-screenings/{id}` to read one result and `GET /api/v1/wallet-screenings?limit=50&cursor=...` for scoped history. A different organisation or environment cannot read the screening.

`GET /api/v1/wallet-screenings/{id}/report` downloads a Myaza PDF of the saved decision, exposures and triggered rule versions. It does not rerun screening and is not cryptographically signed. For a completed Didit address result, `GET /api/v1/wallet-screenings/{id}/signed-report` renders the vendor-signed PDF from the retained exact provider response. It returns `404` when no signed report exists and `502` when the vendor cannot render it. Neither route exposes the raw provider JSON. Both require the same scoped secret key as the result read.

`fundFlow` is a bounded exposure map: the first 40 normalised exposure rows, with `totalExposures` and `truncated` indicating whether more were retained in `exposures`. It does not claim a verified transfer path or expose the provider's raw graph.

| Result | Next step |
|---|---|
| `201` created | Save `screening.id` and act on `decision`. |
| `200` replay | Reuse the original result; no second screening or charge was created. |
| `202` in progress | Read the returned screening or assessment status before acting. |
| `422` invalid wallet or malformed network | Correct the input (`invalid_wallet` or `invalid_network`). No provider call or billable usage was made. |
| `409` idempotency conflict | Use the original payload with that key, or choose a new key for a new screening. |

If the provider rejects a network after receiving the request, Myaza records the failed attempt. This is separate from pre-call input validation; do not blindly retry it or treat it as a clean result.

Operational uncertainty becomes a reviewable result, never a clean wallet. To
receive updates, subscribe to [Wallet Intelligence webhooks](https://trust.myaza.co/documentation/webhook-wallet-intelligence/markdown).
