---
name: anlyon-troubleshooting
description: Diagnoses failing Anlyon API, SDK, MCP and CLI calls from their error codes and invocation results, and gives the fix for each. Use when an Anlyon call returns an error such as AUTHENTICATION_ERROR, AUTHORIZATION_ERROR, NOT_FOUND, PAYMENT_REQUIRED, QUOTA_EXCEEDED, END_USER_REQUIRED, END_USER_NOT_CONNECTED, END_USER_MISMATCH, REDIRECT_URL_REQUIRED, PREVIEW_MISMATCH, idempotency_key_reuse or ORIGIN_NOT_ALLOWED, when an invocation is refused (impact_limit_exceeded, policy_denied) or stuck in unknown, or when the Anlyon MCP server or a connect link does not work.
license: MIT
metadata:
  author: Anlyon
  version: "1.0.0"
  docs: https://docs.anlyon.com/skills/anlyon-troubleshooting
---

# Troubleshooting Anlyon

## Read the error

Every API error has one shape. Match on `error.code`, not the message text or the status alone.

```json
{ "success": false, "error": { "code": "AUTHORIZATION_ERROR", "message": "API key is missing required scope: actions:invoke" }, "requestId": "…" }
```

SDK errors expose `code`, `status`, `message` and `requestId`. Quote `requestId` when the user contacts support@anlyon.com.

**A refused action call is not an HTTP error.** A call Anlyon stops at dispatch answers `200` with `status: "failed"`, `grade: "refused"` and the reason in `error` (for example `Dispatch refused (impact_limit_exceeded): …`, or `impact_unbounded`). A policy that denies it gives `status: "denied"` with `decision.code: "POLICY_DENIED"` and `decision.explanation`; an approval nobody answered gives `status: "expired"`. Read the body.

## Never do these while debugging

- Never print, log or echo the API key or any provider credential, even partly, even to "check it is set". Check presence with a boolean (`Boolean(process.env.ANLYON_API_KEY)`), and inspect the key with `client.auth.identity()`, which returns its type, environment, scopes and expiry and never the key.
- Never retry an invocation whose status is `unknown`.
- Never "fix" a refusal by raising a limit, disabling a policy or ungating an action. Those are the user's decisions; tell them what blocked the call.

## Workflow

```
- [ ] 1. Get the code, status and requestId (or the invocation's status, grade and error)
- [ ] 2. Look the code up below, then in references/error-codes.md
- [ ] 3. Check identity: right workspace, environment and scopes?
- [ ] 4. Apply the fix; if it needs the console or a new key, tell the user exactly what to change
- [ ] 5. Re-run the one failing call, with the same idempotency key if it is the same operation
```

## The usual suspects

| Symptom | Cause | Fix |
| --- | --- | --- |
| `401 AUTHENTICATION_ERROR` | Key wrong, expired or revoked (the API does not say which) | The user creates a new key and updates the env var. Check the header is `Authorization: Bearer <key>` |
| `403 AUTHORIZATION_ERROR` naming a scope | The key lacks that scope. Scopes are fixed at creation | A new key with the scope. Operator scopes belong on the operator key, not the agent's |
| `404 NOT_FOUND` for something that exists | The key belongs to another environment | Use that environment's key; `auth.identity()` shows the key's environment |
| `402 PAYMENT_REQUIRED` | The workspace has no plan | An admin chooses one: **Settings → Plan & billing** |
| `400 END_USER_REQUIRED` | The key requires an end user and the call named none | Use `anlyon.forUser(id)` / `client.for_user(id)` |
| `409 END_USER_NOT_CONNECTED` / `END_USER_RECONNECT_REQUIRED` | The user has no working account for the integration | Send them to `connectUrl` (or a new connect link), then retry |
| `400 VALIDATION_ERROR` "No integration named … can be connected" | The integration is not available to connect | Pick one listed under **Integrations** in the console, or set your own OAuth app for it |
| `403 END_USER_MISMATCH` on confirm | The flow was started for a different user | Confirm with the user from your session; start a new link for this user |
| `400 REDIRECT_URL_REQUIRED` / `REDIRECT_URL_NOT_ALLOWED` | No default redirect URL, or `redirectUrl` is not allowed | Add it under **Integrations → Connect settings** |
| `400 END_USER_HEADER_NOT_ALLOWED` | `Anlyon-End-User` sent with an OAuth (MCP sign-in) token | Act for users with an API key; OAuth connections act for the workspace |
| `403 ORIGIN_NOT_ALLOWED` (React components) | The page's origin is not an allowed redirect URL's origin | Add a redirect URL on that origin to the environment |
| `status: failed`, `grade: refused`, `impact_limit_exceeded` | A limit has no room. Nothing was sent | Tell the user; it frees at the period reset, or they raise it |
| `status: unknown` | It may have happened | Stop; a person reconciles (see references) |
| `409 idempotency_key_reuse` | Same key, different body | A new key for a new operation; the same key and body for a resend |

Everything else, by code: [references/error-codes.md](references/error-codes.md).

## MCP and connect-link problems

- **MCP tool list is missing actions:** the OAuth permissions granted cover fewer tools, or the key lacks `actions:read`. A new action appears within 30 seconds. Some hosts dislike changing tool lists: set `ANLYON_MCP_DYNAMIC_ACTIONS=0` and use `invoke_action`.
- **MCP sign-in fails in CI:** OAuth needs a browser once; use an API key from an env var instead.
- **`decide_approval` refused:** a key or app cannot decide its own request (`403 AUTHORIZATION_ERROR` with "cannot decide an approval it requested"; read the message before blaming a scope); decide in the console.
- **Connect link "used up":** it works once, for 10 minutes; a key form allows 5 refused keys. Create a new link.
- **`400 OWN_OAUTH_APP_REQUIRED`:** the integration needs the environment's own OAuth app (**Integrations → <integration> → Sign-in**).

Docs for any topic: https://docs.anlyon.com/llms.txt, and append `.md` to a page URL.
