> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentscore.com/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /v1/assess

> Identity verification gate with policy evaluation. Available on all tiers, metered by quota.

<Info>
  Available on every tier including Free, metered by your monthly quota ([pricing](https://www.agentscore.com/pricing)).
</Info>

## Request

### Headers

| Header         | Required | Description                             |
| -------------- | -------- | --------------------------------------- |
| `X-API-Key`    | Yes      | Your API key (any tier, including Free) |
| `Content-Type` | Yes      | `application/json`                      |

### Body

Provide either `address` (wallet) or `operator_token` (credential). At least one is required.

```json theme={"dark"}
{
  "address": "0xdb5aa553feeb2c3e3d03e8360b36fb0f7e480671",
  "chain": "base",
  "refresh": false,
  "policy": {
    "require_kyc": true,
    "require_sanctions_clear": true,
    "min_age": 21,
    "blocked_jurisdictions": ["KP", "IR"]
  }
}
```

Or with an operator credential (for non-wallet agents):

```json theme={"dark"}
{
  "operator_token": "opc_abc123...",
  "policy": {
    "require_kyc": true,
    "min_age": 21
  }
}
```

| Field                            | Type           | Required                       | Description                                                                                                                                                                                                                                                                                                          |
| -------------------------------- | -------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address`                        | string         | One of address/operator\_token | Wallet address: EVM (`0x...` 40-hex) or Solana (base58, 32–44 chars). Network is auto-detected from the address format.                                                                                                                                                                                              |
| `operator_token`                 | string         | One of address/operator\_token | Operator credential (`opc_...`) for non-wallet agents. Credential resolves to an account's KYC status.                                                                                                                                                                                                               |
| `chain`                          | string         | No                             | Chain to score. If omitted, scores the chain with the highest existing score. Defaults to `base` for unknown addresses.                                                                                                                                                                                              |
| `refresh`                        | boolean        | No                             | Force score recomputation even if cached                                                                                                                                                                                                                                                                             |
| `policy`                         | object         | No                             | Policy rules for allow/deny decision                                                                                                                                                                                                                                                                                 |
| `policy.require_kyc`             | boolean        | No                             | Require operator KYC verification                                                                                                                                                                                                                                                                                    |
| `policy.require_sanctions_clear` | boolean        | No                             | Require clean sanctions status                                                                                                                                                                                                                                                                                       |
| `policy.min_age`                 | number         | No                             | Minimum age bracket (18 or 21)                                                                                                                                                                                                                                                                                       |
| `policy.blocked_jurisdictions`   | string\[]      | No                             | ISO country codes to block (blocklist)                                                                                                                                                                                                                                                                               |
| `policy.allowed_jurisdictions`   | string\[]      | No                             | ISO country codes to allow (allowlist: only these pass)                                                                                                                                                                                                                                                              |
| `signer`                         | object         | No                             | Server-side wallet-signer verdicts. When present, the API resolves `signer.address` to its operator and emits both a `signer_match` block (wallet-binding) and a `signer_sanctions` block (OFAC SDN wallet check) on the response: lets commerce gates collapse multiple follow-up assess calls into one round trip. |
| `signer.address`                 | string \| null | If `signer` set                | Recovered payment-signer wallet. `null` indicates the rail carries no wallet signature (Stripe SPT, card): produces `signer_match.kind = "wallet_auth_requires_wallet_signing"`.                                                                                                                                     |
| `signer.network`                 | string         | If `signer` set                | Key-derivation family of the signer wallet. `evm` or `solana`.                                                                                                                                                                                                                                                       |
| `test`                           | boolean        | No                             | Enable sandbox/test mode with reserved addresses                                                                                                                                                                                                                                                                     |

## Test mode

Set `"test": true` to get controlled responses without hitting the database, rate limits, or billing. Test calls are not logged to the production audit trail.

Test mode only works with these reserved addresses:

| Address                                      | Behavior                                                                     |
| -------------------------------------------- | ---------------------------------------------------------------------------- |
| `0x0000000000000000000000000000000000000001` | `allow`: verified individual, US, 21+                                        |
| `0x0000000000000000000000000000000000000002` | `deny`: kyc\_required, includes `verify_url`                                 |
| `0x0000000000000000000000000000000000000003` | `deny`: sanctions\_flagged                                                   |
| `0x0000000000000000000000000000000000000004` | `allow`: verified individual, DE                                             |
| `0x0000000000000000000000000000000000000005` | `allow`: verified individual, US, 18+ (denies on `min_age: 21`)              |
| `0x0000000000000000000000000000000000000006` | `allow`: verified individual, IR (denies on `blocked_jurisdictions: ["IR"]`) |
| `0x0000000000000000000000000000000000000007` | `allow`: verified individual, BR (denies on `allowed_jurisdictions: ["US"]`) |

Using `test: true` with any other address returns a 400 error.

Test responses include `"test": true` in the response body. If you include a `policy` object, the test fixtures evaluate the policy rules against their built-in verification data and return realistic `decision`, `decision_reasons`, and `policy_result` fields.

```json theme={"dark"}
{
  "address": "0x0000000000000000000000000000000000000001",
  "test": true,
  "policy": {
    "require_kyc": true,
    "min_age": 21
  }
}
```

## Response

```json theme={"dark"}
{
  "decision": "allow",
  "decision_reasons": [],
  "policy_result": {
    "all_passed": true,
    "checks": [
      { "rule": "require_kyc", "passed": true, "required": "verified", "actual": "verified" },
      { "rule": "require_sanctions_clear", "passed": true, "required": "clear", "actual": "clear" }
    ]
  },
  "explanation": [
    {
      "rule": "require_kyc",
      "passed": true,
      "required": "verified",
      "actual": "verified",
      "message": "Identity verification is complete",
      "how_to_remedy": null
    },
    {
      "rule": "require_sanctions_clear",
      "passed": true,
      "required": "clear",
      "actual": "clear",
      "message": "Sanctions screening is clear",
      "how_to_remedy": null
    }
  ],
  "identity_method": "wallet",
  "operator_verification": {
    "level": "kyc_verified",
    "operator_type": "individual",
    "verified_at": "2026-03-02T14:00:00Z"
  },
  "resolved_operator": "0xdb5aa553feeb2c3e3d03e8360b36fb0f7e480671",
  "linked_wallets": [
    "0xdb5aa553feeb2c3e3d03e8360b36fb0f7e480671",
    "0x1111111111111111111111111111111111111111"
  ],
}
```

<Note>
  The assess endpoint returns identity verification data only. For reputation scores, use `GET /v1/reputation/:address`.
</Note>

### Wallet-mode response fields

When the request was authenticated by `X-Wallet-Address` (or `address` was supplied), the response includes:

| Field               | Type      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `resolved_operator` | string    | Canonical operator wallet (the earliest claimed wallet, or the lexically-smallest active-captured wallet). All same-operator sibling wallets resolve to this address.                                                                                                                                                                                                                                                                        |
| `linked_wallets`    | string\[] | All same-operator sibling wallets, normalized per network: EVM addresses lowercased, Solana base58 preserved verbatim. Includes both wallets claimed via identity verification and wallets captured via `POST /v1/credentials/wallets`. May contain a mix of EVM and Solana addresses for multi-chain operators. Merchants doing wallet-signer-match checks should accept a payment signed by any address in this list, regardless of chain. |
| `signer_match`      | object    | Returned only when the request supplied `signer`. Server-side wallet-signer-match verdict: see below.                                                                                                                                                                                                                                                                                                                                        |
| `signer_sanctions`  | object    | Returned only when the request supplied `signer`. Server-side OFAC SDN wallet-address verdict: see below.                                                                                                                                                                                                                                                                                                                                    |

### `signer_match` response (when `signer` was supplied)

When the request body included `signer`, the response carries a `signer_match` block describing whether the supplied signer wallet resolves to the same operator as the claimed `address`. Lets commerce gates skip the legacy 2 follow-up assess calls.

```json theme={"dark"}
{
  "decision": "allow",
  "decision_reasons": [],
  "resolved_operator": "0xclaimed...",
  "linked_wallets": ["0xclaimed...", "0xsigner..."],
  "signer_match": {
    "kind": "pass",
    "claimed_operator": "0xclaimed...",
    "signer_operator": "0xclaimed..."
  },
  "identity_method": "wallet",
}
```

| Field                             | Type           | Description                                                                                                                                                                                                                                            |
| --------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `signer_match.kind`               | string         | `pass` (claimed and signer resolve to the same operator, or are byte-equal); `wallet_signer_mismatch` (operators differ); `wallet_auth_requires_wallet_signing` (request supplied `signer.address: null`: agent should switch to operator-token auth). |
| `signer_match.claimed_operator`   | string \| null | Operator the claimed wallet resolves to. `null` if unlinked.                                                                                                                                                                                           |
| `signer_match.signer_operator`    | string \| null | Operator the signer wallet resolves to. `null` if unlinked.                                                                                                                                                                                            |
| `signer_match.expected_signer`    | string         | Echoed on `wallet_signer_mismatch`: the claimed wallet, normalized.                                                                                                                                                                                    |
| `signer_match.actual_signer`      | string         | Echoed on `wallet_signer_mismatch`: the signer wallet, normalized.                                                                                                                                                                                     |
| `signer_match.linked_wallets`     | string\[]      | Same-operator linked wallets the agent could re-sign from to satisfy the claim. Omitted when the list is empty (the deny-guard zeroes the list under a top-level `deny`, but it's also omitted for any non-mismatch verdict).                          |
| `signer_match.agent_instructions` | string         | JSON-encoded `{action, steps, user_message}` envelope for SDK denial bodies. Spread verbatim into the gate's 403 response.                                                                                                                             |

### `signer_sanctions` response (when `signer` was supplied)

In addition to the wallet-binding verdict, the same `signer` request field powers a wallet-address OFAC SDN check. AgentScore pulls the OFAC SDN Advanced XML hourly into an indexed `ofac_sanctioned_addresses` table; the lookup is keyed on the format-classified address family (`evm`, `solana`, ...). The verdict slots in under `signer_sanctions` alongside `signer_match`.

Three terminal shapes:

```json theme={"dark"}
{ "signer_sanctions": { "status": "clear" } }
```

```json theme={"dark"}
{
  "signer_sanctions": {
    "sanctioned": true,
    "ofac_label": "ETH",
    "sdn_uid": "19011",
    "listed_at": "2018-05-03"
  }
}
```

```json theme={"dark"}
{ "signer_sanctions": { "status": "unavailable" } }
```

| Field                         | Type           | Description                                                                                                                                                                               |
| ----------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `signer_sanctions.status`     | string         | `clear` (address not on the list) OR `unavailable` (the lookup itself failed; the API fail-closes; see below).                                                                            |
| `signer_sanctions.sanctioned` | boolean        | Present and `true` ONLY on a hit; omitted on `clear` / `unavailable`.                                                                                                                     |
| `signer_sanctions.ofac_label` | string         | The Digital Currency Address label OFAC published the hit under (`ETH`, `XBT`, `USDT`, `SOL`, ...). Investigation-history metadata; the gate enforcement is keyed on `family`, not label. |
| `signer_sanctions.sdn_uid`    | string         | The SDN entry's Identity ID. Same `sdn_uid` may surface multiple addresses (one entity, multiple wallets); join key for audit.                                                            |
| `signer_sanctions.listed_at`  | string \| null | ISO date OFAC initially designated the entity. May be `null` if upstream omits the date.                                                                                                  |

**Unconditional enforcement on the signer block.** When a `signer` is supplied in the request body, `signer_sanctions` enforcement is automatic; no policy flag opts in or out. SDN hit (`sanctioned === true`) → `decision: deny` with `decision_reasons: ["sanctions_flagged"]`. Lookup failure (`status === "unavailable"`) → `decision: deny` with `decision_reasons: ["sanctions_check_unavailable"]`. The asymmetric cost (falsely allowing a sanctioned settle is an OFAC strict-liability violation, falsely denying a clean buyer is just bad UX) justifies the fail-closed posture. Wallet-OFAC SDN screening is strict-liability under US law; merchants cannot legally opt out of receiving funds from SDN-listed wallets, so the API does not expose an opt-out.

This is distinct from `policy.require_sanctions_clear`, which enforces the NAME-based sanctions screen on the resolved operator's KYC identity (sourced from the KYC vendor at verification time). `require_sanctions_clear` is opt-in; the wallet-address `signer_sanctions` enforcement is not.

### Deny response

When a policy check fails, the response includes `verify_url` if the denial is resolvable through verification:

```json theme={"dark"}
{
  "decision": "deny",
  "decision_reasons": ["kyc_required"],
  "policy_result": {
    "all_passed": false,
    "checks": [
      { "rule": "require_kyc", "passed": false, "required": "verified", "actual": "none" }
    ]
  },
  "explanation": [
    {
      "rule": "require_kyc",
      "passed": false,
      "required": "verified",
      "actual": "none",
      "message": "Identity verification has not been completed for this account",
      "how_to_remedy": "Direct the user to https://www.agentscore.com/dashboard/verify to complete Stripe Identity verification."
    }
  ],
  "identity_method": "wallet",
  "operator_verification": { "level": "none" },
  "verify_url": "https://www.agentscore.com/dashboard/verify?address=0x5678...&chain=base",
}
```

### Decision values

| Value   | Meaning                                             |
| ------- | --------------------------------------------------- |
| `allow` | All policy rules passed, or no policy was specified |
| `deny`  | One or more policy rules failed                     |

### Decision reasons

When all policy checks pass, `decision_reasons` is an empty array.

### Explanation

The `explanation` array is returned alongside `policy_result` whenever a policy is provided. Each element describes one rule evaluation:

| Field           | Type           | Description                                                                                                                       |
| --------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `rule`          | string         | The policy rule name (e.g., `require_kyc`)                                                                                        |
| `passed`        | boolean        | Whether the rule passed                                                                                                           |
| `required`      | string         | The required value for the rule                                                                                                   |
| `actual`        | string         | The actual value found                                                                                                            |
| `message`       | string         | Human-readable description of the result                                                                                          |
| `how_to_remedy` | string \| null | Action to resolve a failure, or `null` if the denial is not fixable (e.g., age below minimum on a verified ID) or the rule passed |

### Deny reasons

When a policy check fails, the following reason codes are used:

| Reason                        | When                                                                                                                                                                 | `verify_url`?   |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `kyc_required`                | Not verified                                                                                                                                                         | Yes             |
| `kyc_pending`                 | KYC in progress                                                                                                                                                      | No              |
| `kyc_failed`                  | KYC failed                                                                                                                                                           | Yes (can retry) |
| `sanctions_flagged`           | Account-level sanctions match (KYC name screening under `require_sanctions_clear`) OR wallet-OFAC SDN hit on the `signer` block (always-on; no policy flag required) | No              |
| `sanctions_check_unavailable` | Wallet-OFAC lookup failed (`signer_sanctions.status === "unavailable"`); fail-closed posture, always-on                                                              | No              |
| `age_insufficient`            | Below `min_age`                                                                                                                                                      | No              |
| `jurisdiction_restricted`     | Blocked country                                                                                                                                                      | No              |

When a compliance denial includes a `verify_url`, the response contains a URL the agent's operator can visit to complete or retry verification. This field is only present on compliance denials that are resolvable through verification.

For merchant-initiated flows where you need to create a verification session and poll for completion, see [POST /v1/sessions](/api-reference/post-sessions).

<Note>
  `require_sanctions_clear` checks whether the operator has been flagged in sanctions screening, which happens during KYC verification. For operators who have not completed KYC, this check fails with `kyc_required`. Sanctions screening is performed automatically as part of the KYC process.
</Note>

<Note>
  `require_sanctions_clear` enforces a sanctions-screening freshness window (default 90 days, configurable). The check passes only when the operator was screened within that window:

  * **Screened-clear, fresh**: passes
  * **Screened-clear, older than the freshness window**: denied with `kyc_required` (re-running KYC re-screens)
  * **Unscreened** (no screening on record, or never KYC'd): denied with `kyc_required`
  * **Sanctions hit**: denied with `sanctions_flagged`

  The remediation for stale or unscreened operators is the same: complete (or re-run) KYC at the `verify_url` returned in the deny response.

  The `actual` field on a `require_sanctions_clear` check reflects the effective verdict, not the raw column:

  * `clear`: screened within the freshness window
  * `flagged`: real sanctions hit (no remediation)
  * `unscreened`: no screening on record, or screening is older than the freshness window
  * `none`: operator has no verification record at all
</Note>

### No policy provided

When you omit the `policy` field, the endpoint returns `decision: "allow"` with `decision_reasons: ["no_policy_applied"]`. This is useful for on-the-fly scoring without enforcement.

### Unknown addresses

If the address is not yet in the database, `/v1/assess` creates a minimal entry and triggers scoring as a side effect. The assess response is returned immediately based on identity + compliance state; `GET /v1/reputation/:address` is the endpoint that returns score data.

## Response headers

Every `/v1/assess` response (success and 429) carries account-level quota observability headers when the account has a per-period quota:

| Header          | Description                                                                                             |
| --------------- | ------------------------------------------------------------------------------------------------------- |
| `X-Quota-Limit` | Total quota for the current period (numeric)                                                            |
| `X-Quota-Used`  | Current usage within the period (numeric)                                                               |
| `X-Quota-Reset` | Date when the monthly quota period resets, e.g. `2026-05-01`. Omitted for Enterprise / unlimited tiers. |

Direct HTTP callers can read these from `Response.headers`. The Node and Python SDKs surface them on `AssessResponse.quota` (via `assess()` / `aassess()`) so consumers can monitor approach-to-cap proactively (warn at 80%, alert at 95%) before a 429. See [`@agent-score/sdk`](https://www.npmjs.com/package/@agent-score/sdk) and [`agentscore-py`](https://pypi.org/project/agentscore-py/) READMEs for usage.

Accounts without a per-period quota (Enterprise / unlimited tiers) receive no `X-Quota-*` headers.

## Error responses

| Status | `error.code`       | When                                                                                                                                                                                   | Retry posture                                                                                                                                                            |
| ------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`  | `invalid_identity` | Neither a valid wallet address nor `operator_token` was provided (malformed/missing identity). Note: `invalid_address` is returned only in test mode for a malformed reserved address. | Don't retry: fix the request                                                                                                                                             |
| `401`  | `signup_required`  | Missing/invalid `X-API-Key` header                                                                                                                                                     | Don't retry: sign up for a free account, then fix the key                                                                                                                |
| `429`  | `quota_exceeded`   | Account-level cap reached                                                                                                                                                              | Don't retry: the cap won't lift through retry alone. Body carries `agent_instructions`; commerce SDKs can opt in to fail-open on this code via `failOpen` / `fail_open`. |
| `5xx`  | `api_error`        | Transient AgentScore infra failure                                                                                                                                                     | Retry with backoff per `agent_instructions` envelope (see [errors](/api-reference/errors)). Commerce SDKs surface this via `failOpen` / `fail_open` opt-in.              |

The 429 + 5xx classes are what the Node and Python commerce SDKs gate on when `failOpen` is enabled; see [compliance-gating › Fail-open behavior](/compliance-gating#fail-open-behavior-opt-in).

## Example: middleware integration

```javascript theme={"dark"}
const assessment = await fetch("https://api.agentscore.com/v1/assess", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.AGENTSCORE_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    address: walletAddress,
    policy: { require_kyc: true },
  }),
}).then((r) => r.json());

if (assessment.decision === "deny") {
  return res.status(403).json({
    error: "wallet_not_trusted",
    reasons: assessment.decision_reasons,
  });
}
```
