Interact with a page you fetched by prompting or running code.
Scrape a page to get clean data, then call /interact to start taking actions in that page - click buttons, fill forms, extract dynamic content, or navigate deeper. Just describe what you want, or write code if you need full control.
AI prompts
Describe what action you want to take in the page
Code execution
Interact via code execution securely with playwright, agent-browser
Live view
Watch or interact with the browser in real time via embeddable stream
Scrape a URL with POST /v2/scrape. The response includes a scrapeId in data.metadata.scrapeId. Optionally pass a profile to persist browser state across sessions.
Interact by calling POST /v2/scrape/{scrapeId}/interact with a prompt or with playwright code. On the first call, the scraped session is resumed and you can start interacting with the page.
Stop the session with DELETE /v2/scrape/{scrapeId}/interact when you’re done.
Scrape a page, interact with it, and stop the session:
from firecrawl import Firecrawlapp = Firecrawl(api_key="fc-YOUR-API-KEY")# 1. Scrape Amazon's homepageresult = app.scrape("https://www.amazon.com", formats=["markdown"])scrape_id = result.metadata.scrape_id# 2. Interact — search for a product and get its priceapp.interact(scrape_id, prompt="Search for iPhone 16 Pro Max")response = app.interact(scrape_id, prompt="Click on the first result and tell me the price")print(response.output)# 3. Stop the sessionapp.stop_interaction(scrape_id)
import Firecrawl from '@mendable/firecrawl-js';const app = new Firecrawl({ apiKey: 'fc-YOUR-API-KEY' });// 1. Scrape Amazon's homepageconst result = await app.scrape('https://www.amazon.com', { formats: ['markdown'] });const scrapeId = result.metadata?.scrapeId;// 2. Interact — search for a product and get its priceawait app.interact(scrapeId, { prompt: 'Search for iPhone 16 Pro Max' });const response = await app.interact(scrapeId, { prompt: 'Click on the first result and tell me the price' });console.log(response.output);// 3. Stop the sessionawait app.stopInteraction(scrapeId);
# 1. Scrape Amazon's homepageRESPONSE=$(curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://www.amazon.com", "formats": ["markdown"]}')SCRAPE_ID=$(echo $RESPONSE | jq -r '.data.metadata.scrapeId')# 2. Interact — search for a product and get its pricecurl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "Search for iPhone 16 Pro Max"}'curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "Click on the first result and tell me the price"}'# 3. Stop the sessioncurl -s -X DELETE "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY"
# 1. Scrape Amazon's homepage (scrape ID is saved automatically)firecrawl scrape https://www.amazon.com# 2. Interact — search for a product and get its pricefirecrawl interact "Search for iPhone 16 Pro Max"firecrawl interact "Click on the first result and tell me the price"# 3. Stop the sessionfirecrawl interact stop
Response
{ "success": true, "liveViewUrl": "https://liveview.firecrawl.dev/...", "interactiveLiveViewUrl": "https://liveview.firecrawl.dev/...", "output": "The iPhone 16 Pro Max (256GB) is priced at $1,199.00.", "exitCode": 0, "killed": false}
The simplest way to interact with a page. Describe what you want in natural language and it will click, type, scroll, and extract data automatically.
response = app.interact(scrape_id, prompt="What are the customer reviews saying about battery life?")print(response.output)
const response = await app.interact(scrapeId, { prompt: 'What are the customer reviews saying about battery life?',});console.log(response.output);
curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "What are the customer reviews saying about battery life?" }'
firecrawl interact "What are the customer reviews saying about battery life?"
The response includes an output field with the agent’s answer:
Response
{ "success": true, "liveViewUrl": "https://liveview.firecrawl.dev/...", "interactiveLiveViewUrl": "https://liveview.firecrawl.dev/...", "output": "Customers are generally positive about battery life. Most reviewers report 8-10 hours of use on a single charge. A few noted it drains faster with heavy multitasking.", "stdout": "...", "result": "...", "stderr": "", "exitCode": 0, "killed": false}
Prompts work best when each one is a single, clear task. Instead of asking the agent to do a complex multi-step workflow in one shot, break it into separate interact calls. Each call reuses the same browser session, so state carries over between them.
For full control, you can execute code directly in the browser sandbox. The page variable (a Playwright Page object) is available in Node.js and Python. Bash mode has agent-browser pre-installed. You can also take screenshots within the session — use (await page.screenshot()).toString("base64") in Node.js, await page.screenshot(path="/tmp/screenshot.png") in Python, or agent-browser screenshot in Bash.
The default language. Write Playwright code directly — page is already connected to the browser.
response = app.interact(scrape_id, code="""// Click a button and wait for navigationawait page.click('#next-page');await page.waitForLoadState('networkidle');// Extract content from the new pageconst title = await page.title();const content = await page.$eval('.article-body', el => el.textContent);JSON.stringify({ title, content });""")print(response.result)
const response = await app.interact(scrapeId, { code: ` // Click a button and wait for navigation await page.click('#next-page'); await page.waitForLoadState('networkidle'); // Extract content from the new page const title = await page.title(); const content = await page.$eval('.article-body', el => el.textContent); JSON.stringify({ title, content }); `,});console.log(response.result);
agent-browser is a CLI pre-installed in the sandbox with 60+ commands. It provides an accessibility tree with element refs (@e1, @e2, …) — ideal for LLM-driven automation.
# Take a snapshot to see interactive elementssnapshot = app.interact( scrape_id, code="agent-browser snapshot -i", language="bash",)print(snapshot.stdout)# Output:# [document]# @e1 [input type="text"] "Search..."# @e2 [button] "Search"# @e3 [link] "About"# Interact with elements using @refsapp.interact( scrape_id, code='agent-browser fill @e1 "firecrawl" && agent-browser click @e2', language="bash",)
// Take a snapshot to see interactive elementsconst snapshot = await app.interact(scrapeId, { code: 'agent-browser snapshot -i', language: 'bash',});console.log(snapshot.stdout);// Output:// [document]// @e1 [input type="text"] "Search..."// @e2 [button] "Search"// @e3 [link] "About"// Interact with elements using @refsawait app.interact(scrapeId, { code: 'agent-browser fill @e1 "firecrawl" && agent-browser click @e2', language: 'bash',});
# Take a snapshot to see interactive elementscurl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"code": "agent-browser snapshot -i", "language": "bash"}'# Interact with elements using @refscurl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"code": "agent-browser fill @e1 \"firecrawl\" && agent-browser click @e2", "language": "bash"}'
# Take a snapshot to see interactive elementsfirecrawl interact --bash -c "agent-browser snapshot -i"# Interact with elements using @refsfirecrawl interact --bash -c 'agent-browser fill @e1 "firecrawl" && agent-browser click @e2'
Every interact response returns a liveViewUrl that you can embed to watch the browser in real time. Useful for debugging, demos, or building browser-powered UIs.
The response also includes an interactiveLiveViewUrl. Unlike the standard live view which is view-only, the interactive live view allows users to click, type, and interact with the browser session directly through the embedded stream. This is useful for building user-facing browser UIs — such as login flows, or guided workflows where end users need to control the browser.
Subsequent interact calls on the same scrapeId reuse the existing session. The browser stays open and maintains its state between calls, so you can chain multiple interactions:
# First call — click a tabapp.interact(scrape_id, code="await page.click('#tab-2')")# Second call — the tab is still selected, extract its contentresult = app.interact(scrape_id, code="await page.$eval('#tab-2-content', el => el.textContent)")print(result.result)
// First call — click a tabawait app.interact(scrapeId, { code: "await page.click('#tab-2')" });// Second call — the tab is still selected, extract its contentconst result = await app.interact(scrapeId, { code: "await page.$eval('#tab-2-content', el => el.textContent)",});console.log(result.result);
# First call — click a tabfirecrawl interact -c "await page.click('#tab-2')"# Second call — the tab is still selected, extract its contentfirecrawl interact -c "await page.\$eval('#tab-2-content', el => el.textContent)"
By default, each scrape + interact session starts with a clean browser. With profile, you can save and reuse browser state (cookies, localStorage, sessions) across scrapes. This is useful for staying logged in and preserving preferences.Pass the profile parameter when calling scrape. Sessions with the same profile name share state.
from firecrawl import Firecrawlapp = Firecrawl(api_key="fc-YOUR-API-KEY")# Session 1: Scrape with a profile, log in, then stop (state is saved)result = app.scrape( "https://app.example.com/login", formats=["markdown"], profile={"name": "my-app", "save_changes": True},)scrape_id = result.metadata.scrape_idapp.interact(scrape_id, prompt="Fill in user@example.com and password, then click Login")app.stop_interaction(scrape_id)# Session 2: Scrape with the same profile — already logged inresult = app.scrape( "https://app.example.com/dashboard", formats=["markdown"], profile={"name": "my-app", "save_changes": True},)scrape_id = result.metadata.scrape_idresponse = app.interact(scrape_id, prompt="Extract the dashboard data")print(response.output)app.stop_interaction(scrape_id)
import Firecrawl from '@mendable/firecrawl-js';const app = new Firecrawl({ apiKey: 'fc-YOUR-API-KEY' });// Session 1: Scrape with a profile, log in, then stop (state is saved)const result1 = await app.scrape('https://app.example.com/login', { formats: ['markdown'], profile: { name: 'my-app', saveChanges: true },});const scrapeId1 = result1.metadata?.scrapeId;await app.interact(scrapeId1, { prompt: 'Fill in user@example.com and password, then click Login' });await app.stopInteraction(scrapeId1);// Session 2: Scrape with the same profile — already logged inconst result2 = await app.scrape('https://app.example.com/dashboard', { formats: ['markdown'], profile: { name: 'my-app', saveChanges: true },});const scrapeId2 = result2.metadata?.scrapeId;const response = await app.interact(scrapeId2, { prompt: 'Extract the dashboard data' });console.log(response.output);await app.stopInteraction(scrapeId2);
# Session 1: Scrape with a profileRESPONSE=$(curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://app.example.com/login", "formats": ["markdown"], "profile": { "name": "my-app", "saveChanges": true } }')SCRAPE_ID=$(echo $RESPONSE | jq -r '.data.metadata.scrapeId')# Log in via interactcurl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "Fill in user@example.com and password, then click Login"}'# Stop — state is saved to the profilecurl -s -X DELETE "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY"# Session 2: Scrape again with the same profile — already logged inRESPONSE=$(curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://app.example.com/dashboard", "formats": ["markdown"], "profile": { "name": "my-app", "saveChanges": true } }')SCRAPE_ID=$(echo $RESPONSE | jq -r '.data.metadata.scrapeId')curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \ -H "Authorization: Bearer $FIRECRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"prompt": "Extract the dashboard data"}'
# Session 1: Scrape with a profile, log in, then stop (state is saved)firecrawl scrape https://app.example.com/login --profile my-appfirecrawl interact "Fill in user@example.com and password, then click Login"firecrawl interact stop# Session 2: Scrape with the same profile — already logged infirecrawl scrape https://app.example.com/dashboard --profile my-appfirecrawl interact "Extract the dashboard data"firecrawl interact stop# Read-only: load profile state without saving changes backfirecrawl scrape https://app.example.com/dashboard --profile my-app --no-save-changes
Parameter
Default
Description
name
—
A name for the persistent profile. Scrapes with the same name share browser state.
saveChanges
true
When true, browser state is saved back to the profile when the interact session stops. Set to false to load existing data without writing — useful when you need multiple concurrent readers.
Only one session can save to a profile at a time. If another session is already saving, you’ll get a 409 error. You can still open the same profile with saveChanges: false, or try again later.
The browser state is saved when the interact session is stopped. Always stop the session when you’re done so the profile can be reused.
Interact vs Browser Sandbox: Interact is built on the same infrastructure as Browser Sandbox but provides a better interface for the most common pattern — scrape a page, then go deeper. Browser Sandbox is better when you need a standalone browser session that isn’t tied to a specific scrape.