Skip to main content
POST
POST /v1/assess
Available on every tier including Free, metered by your monthly quota (pricing).

Request

Headers

Body

Provide either address (wallet) or operator_token (credential). At least one is required.
Or with an operator credential (for non-wallet agents):

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: 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.

Response

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

Wallet-mode response fields

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

operator_handle: the identity to key durable state on

When the request identifies the buyer with an operator_token, the response carries a stable pairwise handle for the account behind it. Key merchant state that must outlive a credential (prepaid balances above all) on this handle rather than on the token. An opc_ access token lives 24 hours and rotates silently off its refresh credential, so state keyed on the token instance is stranded every time one rotates, and revoking a leaked token would forfeit whatever it was holding. The handle derives from the account, so rotation, expiry and revocation cost you nothing. It is pairwise: derived per (account, calling merchant), so the same buyer presents an unrelated handle to every merchant and handles cannot be correlated across them. It carries no compliance meaning. A registration-only credential resolves to a handle exactly like a fully verified one does, so read decision and policy_result for policy questions. The handle answers only “which account is this?”. It is returned on denials too, since it is identity rather than a verdict, which lets you record a denial against the same buyer you would have served.
Because it rides this response, there is no separate lookup to make and no additional request metered against your account.

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.

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:
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:

Decision values

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:

Deny reasons

When a policy check fails, the following reason codes are used: 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.
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.
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

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: 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 and agentscore-py READMEs for usage. Accounts without a per-period quota (Enterprise / unlimited tiers) receive no X-Quota-* headers.

Error responses

The 429 + 5xx classes are what the Node and Python commerce SDKs gate on when failOpen is enabled; see compliance-gating › Fail-open behavior.

Example: middleware integration