# Errors

Common error scenarios, status codes, and guidance on how to handle them.

## Read an error before retrying

Every SDK failure is a `ValironError`. Its stable `code` is for program logic; its `message` is safe to show in developer logs; and `details` provides the recovery information that was available from the API.

```typescript
import { ValironError } from "@valiron/sdk";

try {
  await valiron.getAgentProfile("99999");
} catch (error) {
  if (error instanceof ValironError) {
    console.error({
      code: error.code,
      status: error.statusCode,
      message: error.message,
      nextStep: error.details?.action,
      requestId: error.details?.requestId,
      docs: error.details?.docsUrl,
      retryAfterMs: error.details?.retryAfterMs,
      serverDetails: error.details?.details,
    });
  }
}
```

HTTP errors from SDK calls and trust-gate responses use these fields where applicable:

| Field | Purpose |
|--------|---------|
| `code` | Stable, machine-readable category such as `MISSING_AGENT_IDENTITY` or `TRUST_GATE_DENIED` |
| `message` | Human-readable explanation of what happened |
| `action` / `details.action` | Concrete next debugging or recovery step |
| `docsUrl` / `details.docsUrl` | Relevant documentation section |
| `requestId` / `details.requestId` | Include with a timestamp in a support request |
| `retryAfterMs` | Minimum delay before retrying, when retrying is appropriate |

Treat `code` as the contract; wording may be refined over time.

## HTTP Status Codes

| Status | Meaning | Action |
|--------|---------|--------|
| 200 | Success | Process the response |
| 400 | Bad request | Check your request parameters |
| 401 | Unauthorized | Verify your API key or credentials. **Do not retry.** |
| 403 | Forbidden | You do not have access. **Do not retry.** |
| 404 | Not found | Agent ID does not exist in the ERC-8004 registry |
| 429 | Too many requests | **Stop immediately.** Honor the `Retry-After` header. |
| 500 | Internal server error | Retry with exponential backoff |
| 502 | Bad gateway | Retry after a short delay |
| 503 | Service unavailable | Retry after `Retry-After` header duration |

## SDK Error Handling

The SDK throws typed errors that you can catch:

```typescript
import { ValironSDK } from "@valiron/sdk";

const valiron = new ValironSDK({ apiKey: process.env.VALIRON_API_KEY });

try {
  const profile = await valiron.getAgentProfile("99999");
} catch (error) {
  if (error instanceof ValironError && error.statusCode === 404) {
    // Agent not registered — register via ERC-8004 first
  } else if (error instanceof ValironError && error.statusCode === 429) {
    // Rate limited — wait and retry
    await sleep(error.details?.retryAfterMs ?? 5_000);
  } else {
    // Unexpected error — log and retry with backoff
    console.error(error.message);
  }
}
```

## Retry Strategy

When retrying requests, follow this pattern:

```typescript
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      // Never retry auth errors
      const status = error instanceof ValironError ? error.statusCode : undefined;
      if (status === 401 || status === 403) throw error;

      // Never retry client errors (except 429)
      if (status && status >= 400 && status < 500 && status !== 429) throw error;

      if (attempt === maxRetries) throw error;

      // Exponential backoff: 1s, 2s, 4s
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error("Unreachable");
}

// Usage
const profile = await withRetry(() => valiron.getAgentProfile("25459"));
```

## Common Issues

### "Agent not found" (404)

The agent ID is not registered in the ERC-8004 Identity Registry on the specified chain. Ensure:
- The agent has been minted in the Identity Registry
- You are querying the correct chain (`?chain=monad`, etc.)
- The agent ID is the correct ERC-721 token ID (numeric string)

### "No behavioral data" (sandbox result is null)

The agent has not been evaluated in the sandbox yet. Trigger a sandbox test:

```typescript
await valiron.triggerSandboxTest("YOUR_AGENT_ID");
```

### Provisional scores

First-time agents that haven't been sandbox-tested yet receive provisional scores. Provisional evaluations use a higher trust threshold — meaning the agent must prove itself more thoroughly on its first interaction. After the first sandbox run completes, future gate() calls use the standard threshold.

### Timeout errors

The SDK uses method-aware default timeouts: 10 seconds generally, 15 seconds for `gate()`/`proxy()`, and 30 seconds for sandbox triggers. To force a single timeout for all calls, configure it explicitly:

```typescript
const valiron = new ValironSDK({ apiKey: process.env.VALIRON_API_KEY, timeout: 30000 }); // 30 seconds
```

### Chain mismatch

If the agent is registered on Monad but you query Ethereum (the default), it will return 404. Always specify the correct chain:

```typescript
const valiron = new ValironSDK({ apiKey: process.env.VALIRON_API_KEY, chain: "monad" });
```

### Wallet intelligence unavailable

`getWalletRisk()` may return HTTP 200 with `available: false`. This is a graceful degradation response, not an SDK transport error. Inspect `error` for the exact reason:

- `ALCHEMY_API_KEY is not configured` for supported EVM mainnets
- `HELIUS_API_KEY is not configured` for Solana
- `Wallet history is not configured for <network>` when the selected chain is outside the supported history subset
- `Wallet history lookup timed out` or a provider HTTP/RPC error

Unavailable wallet intelligence is excluded from profile and gate composite scoring. Retry provider failures according to your policy; do not reinterpret an unavailable assessment as malicious activity.

### Wallet intelligence requires Pro

Direct wallet-risk requests and policies containing `trustSignals: ["wallet"]` require a valid Pro or Enterprise operator key. A Free or unauthenticated request returns:

```json
{
  "error": "upgrade_required",
  "feature": "enriched_profiles",
  "message": "World ID, Self, Icebreaker, and wallet intelligence signals require a Pro plan. Free plans can use ERC-8004 and sandbox signals."
}
```

Invalid EVM or Solana wallet formats return HTTP 400 before any provider request is made.

---

## Solana-Specific Errors

### "Agent not found" with Solana sequential ID

If you pass a numeric ID (e.g. `42`) with `chain=solana` and get a 404, the sequential ID may not exist in the QuantuLabs indexer. Verify the ID exists on [8004market.io](https://8004market.io).

### Invalid Solana public key

Solana agent IDs must be valid base-58 public keys (32–44 characters) or numeric sequential IDs. Invalid formats return:

```json
{
  "error": "Invalid Solana public key"
}
```

### Liveness check failures

The `GET /operator/agent/:agentId/liveness?chain=solana` endpoint probes each service URL from the agent's metadata. Common issues:

- **404 on liveness endpoint**: The agent has no service endpoints registered, or the chain is not solana.
- **Partial liveness** (`status: "degraded"`): Some endpoints are down. Check `results[]` for per-endpoint details.
- **Full failure** (`status: "dead"`): All endpoints unreachable. The agent's infrastructure may be offline.

### Feedback write-back errors

If on-chain feedback is unavailable, the evaluation result is still returned. Contact Valiron support if feedback remains unavailable.

### Solana RPC rate limits

Public Solana RPC endpoints have aggressive rate limits. If you see frequent timeouts or 429s from Solana calls, configure a dedicated RPC:

```bash
SOLANA_RPC_URL=https://your-private-rpc.example.com
```

---

## Middleware Error Responses

All Valiron middleware (Express, Fastify, Next.js, generic) returns consistent error responses:

| Status | Code | When | What to do |
|--------|------|------|
| 401 | `MISSING_AGENT_IDENTITY` | No identity header | Send one supported identity header: `x-agent-id` or `x-agent-address` |
| 403 | `TRUST_GATE_DENIED` | Agent did not meet the configured policy | Inspect `score`, `tier`, `route`, and `reasons`; do not blindly retry |
| 403 | `AGENT_EVALUATION_PENDING` | New identity is sandbox-evaluating | Keep the same identity and retry after `retryAfterMs` |
| 503 | `TRUST_GATE_UNAVAILABLE` | Trust evaluation could not complete | Fail closed; retry shortly and inspect the logged `cause` |

Each response includes `message`, `action`, and `docsUrl`; denials retain the trust result fields so an application can make a deliberate fallback decision.

> **Fail-closed:** When the Valiron service is unreachable, middleware returns 503 — it never fails open and never allows unverified agents through.

## Provider-Backed Identity Errors

Provider identity proof failures are classifications, not transport failures:
the SDK and `POST /operator/identity/verify` return
HTTP 200 with `identityStatus: "claimed"` and one of these `failureReason`
values.

Dedicated identity middleware maps policy actions to HTTP responses: `challenge`
to 401, `block` to 403, and `limit` to 429. `IDENTITY_RAW_BODY_REQUIRED` is a
local HTTP 400 response when Express or Fastify parsed a body without retaining
its exact bytes. `IDENTITY_GATE_UNAVAILABLE` is a fail-closed HTTP 503.

| Failure reason | Meaning | Action |
| --- | --- | --- |
| `provider_missing` | An agent ID was supplied without a provider | Send the resolver provider name |
| `resolver_unavailable` | No adapter is registered for the provider | For ANS, configure credentials and a trusted CA bundle; never treat the claim as verified |
| `proof_incomplete` | Signature, timestamp, nonce, or digest is missing | Construct and sign the complete proof |
| `request_digest_invalid` | Digest is not `sha256:` plus 64 hex characters | Recalculate and format the SHA-256 digest |
| `nonce_invalid` | Nonce is shorter than 16 characters or contains unsupported characters | Generate a 16–256 character URL-safe nonce |
| `timestamp_invalid` | Timestamp cannot be parsed | Send an ISO-8601 timestamp |
| `timestamp_out_of_window` | Timestamp is outside the default five-minute window | Sign a fresh request; do not reuse the old proof |
| `resolution_failed` | Provider resolution threw an error | Retry only if the provider failure is transient |
| `identity_not_found` | Provider has no acceptable matching identity; ANS also uses this when strict lifecycle, certificate, or Transparency Log checks fail | Correct the ID or repair the provider registration outside Valiron |
| `provider_mismatch` | Resolved record belongs to a different provider | Reject the proof and inspect resolver configuration |
| `ownership_chain_unverified` | Provider authority or organization/domain binding did not validate | Do not grant verified access |
| `public_key_not_found` | No verification key matched the optional key ID | Refresh provider metadata or use an active key ID |
| `signature_invalid` | Signature encoding is invalid | Send base64/base64url or `0x`-hex as required by the resolver |
| `signature_verification_failed` | Proof does not match a resolved key or provider verifier | Reject the proof and sign the exact canonical payload |
| `replay_detected` | This identity and nonce were already accepted | Generate a fresh nonce and signature |

Malformed policies return HTTP 400 with `invalid_identity_policy`. A replay
store or other safe-verification failure returns HTTP 503 with
`identity_verification_unavailable`; the endpoint fails closed.

## Proxy Gateway Errors

| Status | When | Action |
|--------|------|--------|
| 400 | Missing `targetUrl` or invalid request | Check your proxy request fields |
| 403 | Agent denied by trust gate | Agent does not meet the trust threshold |
| 403 | SSRF blocked | The `targetUrl` resolves to a private IP, localhost, or metadata endpoint |
| 503 | Origin server unreachable | Your upstream API base URL is down or timed out |

## Hosted Wrapper Errors

| Status | Code | When | Action |
|--------|------|------|--------|
| 400 | `MISSING_AGENT_IDENTITY` | A trust-enabled hosted wrapper did not receive `x-agent-id` | Hosted wrappers currently require `x-agent-id`; use SDK middleware for key/address identity support |
| 404 | `WRAPPER_NOT_FOUND` | Operator ID, path, method, or endpoint activation does not match | Verify all four values in the dashboard |
| 413 | `WRAPPER_BODY_TOO_LARGE` | Body exceeds the wrapper limit | Send a smaller request or use an external input reference |
| 429 | `WRAPPER_RATE_LIMITED` | Endpoint rate limit reached | Honor `retryAfter` and use exponential backoff |
| 502 / 504 | `WRAPPER_UPSTREAM_REQUEST_FAILED` / `WRAPPER_UPSTREAM_TIMEOUT` | The target API failed or was too slow | Check upstream URL/auth; include `requestId` and timestamp when escalating |

## Key-Based Agent Errors

| Status | When | Action |
|--------|------|--------|
| 400 | Invalid Ethereum address format | Verify the `x-agent-address` header is a valid `0x...` address |
| 401 | Challenge expired | Request a new challenge — challenges expire after a short window |
| 401 | Invalid signature | The EIP-191 signature does not match the address |

## World ID Errors

| Status | When | Action |
|--------|------|--------|
| 400 | Invalid or malformed proof | Verify your World ID proof payload fields |
| 404 | Agent has no World ID verification | The agent hasn't completed World ID verification yet |
| 409 | Nullifier hash already used | This World ID proof has already been submitted |

## Self Errors

| Status | When | Action |
|--------|------|--------|
| 400 | Raw webhook body unavailable or Self webhook signature invalid | Valiron manages the provider callback. Check session status and contact support if verification remains pending after completing the Self flow |
| 400 | Self webhook cannot be mapped to a Valiron session | Create sessions through `createSelfSession()` or `POST /operator/self/session/:agentId` so `externalUuid` is stored before the webhook arrives |
| 403 | Trusted Self payload ingestion is disabled | Use `createSelfSession()` and check `getSelfStatus()`; direct proof ingestion is not supported for hosted customer integrations |
| 404 | Agent not found on-chain | Use an existing ERC-8004 agent ID or a supported Solana agent ID before creating a Self session |
| 409 | Agent already has a verified Self link | Self links are immutable once verified |

## Monthly request allowance

`429 MONTHLY_REQUEST_LIMIT_EXCEEDED` means the account has used its 100,000
Free requests for the UTC calendar month. Upgrade to Pro or wait until
`resetsAt`; use `Retry-After` rather than retrying continuously. This allowance
is shared across API keys and hosted endpoints.

`503 MONTHLY_USAGE_UNAVAILABLE` means request usage could not be checked.
Retry after the response's `Retry-After` delay. No work is admitted without
a successful usage check.
