Skip to main content

Overview

Operator credentials (opc_...) let agents identify their human operator without a wallet. After the operator completes KYC, they (or their agent) can create credentials via the API or MCP tools. Credentials expire after a configurable TTL (default 24 hours). Agents can self-serve new credentials via the API.

Create credential

POST /v1/credentials
X-API-Key: as_live_...
Content-Type: application/json
{
  "label": "claude-code-agent",
  "ttl_days": 1
}
FieldTypeRequiredDescription
labelstringNoHuman-readable label (max 100 chars)
ttl_daysnumberNoTime to live in days (1-365, default 1)

Response

{
  "id": "uuid",
  "credential": "opc_abc123...",
  "prefix": "opc_abc1",
  "label": "claude-code-agent",
  "expires_at": "2026-04-10T12:00:00Z",
  "created_at": "2026-04-09T12:00:00Z"
}
The credential value is shown only once. Store it securely.

409 — KYC required

If the account has not completed identity verification, credential creation returns 409 Conflict:
{
  "error": {
    "code": "kyc_required",
    "message": "Complete identity verification before creating an operator credential."
  },
  "verify_url": "https://agentscore.sh/dashboard/verify",
  "next_steps": {
    "action": "complete_kyc_then_retry",
    "user_message": "Visit the verify_url, complete Stripe Identity verification, then retry this request."
  }
}

List credentials

GET /v1/credentials
X-API-Key: as_live_...
Returns active (non-expired, non-revoked) credentials for your account, along with the account’s verification status.
{
  "account_verification": {
    "kyc_status": "verified",
    "kyc_verified_at": "2026-04-07T17:13:56.525Z",
    "jurisdiction": "US",
    "age_verified": true,
    "age_bracket": "21+",
    "sanctions_status": "clear",
    "operator_type": "individual"
  },
  "credentials": [
    {
      "id": "uuid",
      "prefix": "opc_abc1",
      "label": "claude-code-agent",
      "expires_at": "2026-04-10T12:00:00Z",
      "last_used_at": "2026-04-09T14:30:00Z",
      "created_at": "2026-04-09T12:00:00Z"
    }
  ]
}
When the account has no KYC, account_verification contains only the status:
{
  "account_verification": {
    "kyc_status": "none"
  },
  "credentials": []
}

Revoke credential

DELETE /v1/credentials/{id}
X-API-Key: as_live_...
{
  "id": "uuid",
  "revoked": true
}

Usage

Pass the credential as the X-Operator-Token header when calling /v1/assess or merchant endpoints:
curl -X POST https://api.agentscore.sh/v1/assess \
  -H "X-API-Key: as_live_..." \
  -H "Content-Type: application/json" \
  -d '{"operator_token": "opc_abc123...", "policy": {"require_kyc": true, "min_age": 21}}'
Or via the gate middleware — agents send X-Operator-Token alongside requests to gated services.

Report a captured wallet

Merchants call this after a successful payment to report the signer wallet back to AgentScore. Builds a cross-merchant credential↔wallet profile that powers unified reputation, cross-merchant sanctions, and already-verified detection on future credentials from known wallets.
POST /v1/credentials/wallets
X-API-Key: as_live_...
Content-Type: application/json
{
  "operator_token": "opc_abc123...",
  "wallet_address": "0xabcdef1234567890abcdef1234567890abcdef12",
  "network": "evm"
}
FieldTypeRequiredDescription
operator_tokenstringYesThe credential the agent authenticated with on the gated endpoint.
wallet_addressstringYesThe signer wallet recovered from the payment payload (EIP-3009 from for x402, Tempo MPP DID address for mppx, base58 Solana pubkey). EVM addresses are lowercased server-side; Solana base58 addresses are kept verbatim. The EVM zero address (0x0000…0000) is rejected with invalid_wallet.
networkstringYesKey-derivation family. "evm" covers every EVM chain (Base, Tempo, Ethereum, …) because EOAs share identity across them. "solana" is a separate namespace.
idempotency_keystring (max 200 chars)NoStable per-payment key (e.g., Stripe PI id, x402 tx hash). When the same key is reported again for the same (credential, wallet, network), the server no-ops — agent retries of the same logical payment don’t inflate transaction_count. Values longer than 200 characters are truncated server-side.

Response

{
  "associated": true,
  "first_seen": true
}
first_seen is true the first time this (credential, wallet, network) tuple is reported, and false on subsequent observations (internal transaction_count is incremented). When an idempotency_key matches the most recent capture’s key, the response is:
{
  "associated": true,
  "first_seen": false,
  "deduped": true
}
and no state change happens (no transaction_count bump, no last_seen update).

Auth & tier

Any authenticated tier (free or paid). Rate limits still apply. Call this fire-and-forget — a failed capture must never block a payment response.

Errors

StatusCodeMeaning
400bad_requestMissing operator_token, wallet_address, or network.
400invalid_networkNetwork must be "evm" or "solana".
400invalid_walletMalformed wallet address for the given network, or the EVM zero address.
401signup_requiredMissing or invalid API key.
401invalid_credentialOperator credential not found, expired, or revoked. Returned as 401 (rather than 404) to prevent credential enumeration.
429rate_limitedRate limit exceeded.

SDK helpers

  • node-sdk: client.associateWallet({ operatorToken, walletAddress, network })
  • python-sdk: client.associate_wallet(operator_token, wallet_address, network) (plus async aassociate_wallet)
  • node-gate adapters expose captureWallet(ctx, { walletAddress, network }) which reads the operator_token from the gate state — no need to re-pass it.