Browser API
Page actions
Some content only appears after you scroll or press "Load more". Page actions do that in the browser before the page is captured. They are read-only: they reveal content, they never enter data.
Send actions on render, screenshot, PDF or extract. They run in order, after the page loads (and after wait_for / wait_ms), and then the page is captured as usual. Actions need a real browser: mode: "auto" switches to it, and mode: "http" with actions is a 400. Crawl and batch do not take actions.
Example: load everything, then read it
curl -X POST https://api.104-236-84-15.sslip.io/v1/render \
-H "Authorization: Bearer $PAGETEER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://shop.example/catalog","formats":["markdown"],"actions":[{"type":"click","selector":"button.load-more","repeat":5},{"type":"scroll","to":"bottom","repeat":10,"delay_ms":800},{"type":"click","selector":"[role=tab][aria-controls=specs]","optional":true}]}'const res = await fetch('https://api.104-236-84-15.sslip.io/v1/render', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAGETEER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"url": "https://shop.example/catalog",
"formats": [
"markdown"
],
"actions": [
{
"type": "click",
"selector": "button.load-more",
"repeat": 5
},
{
"type": "scroll",
"to": "bottom",
"repeat": 10,
"delay_ms": 800
},
{
"type": "click",
"selector": "[role=tab][aria-controls=specs]",
"optional": true
}
]
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data, res.headers.get('x-credits-remaining'));import os
import requests
headers = {"Authorization": f"Bearer {os.environ['PAGETEER_API_KEY']}"}
res = requests.post(
"https://api.104-236-84-15.sslip.io/v1/render",
headers=headers,
json={
"url": "https://shop.example/catalog",
"formats": ["markdown"],
"actions": [{
"type": "click",
"selector": "button.load-more",
"repeat": 5,
}, {
"type": "scroll",
"to": "bottom",
"repeat": 10,
"delay_ms": 800,
}, {
"type": "click",
"selector": "[role=tab][aria-controls=specs]",
"optional": True,
}],
},
timeout=120,
)
res.raise_for_status()
print(res.json(), res.headers["x-credits-remaining"]){
"url": "https://shop.example/catalog",
"status": 200,
"markdown": "# Catalog\n…",
"mode_used": "browser",
"actions": [
{ "index": 0, "type": "click", "status": "ok", "ms": 3120, "count": 3 },
{ "index": 1, "type": "scroll", "status": "ok", "ms": 2480, "count": 3 },
{ "index": 2, "type": "click", "status": "skipped", "ms": 5004, "code": "not_found",
"message": "no matching element (optional)" }
],
"actions_ms": 10604,
"credits": 5
}The button disappeared after three pages, so the click stopped there (count: 3); the scroll stopped once nothing more loaded.
Actions
Selectors are plain CSS ([role=tab] and other attribute selectors are fine; Playwright selector engines such as text=, xpath= or >> chains are not), matched in the page itself and never inside iframes. The first match is used.
| Name | Type | Description |
|---|---|---|
wait | { type: "wait", ms } | Wait a fixed time. ms: 1–10000. |
wait_for | { type: "wait_for", selector, timeout_ms?, visible?, optional? } | Wait until an element is visible (visible: false: until it exists). timeout_ms: 100–10000, default 5000. With optional: true a missing element is skipped instead of an error. |
scroll | { type: "scroll", to? | by? | selector?, repeat?, delay_ms? } | Exactly one of to: "bottom" | "top", by (pixels, ±20000) or selector (scrolls that element into view). repeat (1–20) scrolls again after delay_ms (0–5000, default 500); with to: "bottom" it stops early once the page stops growing, which is what you want for infinite scroll. |
click | { type: "click", selector, repeat?, timeout_ms?, wait_after_ms?, optional? } | Click a read-only control: "Load more", "Show more", tabs, accordions, "Accept cookies"-style dismissals, or a link to another page of the same site. repeat (1–10) clicks again until the element disappears. wait_after_ms (0–5000, default 1000) waits for the page to update after each click. Clicks are checked first; see refused clicks. |
press | { type: "press", key, repeat?, delay_ms? } | Press a navigation key: End, Home, PageDown, PageUp, ArrowDown, ArrowUp or Escape (to close a popup). Focus is taken out of any form field first, so a key never lands in one. |
screenshot | { type: "screenshot", full_page?, format?, quality? } | An intermediate screenshot, returned as screenshot_base64 in that action's result (at most 3 per request; format jpeg (default) or png). On /v1/screenshot and /v1/pdf send Accept: application/json to get them. |
Limits
- At most 20 actions per request, and at most 3 screenshot actions.
- 30 seconds in total for all actions, and never past your
timeout_ms(some time is kept for the capture). Waits you ask for up front (wait, delays × repeats) must fit in the 30 seconds, or the request is a400; actions that do not fit at run time areskippedwithbudget_exhausted. - 10 seconds per action (timeouts of wait_for and click).
Results
The response gains actions (one result per action, in order) and actions_ms (their total time). Binary screenshots and PDFs carry the summary in the x-actions header (ok=2;skipped=0;error=1) and the time in x-actions-ms; ask for JSON (Accept: application/json) for the full results.
| Name | Type | Description |
|---|---|---|
index | integer | Position of the action in your array. |
type | string | The action type. |
status | "ok" | "skipped" | "error" | skipped: an optional element was missing, the time budget ran out, or an earlier step stopped the actions. A failed action does not stop the ones after it. |
ms | integer | How long the action took. |
code / message | string | Why it was skipped or failed: not_found, timeout, budget_exhausted, navigation_blocked, page_blocked, challenge, or one of the refused_* codes below. |
count | integer | click, scroll, press: how many times it was done. |
screenshot_base64 | string | screenshot actions: the image. |
What is not allowed
Page actions exist to reveal public content. Anything that enters data, acts as a user or gets past a bot check is not an action, and requests that try are refused with 400 invalid_request before anything is fetched:
- Typing or filling: no
type,fill,select,checkactions, and notext,valueorkeysfields.pressonly takes the navigation keys above: not Enter, Tab or characters. - Submitting forms, placing orders or checking out.
- Logging in, signing up or using an account. (Login and account pages are also refused for cookies.)
- File uploads and downloads.
- Custom JavaScript (
evaluate,js,script). - Solving, clicking or waiting out CAPTCHAs and bot challenges. CAPTCHA widgets are never touched; if one appears after an action, the actions stop and the request ends with
422 captcha_required(not charged), as any CAPTCHA does. - Following links to other sites, opening new windows, hovering, dragging and free navigation.
The error message names the action and links to the Acceptable Use Policy. The same goes for top-level fields such as click, type, fill, form or submit.
Refused clicks
Each click is checked in the page before it happens. A refused click is not performed; its result is an error with one of these codes, and the next actions still run.
| code | When |
|---|---|
refused_input | The element is a form field (input, textarea, select, contenteditable) or its label. |
refused_submit | The element is a submit or reset button, including a <button> without a type inside a form. |
refused_form | The element is inside a form that asks for a password, email, phone, username, card number or a file. |
refused_cross_origin | The link leads to another site. |
refused_new_window | The link opens a new window or tab. |
refused_download | The link downloads a file. |
refused_state_change | The control reads like it changes something: buy, add to cart, sign in, sign up, subscribe, delete, post, send, vote, follow, upload, confirm… |
refused_captcha | The element is part of a CAPTCHA or bot-challenge widget. |
While actions run, the page may only navigate within the same site, with a normal page load: a click (or the page's own script) that would submit a form, post data or go to another site is stopped (navigation_blocked), and new windows are closed. Every connection still goes through our private network and denylist checks.
Credits
- The browser price of the endpoint, as without actions: render or extract 5, screenshot or PDF 5, plus the unblocking tier that succeeded.
- Actions that take more than 15 seconds in total add 5 credits (once): the request then holds a browser for about as long as two renders.
- Cached responses (
max_age) cost 0; actions are part of the cache key. Failed requests, includingcaptcha_required, are free.
Test keys (pk_test_…) return deterministic fake results: a click whose selector names a form control or submit (input, [type=…], submit, login…) is refused, and a selector containing missing matches nothing.