The official TypeScript/Node.js client for AgentScore. Verify the human behind an agent (KYC, age, sanctions, jurisdiction) and gate payments, in a few lines.
npm install @agent-score/sdk
# or
bun add @agent-score/sdkimport{AgentScore}from"@agent-score/sdk";constclient=newAgentScore({apiKey: "as_live_..."});// Look up cached reputation (free)constrep=awaitclient.getReputation("0x1234...");console.log(rep.score.value,rep.score.grade);// Filter to a specific chainconstbaseRep=awaitclient.getReputation("0x1234...",{chain: "base"});console.log(baseRep.agents);// only Base agents// Identity gate with policy (paid)constgated=awaitclient.assess("0x1234...",{policy: {require_kyc: true,require_sanctions_clear: true,min_age: 21,},});if(gated.decision==="deny"){console.log(gated.decision_reasons);// ["kyc_required"]console.log(gated.verify_url);// URL for operator verification}// Check verification level on reputationconstverified=awaitclient.getReputation("0x1234...");console.log(verified.verification_level);// "none" | "wallet_claimed" | "kyc_verified"Agents without wallets can use operator credentials for identity:
// Assess with an operator credential instead of a wallet addressconstresult=awaitclient.assess(null,{operatorToken: "opc_..."});console.log(result.decision);// "allow" | "deny"Bootstrap identity for first-time agents. The success body carries structured next_steps (with action: "deliver_verify_url_and_poll") and a cross-merchant agent_memory hint. Poll responses carry next_steps.action from the typed NextStepsAction union (continue_polling, retry_merchant_request_with_operator_token, use_stored_operator_token, create_new_session, verification_failed, contact_support).
// Create a session, returns a verify_url for the user and a poll_url for the agentconstsession=awaitclient.createSession();console.log(session.verify_url,session.poll_url,session.poll_secret);console.log(session.next_steps.action);// "deliver_verify_url_and_poll"// Poll until the user completes verificationconststatus=awaitclient.pollSession(session.session_id,session.poll_secret);if(status.status==="verified"){console.log(status.operator_token);// "opc_...", use for future requests}// Optional pre-association: attach the session to a known wallet or refresh KYC// for an existing operator credential.awaitclient.createSession({address: "0x..."});awaitclient.createSession({operator_token: "opc_..."});// KYC refreshassess() responses include resolved_operator and linked_wallets[], all same-operator sibling wallets (claimed via SIWE or captured via prior associateWallet). The list may mix EVM addresses (0x... lowercased) and Solana addresses (base58, case-preserved) for cross-chain operators; merchants doing wallet-signer-match checks should accept a payment signed by any address in the list, regardless of chain. The address parameter on assess() and getReputation() accepts either format; the network is auto-detected from the address shape.
Pass signer: { address, network } on assess() to opt into server-side wallet-signer-match and OFAC SDN wallet-address screening on the same call. The response carries two new verdicts:
constresult=awaitclient.assess("0xclaimed...",{signer: {address: "0xsigner...",network: "evm"},policy: {require_sanctions_clear: true},});// signer_match: wallet-binding verdict// kind: 'pass' | 'wallet_signer_mismatch' | 'wallet_auth_requires_wallet_signing'// plus claimed_operator / signer_operator / expected_signer / actual_signer / linked_wallets / agent_instructionsif(result.signer_match?.kind==="wallet_signer_mismatch"){// signer wallet resolves to a different operator than the claimed address}// signer_sanctions: OFAC SDN wallet-address verdict (discriminated union)// { status: 'clear' } | { sanctioned: true, ofac_label, sdn_uid, listed_at } | { status: 'unavailable' }if(result.signer_sanctions&&"sanctioned"inresult.signer_sanctions){console.log("OFAC hit:",result.signer_sanctions.ofac_label,result.signer_sanctions.sdn_uid);}Wallet-OFAC SDN enforcement on the signer block is unconditional whenever a signer is supplied; no policy.require_sanctions_clear opt-in is required. The API flips decision to deny when signer_sanctions is sanctioned: true OR status: 'unavailable', with decision_reasons including sanctions_flagged or sanctions_check_unavailable respectively (fail-closed; OFAC strict-liability). policy.require_sanctions_clear is the separate NAME-based screen on the resolved operator's KYC identity.
Pass signer.address: null for rails without a wallet signer (Stripe SPT, card-only). The API responds with signer_match.kind = 'wallet_auth_requires_wallet_signing' and a parsed agent_instructions block telling the agent to switch to X-Operator-Token auth, spread the block directly into a 403 body.
constcred=awaitclient.createCredential({label: "my-agent",ttl_days: 7});console.log(cred.credential);// shown onceconstlist=awaitclient.listCredentials();console.log(list);// active, non-expired credentialsawaitclient.revokeCredential(cred.id);After an agent authenticated via operator_token completes a payment, report the signer wallet so AgentScore can build a cross-merchant credential↔wallet profile. Fire-and-forget, first_seen is informational only. network is the key-derivation family: "evm" for any EVM chain (Base, Tempo, Ethereum, …) or "solana" for Solana.
awaitclient.associateWallet({operatorToken: "opc_...",walletAddress: signerFromPayment,// e.g. EIP-3009 `from` or Tempo MPP DID addressnetwork: "evm",idempotencyKey: paymentIntentId,// optional, agent retries of the same payment no-op});| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | --- | API key from agentscore.com |
baseUrl | string | https://api.agentscore.com | API base URL |
timeout | number | 10000 | Request timeout in ms |
userAgent | string | --- | Prepended to the default User-Agent as "{userAgent} (@agent-score/sdk@{version})". Use to attribute API calls to your app. |
import{AgentScore,AgentScoreError}from"@agent-score/sdk";try{awaitclient.getReputation("0xinvalid");}catch(err){if(errinstanceofAgentScoreError){console.error(err.code,err.message,err.status);}}AgentScoreError.details carries the rest of the response body (verify_url, linked_wallets, claimed_operator, actual_signer, expected_signer, reasons, agent_memory) so callers can branch on granular denial codes without re-parsing.
For status-code-specific recovery, the SDK throws typed subclasses of AgentScoreError. All inherit from AgentScoreError so existing catch (err) { if (err instanceof AgentScoreError) ... } still works.
| Class | Triggered by | What it adds |
|---|---|---|
PaymentRequiredError | HTTP 402 | The endpoint is not enabled for this account |
TokenExpiredError | HTTP 401 with error.code = "token_expired" | Parsed body fields exposed on the instance: verifyUrl, sessionId, pollSecret, pollUrl, nextSteps, agentMemory, recover without re-parsing details |
InvalidCredentialError | HTTP 401 with error.code = "invalid_credential" | Permanent, switch tokens or restart |
QuotaExceededError | HTTP 429 with error.code = "quota_exceeded" | Account-level cap reached; don't retry |
RateLimitedError | HTTP 429 with error.code = "rate_limited" | Per-second sliding-window cap; retry after Retry-After |
TimeoutError | Request aborted before a response arrived | Distinct from generic network errors |
import{AgentScore,AgentScoreError,TokenExpiredError,QuotaExceededError,TimeoutError,}from"@agent-score/sdk";try{awaitclient.assess("0xabc...",{policy: {require_kyc: true}});}catch(err){if(errinstanceofTokenExpiredError){console.log("Verify at:",err.verifyUrl,"poll with:",err.pollSecret);}elseif(errinstanceofQuotaExceededError){console.log("Account quota reached, surface to user; don't retry.");}elseif(errinstanceofTimeoutError){console.log("Network timeout, retry with backoff.");}elseif(errinstanceofAgentScoreError){console.error(err.code,err.message);}}assess() responses include an optional quota field captured from X-Quota-Limit / X-Quota-Used / X-Quota-Reset response headers. Use it to monitor approach-to-cap proactively (warn at 80%, alert at 95%) before a 429:
constresult=awaitclient.assess("0xabc...",{policy: {require_kyc: true}});if(result.quota&&result.quota.limit&&result.quota.used){constpct=(result.quota.used/result.quota.limit)*100;if(pct>80)console.warn(`AgentScore quota at ${pct.toFixed(1)}%, resets ${result.quota.reset}`);}quota is undefined when the API doesn't emit the headers (Enterprise / unlimited tiers). On a 429 response the SDK throws QuotaExceededError / RateLimitedError instead of returning a body, so quota is only readable on successful calls; drive proactive alerting off the success-path field.
telemetrySignerMatch(payload) is a fire-and-forget POST to /v1/telemetry/signer-match so AgentScore can track aggregate signer-binding behavior across merchants. Used internally by @agent-score/commerce's gate; available directly for custom integrations that perform their own wallet-signer-match checks.