spicrawlspicrawlDocs

Click, type and scroll with browser actions

Drive a rendered page with the actions array: wait_for, wait_for_navigation, click, fill, select, scroll, evaluate and screenshot steps, up to 50 per request, before the page is captured.

Use this when the content you need appears only after you interact with the page: a "Load more" button, a cookie wall, a tab, a search box, a login form, or an infinite-scroll feed. actions is an ordered list of steps the browser runs after the page loads and before it is captured. The whole list is validated before anything is spent.

Actions are browser-only. Set js_render: true or pin a browser engine; on the fetch tier they are a 400.

Minimal request

Click "Load more", wait for the new rows, and return the page as markdown:

curl https://api.spicrawl.com/v1/scrape \
  -H "Authorization: Bearer $SPICRAWL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/products?category=lamps",
    "js_render": true,
    "response_format": "markdown",
    "actions": [
      {"click": "button.load-more"},
      {"wait_for": ".product-card:nth-child(40)"}
    ]
  }'

The response is the page after the last step, in the response_format you asked for, with two exceptions that switch it to the JSON envelope:

  • A screenshot step. An image cannot travel in a markdown, text or HTML body, so a request with a screenshot step is answered with the JSON envelope whatever response_format says: the rendering under content (still in the format you asked for) and the images under screenshots, each under its step's label. The response carries a FORMAT_COERCED warning saying so. Send response_format: "json" to get the envelope without the warning.
  • An evaluate step with return_value: true. Same rule: the value comes back under actions in the envelope, with the same warning.

The envelope's actions array echoes every step: index, label, ok, duration_ms, url_after (the page's URL when the step finished) and, for evaluate, return_value.

Step shape

Each step is an object with exactly one verb key, plus optional label, timeout_ms and on_error. Two verb keys in one step, or an unknown verb, is a 400 before anything runs.

VerbBodyNotes
wait_for".price" or {"selector": ".price"} or {"ms": 1500}Waits for a selector, or sleeps. ms (alias wait) is 0–30000. Needs selector or a positive ms.
click"button.buy" or {"selector": "button.buy"}, {"selector": "a.next", "wait_for_navigation": true}Scrolls the element into view, then clicks it once. A click is a click: on a checkbox or radio it toggles the current state, there is no "set checked". To make sure a box ends up ticked, click it only when evaluate says it is not, or set it with evaluate (el.checked = true plus a change event). With "wait_for_navigation": true the step finishes only once the page the click navigates to has loaded; add "until" (see wait_for_navigation) to choose the load state.
wait_for_navigation{}, "networkidle" or {"until": "domcontentloaded"}Waits for a navigation that started after the previous step began (a clicked link, a submitted form, a script redirect) to commit and reach until: load (default), domcontentloaded or networkidle. No navigation within timeout_ms fails the step (ERR::UPSTREAM::TIMEOUT). Needs obscura or chromium.
fill{"selector": "#email", "value": "ada@example.com"}Clears the input, then types value. value must be a string. Add "secret": true for passwords.
select{"selector": "#size", "value": "L"} or {"selector": "#tags", "values": ["a", "b"]}Chooses option values in a <select>. values wins over value.
scroll{"y": 1200}, {"selector": "#reviews"} or {"to_bottom": true}See Scrolling.
evaluate"document.querySelector('.modal')?.remove()" or {"expression": "…"}Runs JavaScript in the page.
screenshot{"full_page": true}, {"selector": ".chart"}, {"format": "jpeg", "quality": 80}Captures an image mid-workflow. Needs chromium. full_page and selector are exclusive; quality is 0–100.

String shorthand works for wait_for, wait_for_navigation, click and evaluate only. fill, select, scroll and screenshot need an object.

Shared keys, accepted on the step or inside the verb object (both spellings mean the same thing):

KeyDefaultEffect
label<verb>[index], e.g. click[0]Name used in diagnostics, request logs and screenshot labels.
timeout_msengine defaultPer-step timeout in ms. 0 or omitted uses the engine default. The step is cut off at timeout_ms plus at most 250 ms, and never runs into the time reserved for capturing the page.
on_errorfailfail ends the request at 0 credits, naming the step. skip records the failure and continues with the next step.

Use on_error: "skip" for steps that may legitimately find nothing, such as dismissing a cookie banner that only some visitors see.

Scrolling

scroll accepts one target. If you set more than one, to_bottom wins, then selector, then y.

  • {"y": 1200} scrolls vertically by that many pixels (negative scrolls up). pixels is an alias. There is no horizontal scroll.
  • {"selector": "#reviews"} brings that element to the centre of the viewport. No match fails the step.
  • {"to_bottom": true} scrolls to the bottom repeatedly and stops when the page height stops growing, or when the step's timeout runs out. Use it for infinite-scroll feeds.

A scroll with none of these (or y: 0) is a 400.

evaluate and secret

evaluate is for side effects: removing an overlay, expanding collapsed sections, setting a value a widget ignores from fill. With {"expression": "…", "return_value": true} the expression's value, as JSON, is returned in the envelope's actions array under that step (return_value); add "await_promise": true for an async expression. Asking for a value switches the response to the JSON envelope, as a screenshot does.

"secret": true on fill redacts the value from logs, results, diagnostics and your request history. Set it on every password, token or one-time code.

Worked example: log in, scroll, extract

{
  "url": "https://example.com/login",
  "engine": "chromium",
  "actions": [
    {"click": "#accept-cookies", "on_error": "skip"},
    {"fill": {"selector": "input[name=email]", "value": "ada@example.com"}},
    {"fill": {"selector": "input[name=password]", "value": "correct-horse-battery", "secret": true}},
    {"click": {"selector": "button[type=submit]", "wait_for_navigation": true}, "label": "submit-login", "timeout_ms": 15000},
    {"wait_for": {"selector": ".orders-table", "timeout_ms": 15000}},
    {"scroll": {"to_bottom": true}, "timeout_ms": 20000},
    {"screenshot": {"full_page": true}, "label": "orders"}
  ],
  "extract": {
    "orders": {"selector": ".orders-table tbody tr", "kind": "list", "output": "text"},
    "next_page": "a.pagination-next@href"
  }
}

The response is the envelope: data.orders and data.next_page from the page as it stood after the last step, plus a screenshot labelled orders. To stay logged in across requests instead of logging in every time, run the login steps once with a session_id; see Sessions and logins.

Failure modes

CodeHTTPWhat to do
ERR::REQUEST::INVALID_PARAMETER400More than 50 steps, two verbs in one step, an unknown verb, a missing selector/value, or a bad on_error. detail names the step, e.g. actions[3]. 0 credits.
ERR::REQUEST::INCOMPATIBLE_FLAGS400Actions on the fetch tier. Add js_render: true or pin a browser engine.
ERR::ENGINE::RENDER_FAILED502A step failed (no element matched, a click was intercepted, a script threw). detail names the step's label. Fix the selector, add a wait_for before it, or set on_error: "skip". 0 credits.
ERR::UPSTREAM::TIMEOUT504A step or the whole render ran out of time. Raise that step's timeout_ms, or split the workflow. 0 credits.

Each step holds the browser for up to its own timeout, which is why the list is capped at 50.

Cost

Actions add no credits. You pay the engine that ran: obscura (js_render: true) 3 credits, chromium 8. A failed step costs 0.

Requests with actions are never served from or written to the cache, and they turn off the default image and font blocking so that the layout you click against is complete. See Credits.

On this page