Skip to main content
Look up any call in @agentvault/playwright, and see where it behaves differently from Playwright.

Overview

The SDK has two halves:
  • HTTP helpers that need no browser: the token catalog, signup and login writes, and the recipe registry.
  • A proxied browser (chromium.launch()) shaped like Playwright’s, where every call runs in the Agent Vault proxy and token fills are resolved there.
Every failure from the proxy or the API is a SelfAgentPayError. Branch on its code; see Error codes. The exceptions throw native JavaScript errors:
  • Saving a screenshot to a local file with screenshot({ path }) can throw a native TypeError or a filesystem error, listed under screenshot(options?) below.
  • recipeSubmission() rejects with the native fetch error on a network failure or an abort.
  • recipesForUrl() and submitRecipe() reject with the native abort error when your signal aborts while the body is being read.
  • listTokens(), tokensForUrl(), recipesForUrl() and buyableMerchants() throw a native SyntaxError on a successful response that is not valid JSON. A body cut off mid-read, or for the two registry helpers JSON of the wrong shape, can throw a native TypeError.

Exports

The module also exports actionTimeoutFor, currentOwnerWaitObserver, podResumeUrl, unreachable, signupPollIntervalMs, signupPollSleepMs and signupPollTimeoutMs. They are internal helpers, not part of the documented API, and can change without notice. The default export carries chromium, listTokens, tokensForUrl, the signup and login helpers (createLogin, getSignupRequest, awaitSignupApproval, setLoginUsername, rotateLoginPassword), the registry helpers (recipesForUrl, submitRecipe, recipeSubmission) and sdkCapabilities. Everything else, including observingOwnerWaits, defaultIdentity, buyableMerchants, fingerprintUrl, requestTimeoutMs and SelfAgentPayError, is a named export only. The checkout playbooks are a separate entry point: @agentvault/playwright/playbooks.

Catalog helpers

listTokens(options?)

Fetch the mock tokens this key may use. Needs no browser.
string
Narrows cards and logins to those registered for this page. Cards marked anySite: true are always kept, after the ones registered for the page. A URL that does not parse matches no site: site-scoped cards and every login drop out, and the anySite cards remain. Identity and addresses always come back in full.
string
Overrides SELF_AGENT_PAY_API_KEY.
string
Overrides the API base. Defaults to SELF_AGENT_PAY_API_URL. If that is unset, the SDK falls back to the backend URL variables that an Agent Vault deployment’s own build sets, so code running inside a deployment needs no value.
Returns a TokenCatalog: userId, identity, addresses, cards, logins, and relatedLogins on a URL-narrowed catalog. Never a real card number or CVV. Full shape: Token catalog. Errors: coded from the HTTP status. A 401 is invalid_api_key. A 429 is rate_limited, with retryAfterSeconds when the response’s Retry-After was usable. Any other failing status, an unreachable API and a missing API key throw a SelfAgentPayError with no code.

tokensForUrl(url, options?)

The same filter as listTokens({ url }), in a flatter shape. Accepts the same options, plus catalog to reuse one you already fetched. Pass only a full catalog from listTokens() called without url. The filter runs locally, and a catalog narrowed to one site has already lost every other site’s cards and logins, so reusing it for a different host returns too little. For a new host, pass a full catalog or leave catalog out to fetch again. Returns userId, identity, addresses, cards, logins and relatedLogins. relatedLogins names logins registered on a sibling host of the same site, hosts only, with no tokens. They will not fill here: navigate to that host and ask again.

defaultIdentity(catalog)

Returns the identity labelled default (the account profile), or the first entry if there is no such label. undefined when the catalog has no identity.

Launching

chromium.launch(options?)

Open a proxied browser. This starts one proxy session; see Sessions and timeouts.
string
The API key this session authenticates with. Overrides SELF_AGENT_PAY_API_KEY for this launch only, so one process can open sessions for several keys. When omitted, the environment variable is used. An empty string does not fall back to it: with no key from either place, launch() throws “chromium.launch: no API key.” before connecting. A resumed session reconnects with the same key.
string
The proxy’s WebSocket URL. Overrides SELF_AGENT_PAY_PROXY_URL for this launch only. When omitted or blank, the environment variable is used, then ws://localhost:4000/v1, a local development proxy. For the hosted service, set wss://proxy.agentvault.io/v1. A resumed session reconnects to the same URL.
boolean
How the browser’s traffic leaves. Omitted (the default): prefer a tunnel through your machine and fall back, with a warning, to the deployment’s own egress: the proxy’s address, or its residential egress where one is configured. true: require the tunnel and refuse to launch without one. false: never tunnel. Overrides SELF_AGENT_PAY_TUNNEL. See Egress and tunnelling.
boolean
default:"false"
Require residential egress without a tunnel through your machine, for a hosted agent that has no machine of its own. The proxy lets the session in only once its residential provider answers for the session’s country. Otherwise launch() fails with egress_unavailable and a retryAfterSeconds. It never falls back to the proxy’s own address. The country comes from your key’s cards, then your saved addresses. When neither agrees on one country, the deployment’s fallback country is used instead, which may not be yours. See Residential egress country. If that egress stops working mid-session, every later call except outcome() and the close() calls fails with egress_lost: close and launch again. Defaults to SELF_AGENT_PAY_RESIDENTIAL_EGRESS=1 when set. A working tunnel through your machine also satisfies it.Without this option, a session with no tunnel still uses the deployment’s residential egress when it has one; the option only makes it a requirement. Either way the session is HTTPS only, and goto() throws insecure_site for a site that will not load over HTTPS. See Residential egress.
number
A hard bound, in milliseconds, on each page call. A call that runs past it rejects with action_timeout and the session stays open, unlike a request_timeout. The call may still be running in the proxy, so treat an action as possibly landed. A per-call timeout does not raise it. Never applied to fills, typing, selectOption, attestedCheckout, requestChallengeHandoff or close(), which can wait on the owner. Defaults to SELF_AGENT_PAY_ACTION_TIMEOUT_MS. Unset means no bound. See The per-action bound.
boolean
default:"true"
After a network drop, reconnect to the same session instead of ending it. Only a call in flight across the drop fails, with connection_resumed. Set false (or SELF_AGENT_PAY_RESUME=0) to end the session on any drop. See Resume after a dropped connection.
boolean
default:"false"
Hold for the owner’s approval even when the amount you report is under the card’s approval threshold. Defaults to SELF_AGENT_PAY_STRICT_APPROVAL=1 when set. A checkout whose total cannot be read always holds when a threshold is set, with or without this option.
boolean
default:"true"
Report what a challenge handoff actually did (solved, released_unsolved) instead of a flat completed. Set false (or SELF_AGENT_PAY_VERIFIED_OUTCOMES=0) only if your code branches on outcome !== "completed" and cannot be updated yet.
These eight are the only options launch() reads. The options type still accepts Playwright’s LaunchOptions, so existing code compiles, but none of them reach the remote browser: args, headless, proxy, timeout and the rest are silently ignored. The proxy launches and configures its own browser. launch() is the only method chromium has. Its type still carries the rest of Playwright’s BrowserType, so chromium.connect() or chromium.launchPersistentContext() compiles, but those members are undefined at runtime.

SdkBrowser

newContext(options?)

The options type is Playwright’s BrowserContextOptions, so existing code compiles, but nothing in it reaches the proxy. The SDK sends only the browser’s id. storageState, locale, timezoneId, userAgent, viewport, extraHTTPHeaders and every other context option are silently ignored. The proxy configures the browser itself. A context is not an isolated profile either. Every context opened from one browser shares that browser’s single profile, so cookies, storage and sign-in are shared between them. For a clean profile, launch a new browser.

Handoff records

takeHandoffs() returns, and recordingHandoffs() fills, a HandoffRecord:
ProxyHandoff[]
One entry per pause the proxy reported. cvv_entry: the owner typed the card’s security code while a fill waited. purchase_approval: the owner was asked to approve the purchase before the card number went in. destination_approval: the owner was asked to approve a new site for the account’s saved details; host names the site. ms is how long the call waited for the person. A call that failed after its pause still reports it.
number
How many calls ended without a reply from the proxy: a deadline that fired, a socket that closed, or a send that failed. Such a call may have paused for the owner and never said so.
Read the record this way: For recordingHandoffs(), pass a record you created, initialized to zero. The record is yours, so it keeps what was collected even when run throws. Calls made inside run do not reach takeHandoffs().
Handoffs you start yourself with requestChallengeHandoff() are not listed.

Owner waits as they happen

A handoff record says who took part once a call has finished. To know what a call is waiting on while it waits, run it inside observingOwnerWaits(observer, run). The observer is told each wait as it starts, and null when it ends:
It is a function, not a browser method, so it can scope work before a browser exists. The report never carries the approval link: that goes to the owner alone. A proxy that predates the report sends nothing, so read silence as unknown, not as “nobody was asked”. That call reports its own result.

outcome()

Returns a SessionOutcome: filledFieldKinds, deniedCount, confirmationDetected, orderTotal and credentialWrites (what the session changed in the vault, as label:change; these outlive the session).
browser.outcome() is not proof an order landed. confirmationDetected is set only by your own recordOutcome() call, never by the proxy observing the merchant, so a false is inconclusive. Once the socket has closed, outcome() is not available at all.

attestedCheckout(opts)

Hand a card checkout to the attested enclave. The enclave reveals the card number only inside itself and types it in its own browser.
string
required
A card number token, for example av_card_1_number_u7x2k. Any other token kind, including that card’s CVV token, is refused with token_not_found.
object
required
A self-contained, declarative plan. The enclave starts from a blank page, so a cart or sign-in built up in your own browser does not carry over.
number
The order total, for the owner’s approval threshold.
string
The total’s ISO currency code.
Returns { last4, transactionId? }. Plan steps are goto, click, fill, fillSecret, waitFor and wait. screenshot is refused for SDK callers. fillSecret must be followed by exactly one state-changing step, a click on the merchant’s pay button. Fewer, and the card is typed into a form that is never submitted. More, and one approval would authorize two submissions. Only wait and waitFor may follow the click. Every navigation before fillSecret must target the same merchant. Know these limits before you use it. Each case needs the ordinary in-proxy fill instead:
A dropped connection is not a failed checkout. If the socket closes after the plan is submitted, this call rejects while the enclave carries on, and there is no way for a retry to join the run you lost. Retrying blind can pay twice. Check the merchant’s order state or the session on the dashboard before sending the plan again; outcome() cannot help, because it runs over the socket that closed.
Errors: invalid_plan, and the attested codes on Error codes. A verification-only key is refused with scope_denied.
This path is off by default on most deployments. See Attested detokenization for its status.

SdkBrowserContext

SdkPage

SdkPage has an id getter but no url() getter. Pass the URL you navigated to when a helper needs it, for example recipesForUrl(productUrl).
string
Usually where the navigation landed, reduced to scheme, host and path. The proxy strips the query, the fragment and any userinfo, and cuts back a path that carries a hosted checkout’s session. It can differ from the URL you passed, because merchants redirect.Two cases return the url you passed, unchanged, with its query and fragment intact:
  • Any token has been filled in this session. From then on the proxy does not report the landing address at all, because a page can write its own address.
  • The proxy is older than this contract and sent no sameDocument.
In both cases the value is not evidence of where the tab ended up. It need not be the final landing page.
boolean | undefined
The browser’s own answer to whether the document stayed. true for a fragment jump or a History API route change, where the form and what was typed into it are untouched. false when the document was replaced. undefined from a proxy too old to say.
An insecure_site from goto never committed anything: the tab is still on the page it was on. Do not treat it as a navigation that may have landed, and do not retry the same address. See insecure_site. When a goto times out after the page committed a new document, the error carries navigation: { committed: true, status? }: the tab is on the new document, and whatever was typed into the old one is gone. A status of 403 or 429 there is a block wall, not a slow checkout.

Filling and interacting

Fill options

fill, type, pressSequentially and selectOption take:
number
The checkout total this fill commits to. Pass it on the call that fills the card number. At or above the card’s approval threshold, the proxy holds and asks the owner before any real value is typed. Ignored for non-card values.
string
The total’s ISO code, for example GBP. Always pass the real code for a non-USD checkout. Limits and approval thresholds are in US dollars, so a foreign total is converted at a daily reference rate, plus a 3% safety buffer for card network fees, and then checked normally. A currency with no rate, or a rate older than 48 hours, cannot be converted: the total is treated as unknown, and the fill is held for approval or refused as checkout_total_unknown. Omit it (or pass USD) for dollar checkouts.
number
default:"30000"
Bounds the browser work: waiting for the field to become actionable, and the action. It does not count time spent waiting on the owner (an approval or a live CVV entry). 0 means no timeout at all. Pass one whenever a miss is a possible outcome rather than a fault.

FillReport

fill, type and pressSequentially, on pages, locators and frame elements, resolve to a FillReport:
On a verification-only key, a card number or CVV fill types a public test card instead of the owner’s card, and reports { verification: true, card_fields_verified: true }. Nothing is reserved, no spending limit is checked and the owner is not alerted for that fill. Every other fill reports {}. Use it to tell a probe of a checkout’s card fields from a purchase.

Select choices

A choice that names no option, or more than one, is refused with invalid_choice before anything is sent. So is an empty one: "", or a label that is blank once invisible characters and white space are trimmed, which would select the placeholder option. { value } and { index } need a proxy that supports them. Against an older proxy they are refused locally with invalid_choice, because that proxy would select the placeholder and report success. A plain string still works there.

ActionResult

boolean
The action opened a new tab. Switch with waitForPopup(). Absent is inconclusive: it means no tab arrived within the short window the proxy watches after the action. A tab the site opens later, for example a 3-D Secure or wallet window after a server round trip, is still queued for waitForPopup() with nothing reporting it. Before you treat the opener as the result of a submit, call hasPendingPopup(), and call waitForPopup() when the checkout plainly moved to another tab.
boolean | 'unsettled' | undefined
Whether a card number this page filled is still in its form after the action. false means the action took the tab off that checkout or edited the field. "unsettled" means a navigation had not settled; the next click or press reports the settled state. undefined (the field is absent) means the proxy did not report it, for example an older proxy. It does not mean false: treat it as unknown, and neither refill the card nor discard the form because of it.
Some fields ignore fill(). A field that filters digits on keydown, or enables its submit on keyup, never registers the value. Use locator(sel).pressSequentially(token) instead. For a form that enables its submit only on change, follow the fill with locator(sel).press("Tab").
Hosted card widgets such as Stripe Elements ignore value-only fills entirely:

Looking at the page

snapshot(options?)

The accessibility tree as YAML, reaching into iframes including cross-origin ones.
number
default:"12"
How deep to walk. The proxy clamps it to 30.
number
Line offset for the next page of a truncated capture. Pass it with the depth the truncation note names: a cursor is an offset into a capture at that depth.
Returns a string, capped at 24,000 characters. When the tree does not fit, the last line names the cursor and depth for the next page. A [ref=eN] handle is a selector: aria-ref=e7 works for a count, a wait, a click or a press. A fill refuses a ref, because an in-frame ref reaches into an iframe from the top-level page and would bypass the check on that frame’s origin. Field values are never in a snapshot.

screenshot(options?)

Options: path, timeout (default 20000), fullPage. Returns a Uint8Array (a Buffer at runtime) carrying redaction and reason as properties. redaction is none, fields (substituted fields boxed out), blackout or unknown. unknown means the proxy sent no redaction metadata, which an older proxy than your SDK does. Treat it as possibly sensitive and possibly blacked out, never as none. Only none tells you the pixels are the untouched page.
With path, the SDK also writes the PNG to that file. It checks the path before the capture, and these failures are not SelfAgentPayError: In the last case, read err.screenshot for the image and its redaction, reason and securityCodeAuthorizationReleased, which the proxy reports only once. Once any token is filled, the image is a solid black rectangle for the rest of the session, across navigation, a new tab and page.close(). Only a new proxy session clears it. A capture between a card number and its security code leaves the pairing in place. A capture that cannot complete fails rather than disturbing the page: the number is still in the form and the security code is still the next fill, so do not refill the number. If a capture ever comes back carrying securityCodeAuthorizationReleased, fill the number again and then the code with nothing in between.
Once the owner has been handed the page for a challenge, stop taking screenshots, even if you have filled nothing. The proxy does not black those out, and the page can echo what the owner typed.

observe()

Returns a PageObservation: passwordField, identifierField, contactField, codeSent, error, rateLimited, a value-free reason, and errorText, the page’s own validation messages (scrubbed of filled values; read it whenever it is non-empty). The fields match the MCP observe_page tool. “On screen” means what the proxy can establish without running script in the page: a laid-out box, not display: none or visibility: hidden, and horizontally on the canvas. A control kept mounted at opacity: 0 still reads as present.
A result carrying inconclusive: true did not finish looking. Every false beside it means unknown, and a true may be a match the scan could not place. A finished scan omits the flag, so test !result.inconclusive.

detectChallenge()

Returns { detected, type?, types, reason?, signature?, deliveredCode?, ambiguousOnly?, inconclusive? }. type and reason are absent when nothing was detected. type is "three_ds", "otp", "captcha" or "blocked". The first three rank 3-D Secure above OTP above CAPTCHA, with one exception: a weak otp carrying ambiguousOnly ranks below captcha. types lists every pattern matched; a weak ambiguous otp is not probed once a stronger challenge is established, so it can be absent. blocked is a silent block: the site stopped answering without saying why. It is reported only when a finished scan found no announced challenge, and then types is ["blocked"]. signature names the shape it took: Hand a blocked page to the owner like a CAPTCHA, with requestChallengeHandoff(). Do not retry, reload or search again to get past it. The owner either gets through or confirms the site refused. deliveredCode: true appears on an otp when the page says the code was sent by SMS, email or another channel. No stored authenticator secret can produce that code, so never fill tokens.totp for it: hand off instead. The flag is absent when the page named no channel. See Handling challenges.

Handing off

requestChallengeHandoff()

Ask the account owner with a dashboard banner, plus an email or alert on each channel they turned on while their A checkout needs me to clear a bank or verification step switch is on, with a link that streams this live page. Blocks until they finish, the wait times out (about 10 minutes), or the session ends. Returns { outcome, challengeType, durationMs }. outcome is solved, completed, released_unsolved, failed or abandoned. Carry on after solved. After completed, look at the page first. After released_unsolved, detect again and hand off again. Stop after failed, abandoned or anything you do not recognise. While it is parked, every other page call is rejected with challenge_required, so you cannot watch the owner type. Afterwards, take no screenshots: in a session with no token fill they are not blacked out, and the page can echo what the owner typed. Errors: challenge_required when the handoff ended without the owner finishing: the wait timed out, a newer handoff superseded this one, or it ended another way. Request a new handoff only after a timeout, never after a supersede. Also handoff_not_applicable (a detection carrying ambiguousOnly; the page stays live and yours), challenge_scan_incomplete (the proxy’s own re-check could not finish; detect again and ask again), and unknown_target, which has two variants:
If the challenge ran in a popup, observe the page you came from. A popup that dismissed itself once the owner finished is a retired handle, and observing it answers unknown_target instead of telling you where the checkout got to.

Hints

hint(kind, target, context?)

Tell the vault where something is on this page. target takes selector, frame, read and attribute; context takes login for login.totp_secret. selector and frame are each at most 512 characters. Returns { kind, accepted, checked }, plus what the vault read: After any token fill in the session, an order.total hint answers { accepted: true, checked: false } with no resolved, amount or untrusted. That holds only for selectors built from ids, classes, element names and presence-only attribute tests such as [data-total]. A selector or frame that compares an attribute value, such as [data-testid="total"], is refused with invalid_hint after a fill, and one declared before the fill is skipped when the card fill reads the total. An order.total hint matching more than 8 elements, or an element over 4,000 characters, adds nothing (accepted: false). With frame, the hint also reads the frame’s whole text and every other frame on the page. Over 20,000 characters in the frame, over 16,384 counting the page’s own text, or more than 32 frames on the page, the hint is dropped: accepted: false even though the selector matched (after a token fill, { accepted: true, checked: false }, and it is dropped at the card fill). The total then stays unknown. Point at a total outside the frame if there is one. Otherwise a card with an approval threshold asks the account holder, and a card with spending limits is refused with checkout_total_unknown. See Totals inside an iframe.
Errors: invalid_hint for a malformed hint, a selector outside the plain CSS grammar hints accept, a selector or frame over 512 characters, an attribute value comparison declared after a token fill, or a fifth distinct hint of the same kind on one page (the limit is 4 per kind, per page; repeating a hint does not count, and a navigation clears the count), and hint_unresolved when a storing hint found nothing usable where you pointed. Fix what they name and retry. hint_uncertain means a storing hint’s write may have committed and only its answer was lost. Do not retry it: enrollment never overwrites a stored secret, so a retry is refused with hint_unresolved and reads like a second failure. Carry on, and tell the owner to check the login on their Logins page. Full guide: Page hints.

hintKinds()

Returns the kinds this deployment accepts, as { kind, purpose, when, reads, attributes, allowAfterFill }[]. reads lists the read modes (text, attribute), attributes the attribute names read: "attribute" may name for that kind, and allowAfterFill: false means the reply stops reporting what was read once a token has been filled. Kinds are added server side, so ask rather than assume.

Recording

recordOutcome(options?)

Record that this session reached a confirmed checkout. This is the only call that sets confirmationDetected. Optionally pass orderTotal, a display string such as "$42.00". It is kept only when it is at most 24 characters and carries a currency sign, a currency code or two decimal places. Anything else, a bare number like "1234" included, is silently left out. A whole total of exactly three or four digits is also dropped even with a marker ("$737", "USD 1234"), because it cannot be told apart from a security code: write it as "$737.00" or "USD 1,234". It checks nothing on the page, so call it only once your own check shows the order landed. Returns the updated SessionOutcome, the same shape as outcome(). A session that filled any token is recorded with no receipt image, because a confirmation page can echo a filled value. That is expected, not a failure. Errors: popup_pending when a live tab this page opened is still queued and no waitForPopup() has taken it, because the 3-D Secure or wallet step may be running there.
Never use recordOutcome() to probe a login. It sets confirmationDetected unconditionally, so a failed login would be recorded as a completed order.

SdkLocator

SdkFrameLocator and SdkFrameElement

page.frameLocator(selector).locator(inner) returns an SdkFrameElement inside an iframe. It has fill, type, pressSequentially (each returning a FillReport), click, press, count, counts and waitFor, with the same options as on SdkLocator. It has no selectOption, first or nth. This is the supported way to target a hosted payment frame, because listFrames() is refused once a token has been filled. Input names vary by integration, so probe with count() and fall back rather than blocking for 30 seconds.

Signup and login helpers

These call the HTTP API, not the proxy, and need a key the owner has allowed to create logins. Without it they refuse with signup_not_enabled. Each takes optional apiKey and baseUrl. The agent signup guide walks through the flow.

createLogin(opts)

string
required
The site’s registrable domain, for example doordash.com. Locked to that domain and its subdomains.
string
required
A mailbox the agent controls. The owner sees it before approving.
string
Only a handle the site has already accepted. Approval stores it.
PasswordRules
The site’s stated password rules. The password is 12 to 64 characters, 24 by default. A maxLength below 12 is refused with password_too_short. A minLength above 64, or greater than maxLength, is refused with invalid_policy: on a site that needs more than 64, the account holder adds the login. symbols filters the vault’s safe set, !@#$*+-_=.?, and can never widen it. A symbols with none of those characters is refused with invalid_policy. The password always holds a lowercase letter, an uppercase letter and a digit, plus a symbol unless noSymbols is set.
Returns { requestId, status, expiresAt }. status is "pending", or "approved" when the owner has pre-approved signups for this site with this exact email. Pre-approval completes at once for at most 2 signups per listed site and, by default, 60 per account in a 24-hour window. Past either ceiling, or when another approval check fails, the request comes back "pending" instead. See Limits. The 500-login ceiling is the exception: an account that already holds 500 logins gets login_limit straight away, pre-approved site or not, and no request is created. An approved handle still carries no tokens: poll requestId right away and the first answer has them. A pending result is a stop for createLogin, not a retry point. Errors: signup_not_enabled, too_many_pending, login_limit (the account already holds 500 logins, counting ones the account holder added; no request is created), invalid_website, invalid_email, invalid_username, invalid_policy, password_too_short, rate_limited, write_outcome_unknown. rate_limited means the key’s shared budget of 20 credential writes per minute is spent. createLogin, setLoginUsername and rotateLoginPassword all draw on it. Wait retryAfterSeconds, then call again. too_many_pending is also a 429, but it clears only when the owner decides a waiting request, so it carries no retryAfterSeconds.

getSignupRequest(opts)

Read one request by requestId. Optional timeoutMs bounds this one poll: default 30000, at most 30000, and never under 1000. Returns { status: "pending", expiresAt }, { status: "denied" }, { status: "expired" }, or { status: "approved", login, passwordPolicy, passwordOwnerManaged? }. login is the new login’s catalog entry, so its label is login.label and its tokens are under login.tokens, with login.hasEmail and login.hasUsername saying which identifier it carries. passwordPolicy describes the password the vault generated. passwordOwnerManaged is true when the account holder replaced that password from their dashboard before you polled: the policy then describes a password that no longer exists, the token still fills the account holder’s password, and rotateLoginPassword is refused for this login for good. If the site rejects the password, tell the account holder. Absent or false means the generated password is still in place. Errors: request_not_found, scope_denied.

awaitSignupApproval(opts)

Poll until the owner decides. Takes requestId, plus timeoutMs (default 15 minutes) and intervalMs (default 5000, never faster than 2000). A 429’s Retry-After is honoured before the next poll, within the same deadline. Returns the approved { login, passwordPolicy, passwordOwnerManaged? }. A failing poll that another tick can get past (a dropped connection, a 408, 429 or 5xx) is absorbed and the wait carries on. A refusal that says the caller is wrong (request_not_found, any other 4xx, a missing key) is thrown at once. Errors: signup_denied, signup_expired, approval_timeout (the wait ended; the request is still live), and, thrown at once, request_not_found and scope_denied (the owner removed the approved login from this key’s access).

setLoginUsername(opts)

Record a handle the site has already accepted. Takes label and username. Returns { login }. An exact replay of the same handle is idempotent. Only the API key that created the login can set its username, and only within seven days of the login being created. Seven days is the default; a deployment can set a different window. Any other key, or a later call, gets not_found, the same answer as an unknown label. An exact replay of a handle already stored still succeeds after the seven days. Errors: username_already_set, not_found, invalid_username, daily_limit, rate_limited (with retryAfterSeconds; an exact replay of a handle already stored is not charged), write_outcome_unknown (re-send the same handle).

rotateLoginPassword(opts)

Replace the generated password on a login an agent created, when the site rejected it. Takes label plus the password rules, with the same bounds as createLogin: a minLength above 64 or a symbols outside the safe set is invalid_policy, and a maxLength below 12 is password_too_short. The mock tokens do not change. Never use it on a form that asks for the current password. Only the API key that created the login can rotate its password, only within seven days of the login being created, and only while the account holder has not replaced the password. Seven days is the default; a deployment can set a different window. Any other key, or a later call, gets not_rotatable, the same answer as an unknown label. Returns { login, passwordPolicy, rotated: true }: the login’s catalog entry with the same tokens, and the rules the new password was generated under. Errors: not_rotatable, not_found, invalid_policy, password_too_short, daily_limit, rate_limited (with retryAfterSeconds), write_outcome_unknown (stop and hand it to the owner).

Registry helpers

recipesForUrl strips the URL because the lookup is unauthenticated and can reach logs: for an absolute URL such as https://shop.example.com/cart, the query, fragment and any hosted-checkout token (a Stripe cs_... session, a Shopify checkout token) never leave your process. Stripping needs a URL that parses. A relative or malformed string, such as shop.example.com/cart, is sent to the registry exactly as you passed it, so never pass one that contains a session token. recipesForUrl and buyableMerchants set no code on failure: a non-2xx reports the status in its message. Branch on which call threw. A malformed successful body, or an abort mid-body, escapes as a native error, not a SelfAgentPayError (see Overview). submitRecipe and recipeSubmission code from the response body: invalid_recipe (with validationErrors), rate_limited (the budget of 20 in any 24 hours, with no retryAfterSeconds), not_found and unauthorized.
A note sent with verificationRequested: true is published with the recipe, word for word, once approved. Send what shows the purchase happened and nothing that identifies who made it.

Utilities

SelfAgentPayError

requestTimeoutMs(method, params?)

Returns the deadline in milliseconds the SDK applies to one call. See Sessions and timeouts.

sdkCapabilities

Markers for features that live inside existing methods, so they cannot be detected by checking for a method: fillTimeout (fill options carry timeout to the proxy) and locatorCounts (locators answer counts()). Absent means an older build.

Sessions and timeouts

Deadlines, and which errors end a session.

Error codes

Every code, and whether retrying is safe.