Skip to main content
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, 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, 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.

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.

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 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 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.

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 for earlier fills. See Error codes.

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.

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.

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 first. See Error codes. 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.

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.

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.

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.

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.

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.

Still stuck

Error codes

Every code, with whether to retry.

Support

Include the session id from the Activity page.