@roveapi/browser
v1.3.0
Published
Browser automation SDK for AI agents — Playwright-as-a-Service with accessibility trees
Maintainers
Readme
@roveapi/browser
Browser automation SDK for AI agents. Wraps the Rove API — Playwright-as-a-Service with accessibility trees for 77% fewer tokens.
Install
npm install @roveapi/browserQuickstart
import { BrowserClient } from '@roveapi/browser'
const client = new BrowserClient({ apiKey: 'rvp_live_...' })
// One-shot screenshot
const screenshot = await client.screenshot({ url: 'https://example.com' })
console.log(screenshot.url) // signed S3 URL
// Session-based workflow
const session = await client.session()
await session.navigate('https://example.com')
const tree = await session.getA11yTree()
console.log(tree.result.tree) // accessibility tree (~26K tokens vs ~114K for screenshot)
await session.click({ label: 'Get Started' })
await session.fill({ selector: '#email', value: '[email protected]' })
await session.close()Supervised sessions
Supervised mode is explicit; ordinary sessions are unchanged. It requires an rvp_live_ API key because test-mode keys do not allocate a streamable browser. The viewer grant is one-time and bound to the exact viewer origin.
const session = await client.session({ supervised: true })
const grant = await client.createViewerGrant(session.id)
// Give grant.admissionCode to that operator and open grant.viewerUrl there.
// Viewer control is exclusive. After handback, the agent must observe again.
// The hosted viewer performs admission, takeover, and explicit handback at grant.origin.
await session.getA11yTree()
await session.close()Error Handling
import { BrowserClient, InsufficientCreditsError, RateLimitError } from '@roveapi/browser'
try {
const session = await client.session()
} catch (err) {
if (err instanceof InsufficientCreditsError) {
// nextStep: 'buy_credits' (buy one of err.packsWithinLimits) or 'ask_owner' (give a person err.ownerActionUrl)
console.log(err.nextStep, err.packsWithinLimits, err.ownerActionUrl)
}
if (err instanceof RateLimitError) {
console.log(`Retry after ${err.retryAfter} seconds`)
}
}Credits and purchases
An API key can read its balance and the catalogue, and buy credit packs when the account owner has allowed it.
import { BrowserClient, formatCredits, PurchaseNotPermittedError, SpendLimitExceededError } from '@roveapi/browser'
const balance = await client.balance()
console.log(formatCredits(balance.credits_remaining_millicredits)) // "1.75" (integer arithmetic)
if (balance.purchasing.allowed && balance.purchasing.packs_within_limits.length > 0) {
const purchase = await client.purchaseCredits({ itemId: balance.purchasing.packs_within_limits[0] })
if (purchase.next_action) console.log(purchase.next_action.instruction, purchase.next_action.url) // a person pays here
// Poll every 5 s or slower until it is paid:
const latest = await client.getPurchase(purchase.id)
}purchaseCreditsgenerates anIdempotency-Keywhen you pass none, reuses it on its own retries (network errors,409 idempotency-in-progress,429,503; at most 3 attempts, honouringRetry-Afterup to 60 seconds), returns it aspurchase.idempotencyKey, and setserr.idempotencyKeyon every error it throws. Retry with that key to never buy twice; use a new key when the error'sretryisnew_key.- Errors:
PurchaseNotPermittedError(reason,suggestedPayment),SpendLimitExceededError(limit,limitCents,committedCents,requestedCents,resetsAt,packsWithinLimits),PaymentFailedError(purchaseId,failureCode),IdempotencyKeyReusedError,BillingUnavailableError(retryAfter). EveryRoveErrorcarries the problem'sretryandownerActionUrlwhen the API sent them, and the raw body asproblem. Problems are matched by the last path segment of theirtype. - With a test key (
rvp_test_), purchases are simulated:client.simulatePurchase(id, 'paid' | 'expired' | 'failed' | 'authentication_required').
API
BrowserClient
new BrowserClient({ apiKey, baseUrl?, retries?, retryDelay? })client.session(options?)→Sessionclient.screenshot({ url, selector?, fullPage?, format? })→ScreenshotResponseclient.extract({ url, schema, waitForSelector? })→ExtractResponseclient.usage()→UsageResponse(itsapi_keyfield is deprecated: the masked prefixrvp_live_…/rvp_test_…, never the token; exact*_millicreditsfields alongside the whole credits)client.balance()→BalanceViewclient.catalogue()→Catalogueclient.purchaseCredits({ itemId, payment?, idempotencyKey? })→Purchase(PurchaseViewplusidempotencyKey);paymentis'auto'(default),'checkout'or'saved_method'client.getPurchase(id)→PurchaseViewclient.listPurchases({ limit?, cursor? })→{ data: PurchaseView[], nextCursor }client.cancelPurchase(id)→PurchaseViewclient.simulatePurchase(id, outcome)→PurchaseView(test keys only)formatCredits(millicredits)→string
BalanceView, Catalogue and PurchaseView are generated from the API's OpenAPI document.
Session
session.navigate(url, options?)— load URL, return title + statussession.getA11yTree(options?)— accessibility tree (primary tool for LLMs)session.click({ selector?, label? })— click elementsession.fill({ selector?, label?, value })— type into inputsession.select({ selector?, value? })— select dropdown optionsession.scroll({ direction?, amount? })— scroll pagesession.waitFor({ type, value })— wait for conditionsession.screenshot({ fullPage?, format? })— capture pagesession.getText(selector)— extract textsession.getAttribute(selector, attribute)— get attributesession.evaluate(expression)— run JavaScriptsession.close()— close session, finalize artifacts
Auto-retry on 503 (pool exhausted, browser crash) with exponential backoff. No retry on 402 (insufficient credits) or 410 (session expired).
