curl -X POST https://api.scrapio.dev/v1/interact \
-H "Authorization: Bearer $SCRAPIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","actions":[{"type":"wait_ms","duration_ms":200}]}'import requests
url = "https://api.scrapio.dev/v1/interact"
payload = {
"url": "https://the-internet.herokuapp.com/key_presses",
"actions": [
{
"type": "click",
"selector": "#target"
},
{
"type": "press",
"key": "A"
}
],
"output": ["json"],
"extract": {
"mode": "selectors",
"fields": { "result_text": {
"selector": "#result",
"type": "text"
} }
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
url: 'https://the-internet.herokuapp.com/key_presses',
actions: [{type: 'click', selector: '#target'}, {type: 'press', key: 'A'}],
output: ['json'],
extract: {mode: 'selectors', fields: {result_text: {selector: '#result', type: 'text'}}}
})
};
fetch('https://api.scrapio.dev/v1/interact', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.scrapio.dev/v1/interact",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://the-internet.herokuapp.com/key_presses',
'actions' => [
[
'type' => 'click',
'selector' => '#target'
],
[
'type' => 'press',
'key' => 'A'
]
],
'output' => [
'json'
],
'extract' => [
'mode' => 'selectors',
'fields' => [
'result_text' => [
'selector' => '#result',
'type' => 'text'
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.scrapio.dev/v1/interact"
payload := strings.NewReader("{\n \"url\": \"https://the-internet.herokuapp.com/key_presses\",\n \"actions\": [\n {\n \"type\": \"click\",\n \"selector\": \"#target\"\n },\n {\n \"type\": \"press\",\n \"key\": \"A\"\n }\n ],\n \"output\": [\n \"json\"\n ],\n \"extract\": {\n \"mode\": \"selectors\",\n \"fields\": {\n \"result_text\": {\n \"selector\": \"#result\",\n \"type\": \"text\"\n }\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.scrapio.dev/v1/interact")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"https://the-internet.herokuapp.com/key_presses\",\n \"actions\": [\n {\n \"type\": \"click\",\n \"selector\": \"#target\"\n },\n {\n \"type\": \"press\",\n \"key\": \"A\"\n }\n ],\n \"output\": [\n \"json\"\n ],\n \"extract\": {\n \"mode\": \"selectors\",\n \"fields\": {\n \"result_text\": {\n \"selector\": \"#result\",\n \"type\": \"text\"\n }\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scrapio.dev/v1/interact")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"https://the-internet.herokuapp.com/key_presses\",\n \"actions\": [\n {\n \"type\": \"click\",\n \"selector\": \"#target\"\n },\n {\n \"type\": \"press\",\n \"key\": \"A\"\n }\n ],\n \"output\": [\n \"json\"\n ],\n \"extract\": {\n \"mode\": \"selectors\",\n \"fields\": {\n \"result_text\": {\n \"selector\": \"#result\",\n \"type\": \"text\"\n }\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"request_id": "80a01b87-640f-4240-afe9-d2f8d15a3145",
"id": "80a01b87-640f-4240-afe9-d2f8d15a3145",
"kind": "interact",
"mode": "inline",
"status": "completed",
"steps": [
{
"step_id": "step_1",
"type": "click",
"status": "completed",
"started_at": "2026-07-17T14:28:16.053Z",
"completed_at": "2026-07-17T14:28:16.110Z"
},
{
"step_id": "step_2",
"type": "press",
"status": "completed",
"started_at": "2026-07-17T14:28:16.110Z",
"completed_at": "2026-07-17T14:28:16.112Z"
}
],
"result": {
"outputs": {
"json": {
"result_text": "You entered: A"
}
},
"final_url": "https://the-internet.herokuapp.com/key_presses"
},
"diagnostics": {
"step_errors": [],
"output_failures": []
}
}{
"request_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
},
"diagnostics": {
"outcome": "failed",
"retryable": true,
"blocked": true,
"timed_out": true
}
}{
"request_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
},
"diagnostics": {
"outcome": "failed",
"retryable": true,
"blocked": true,
"timed_out": true
}
}{
"request_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
},
"diagnostics": {
"outcome": "failed",
"retryable": true,
"blocked": true,
"timed_out": true
}
}Interact
Drive a real browser with a sequence of actions — click, type, scroll, screenshot, and extract.
curl -X POST https://api.scrapio.dev/v1/interact \
-H "Authorization: Bearer $SCRAPIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","actions":[{"type":"wait_ms","duration_ms":200}]}'import requests
url = "https://api.scrapio.dev/v1/interact"
payload = {
"url": "https://the-internet.herokuapp.com/key_presses",
"actions": [
{
"type": "click",
"selector": "#target"
},
{
"type": "press",
"key": "A"
}
],
"output": ["json"],
"extract": {
"mode": "selectors",
"fields": { "result_text": {
"selector": "#result",
"type": "text"
} }
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
url: 'https://the-internet.herokuapp.com/key_presses',
actions: [{type: 'click', selector: '#target'}, {type: 'press', key: 'A'}],
output: ['json'],
extract: {mode: 'selectors', fields: {result_text: {selector: '#result', type: 'text'}}}
})
};
fetch('https://api.scrapio.dev/v1/interact', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.scrapio.dev/v1/interact",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://the-internet.herokuapp.com/key_presses',
'actions' => [
[
'type' => 'click',
'selector' => '#target'
],
[
'type' => 'press',
'key' => 'A'
]
],
'output' => [
'json'
],
'extract' => [
'mode' => 'selectors',
'fields' => [
'result_text' => [
'selector' => '#result',
'type' => 'text'
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.scrapio.dev/v1/interact"
payload := strings.NewReader("{\n \"url\": \"https://the-internet.herokuapp.com/key_presses\",\n \"actions\": [\n {\n \"type\": \"click\",\n \"selector\": \"#target\"\n },\n {\n \"type\": \"press\",\n \"key\": \"A\"\n }\n ],\n \"output\": [\n \"json\"\n ],\n \"extract\": {\n \"mode\": \"selectors\",\n \"fields\": {\n \"result_text\": {\n \"selector\": \"#result\",\n \"type\": \"text\"\n }\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.scrapio.dev/v1/interact")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"url\": \"https://the-internet.herokuapp.com/key_presses\",\n \"actions\": [\n {\n \"type\": \"click\",\n \"selector\": \"#target\"\n },\n {\n \"type\": \"press\",\n \"key\": \"A\"\n }\n ],\n \"output\": [\n \"json\"\n ],\n \"extract\": {\n \"mode\": \"selectors\",\n \"fields\": {\n \"result_text\": {\n \"selector\": \"#result\",\n \"type\": \"text\"\n }\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scrapio.dev/v1/interact")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"url\": \"https://the-internet.herokuapp.com/key_presses\",\n \"actions\": [\n {\n \"type\": \"click\",\n \"selector\": \"#target\"\n },\n {\n \"type\": \"press\",\n \"key\": \"A\"\n }\n ],\n \"output\": [\n \"json\"\n ],\n \"extract\": {\n \"mode\": \"selectors\",\n \"fields\": {\n \"result_text\": {\n \"selector\": \"#result\",\n \"type\": \"text\"\n }\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"request_id": "80a01b87-640f-4240-afe9-d2f8d15a3145",
"id": "80a01b87-640f-4240-afe9-d2f8d15a3145",
"kind": "interact",
"mode": "inline",
"status": "completed",
"steps": [
{
"step_id": "step_1",
"type": "click",
"status": "completed",
"started_at": "2026-07-17T14:28:16.053Z",
"completed_at": "2026-07-17T14:28:16.110Z"
},
{
"step_id": "step_2",
"type": "press",
"status": "completed",
"started_at": "2026-07-17T14:28:16.110Z",
"completed_at": "2026-07-17T14:28:16.112Z"
}
],
"result": {
"outputs": {
"json": {
"result_text": "You entered: A"
}
},
"final_url": "https://the-internet.herokuapp.com/key_presses"
},
"diagnostics": {
"step_errors": [],
"output_failures": []
}
}{
"request_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
},
"diagnostics": {
"outcome": "failed",
"retryable": true,
"blocked": true,
"timed_out": true
}
}{
"request_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
},
"diagnostics": {
"outcome": "failed",
"retryable": true,
"blocked": true,
"timed_out": true
}
}{
"request_id": "<string>",
"error": {
"code": "<string>",
"message": "<string>"
},
"diagnostics": {
"outcome": "failed",
"retryable": true,
"blocked": true,
"timed_out": true
}
}Prompt mode
actions array, describe the task in plain language and Scrapio figures out the clicks, typing, and navigation itself:
curl -X POST https://api.scrapio.dev/v1/interact \
-H "Authorization: Bearer $SCRAPIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/search",
"prompt": "Search for iPhone 16 Pro Max and click the first result",
"output": ["html"]
}'
prompt and actions are mutually exclusive — send one or the other, never both. Everything else (url, device, output, timeout_ms) works the same as the actions-based request.
For targets that block non-residential traffic, add "proxy": "stealth" (or the deprecated "use_proxy": true) to launch the session through a residential proxy instead of the default direct connection. Requires a paid plan (Starter or higher) and adds a 65-credit surcharge on top of the base 25 (90 total) — see Rate Limits & Credits.
The response carries the model’s own text answer alongside a step-by-step record of what it actually did:
{
"request_id": "req_...",
"kind": "interact",
"mode": "prompt",
"status": "completed",
"answer": "iPhone 16 Pro Max: $1,199",
"steps": [
{ "step_id": "step_1", "tool": "snapshot", "status": "completed", "started_at": "...", "completed_at": "..." },
{ "step_id": "step_2", "tool": "act", "status": "completed", "started_at": "...", "completed_at": "..." }
],
"result": {
"outputs": { "html": "<html>...</html>" },
"final_url": "https://example.com/search/iphone-16-pro-max"
},
"diagnostics": { "step_errors": [] }
}
statusis"partial"(not"completed") if the task hit its step budget before finishing — checkdiagnostics.step_limit_reachedand treatansweras best-effort in that case.outputonly supportshtmlandscreenshotin prompt mode (nomarkdown/json) — the model’s ownansweris the primary structured result, not a page extraction.steps[].toolreflects what the model actually called (snapshot,act,scroll,wait,get_url,read_text,list_items), not a caller-supplied action list — a failed step still returnsstatus: "completed"overall as long as the model recovers and completes the task.
Dedicated-site hints on error
If a request (either mode) targets a site we have a purpose-built API for — Amazon, Walmart, YouTube, Reddit, Booking.com, Agoda, Airbnb, AutoTrader, Target, Product Hunt, TikTok, or the Apple App Store — and the request errors, the response includes an extrahint field pointing you at that dedicated endpoint instead, since it’s faster, cheaper, and more reliable for that specific site than driving a general-purpose browser:
{
"request_id": "req_...",
"error": { "code": "selector_not_found", "message": "..." },
"hint": "Amazon URLs are better served by our dedicated Amazon API, which is faster and more reliable than a general-purpose browser for this site: https://docs.scrapio.dev/api-reference/amazon"
}
hint is only present on an error response, and only when the URL matches one of these sites — a successful /v1/interact call, or a call against a site with no dedicated API, never includes it.Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
Target page URL to open
Ordered sequence of interaction steps to run
1- Option 1
- Option 2
- Option 3
- Option 4
- Option 5
- Option 6
- Option 7
- Option 8
Show child attributes
Show child attributes
Device profile to emulate
desktop, mobile, tablet Reuse an existing browser session by id
Show child attributes
Show child attributes
Which output formats to return after actions
1Which output formats to return
html, markdown, json, screenshot Structured extraction config, requires json output
- Option 1
- Option 2
- Option 3
- Option 4
Show child attributes
Show child attributes
Max time for the whole interaction, in ms
x <= 300000Proxy and escalation mode. "basic": no proxy at all (cheapest; if the target blocks it the request fails and tells you to use "auto"). "stealth": go straight to the strongest tier, a hardened browser behind a residential proxy (paid plans). "auto": try cheap steps first and escalate to a residential proxy only when needed, billed by the step that served the request (paid plans, the default). The Free plan is always "basic".
basic, stealth, auto Deprecated: use proxy: "stealth". Go straight to the strongest access tier. Requires a paid plan.
Response
Action sequence completed inline.
Common envelope every workflow endpoint (crawl, map, interact, search) returns: request/step metadata plus a kind-specific result.
crawl, map, interact, search inline completed, partial, failed Kind-specific payload -- see the endpoint's example.