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

# Operator Credentials

> Create, list, and revoke operator credentials for non-wallet agent identity.

## 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
```

```json theme={"dark"}
{
  "label": "claude-code-agent",
  "ttl_days": 1
}
```

| Field      | Type   | Required | Description                             |
| ---------- | ------ | -------- | --------------------------------------- |
| `label`    | string | No       | Human-readable label (max 100 chars)    |
| `ttl_days` | number | No       | Time to live in days (1-365, default 1) |

### Response

```json theme={"dark"}
{
  "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",
  "agent_memory": {
    "pattern_summary": "...",
    "identity_paths": { "...": "..." },
    "bootstrap": { "...": "..." },
    "do_not_persist_in_memory": ["operator_token", "poll_secret"],
    "persist_in_credential_store": ["operator_token"]
  }
}
```

<Warning>
  The `credential` value is shown only once. Store it securely.
</Warning>

The `agent_memory` block is a cross-merchant pattern hint; LLM-hosted agents should persist the pattern (never secrets) to long-term memory so future AgentScore-gated endpoints become returning-customer encounters. Same block is emitted on `POST /v1/sessions`, on `missing_identity` bootstrap denials (when no auto-session is created), and inline on the first-mint `402 Payment Required` from merchants that echo the hint.

### 409: KYC required

If the account has not completed identity verification, credential creation returns **409 Conflict**:

```json theme={"dark"}
{
  "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.

```json theme={"dark"}
{
  "account_verification": {
    "kyc_status": "verified",
    "kyc_verified_at": "2026-04-07T17:13:56.525Z",
    "jurisdiction": "US",
    "age_verified": true,
    "age_bracket": "21+",
    "sanctions_clear": true,
    "sanctions_checked_at": "2026-04-07T17:13:56.525Z",
    "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"
    }
  ]
}
```

The sanctions gate is exposed as a paired field rather than a raw status:

| Field                  | Type              | Description                                                                                                                                                                                                  |
| ---------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sanctions_clear`      | `boolean \| null` | `true` only when the account has been screened AND the screening is within the freshness window. `false` when the account is on a sanctions list. `null` when never screened or when the screening is stale. |
| `sanctions_checked_at` | `string \| null`  | ISO-8601 timestamp of the last sanctions screening, or `null` if never screened. Lets clients apply their own freshness logic if the default window is too loose.                                            |

When the account has no KYC, `account_verification` contains only the status:

```json theme={"dark"}
{
  "account_verification": {
    "kyc_status": "none"
  },
  "credentials": []
}
```

## Revoke credential

```
DELETE /v1/credentials/{id}
X-API-Key: as_live_...
```

```json theme={"dark"}
{
  "id": "uuid",
  "revoked": true
}
```

## Usage

Pass the credential as the `X-Operator-Token` header when calling `/v1/assess` or merchant endpoints:

```bash theme={"dark"}
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 AgentScore 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
```

```json theme={"dark"}
{
  "operator_token": "opc_abc123...",
  "wallet_address": "0xabcdef1234567890abcdef1234567890abcdef12",
  "network": "evm"
}
```

| Field             | Type                   | Required | Description                                                                                                                                                                                                                                                                                               |
| ----------------- | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `operator_token`  | string                 | Yes      | The credential the agent authenticated with on the gated endpoint.                                                                                                                                                                                                                                        |
| `wallet_address`  | string                 | Yes      | The 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`.       |
| `network`         | string                 | Yes      | Key-derivation family. `"evm"` covers every EVM chain (Base, Tempo, Ethereum, …) because EOAs share identity across them. `"solana"` is a separate namespace.                                                                                                                                             |
| `idempotency_key` | string (max 200 chars) | No       | Stable 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

```json theme={"dark"}
{
  "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:

```json theme={"dark"}
{
  "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

| Status | Code                 | Meaning                                                                                                                  |
| ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| 400    | `bad_request`        | Missing `operator_token`, `wallet_address`, or `network`.                                                                |
| 400    | `invalid_network`    | Network must be `"evm"` or `"solana"`.                                                                                   |
| 400    | `invalid_wallet`     | Malformed wallet address for the given network, or the EVM zero address.                                                 |
| 401    | `signup_required`    | Missing or invalid API key.                                                                                              |
| 401    | `invalid_credential` | Operator credential not found, expired, or revoked. Returned as 401 (rather than 404) to prevent credential enumeration. |
| 429    | `rate_limited`       | Rate 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-commerce** identity adapters expose `captureWallet(ctx, { walletAddress, network })` which reads the `operator_token` from the gate state; no need to re-pass it.
