> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firecrawl.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Interact tras el scraping

> Interactúa con una página que has obtenido mediante prompts o ejecutando código.

Extrae una página para obtener datos limpios y luego llama a `/interact` para empezar a realizar acciones en esa página: hacer clic en botones, completar formularios, extraer contenido dinámico o navegar más a fondo. Solo describe lo que quieres hacer, o escribe código si necesitas un control total.

<CardGroup cols={3}>
  <Card title="Prompts de IA" icon="wand-magic-sparkles">
    Describe qué acción quieres realizar en la página
  </Card>

  <Card title="Ejecución de código" icon="code">
    Interactúa de forma segura mediante la ejecución de código con playwright, agent-browser
  </Card>

  <Card title="Vista en vivo" icon="eye">
    Observa o interactúa con el browser en tiempo real mediante un stream incrustable
  </Card>
</CardGroup>

<div id="how-it-works">
  ## Cómo funciona
</div>

1. **Haz scraping de** una URL con `POST /v2/scrape`. La respuesta incluye un `scrapeId` en `data.metadata.scrapeId`. Si quieres conservar el estado del navegador, pasa `profile` en esta solicitud.
2. **Interactúa** llamando a `POST /v2/scrape/{scrapeId}/interact` con un `prompt` o con código de Playwright `code`. No pases `profile` aquí; la sesión de interacción hereda el perfil del trabajo de scraping.
3. **Detén** la sesión con `DELETE /v2/scrape/{scrapeId}/interact` cuando hayas terminado. En los perfiles con escritura, los cambios se guardan cuando la sesión se detiene.

<div id="quick-start">
  ## Inicio rápido
</div>

Haz scraping de una página, interactúa con ella y detén la sesión:

<CodeGroup>
  ```python Python theme={null}
  from firecrawl import Firecrawl

  app = Firecrawl(
    # No se necesita API key para comenzar — añade una para límites de tasa más altos:
    # api_key="fc-YOUR-API-KEY",
  )

  # 1. Extraer la página principal de Amazon
  result = app.scrape("https://www.amazon.com", formats=["markdown"])
  scrape_id = result.metadata.scrape_id

  # 2. Interactuar — buscar un producto y obtener su precio
  app.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. Detener la sesión
  app.stop_interaction(scrape_id)
  ```

  ```js Node theme={null}
  import { Firecrawl } from 'firecrawl';

  const app = new Firecrawl({
    // No se necesita API key para comenzar — agrega una para límites de tasa más altos:
    // apiKey: 'fc-YOUR-API-KEY',
  });

  // 1. Extraer la página principal de Amazon
  const result = await app.scrape('https://www.amazon.com', { formats: ['markdown'] });
  const scrapeId = result.metadata?.scrapeId;

  // 2. Interactuar — buscar un producto y obtener su precio
  await 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. Detener la sesión
  await app.stopInteraction(scrapeId);
  ```

  ```bash cURL theme={null}
  # 1. Extraer la página principal de Amazon
  # No se necesita API key para comenzar — añade -H "Authorization: Bearer $FIRECRAWL_API_KEY" para límites de tasa más altos:
  RESPONSE=$(curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
    -H "Content-Type: application/json" \
    -d '{"url": "https://www.amazon.com", "formats": ["markdown"]}')

  SCRAPE_ID=$(echo $RESPONSE | jq -r '.data.metadata.scrapeId')

  # 2. Interactuar — buscar un producto y obtener su precio
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
    -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 "Content-Type: application/json" \
    -d '{"prompt": "Click on the first result and tell me the price"}'

  # 3. Detener la sesión
  curl -s -X DELETE "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact"
  ```

  ```bash CLI theme={null}
  # 1. Scrape la página principal de Amazon (el ID de scrape se guarda automáticamente)
  firecrawl scrape https://www.amazon.com

  # 2. Interactúa — busca un producto y obtén su precio
  firecrawl interact "Search for iPhone 16 Pro Max"
  firecrawl interact "Click on the first result and tell me the price"

  # 3. Detén la sesión
  firecrawl interact stop
  ```
</CodeGroup>

```json Response theme={null}
{
  "success": true,
  "cdpUrl": "wss://browser.firecrawl.dev/...",
  "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
}
```

<div id="interact-via-prompting">
  ## Interactúa mediante prompts
</div>

La forma más simple de interactuar con una página. Describe lo que quieres en lenguaje natural y hará clic, escribirá, se desplazará y extraerá datos automáticamente.

<CodeGroup>
  ```python Python theme={null}
  response = app.interact(scrape_id, prompt="What are the customer reviews saying about battery life?")
  print(response.output)
  ```

  ```js Node theme={null}
  const response = await app.interact(scrapeId, {
    prompt: 'What are the customer reviews saying about battery life?',
  });
  console.log(response.output);
  ```

  ```bash cURL theme={null}
  # No se necesita API key para comenzar — agrega -H "Authorization: Bearer $FIRECRAWL_API_KEY" para límites de tasa más altos:
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
    -H "Content-Type: application/json" \
    -d '{
      "prompt": "What are the customer reviews saying about battery life?"
    }'
  ```

  ```bash CLI theme={null}
  firecrawl interact "What are the customer reviews saying about battery life?"
  ```
</CodeGroup>

La respuesta incluye un campo `output` con la respuesta del agente:

```json Response theme={null}
{
  "success": true,
  "cdpUrl": "wss://browser.firecrawl.dev/...",
  "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
}
```

<div id="keep-prompts-small-and-focused">
  ### Mantén los prompts pequeños y enfocados
</div>

Los prompts funcionan mejor cuando cada uno consiste en una **única tarea clara**. En lugar de pedirle al agente que realice un flujo de trabajo complejo de varios pasos de una sola vez, divídelo en llamadas `interact` independientes. Cada llamada reutiliza la misma sesión del navegador, por lo que el estado se conserva entre una y otra.

<div id="running-code">
  ## Ejecución de código
</div>

Para tener control total, puedes ejecutar código directamente en el sandbox del navegador. La variable `page` (un objeto Page de Playwright) está disponible en Node.js y Python. El modo Bash incluye [agent-browser](https://github.com/vercel-labs/agent-browser) preinstalado. También puedes hacer capturas de pantalla dentro de la sesión: usa `(await page.screenshot()).toString("base64")` en Node.js, `await page.screenshot(path="/tmp/screenshot.png")` en Python o `agent-browser screenshot` en Bash.

<div id="nodejs-playwright">
  ### Node.js (Playwright)
</div>

El lenguaje predeterminado. Escribe código de Playwright directamente. `page` ya está conectado al navegador.

<CodeGroup>
  ```python Python theme={null}
  response = app.interact(scrape_id, code="""
  // Hacer clic en un botón y esperar la navegación
  await page.click('#next-page');
  await page.waitForLoadState('networkidle');

  // Extraer contenido de la nueva página
  const title = await page.title();
  const content = await page.$eval('.article-body', el => el.textContent);
  JSON.stringify({ title, content });
  """)
  print(response.result)
  ```

  ```js Node theme={null}
  const response = await app.interact(scrapeId, {
    code: `
      // Hacer clic en un botón y esperar la navegación
      await page.click('#next-page');
      await page.waitForLoadState('networkidle');

      // Extraer contenido de la nueva página
      const title = await page.title();
      const content = await page.$eval('.article-body', el => el.textContent);
      JSON.stringify({ title, content });
    `,
  });
  console.log(response.result);
  ```

  ```bash cURL theme={null}
  # No se necesita API key para comenzar — agrega -H "Authorization: Bearer $FIRECRAWL_API_KEY" para límites de tasa más altos:
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
    -H "Content-Type: application/json" \
    -d '{
      "code": "await page.click(\"#next-page\"); await page.waitForLoadState(\"networkidle\"); const title = await page.title(); JSON.stringify({ title });",
      "language": "node",
      "timeout": 30
    }'
  ```

  ```bash CLI theme={null}
  # Usa el último scrape automáticamente
  firecrawl interact -c "
    await page.click('#next-page');
    await page.waitForLoadState('networkidle');
    const title = await page.title();
    const content = await page.\$eval('.article-body', el => el.textContent);
    JSON.stringify({ title, content });
  "

  # O pasa un ID de scrape explícitamente
  # firecrawl interact <scrape-id> -c "await page.title()"
  ```
</CodeGroup>

<div id="python">
  ### Python
</div>

Configura `language` como `"python"` para usar la API de Python de Playwright.

<CodeGroup>
  ```python Python theme={null}
  response = app.interact(
      scrape_id,
      code="""
  import json

  await page.click('#load-more')
  await page.wait_for_load_state('networkidle')

  items = await page.query_selector_all('.item')
  data = []
  for item in items:
      text = await item.text_content()
      data.append(text.strip())

  print(json.dumps(data))
  """,
      language="python",
  )
  print(response.stdout)
  ```

  ```js Node theme={null}
  const response = await app.interact(scrapeId, {
    code: `
  import json

  await page.click('#load-more')
  await page.wait_for_load_state('networkidle')

  items = await page.query_selector_all('.item')
  data = []
  for item in items:
      text = await item.text_content()
      data.append(text.strip())

  print(json.dumps(data))
  `,
    language: 'python',
  });
  console.log(response.stdout);
  ```

  ```bash cURL theme={null}
  # No se necesita API key para comenzar — agrega -H "Authorization: Bearer $FIRECRAWL_API_KEY" para obtener límites de tasa más altos:
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
    -H "Content-Type: application/json" \
    -d '{
      "code": "import json\nawait page.click(\"#load-more\")\nawait page.wait_for_load_state(\"networkidle\")\nitems = await page.query_selector_all(\".item\")\ndata = [await i.text_content() for i in items]\nprint(json.dumps(data))",
      "language": "python"
    }'
  ```

  ```bash CLI theme={null}
  firecrawl interact --python -c "
  import json
  await page.click('#load-more')
  await page.wait_for_load_state('networkidle')
  items = await page.query_selector_all('.item')
  data = [await i.text_content() for i in items]
  print(json.dumps(data))
  "
  ```
</CodeGroup>

<div id="bash-agent-browser">
  ### Bash (agent-browser)
</div>

[agent-browser](https://github.com/vercel-labs/agent-browser) es una CLI preinstalada en el sandbox con más de 60 comandos. Proporciona un árbol de accesibilidad con referencias de elementos (`@e1`, `@e2`, ...), lo que es ideal para la automatización controlada por LLM.

<CodeGroup>
  ```python Python theme={null}
  # Tomar una instantánea para ver los elementos interactivos
  snapshot = app.interact(
      scrape_id,
      code="agent-browser snapshot -i",
      language="bash",
  )
  print(snapshot.stdout)
  # Salida:
  # [document]
  #   @e1 [input type="text"] "Search..."
  #   @e2 [button] "Search"
  #   @e3 [link] "About"

  # Interactuar con elementos usando @refs
  app.interact(
      scrape_id,
      code='agent-browser fill @e1 "firecrawl" && agent-browser click @e2',
      language="bash",
  )
  ```

  ```js Node theme={null}
  // Tomar una instantánea para ver los elementos interactivos
  const snapshot = await app.interact(scrapeId, {
    code: 'agent-browser snapshot -i',
    language: 'bash',
  });
  console.log(snapshot.stdout);
  // Salida:
  // [document]
  //   @e1 [input type="text"] "Search..."
  //   @e2 [button] "Search"
  //   @e3 [link] "About"

  // Interactuar con elementos usando @refs
  await app.interact(scrapeId, {
    code: 'agent-browser fill @e1 "firecrawl" && agent-browser click @e2',
    language: 'bash',
  });
  ```

  ```bash cURL theme={null}
  # Tomar una instantánea para ver los elementos interactivos
  # No se necesita API key para comenzar — agrega -H "Authorization: Bearer $FIRECRAWL_API_KEY" para mayores límites de tasa:
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
    -H "Content-Type: application/json" \
    -d '{"code": "agent-browser snapshot -i", "language": "bash"}'

  # Interactuar con los elementos utilizando @refs
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
    -H "Content-Type: application/json" \
    -d '{"code": "agent-browser fill @e1 \"firecrawl\" && agent-browser click @e2", "language": "bash"}'
  ```

  ```bash CLI theme={null}
  # Tomar una instantánea para ver los elementos interactivos
  firecrawl interact --bash -c "agent-browser snapshot -i"

  # Interactuar con elementos utilizando @refs
  firecrawl interact --bash -c 'agent-browser fill @e1 "firecrawl" && agent-browser click @e2'
  ```
</CodeGroup>

Comandos comunes de agent-browser:

| Comando                   | Descripción                                                  |
| ------------------------- | ------------------------------------------------------------ |
| `snapshot`                | Árbol de accesibilidad completo con referencias de elementos |
| `snapshot -i`             | Solo elementos interactivos                                  |
| `click @e1`               | Hacer clic en un elemento por referencia                     |
| `fill @e1 "text"`         | Borrar el campo y escribir texto                             |
| `type @e1 "text"`         | Escribir sin borrar                                          |
| `press Enter`             | Pulsar una tecla del teclado                                 |
| `scroll down 500`         | Desplazarse 500 píxeles hacia abajo                          |
| `get text @e1`            | Obtener el contenido de texto                                |
| `get url`                 | Obtener la URL actual                                        |
| `wait @e1`                | Esperar a que aparezca el elemento                           |
| `wait --load networkidle` | Esperar a que la red quede inactiva                          |
| `find text "X" click`     | Buscar un elemento por texto y hacer clic                    |
| `screenshot`              | Tomar una captura de pantalla de la página actual            |
| `eval "js code"`          | Ejecutar JavaScript en la página                             |

<div id="live-view">
  ## vista en vivo
</div>

Cada respuesta de interact devuelve una `liveViewUrl` que puedes incrustar para ver el navegador en tiempo real. Es útil para depuración, demos o para crear interfaces con navegador.

```json Response theme={null}
{
  "success": true,
  "cdpUrl": "wss://browser.firecrawl.dev/...",
  "liveViewUrl": "https://liveview.firecrawl.dev/...",
  "interactiveLiveViewUrl": "https://liveview.firecrawl.dev/...",
  "stdout": "",
  "result": "...",
  "exitCode": 0
}
```

```html theme={null}
<iframe src="LIVE_VIEW_URL" width="100%" height="600" />
```

<div id="interactive-live-view">
  ### Vista en vivo interactiva
</div>

La respuesta también incluye un `interactiveLiveViewUrl`. A diferencia de la vista en vivo estándar, que es solo de visualización, la vista en vivo interactiva permite a los usuarios hacer clic, escribir e interactuar con la sesión del navegador directamente a través del stream integrado. Esto resulta útil para crear interfaces de navegador orientadas al usuario, como flujos de inicio de sesión o flujos de trabajo guiados en los que los usuarios finales necesitan controlar el navegador.

```html theme={null}
<iframe src="INTERACTIVE_LIVE_VIEW_URL" width="100%" height="600" />
```

<div id="cdp-url">
  ### URL de CDP
</div>

Cada respuesta de interact también devuelve una `cdpUrl`: la URL de WebSocket sin procesar del Chrome DevTools Protocol (CDP) para la sesión del navegador. Úsala para conectarte directamente a la sesión activa desde Playwright, Puppeteer o cualquier cliente CDP, y controlar el navegador con tu propio código.

```js theme={null}
import { chromium } from "playwright";

const browser = await chromium.connectOverCDP(cdpUrl);
const context = browser.contexts()[0];
const page = context.pages()[0];
```

<div id="session-lifecycle">
  ## Ciclo de vida de la sesión
</div>

<div id="creation">
  ### Creación
</div>

La primera llamada `POST /v2/scrape/{scrapeId}/interact` continúa la sesión de scraping e inicia la interacción.

<div id="reuse">
  ### Reutilización
</div>

Las llamadas posteriores a `interact` sobre el mismo `scrapeId` reutilizan la sesión existente. El navegador permanece abierto y conserva su estado entre llamadas, por lo que puedes encadenar varias interacciones:

<CodeGroup>
  ```python Python theme={null}
  # Primera llamada: haz clic en una pestaña
  app.interact(scrape_id, code="await page.click('#tab-2')")

  # Segunda llamada: la pestaña sigue seleccionada, extrae su contenido
  result = app.interact(scrape_id, code="await page.$eval('#tab-2-content', el => el.textContent)")
  print(result.result)
  ```

  ```js Node theme={null}
  // Primera llamada: haz clic en una pestaña
  await app.interact(scrapeId, { code: "await page.click('#tab-2')" });

  // Segunda llamada: la pestaña sigue seleccionada, extrae su contenido
  const result = await app.interact(scrapeId, {
    code: "await page.$eval('#tab-2-content', el => el.textContent)",
  });
  console.log(result.result);
  ```

  ```bash CLI theme={null}
  # Primera llamada: haz clic en una pestaña
  firecrawl interact -c "await page.click('#tab-2')"

  # Segunda llamada: la pestaña sigue seleccionada, extrae su contenido
  firecrawl interact -c "await page.\$eval('#tab-2-content', el => el.textContent)"
  ```
</CodeGroup>

<div id="cleanup">
  ### Limpieza
</div>

Detén la sesión explícitamente al terminar:

<CodeGroup>
  ```python Python theme={null}
  app.stop_interaction(scrape_id)
  ```

  ```js Node theme={null}
  await app.stopInteraction(scrapeId);
  ```

  ```bash cURL theme={null}
  # No se necesita API key para comenzar — agrega -H "Authorization: Bearer $FIRECRAWL_API_KEY" para límites de tasa más altos:
  curl -s -X DELETE "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact"
  ```

  ```bash CLI theme={null}
  # Detiene la última sesión de scrape
  firecrawl interact stop

  # O detener una sesión específica por ID
  # firecrawl interact stop <scrape-id>
  ```
</CodeGroup>

Las sesiones también expiran automáticamente según el TTL (predeterminado: 10 minutos) o el timeout de inactividad (predeterminado: 5 minutos).

<Warning>
  Detén siempre las sesiones al terminar para evitar Billing innecesario. Los credits se prorratean por segundo.
</Warning>

<div id="persistent-profiles-with-scrape-interact">
  ## Perfiles persistentes con Scraping + Interact
</div>

De forma predeterminada, cada sesión de scraping + interact se inicia con un navegador limpio. Con `profile`, puedes guardar y reutilizar el estado del navegador (cookies, localStorage, sesiones) entre scrapings. Esto resulta útil para mantener la sesión iniciada y conservar las preferencias.

Pasa el objeto `profile` en la solicitud inicial `POST /v2/scrape`. No pases `profile` a `POST /v2/scrape/{scrapeId}/interact`; la sesión de interact reutiliza la sesión del navegador y la configuración del perfil del trabajo de scraping. Detén la sesión de interact con `DELETE /v2/scrape/{scrapeId}/interact` para que puedan guardarse los cambios del perfil.

```bash cURL theme={null}
curl -X POST "https://api.firecrawl.dev/v2/scrape" \
  -H "Authorization: Bearer fc-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "formats": ["markdown"],
    "profile": {
      "name": "my-profile",
      "saveChanges": true
    }
  }'

curl -X POST "https://api.firecrawl.dev/v2/scrape/SCRAPE_ID/interact" \
  -H "Authorization: Bearer fc-YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Click the login button"
  }'

curl -X DELETE "https://api.firecrawl.dev/v2/scrape/SCRAPE_ID/interact" \
  -H "Authorization: Bearer fc-YOUR_API_KEY"
```

El ciclo de vida del perfil es:

1. Crea el scraping con `profile.name` y `saveChanges: true`.
2. Ejecuta interacciones con prompt o código usando el `scrapeId` devuelto.
3. Detén la sesión para guardar cookies, localStorage y otro estado del navegador.
4. Inicia un scraping posterior con el mismo `profile.name`. Usa `saveChanges: false` cuando solo quieras leer el estado existente sin volver a escribir cambios.

<CodeGroup>
  ```python Python theme={null}
  from firecrawl import Firecrawl

  app = Firecrawl(
    # No se necesita API key para comenzar — agrega una para obtener límites de tasa más altos:
    # api_key="fc-YOUR-API-KEY",
  )

  # Sesión 1: Scrape con un perfil, inicia sesión y luego detén (el estado se guarda)
  result = app.scrape(
      "https://app.example.com/login",
      formats=["markdown"],
      profile={"name": "my-app", "save_changes": True},
  )
  scrape_id = result.metadata.scrape_id

  app.interact(scrape_id, prompt="Fill in user@example.com and password, then click Login")
  app.stop_interaction(scrape_id)

  # Sesión 2: Scrape con el mismo perfil en modo de solo lectura — ya con sesión iniciada
  result = app.scrape(
      "https://app.example.com/dashboard",
      formats=["markdown"],
      profile={"name": "my-app", "save_changes": False},
  )
  scrape_id = result.metadata.scrape_id

  response = app.interact(scrape_id, prompt="Extract the dashboard data")
  print(response.output)
  app.stop_interaction(scrape_id)
  ```

  ```js Node theme={null}
  import { Firecrawl } from 'firecrawl';

  const app = new Firecrawl({
    // No se necesita API key para comenzar — agrega una para obtener límites de tasa más altos:
    // apiKey: 'fc-YOUR-API-KEY',
  });

  // Sesión 1: Scrape con un perfil, inicia sesión y luego detén (el estado se guarda)
  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);

  // Sesión 2: Scrape con el mismo perfil en modo de solo lectura — ya con sesión iniciada
  const result2 = await app.scrape('https://app.example.com/dashboard', {
    formats: ['markdown'],
    profile: { name: 'my-app', saveChanges: false },
  });
  const scrapeId2 = result2.metadata?.scrapeId;

  const response = await app.interact(scrapeId2, { prompt: 'Extract the dashboard data' });
  console.log(response.output);
  await app.stopInteraction(scrapeId2);
  ```

  ```bash cURL theme={null}
  # Session 1: Scrape with a profile
  # No se necesita API key para comenzar — agrega -H "Authorization: Bearer $FIRECRAWL_API_KEY" para límites de tasa más altos:
  RESPONSE=$(curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
    -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 interact
  curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
    -H "Content-Type: application/json" \
    -d '{"prompt": "Fill in user@example.com and password, then click Login"}'

  # Detener - el estado se guarda en el perfil
  curl -s -X DELETE "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact"

  # Sesión 2: Scrape nuevamente con el mismo perfil en modo de solo lectura - ya con sesión iniciada
  RESPONSE=$(curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://app.example.com/dashboard",
      "formats": ["markdown"],
      "profile": { "name": "my-app", "saveChanges": false }
    }')

  SCRAPE_ID=$(echo $RESPONSE | jq -r '.data.metadata.scrapeId')

  curl -s -X POST "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
    -H "Content-Type: application/json" \
    -d '{"prompt": "Extract the dashboard data"}'
  ```

  ```bash CLI theme={null}
  # Sesión 1: Scrape con un perfil, inicia sesión y luego detén (el estado se guarda)
  firecrawl scrape https://app.example.com/login --profile my-app
  firecrawl interact "Fill in user@example.com and password, then click Login"
  firecrawl interact stop

  # Sesión 2: Scrape con el mismo perfil — ya has iniciado sesión
  firecrawl scrape https://app.example.com/dashboard --profile my-app
  firecrawl interact "Extract the dashboard data"
  firecrawl interact stop

  # Solo lectura: carga el estado del perfil sin guardar los cambios
  firecrawl scrape https://app.example.com/dashboard --profile my-app --no-save-changes
  ```
</CodeGroup>

| Parámetro     | Predeterminado | Descripción                                                                                                                                                                                                                                                      |
| ------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`        | None           | Nombre del perfil persistente. Los scrapings con el mismo nombre comparten el estado del navegador.                                                                                                                                                              |
| `saveChanges` | `true`         | Cuando es `true`, el estado del navegador se vuelve a guardar en el perfil cuando se detiene la sesión de interact. Establécelo en `false` para cargar datos existentes sin escribir cambios, lo que resulta útil cuando necesitas varios lectores concurrentes. |

<Note>
  Solo una sesión puede guardar en un perfil a la vez. Si otra sesión ya está guardando, recibirás un error `409`. Aun así, puedes abrir el mismo perfil con `saveChanges: false` o volver a intentarlo más tarde.
</Note>

El estado del navegador se guarda cuando se detiene la sesión de interact. Detén siempre la sesión cuando termines para que el perfil pueda reutilizarse.

<div id="validate-persistence">
  ### Validar la persistencia
</div>

Puedes probar la persistencia sin depender de un flujo de inicio de sesión real: escribe un valor en `localStorage` en una sesión, detenla y luego lee ese valor en una segunda sesión con el mismo perfil.

```bash cURL theme={null}
# Sesión 1: escribir el estado del navegador y guardarlo
RESPONSE=$(curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
  -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "formats": ["markdown"],
    "profile": { "name": "profile-validation", "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 '{
    "code": "await page.evaluate(() => { localStorage.setItem(\"firecrawlProfileCheck\", \"saved\"); document.cookie = \"firecrawl_profile_check=saved; path=/; max-age=3600\"; return localStorage.getItem(\"firecrawlProfileCheck\"); });"
  }'

curl -s -X DELETE "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
  -H "Authorization: Bearer $FIRECRAWL_API_KEY"

# Sesión 2: cargar el mismo perfil en modo de solo lectura y verificar el valor
RESPONSE=$(curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \
  -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "formats": ["markdown"],
    "profile": { "name": "profile-validation", "saveChanges": false }
  }')

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 '{
    "code": "await page.evaluate(() => ({ localStorage: localStorage.getItem(\"firecrawlProfileCheck\"), cookie: document.cookie.includes(\"firecrawl_profile_check=saved\") }));"
  }'

curl -s -X DELETE "https://api.firecrawl.dev/v2/scrape/$SCRAPE_ID/interact" \
  -H "Authorization: Bearer $FIRECRAWL_API_KEY"
```

La segunda respuesta de Interact debería mostrar `localStorage` como `"saved"` y `cookie` como `true`.

<Info>
  Puede que los perfiles creados a través de la API todavía no aparezcan en Dashboard > Interact > Profiles. Actualmente, el dashboard no ofrece un inventario completo de los perfiles persistentes creados mediante la API.
</Info>

<div id="when-to-use-what">
  ## Cuándo usar cada opción
</div>

| Caso de uso                                  | Recomendado                     | Por qué                                       |
| -------------------------------------------- | ------------------------------- | --------------------------------------------- |
| Búsqueda web                                 | [Search](/es/features/search)   | Endpoint de búsqueda específico               |
| Obtener contenido limpio de una URL          | [scraping](/es/features/scrape) | Una llamada a la API, sin necesidad de sesión |
| Hacer clic, escribir y navegar en una página | **Interact** (prompt)           | Solo descríbelo en inglés                     |
| Extraer datos tras interacciones             | **Interact** (prompt)           | No se necesitan selectores                    |
| Lógica de scraping compleja                  | **Interact** (code)             | Control total de Playwright                   |

<Info>
  **Interact vs Browser Sandbox**: Interact se basa en la misma infraestructura que [Browser Sandbox](/es/features/browser), pero ofrece una mejor interfaz para el patrón más común: hacer scraping de una página y luego profundizar. Browser Sandbox es mejor cuando necesitas una sesión del navegador independiente que no esté vinculada a un scraping específico.
</Info>

<div id="pricing">
  ## Precios
</div>

* **Solo código** (sin `prompt`): 2 credits por minuto de sesión
* **Con prompts de IA**: 7 credits por minuto de sesión
* **scraping**: se factura por separado (1 credit por scraping, más cualquier costo específico del formato)

<div id="api-reference">
  ## Referencia de la API
</div>

* [Ejecutar Interact](/es/api-reference/endpoint/scrape-execute): `POST /v2/scrape/{scrapeId}/interact`
* [Detener Interact](/es/api-reference/endpoint/scrape-browser-delete): `DELETE /v2/scrape/{scrapeId}/interact`

<div id="request-body-post">
  ### Cuerpo de la solicitud (POST)
</div>

| Campo      | Tipo     | Predeterminado | Descripción                                                                                                               |
| ---------- | -------- | -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `prompt`   | `string` | Ninguno        | Tarea en lenguaje natural para el agente de IA. Obligatorio si no se establece `code`. Máximo 10.000 caracteres.          |
| `code`     | `string` | Ninguno        | Código que se va a ejecutar (Node.js, Python o Bash). Obligatorio si no se establece `prompt`. Máximo 100.000 caracteres. |
| `language` | `string` | `"node"`       | `"node"`, `"python"` o `"bash"`. Solo se usa con `code`.                                                                  |
| `timeout`  | `number` | `30`           | Timeout en segundos (1–300).                                                                                              |
| `origin`   | `string` | Ninguno        | Identificador del llamante para el seguimiento de la actividad.                                                           |

<div id="response">
  ### Respuesta
</div>

| Field                    | Description                                                                                                                                                                             |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `success`                | `true` si la ejecución se completó sin errores                                                                                                                                          |
| `cdpUrl`                 | URL WebSocket sin procesar de Chrome DevTools Protocol (CDP) para la sesión del navegador. Conéctate directamente con Playwright, Puppeteer o cualquier cliente CDP                     |
| `liveViewUrl`            | URL de vista en vivo de solo lectura para la sesión del navegador                                                                                                                       |
| `interactiveLiveViewUrl` | URL de vista en vivo interactiva (quienes la vean pueden controlar el navegador)                                                                                                        |
| `output`                 | La respuesta en lenguaje natural del agente a tu prompt. Solo está presente cuando se utiliza `prompt`.                                                                                 |
| `stdout`                 | Salida estándar de la ejecución del código                                                                                                                                              |
| `result`                 | Valor de retorno sin procesar del sandbox. Para `code`: la última expresión evaluada. Para `prompt`: la captura sin procesar de la página que el agente utilizó para producir `output`. |
| `stderr`                 | Salida de error estándar                                                                                                                                                                |
| `exitCode`               | Código de salida (`0` = éxito)                                                                                                                                                          |
| `killed`                 | `true` si la ejecución se finalizó debido a timeout                                                                                                                                     |

***

¿Tienes comentarios o necesitas ayuda? Envía un correo a [help@firecrawl.com](mailto:help@firecrawl.com) o comunícate en [Discord](https://discord.gg/firecrawl).
