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

# Troubleshooting

> Find the symptom your agent hit, see what causes it, and apply the fix.

Look up what your agent got, see why, and fix it.

## How to use this page

Each entry starts with what you see: an error code, a message or a behaviour.
Then it gives the cause and the fix. Entries are grouped in the order you are
likely to meet them, from setup to signup.

Search the page for the exact code your agent reported. Every code is also listed
in [Error codes](/sdk/error-codes), with whether it is safe to retry.

## Setup and connection

### The MCP server never starts

**Cause.** Almost always the package URL in the install command. A 404 or a DNS
failure points to a mistyped URL.

**Fix.** Check the install URL against the one on
[Install the SDK](/sdk/install#the-install-url), or copy the finished command
from the dashboard's framework picker on **Overview** or **API keys**. On a
self-hosted Agent Vault, always copy it from the dashboard: it uses your
deployment's URLs.

### My agent got `Failed to connect to proxy at ...`

**Cause.** The proxy is unreachable at `SELF_AGENT_PAY_PROXY_URL`.

**Fix.** Check the variable, including the scheme. Then check the proxy with
`GET /health` on its HTTP origin, which returns `{"ok":true,...}`.

### My agent got `invalid_api_key` from `/v1/tokens`

**Cause.** The key is wrong **or** revoked. The API returns the same code for
both, so it cannot be used to test whether a key exists.

**Fix.** Check the key's status on the **API keys** page before assuming it was
mistyped.

### The hosted MCP server stops working and asks me to sign in again

**Cause.** The sign-in access token expired or was refused. The hosted server
answers HTTP 401 with `error="invalid_token"` before any tool runs, so the agent
gets no tool error.

**Fix.** Sign in again from your MCP client, the same way as the first time.
Retrying the tool does not help. If the next call answers that you must choose
what the app may use, choose access for the new row on **Connected apps**. See
[Sign-in responses](/sdk/error-codes#sign-in-responses).

### My agent got `unavailable` when connecting

**Cause.** The proxy instance it reached is shutting down. Nothing was started
and nothing was spent.

**Fix.** Open a new connection rather than retrying on the same one. If it
repeats, an update is rolling out: wait a little and connect again.

On the hosted MCP server, a tool error reading "Agent Vault could not be
reached. Retry in a moment." is a different `unavailable`: the server could not
get a credential for the call. Nothing ran. Retry the same tool in a few
seconds, and keep the connection and your sign-in. See
[Tool errors](/sdk/error-codes#tool-errors).

### My agent got `rate_limited` from `listTokens()`

**Cause.** The key spent its token catalog budget for the current minute.

**Fix.** Wait for `retryAfterSeconds` (from the `Retry-After` header), then
retry. If the value is missing, wait a few seconds anyway: a proxy may have
stripped the header. Fetch the catalog once per task and reuse it, since tokens
do not change. Reuse a full catalog from `listTokens()` without `url`: one
narrowed to a site holds only that site's cards and logins, so fetch again for a
different host.

## Tokens

### My agent got `token_not_found`: no matching record on file

**Cause.** The token parses but matches nothing. One of:

* The record was deleted.
* The account holder removed the login field it stood for.
* The label is wrong, or the account holder renamed the address it named.
* A token of the wrong kind was used.
* An identity token names an empty field.

A record outside this key's scope returns
[`scope_denied`](/sdk/error-codes#authentication-and-scope) instead.

**Fix.** Fetch a fresh catalog and use its tokens. Two cases return the same
token on every refresh:

* The vault cannot fill the card. The account holder removes and re-adds it.
* An identity field is empty and listed in `missing`. The account holder fills
  it in on their profile. Retry once it leaves `missing`: identity tokens never change.

### A `totp` token returns `token_not_found`

**Cause.** That login has no stored 2FA secret, or the account holder removed it.

**Fix.** Fetch a fresh catalog, and fill a `totp` token only for a login showing
`hasTotp: true`. If the site asks for a code anyway, it belongs to the account
holder: hand it off.

### My agent got `cross_user_denied`, then `key_auto_revoked`

**Cause.** A token belongs to a different account than the API key. Three of
these within five minutes revoke the key automatically.

**Fix.** Stop and check where the tokens came from. Never retry with the same
token. After a revocation, the account holder creates a new key on the **API
keys** page.

### A phone fill is rejected or cut short

**Cause.** `fill_rejected`, because the field's `maxlength` is shorter than the
whole number. A form with a country selector next to a number box refuses the
full-number token every time.

**Fix.** Fill `tokens.phone_country` into the country field and
`tokens.phone_national` into the number field. Both resolve for any country.
For a three-box US form, use `phone_area`, `phone_prefix` and `phone_line`. For a
number outside +1 those three return `token_not_found`, which means use
`phone_national`, not that the number is unusable.

### My agent got `merchant_blocked` on a card marked `anySite`

**Cause.** The agent card on this card has a block list, and this host is on it.
`anySite` only means the card is not limited to an allow list. The block list is
private to the account holder.

**Fix.** Do not retry the same merchant. Tell the account holder the card is not
permitted there, or use a different card.

### My agent got `card_blocked`

**Cause.** The card number is ambiguous: it matches several differently
configured cards. Or the revealed card failed its binding checks. The result is
the same on every attempt.

**Fix.** For an ambiguous number, use a different card, or ask the account holder
to remove the duplicate. For a failed binding check, nothing was typed: check
[Activity](/dashboard/activity) for a recorded transaction, then ask the account
holder to delete and re-add the card.

## Sign-in

### My agent got `login_blocked`

**Cause.** Either the page is not the login's registered site, or the login has
no website set at all. The code does not say which.

**Fix.** If the login has never filled anywhere, assume the second: set its
website on the **Logins** page. Otherwise, navigate to the registered host or one
of its `extraWebsites`.

### Filling both fields does nothing on an email-first sign-in

**Cause.** The password field is not on the page yet. Microsoft,
Okta and Google all show the email step alone.

**Fix.** Fill the username and submit it with **Enter**, because some of these
pages ignore clicks on the button. Wait for `input[type="password"]` to be
**visible**, not merely present: many keep a hidden password input in the page
the whole time. Then fill the password.

### The login submitted and nothing obvious happened

**Cause.** Usually a step your agent did not recognise, not wrong credentials.
`detectChallenge()` returning `{ detected: false }` means "matched no known
pattern". A real 2FA prompt with an unusual field name reads as clean.

**Fix.** Wait for either a signed-in marker or the error element, whichever comes
first. Look for a code field directly, and confirm it sits next to sign-in text.
Let a **click** prove whether a control is usable. See
[Sign in to a site](/guides/sign-in).

### A field takes the value but its button stays disabled

**Cause.** `fill()` inserts the value in one step. It raises `input` but no
`keydown`, `keyup` or `change`. A field that filters digits on key press, or
enables its button on key release, never notices.

**Fix.** Use `pressSequentially()`. For a form that enables only on `change`, add
`press("Tab")` after the fill.

## Checkout

### There is no add-to-cart button, and `count()` returns 0

**Cause.** Often a region or delivery gate, not a wrong selector or an
out-of-stock item. Large retailers hide the buy button, and sometimes the price
and stock, until a delivery address is chosen. The proxy's browser starts with
an empty profile every run, so the gate appears every time.

**Fix.** Fill the location box from the address tokens, which resolve on any
authorized destination, then check again. The registry records a known gate as
`preconditions: ["delivery_location"]`.

### A click times out because another element intercepts pointer events

**Cause.** The target is there, but something is on top of it. Retrying will not
help.

**Fix.** `press(selector, "Enter")` skips the hit test, so it works on anything a
key activates: a button, a link with an `href`, or a submit, button or image
input. A checkbox or radio takes `Space`. Inside a frame, use that frame's own
`press`.

A clickable `div` or a label ignores keys: clear the overlay instead. If a press
succeeds but nothing moves, a modal may be blocking the page: clear it.

### My agent got `limit_reached` on a purchase well under the limit

**Cause.** A spending limit on the agent card stopped the purchase. An
unreadable total is `checkout_total_unknown` instead. Likely causes:

* The agent card auto-paused after an earlier breach.
* An earlier card number fill already counted toward a window limit. A fill
  counts the moment it lands, even with no order.
* The agent filled the security code on its own. On a card with a spending
  limit that is always refused.
* On an older deployment, a plan quota (now `plan_quota_reached`).

**Fix.** Do not refill the card to retry. Ask the account holder to resume the
agent card on **Agent cards** if it shows **Limit**, and check
[Activity](/dashboard/activity) for earlier fills. See
[Error codes](/sdk/error-codes#spending-limits-and-approval).

### My agent got `checkout_total_unknown`

**Cause.** The order total could not be established, so nothing was compared
with any limit. No limit was exceeded.

**Fix.** Do not send the account holder to raise a limit: it changes nothing. The message
names one of three causes. Only "reading the page failed" is worth another
attempt. "A figure was found and refused" and "no order total was found in the
page's text" fail the same way on a retry. A page hint can help with the last.

### My agent got `plan_quota_reached`

**Cause.** The account's plan quota, not a card setting. Either the plan's
payments for the rolling 30 days are used up, or the agent card is beyond the
plan's agent card limit.

**Fix.** Payment quota: payments clear as the oldest age out of the 30-day window. On Free
or Plus, upgrading on **Billing** raises the allowance. Enterprise has no payment
quota.

Agent card quota: this never clears by waiting. Slots go to the oldest agent
cards linked to an existing funding card. Deleting a newer card, or one showing
**No funding card**, changes nothing. Upgrade, or remove older linked cards. See [Limits](/reference/limits#plan-limits).

### The account holder was asked to approve the same purchase twice

**Cause.** Something ran between the card number and the security code. Filling
the number authorizes exactly one security code fill, and the next **action** on
the page uses that authorization up: another field in the same batch, a click, or
a wait that returns. Screenshots, `observe()`, `detectChallenge()` and `count()`
do not, unless a screenshot returns `securityCodeAuthorizationReleased`.

**Fix.** Fill expiry and billing first, then the number, then the security code
immediately after. See [Run a checkout end to end](/guides/first-checkout).

### My agent got `cvv_entry_required`

**Cause.** The security code is never stored. Either the account holder did not
enter it live in time, or this deployment does not offer live entry.

**Fix.** If the account holder missed the prompt, or it could not be started,
report it and let them retry when they can respond.

A retry fills the card number again. An earlier number fill is not reversed and
counts toward the card's limits: check [Activity](/dashboard/activity) first. See
[Error codes](/sdk/error-codes#security-code-cvv).

If the message says "Live CVV entry is not yet available", no retry helps: stop
and tell the account holder this deployment cannot complete a checkout that
asks for a security code. Never ask for or type a real security code. See
[Live CVV entry](/guides/live-cvv).

### A card iframe is visible but no selector finds the fields

**Cause.** The fields are inside a payment iframe. `listFrames()` is refused with
`sensitive_data_blocked` once any token has been filled, which by the card step
it has.

**Fix.** Target the frame by CSS: `iframe[title="Secure payment input frame"]` for
the current Stripe Payment Element, `iframe[title="Secure card number input frame"]`
for the older split fields. Input names vary, so probe each with `count()`
**before** filling the number. If nothing matches, stop and report it rather than
submitting blind.

## Reading the page

### Every screenshot is black

**Cause.** Working as designed. After the first token fill, every screenshot for
the rest of the session is blacked out. A page can echo a filled value anywhere,
and pixels cannot be cleaned.

**Fix.** Read the `redaction` field on the result: it says `blackout`. Use
`observe()`, `count()` and `detectChallenge()` instead, and take any screenshots
you need **before** filling. A black image is not a dark page and not proof a
click did nothing.

### My agent got a withheld screenshot

**Cause.** Different from a blackout. The image could not be measured safely, so
it was refused.

**Fix.** Check `redaction` to tell the two apart, and read the page with
`observe()` instead.

### My agent got `sensitive_data_blocked` from `snapshot_page` or `list_frames`

**Cause.** After the first token fill, both are refused, because accessible names
and frame titles are text the page controls.

**Fix.** Inspect the page before filling anything. A new session starts clean, so
if you need frame details late, open one and inspect before filling.

### `observe()` keeps returning `false` for something visible

**Cause.** The scan did not finish. Look for `inconclusive: true`: every `false`
beside it means unknown, not absent.

**Fix.** Test `!result.inconclusive`. A finished scan leaves the flag out rather
than setting it to `false`, so waiting for a literal `false` waits forever.

### A snapshot was truncated

**Cause.** The page was larger than one result.

**Fix.** The last line names a cursor and a depth. Pass both. Do not lower the
depth on its own: the cursor belongs to that depth. Page with the cursor when the
page is wide, such as a long product list. Lower the depth and drop the cursor
when it is deeply nested.

## Sessions

### The session closed with code 4008 or 4009

**Cause.** `idle_timeout` after 15 minutes with no message and no call in
flight, or `hard_timeout` after 60 minutes in total. A long call keeps the idle
timer from firing, but nothing resets the hard timer, so a call still running at
the 60 minute mark can be cut off after it reached the merchant.

**Fix.** Launch a new browser. Tokens do not expire. Before repeating anything
that changes state, such as placing an order, check whether it already happened.

### My agent got `unanswered` and relaunched the browser

**Cause.** `unanswered` means the proxy stopped waiting for one call. The
connection, the browser and its cookies are intact. Relaunching threw away a live
session.

**Fix.** For a read, retry once. For an action, close the tab first, since the
abandoned action can still land until the tab is gone. Then check the merchant's
state and continue in a new tab of the same context.

### My agent got `request_timeout` on every call after one slow one

**Cause.** The SDK's own deadline closed the connection.

**Fix.** Set `SELF_AGENT_PAY_REQUEST_TIMEOUT_MS` to **150000 or above**, or leave
it unset for the 180000 default. The proxy answers a stalled call at 120 seconds
with the recoverable `unanswered`, and a shorter client deadline closes the
session first. A call with its own `timeout` needs nothing extra.

### My agent got `insecure_site` opening a page

**Cause.** The session goes out through the deployment's residential egress,
which is HTTPS only. The site would not load over HTTPS, or it redirected to an
`http://` address. Nothing was sent over plain `http`, and the tab did not move.

**Fix.** Do not retry the same address: it fails the same way every time. Use
another site, or tell the account holder this one cannot be used. See
[Residential egress](/sdk/tunnel#residential-egress).

## Signup

### My agent got `signup_not_enabled`

**Cause.** This API key does not have the signup permission. It is off by default.

**Fix.** Ask the account holder to turn it on for this key on the **API keys**
page. See [Agent signup](/guides/agent-signup).

### A signup request has been pending far longer than expected

**Cause.** Approval may be failing on the account holder's side, with no code
sent to the agent. One of:

* The key's list of allowed logins is full.
* The site's name has no free numbered labels left.
* The account reached 500 logins while the request waited. Every login counts.
* The daily budget for agent-made credential changes is spent.

An account already at 500 logins when the request is filed gets `login_limit`
instead, and no request is created.

**Fix.** Tell the account holder to check their dashboard. They need to free the resource
and approve again.

### My agent got `too_many_pending` with no `Retry-After`

**Cause.** Five signup requests are already waiting for the account holder. There
is no `Retry-After` by design, because it would reveal when the oldest request
was filed.

**Fix.** Do not poll tightly. Tell the account holder requests are waiting for
their decision. It clears as requests are decided or expire after 24 hours.

### My agent got `write_outcome_unknown` from `createLogin()`

**Cause.** The request may or may not have been filed.

**Fix.** Do **not** retry. A second request is a second approval prompt and uses a
second of the five pending slots. Tell the account holder: their dashboard lists
every waiting request, and an approved one appears as a login in `listTokens()`.

## Registry and webhooks

### My agent got `rate_limited` from `submitRecipe()`

**Cause.** The account's recipe submission budget is spent. It is a rolling
24 hours, so there is no `Retry-After`: each submission counts until it is 24
hours old.

**Fix.** Stop submitting until the oldest submission in the last 24 hours turns
24 hours old. Do not confuse it with the catalog's
`rate_limited`, which clears in seconds: tell them apart by which call threw.

### My agent got `invalid_recipe`

**Cause.** The body was not JSON, was over 64 KB, or failed validation.

**Fix.** Read `validationErrors` on the thrown error, or `errors` in the HTTP
response. Fix each problem and submit again. The same body always fails the same
way. See [Recipe format](/registry/recipe-format).

### My webhook signature never matches

**Cause.** Usually the body was parsed and re-serialised before verifying, or the
wrong card's secret is used.

**Fix.** Verify against the raw request bytes, with the signing secret of the
agent card named in `agent_card_id`. Sign `t` + `.` + body. See
[Verify a delivery](/reference/webhooks#verify-a-delivery).

### My webhook never arrives

**Cause.** The account is on Free, the URL is not public HTTPS, the endpoint did
not answer `2xx` in time (10 seconds with no data, or 15 seconds in total), or
it returned a redirect.

**Fix.** Click **Send test** on the agent card and read **Recent deliveries**,
which shows each attempt's status code. See [Webhooks](/reference/webhooks).

## Still stuck

<CardGroup cols={2}>
  <Card title="Error codes" icon="triangle-exclamation" href="/sdk/error-codes">
    Every code, with whether to retry.
  </Card>

  <Card title="Support" icon="envelope" href="mailto:team@self.xyz">
    Include the session id from the **Activity** page.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.