Page Agent — embed the AI agent in your website
Page Agent adds a bounded browser agent to your application. Velqa authentication and explicit user authorization are required before any session.
Pricing and tiers
The subscription covers your active domains. Inference you actually consume is billed on top, from your account's credit balance.
| Tier | Price | Active domains |
|---|---|---|
| Starter | 29 USD / month | 1 |
| Growth | 79 USD / month (flat) | up to 3 |
Growth is a flat price: it does not multiply by your domain count. Starter is billed per active domain.
A site you turn off yourself (the dashboard on/off switch) counts toward neither the domain cap nor billing. Turning it back on counts it again.
Without an active subscription, sessions are refused. Subscribe from Dashboard → Page Agent, or from Dashboard → Billing, which also shows your current tier and a link to manage it.
Creating a domain beyond your tier's cap returns 409 page_agent_domain_cap_reached: move to Growth, or disable an existing domain.
See the agent at work
A fictional storefront, `/en/demo`, is the demonstration ground: the agent searches a catalogue, fills a cart, and stops for confirmation before removing a line or paying. Nothing is sold and nothing is charged there. Signing in is required, as it will be on your site.
Trial on your own domain
An account with a verified email address that has never subscribed can request a 14-day trial from Dashboard → Page Agent. It grants the same entitlement as a Starter tier: one active domain, verification, keys, and real sessions on your own site.
No card is required, but the trial is not free of inference: exactly as on a paid plan, every session is held then charged against your account's credit balance. The credit granted at signup is enough for an evaluation.
The countdown starts when you ask for the trial, not at signup — start it when you are ready to drop in the snippet. A domain you have already verified is activated immediately.
At the deadline the domain goes back to sleep and sessions are refused. Nothing is lost: the verification and the configuration stay in place, and subscribing to a tier reactivates the domain without a second DNS round trip. The trial is granted once per account.
Assertion route (Next Route Handler)
Your backend must authenticate the user, authorize them for the site, check the exact Origin and Fetch Metadata headers (Sec-Fetch-Site, Sec-Fetch-Mode), and protect the route against CSRF. Sign a short-lived assertion containing siteId, sub, iat, exp, jti, aud, and kid. A secret sk_pa_ key must never reach the browser: use only the publishable key and a credentialed assertion URL.
export async function GET(request: Request) {
const user = await requireUser(request);
verifyOriginAndCsrf(request);
const site = await authorizeSite(user.id, new URL(request.url).origin);
if (!site) return Response.json({ error: "forbidden" }, { status: 403 });
return Response.json({ assertion: await signAssertion({ siteId: site.id, sub: user.id, aud: "velqa-page-agent", kid: process.env.PAGE_AGENT_SIGNING_KID }) });
}Session lifecycle
A session lasts 15 minutes. Renew it with POST /v1/page-agent/session/renew (current token plus a fresh assertion) before it expires; renewal rotates the token. An expired session returns 401 page_agent_session_expired: issue a new one rather than retrying.
Each turn consumes one step of the session's budget (maxSteps) and part of its cost budgets.
Turn idempotency
POST /v1/page-agent/chat/completions requires an `Idempotency-Key` header (256 characters maximum). Reuse the same key to replay a turn whose outcome you never received — typically after a 502/503/504: the turn resumes its original reservation instead of opening a second one.
The key is paired with a fingerprint of the request body computed over a canonical form, so reordering the keys of a tool schema does not break a retry. Reusing the same key with genuinely different content returns 409 page_agent_idempotency_conflict — take a new key for a new turn. Cached responses expire after 15 minutes.
One turn at a time per session: a concurrent call returns 409 page_agent_request_in_flight.
Error codes
Every error response has the shape { "error": "<code>" }. The code, not the status alone, tells you what to do.
| Status | Code | What to do |
|---|---|---|
| 400 | page_agent_request_invalid | Fix the payload (see Request limits). |
| 401 | page_agent_session_expired | Issue a new session; do not retry as-is. |
| 402 | page_agent_owner_credit | The site owner's credit balance is insufficient: top up. |
| 402 | page_agent_site_budget | The site's monthly budget cap is reached. |
| 403 | page_agent_assertion_invalid | Invalid assertion, Origin, or domain. |
| 403 | page_agent_site_disabled | The owner turned the site off; they can turn it back on. |
| 403 | page_agent_site_suspended | Administrative suspension: contact support. |
| 409 | page_agent_request_in_flight | A turn is already running on this session; wait. |
| 409 | page_agent_idempotency_conflict | Same Idempotency-Key, different content: use a new key. |
| 409 | page_agent_idempotency_replay_unavailable | The replayed turn is already settled; take a new key. |
| 429 | page_agent_rate_limited | Slow down, then retry. |
| 503 | page_agent_not_available | Transient unavailability (internal dependency): retry. |
page_agent_owner_credit and page_agent_not_available are the two never to conflate: the first needs a billing action, the second only a retry.
Request limits
A turn request is refused with 400 page_agent_request_invalid beyond:
- 1 MiB request body;
- 128 messages, 64 tools;
- 262,144 characters per message;
- 65,536 bytes for a tool's
parametersschema.
Accepted roles are user, assistant, and tool. A tool named execute_javascript is refused.
DOM, security, and confirmations
The DOM is untrusted data: indirect prompt injection can be placed in a page. The agent must never treat page text as a trusted instruction. Passwords, card fields, and data-velqa-mask markers are masked by default. Business-data masking is opt-in and is only a visual interaction mask, not a confidentiality guarantee.
Destructive actions (delete, pay, send, archive, disable, revoke, checkout, and payment submission) pause before dispatch and require an explicit confirmation marker. Navigation is restricted to exact allowed origins; javascript: and JavaScript execution are disabled.
Data, providers, and retention
Selected DOM content and prompts are sent to the authorized inference subprocessors listed in the current provider register. Session metadata is retained for at most 90 days, billing/usage aggregates for 24 months, and the encrypted technical idempotency cache for at most 15 minutes. See the DPA and privacy policy.
Installation
Load the SDK from an immutable, SRI-verified release. Set data-velqa-key, data-velqa-lang, and data-velqa-assertion-url; never put an sk_pa_ key in HTML. data-velqa-api-base defaults to https://api.velqa.dev and only needs setting to point at a different API. data-velqa-lang accepts fr-FR and en-US: it sets the panel's language and the language the agent answers your visitors in. The snippet offered in the dashboard already carries all four attributes. For the full operational integration, see the Page Agent runbook maintained by your team.
