diff --git a/components/faq.tsx b/components/faq.tsx
new file mode 100644
index 00000000..6a9451e1
--- /dev/null
+++ b/components/faq.tsx
@@ -0,0 +1,34 @@
+// ABOUTME: FAQ accordion section rendered from the :::faq MDX directive.
+// ABOUTME: Emits FAQPage JSON-LD (built by remark-custom-directives) for search engines.
+
+import type { ReactNode } from 'react';
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from '@/components/ui/accordion';
+
+export function FAQ({ jsonLd, children }: { jsonLd?: string; children: ReactNode }) {
+ return (
+
+ {jsonLd ? (
+
+ ) : null}
+
+ {children}
+
+
+ );
+}
+
+export function FAQItem({ question, children }: { question: string; children: ReactNode }) {
+ return (
+
+ {question}
+
+ {children}
+
+
+ );
+}
diff --git a/components/mdx/index.tsx b/components/mdx/index.tsx
index bccedc76..f271daa4 100644
--- a/components/mdx/index.tsx
+++ b/components/mdx/index.tsx
@@ -9,6 +9,7 @@ import { AuthorProfile } from '@/components/author-profile';
import { Callout } from '@/components/callout';
import * as CardComponents from '@/components/card';
import { docskit } from '@/components/docskit/components';
+import { FAQ, FAQItem } from '@/components/faq';
import { IntegrationGrid } from '@/components/integration-grid';
import { OrderedList, UnorderedList } from '@/components/lists';
import { RecipeCard, RecipeGrid } from '@/components/recipe-card';
@@ -41,6 +42,8 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
Badge,
Callout,
...CardComponents,
+ FAQ,
+ FAQItem,
...FilesComponents,
...StepsComponents,
...TabsComponents,
diff --git a/content/docs/integrations/agentkit.mdx b/content/docs/integrations/agentkit.mdx
index 0b8391e2..905f054b 100644
--- a/content/docs/integrations/agentkit.mdx
+++ b/content/docs/integrations/agentkit.mdx
@@ -35,6 +35,23 @@ const page = browser.contexts()[0].pages()[0];
Full runnable starter: [Steel + AgentKit recipe →](/cookbook/agentkit)
+### FAQ
+
+:::faq
+### Do I need to change my existing AgentKit code to use Steel?
+
+No — AgentKit keeps handling orchestration, routing, and shared state. Inside a tool handler you create a Steel session, connect Playwright over CDP, and call the resulting `page` from your handlers.
+
+### How do I connect AgentKit to a Steel browser session?
+
+Call `steel.sessions.create()`, connect with `chromium.connectOverCDP()` using the session's `websocketUrl` plus your `apiKey`, and take the first page of `browser.contexts()[0]` as the page your AgentKit tool handlers drive.
+
+### Does AgentKit work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — pass them to `sessions.create()` (e.g. `useProxy`, `solveCaptcha`, `stealthConfig`). They're Steel-side session options, so your agent network and routers don't change.
+:::
+
+
### Resources
* [AgentKit documentation](https://agentkit.inngest.com/overview) – Official documentation for AgentKit
diff --git a/content/docs/integrations/agno.mdx b/content/docs/integrations/agno.mdx
index 182e9f1c..1f708cea 100644
--- a/content/docs/integrations/agno.mdx
+++ b/content/docs/integrations/agno.mdx
@@ -37,6 +37,27 @@ page = browser.contexts[0].pages[0]
Full runnable starter: [Steel + Agno recipe →](/cookbook/agno)
+### FAQ
+
+:::faq
+### Do I need to change my existing Agno code to use Steel?
+
+No — Steel is exposed to Agno as a regular `Toolkit`. You connect Playwright to a Steel session over CDP and wrap the resulting `page` in the toolkit; agents, teams, memory, and reasoning stay as they were.
+
+### How do I connect Agno to a Steel browser session?
+
+Create a session with `steel.sessions.create()`, connect with `playwright.chromium.connect_over_cdp(f"{session.websocket_url}&apiKey={STEEL_API_KEY}")`, take `browser.contexts[0].pages[0]` as your page, and wrap it in an Agno `Toolkit`.
+
+### Does Agno work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — set them at session creation (e.g. `use_proxy`, `solve_captcha`, `stealth_config` on `sessions.create()`). Agno only sees the Playwright page, so these options don't touch your agent code.
+
+### Which model providers can I use with Agno on Steel?
+
+Any Agno-supported provider — Agno is model-agnostic and natively multi-modal, so OpenAI, Anthropic, or others all work. You just need the provider key plus your Steel API key.
+:::
+
+
### Resources
* [Agno documentation](https://docs.agno.com/) – Concepts, APIs, and examples for agents, teams, memory, and reasoning
diff --git a/content/docs/integrations/ai-sdk.mdx b/content/docs/integrations/ai-sdk.mdx
index 5ad1a03c..19e9439c 100644
--- a/content/docs/integrations/ai-sdk.mdx
+++ b/content/docs/integrations/ai-sdk.mdx
@@ -46,6 +46,31 @@ Full runnable starters:
* Server-only typed agent: [Steel + Vercel AI SDK recipe →](/cookbook/vercel-ai-sdk)
* Next.js chat UI with embedded Steel Live View: [Steel + Vercel AI SDK + Next.js recipe →](/cookbook/vercel-ai-sdk-nextjs)
+### FAQ
+
+:::faq
+### Do I need to change my existing Vercel AI SDK code to use Steel?
+
+No — Steel slots in as a typed `tool()` from the `ai` package. The tool's `execute` opens a Steel session and connects Playwright over CDP; your streaming, provider setup, and the rest of the agent stay the same.
+
+### How do I connect the Vercel AI SDK to a Steel browser session?
+
+Inside a `tool()`'s `execute`, call `steel.sessions.create()` and connect with `chromium.connectOverCDP()` using the session's `websocketUrl` plus your `apiKey`. Subsequent tools (navigate, snapshot, extract) drive that browser and return typed results.
+
+### Does the Vercel AI SDK work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — pass them to `sessions.create()` (e.g. `useProxy`, `solveCaptcha`, `stealthConfig`). They're session-creation options and don't touch the AI SDK's tool or streaming layers.
+
+### Can users watch the browser while the agent runs in a chat UI?
+
+Yes — the tool returns `liveViewUrl` (`session.sessionViewerUrl`), and the Next.js recipe embeds Steel's Live View iframe alongside the chat so users watch the browser as the agent works.
+
+### Which model providers can I use?
+
+Any AI SDK-supported provider — install a provider package like `@ai-sdk/anthropic` and bring an Anthropic, OpenAI, or other provider key. Steel only needs the CDP connection, so the model choice is independent. For current model recommendations, see the [Steel leaderboard](https://leaderboard.steel.dev/).
+:::
+
+
### Resources
* [Vercel AI SDK documentation](https://ai-sdk.dev/) – Tools, agents, streaming, and providers
diff --git a/content/docs/integrations/browser-use.mdx b/content/docs/integrations/browser-use.mdx
index c4beaea1..af0d1a18 100644
--- a/content/docs/integrations/browser-use.mdx
+++ b/content/docs/integrations/browser-use.mdx
@@ -40,6 +40,31 @@ Full runnable starters:
* Auto-solve captchas with Steel + Browser Use: [Captcha auto recipe →](/cookbook/browser-use-captcha-auto)
* Hand off captchas to a human via Steel Live View: [Manual captcha recipe →](/cookbook/browser-use-captcha-manual)
+### FAQ
+
+:::faq
+### Do I need to change my existing Browser Use code to use Steel?
+
+No — pass a `BrowserSession(cdp_url=...)` into your `Agent` and Browser Use drives Steel's cloud browser instead of a local one. Your task, LLM config, and `agent.run()` loop stay the same.
+
+### How do I connect Browser Use to a Steel browser session?
+
+Create a session with `client.sessions.create()`, build the CDP URL as `f"{session.websocket_url}&apiKey={STEEL_API_KEY}"`, and pass it to `BrowserSession(cdp_url=cdp_url)` on the `Agent`.
+
+### Does Browser Use work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — these are session-creation options (e.g. `use_proxy`, `solve_captcha`, `stealth_config` on `sessions.create`), invisible to Browser Use itself. The page links dedicated recipes for auto-solving CAPTCHAs and handing them to a human.
+
+### Which LLMs work with Browser Use on Steel?
+
+Vision-capable models — the page calls out GPT-5, Claude Sonnet 4, and Gemini 3 Pro, and the example wires `ChatOpenAI(model="gpt-5")`. You also need Python 3.11+. For up-to-date model recommendations for browser agents, check the [Steel leaderboard](https://leaderboard.steel.dev/).
+
+### Can a human take over when the agent hits a CAPTCHA?
+
+Yes — Steel's Live View lets a person solve the CAPTCHA in the running session while the agent waits. The page links a manual-captcha recipe showing this hand-off pattern.
+:::
+
+
### Resources
* [Browser Use documentation](https://docs.browser-use.com/) – Comprehensive guide to the browser-use library
diff --git a/content/docs/integrations/claude-agent-sdk.mdx b/content/docs/integrations/claude-agent-sdk.mdx
index 245f2b0a..60a79ef2 100644
--- a/content/docs/integrations/claude-agent-sdk.mdx
+++ b/content/docs/integrations/claude-agent-sdk.mdx
@@ -55,6 +55,27 @@ Pass the server into `query()` via `mcpServers` and pre-approve calls with `allo
Full runnable starter: [Steel + Claude Agent SDK recipe →](/cookbook/claude-agent-sdk)
+### FAQ
+
+:::faq
+### Do I need to change my existing Claude Agent SDK code to use Steel?
+
+No — Steel is exposed as in-process MCP tools. Wrap a Steel session in a `tool()`, bundle it with `createSdkMcpServer`, and your `query()` loop and message handling stay exactly the same.
+
+### How do I connect the Claude Agent SDK to a Steel browser session?
+
+Define a `tool()` that calls `steel.sessions.create()` and connects via `chromium.connectOverCDP()` with the session's `websocketUrl` plus `apiKey`, bundle it into an in-process MCP server with `createSdkMcpServer`, then pass that server to `query()` via `mcpServers` and pre-approve calls with `allowedTools`.
+
+### Does the Claude Agent SDK work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — enable them when your tool creates the session (e.g. `useProxy`, `solveCaptcha`, `stealthConfig` on `sessions.create()`). The SDK's agent loop and MCP plumbing are unaffected.
+
+### Is the Steel integration available in both TypeScript and Python?
+
+Yes — use `@anthropic-ai/claude-agent-sdk` for TypeScript or `claude-agent-sdk` for Python, plus `steel-sdk` and `playwright`. You'll also need an Anthropic API key with access to a Claude 4 model.
+:::
+
+
### Resources
* [Claude Agent SDK documentation](https://platform.claude.com/docs/en/agent-sdk/overview) – Agent loop, custom MCP tools, hooks, subagents
diff --git a/content/docs/integrations/claude-code.mdx b/content/docs/integrations/claude-code.mdx
index 4e0b688a..f3880056 100644
--- a/content/docs/integrations/claude-code.mdx
+++ b/content/docs/integrations/claude-code.mdx
@@ -67,6 +67,31 @@ After a successful run, Claude Code can help turn the browser workflow into some
* **First runs are usually the roughest.** Dynamic web apps often need a few retries before the workflow is stable.
* **Authenticated sites work best with prepared Steel auth state.** Reusing profiles or auth context is generally more reliable than repeated interactive logins.
+### FAQ
+
+:::faq
+### Do I need to write integration code to use Steel with Claude Code?
+
+No — the integration works through the Steel CLI. Install it with `curl -LsSf https://setup.steel.dev | sh`, run `steel login`, and Claude Code can start and control Steel sessions, scrape rendered pages, and run computer-use actions from the terminal.
+
+### How does Claude Code connect to a Steel browser session?
+
+Through Steel CLI commands in its shell. For better command discovery and more reliable workflows, install the `steel-browser` skill with `npx skills add steel-dev/skills --skill steel-browser -a claude-code -g` and restart Claude Code so it discovers the skill.
+
+### Does Claude Code work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — those are properties of the Steel session itself, not the client driving it. Sessions Claude Code starts run on the same Sessions API that supports `useProxy`, `solveCaptcha`, and `stealthConfig` at creation time.
+
+### How should I handle sites that require login?
+
+Prepare reusable auth state in Steel ahead of time via the Profiles API or Reusing Auth Context, rather than asking Claude Code to log in from scratch every run. The page calls prepared auth state more reliable than repeated interactive logins.
+
+### Can I watch what Claude Code is doing in the browser?
+
+Yes — Steel sessions return a viewer URL for monitoring the browser in real time. That's especially useful when Claude Code hits a modal, a sign-in wall, or a page that misbehaves, and successful runs can then be turned into scripts or runbooks.
+:::
+
+
### Resources
* [Give Claude Code a real browser](https://steel.dev/blog/give-claude-code-a-real-browser) – Blog post on using Claude Code with Steel
diff --git a/content/docs/integrations/claude-computer-use.mdx b/content/docs/integrations/claude-computer-use.mdx
index d549c91d..92deea16 100644
--- a/content/docs/integrations/claude-computer-use.mdx
+++ b/content/docs/integrations/claude-computer-use.mdx
@@ -42,6 +42,27 @@ Full runnable starters:
* Build a Claude Computer Use loop on Steel: [Steel + Claude Computer Use recipe →](/cookbook/claude-computer-use)
* Drive a mobile-viewport Steel session with Claude: [Mobile recipe →](/cookbook/claude-computer-use-mobile)
+### FAQ
+
+:::faq
+### Do I need Playwright or CSS selectors to use Claude Computer Use with Steel?
+
+No — Claude's computer-use loop is vision-based. Claude looks at screenshots, decides actions like click, type, or scroll, and Steel executes them through its `sessions.computer` API, so there are no custom selectors to write.
+
+### How do I connect Claude Computer Use to a Steel browser session?
+
+Create a session with explicit `dimensions` (the example uses 1024x768), take screenshots via `steel.sessions.computer(session.id, { action: "take_screenshot" })`, send the `base64_image` to Claude's `computer_20251124` tool, and route Claude's returned actions back through `steel.sessions.computer({ action: ... })`.
+
+### Does Claude Computer Use work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — the page notes it pairs well with Steel's anti-bot capabilities, proxy support, and sandboxed environments. These are enabled at session creation (e.g. `useProxy`, `solveCaptcha`, `stealthConfig`) and don't change the agent loop.
+
+### How does the screenshot/action loop actually work?
+
+Steel takes a screenshot, you send it to Claude with the computer-use tool, Claude returns the next action (click, type, scroll), and Steel executes it via `sessions.computer` — then you screenshot again and repeat until the task completes.
+:::
+
+
### Resources
* [Anthropic Computer Use documentation](https://platform.claude.com/docs/en/agents-and-tools/tool-use/computer-use-tool) – Official documentation from Anthropic
diff --git a/content/docs/integrations/codex.mdx b/content/docs/integrations/codex.mdx
index 21eaab29..a770de38 100644
--- a/content/docs/integrations/codex.mdx
+++ b/content/docs/integrations/codex.mdx
@@ -59,6 +59,31 @@ That works well for jobs like recurring research, internal reporting, or lightwe
* **Command approvals depend on your Codex settings.** Codex may ask before running shell commands unless you have configured a more permissive approval mode.
* **Authenticated workflows usually need preconfigured Steel auth state.** For reusable login state, see [Profiles API](/overview/profiles-api/overview) and [Reusing Auth Context](/overview/sessions-api/reusing-auth-context).
+### FAQ
+
+:::faq
+### Do I need to write integration code to use Steel with Codex?
+
+No — the integration works through the Steel CLI. Install it with `curl -LsSf https://setup.steel.dev | sh`, run `steel login`, and Codex can start and control Steel sessions, scrape rendered pages, and run computer-use actions from the terminal.
+
+### How does Codex connect to a Steel browser session?
+
+Once Steel CLI is on your `PATH`, Codex inspects command help, runs the commands it needs, and verifies results from the terminal. For better command discovery, install the `steel-browser` skill with `npx skills add steel-dev/skills --skill steel-browser -a codex -g` and restart Codex.
+
+### Does Codex work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — those are properties of the Steel session, not of Codex. Sessions started from the CLI run on the same Sessions API that supports `useProxy`, `solveCaptcha`, and `stealthConfig` at creation time.
+
+### How do I turn a one-off Codex browser run into a recurring job?
+
+After a successful manual run, prompt Codex with "Write a bash script based on what you just did", then schedule the script with cron or another runner. The page recommends this run-once, convert, schedule pattern for recurring research, reporting, and monitoring.
+
+### What should I know before pointing Codex at authenticated sites?
+
+Authenticated workflows usually need preconfigured Steel auth state — set up reusable login state via the Profiles API or Reusing Auth Context instead of interactive logins each run. Also note Codex may ask for command approvals depending on your approval mode.
+:::
+
+
### Resources
* [Codex + Steel + Resend blog post](https://steel.dev/blog/codex-wired-steel-and-resend-into-a-daily-newsletter) – Example workflow for building a daily newsletter with Codex
diff --git a/content/docs/integrations/crewai.mdx b/content/docs/integrations/crewai.mdx
index 6dadbd81..4d1c7e5b 100644
--- a/content/docs/integrations/crewai.mdx
+++ b/content/docs/integrations/crewai.mdx
@@ -38,6 +38,27 @@ class SteelScrapeTool(BaseTool):
Full runnable starter: [Steel + CrewAI recipe →](/cookbook/crewai)
+### FAQ
+
+:::faq
+### Do I need to change my existing CrewAI code to use Steel?
+
+No — Steel plugs in as a regular CrewAI tool. Define a `BaseTool` subclass whose `_run` method calls Steel, hand it to your agents, and your crews, flows, and memory setup stay untouched.
+
+### How do I connect CrewAI to Steel?
+
+Expose Steel as a CrewAI `BaseTool`: instantiate the Steel client in `__init__` and call it from `_run`. The page's example tool scrapes a URL with `self._steel.scrape(url=url, format=["markdown"])` and returns markdown to the agent.
+
+### Does CrewAI work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — when your tool creates Steel sessions you can enable these at session creation (e.g. `use_proxy`, `solve_captcha`, `stealth_config`). They're Steel-side options, so CrewAI's orchestration doesn't change.
+
+### Do I need a full browser session, or is scraping enough for a crew?
+
+For read-only research, the page's tool uses Steel's `scrape` API to return page markdown — no session management needed. For interactive flows (forms, clicks, multi-step navigation), create a Steel session inside the tool instead.
+:::
+
+
### Resources
* [CrewAI documentation](https://docs.crewai.com/) – Official documentation for CrewAI
diff --git a/content/docs/integrations/gemini-computer-use.mdx b/content/docs/integrations/gemini-computer-use.mdx
index 03f0b403..75527ef3 100644
--- a/content/docs/integrations/gemini-computer-use.mdx
+++ b/content/docs/integrations/gemini-computer-use.mdx
@@ -38,6 +38,27 @@ const { base64_image } = await steel.sessions.computer(session.id, {
Full runnable starter: [Steel + Gemini Computer Use recipe →](/cookbook/gemini-computer-use)
+### FAQ
+
+:::faq
+### Do I need Playwright or CSS selectors to use Gemini Computer Use with Steel?
+
+No — Gemini's loop is vision-based. Gemini reads screenshots, decides actions like click, type, or scroll, and Steel executes them via its `sessions.computer` API, so no custom selectors are needed.
+
+### How do I connect Gemini Computer Use to a Steel browser session?
+
+Create a session with explicit `dimensions` (the example uses 1280x800), take a screenshot with `steel.sessions.computer(session.id, { action: "take_screenshot" })`, send the `base64_image` to Gemini 3 with its built-in computer-use tool, and route Gemini's returned actions back through `steel.sessions.computer({ action: ... })`.
+
+### Does Gemini Computer Use work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — the page notes it pairs well with Steel's anti-bot capabilities, proxy support, and sandboxed environments. These are session-creation options (e.g. `useProxy`, `solveCaptcha`, `stealthConfig`) and don't change the agent loop.
+
+### Which Gemini model do I need, and how does the loop work?
+
+A Gemini 3 model with computer use — the tool is built in. Steel screenshots the session, Gemini decides the next action, Steel executes it via `sessions.computer`, and the loop repeats until the task completes. Consult the [Steel leaderboard](https://leaderboard.steel.dev/) for the most recent model recommendations.
+:::
+
+
### Resources
* [Gemini Computer Use documentation](https://ai.google.dev/gemini-api/docs/computer-use) – Official documentation from Google
diff --git a/content/docs/integrations/langgraph.mdx b/content/docs/integrations/langgraph.mdx
index f8541b49..d571ae3d 100644
--- a/content/docs/integrations/langgraph.mdx
+++ b/content/docs/integrations/langgraph.mdx
@@ -60,6 +60,27 @@ app = graph.compile()
Full runnable starter: [Steel + LangGraph recipe →](/cookbook/langgraph)
+### FAQ
+
+:::faq
+### Do I need to change my existing LangGraph code to use Steel?
+
+No — your `StateGraph`, nodes, and edges stay the same. Steel lives inside your `@tool` functions: a tool creates a session and attaches Playwright via `connect_over_cdp`, and the agent loop drives that page.
+
+### How do I connect LangGraph to a Steel browser session?
+
+Inside a `@tool`, call `steel.sessions.create()` and connect Playwright with `chromium.connect_over_cdp(f"{session.websocket_url}&apiKey={STEEL_API_KEY}")`, then use `browser.contexts[0].pages[0]` as the shared page for your other tools.
+
+### Does LangGraph work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — enable them when the tool creates the session (e.g. `use_proxy`, `solve_captcha`, `stealth_config` on `sessions.create()`). The graph and `ToolNode` wiring are unaffected.
+
+### How does the agent loop actually work in the LangGraph example?
+
+`tools_condition` routes to the `"tools"` node when the assistant message contains tool calls and to `END` when it doesn't, and the `tools -> agent` edge closes the loop. Three nodes and four edges give you a complete tool-calling browser agent.
+:::
+
+
### Resources
* [LangGraph documentation](https://langchain-ai.github.io/langgraph/) – State graphs, prebuilts, checkpointing, and streaming
diff --git a/content/docs/integrations/magnitude.mdx b/content/docs/integrations/magnitude.mdx
index b7f3f2dc..fc93ea65 100644
--- a/content/docs/integrations/magnitude.mdx
+++ b/content/docs/integrations/magnitude.mdx
@@ -38,6 +38,27 @@ const agent = await startBrowserAgent({
Full runnable starter: [Steel + Magnitude recipe →](/cookbook/magnitude)
+### FAQ
+
+:::faq
+### Do I need to change my existing Magnitude code to use Steel?
+
+No — add `browser: { cdp: ... }` to your `startBrowserAgent` config and Magnitude's planning loop runs on the Steel cloud browser. Prompts, typed actions, and structured outputs work as before.
+
+### How do I connect Magnitude to a Steel browser session?
+
+Create a session with `client.sessions.create()`, then set `browser.cdp` in the `startBrowserAgent` config to the session's `websocketUrl` with your `apiKey` appended.
+
+### Does Magnitude work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — configure them when creating the session (e.g. `useProxy`, `solveCaptcha`, `stealthConfig` on `sessions.create()`). Magnitude just connects to the resulting CDP endpoint.
+
+### Which model does Magnitude use for planning?
+
+Anthropic is Magnitude's default planning provider — the example configures `llm: { provider: "anthropic", options: { model: "claude-sonnet-4-6" } }`. You need an Anthropic API key plus Node.js 20+.
+:::
+
+
### Resources
* [Magnitude documentation](https://docs.magnitude.run/) – Concepts, agent APIs, and examples
diff --git a/content/docs/integrations/mastra.mdx b/content/docs/integrations/mastra.mdx
index 640edab1..fb5f1926 100644
--- a/content/docs/integrations/mastra.mdx
+++ b/content/docs/integrations/mastra.mdx
@@ -60,6 +60,23 @@ Tools are passed as a record (not an array): the keys are what the model sees as
Full runnable starter: [Steel + Mastra recipe →](/cookbook/mastra)
+### FAQ
+
+:::faq
+### Do I need to change my existing Mastra code to use Steel?
+
+No — Steel lives inside a standard `createTool` definition whose `execute` opens a session and connects Playwright over CDP. Your `Agent`, the `Mastra` registry, Model Router strings, and Studio all work unchanged.
+
+### How do I connect Mastra to a Steel browser session?
+
+Define a `createTool` whose `execute` calls `steel.sessions.create()` and connects via `chromium.connectOverCDP()` with the session's `websocketUrl` plus `apiKey`, register it on an `Agent` via `tools`, and attach the agent to a top-level `Mastra` registry.
+
+### Does Mastra work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — pass the options to `sessions.create()` inside your tool (e.g. `useProxy`, `solveCaptcha`, `stealthConfig`). Mastra's tool layer is unaffected because these are Steel session settings.
+:::
+
+
### Resources
* [Mastra documentation](https://mastra.ai/docs) – Agents, tools, workflows, memory, and Studio
diff --git a/content/docs/integrations/notte.mdx b/content/docs/integrations/notte.mdx
index 41f055f5..e154e2c2 100644
--- a/content/docs/integrations/notte.mdx
+++ b/content/docs/integrations/notte.mdx
@@ -37,6 +37,27 @@ with notte.Session(cdp_url=cdp_url) as notte_session:
Full runnable starter: [Steel + Notte recipe →](/cookbook/notte)
+### FAQ
+
+:::faq
+### Do I need to change my existing Notte code to use Steel?
+
+No — pass `cdp_url` into `notte.Session` and Notte runs its agent loop against the Steel browser. Your `notte.Agent` setup and `agent.run(task=...)` calls stay the same.
+
+### How do I connect Notte to a Steel browser session?
+
+Create a session with `client.sessions.create()`, build `cdp_url = f"{session.websocket_url}&apiKey={STEEL_API_KEY}"`, and open `notte.Session(cdp_url=cdp_url)` as a context manager, passing it to `notte.Agent(session=notte_session)`.
+
+### Does Notte work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — enable them on `sessions.create()` (e.g. `use_proxy`, `solve_captcha`, `stealth_config`). Notte connects over the CDP URL and is unaware of how the session was provisioned.
+
+### Which model does Notte use by default?
+
+Gemini — the requirements call for a Gemini API key (or another supported provider), and the example sets `reasoning_model="gemini/gemini-2.5-flash"` on the agent. Python 3.11+ is required.
+:::
+
+
### Resources
* [Notte documentation](https://docs.notte.cc) – Concepts, agent APIs, and examples
diff --git a/content/docs/integrations/openai-agents-sdk.mdx b/content/docs/integrations/openai-agents-sdk.mdx
index cc86d246..b8771f0d 100644
--- a/content/docs/integrations/openai-agents-sdk.mdx
+++ b/content/docs/integrations/openai-agents-sdk.mdx
@@ -43,6 +43,31 @@ const openSession = tool({
Full runnable starter: [Steel + OpenAI Agents SDK recipe →](/cookbook/openai-agents)
+### FAQ
+
+:::faq
+### Do I need to change my existing OpenAI Agents SDK code to use Steel?
+
+No — Steel slots in as a typed `tool()`. The tool's `execute` opens a Steel session and connects Playwright over CDP; your agents, handoffs, guardrails, and tracing work as before.
+
+### How do I connect the OpenAI Agents SDK to a Steel browser session?
+
+Inside a `tool()`'s `execute`, call `steel.sessions.create()` and attach Playwright via `chromium.connectOverCDP()` using the session's `websocketUrl` with your `apiKey` appended. Subsequent tools drive that browser.
+
+### Does the OpenAI Agents SDK work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — pass the options when creating the session (e.g. `useProxy`, `solveCaptcha`, `stealthConfig` on `sessions.create()`). They're transparent to the SDK's tool-calling loop.
+
+### Can I watch what the agent is doing in the browser?
+
+Yes — the example tool returns `liveViewUrl` from `session.sessionViewerUrl`, which you can open to watch the session live alongside the agent run.
+
+### Is the Steel integration available in both TypeScript and Python?
+
+Yes — the Agents SDK integration works in TypeScript and Python (Node.js 20+ or Python 3.10+). The page's example is TypeScript; the cookbook recipe has the full runnable starter.
+:::
+
+
### Resources
* [OpenAI Agents SDK documentation](https://openai.github.io/openai-agents-js/) – Agents, tools, handoffs, tracing
diff --git a/content/docs/integrations/openai-computer-use.mdx b/content/docs/integrations/openai-computer-use.mdx
index d949cd9e..49398d97 100644
--- a/content/docs/integrations/openai-computer-use.mdx
+++ b/content/docs/integrations/openai-computer-use.mdx
@@ -38,6 +38,23 @@ const { base64_image } = await steel.sessions.computer(session.id, {
Full runnable starter: [Steel + OpenAI Computer Use recipe →](/cookbook/openai-computer-use)
+### FAQ
+
+:::faq
+### How do I connect OpenAI Computer Use to a Steel browser session?
+
+Create a session with explicit `dimensions` (the example uses 1024x768), take a screenshot with `steel.sessions.computer(session.id, { action: "take_screenshot" })`, send the `base64_image` to OpenAI's Responses API computer-use tool, and route returned actions back through `steel.sessions.computer({ action: ... })`.
+
+### Does OpenAI Computer Use work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — the page notes it pairs well with Steel's anti-bot capabilities, proxy support, and sandboxed environments. These are session-creation options (e.g. `useProxy`, `solveCaptcha`, `stealthConfig`) and don't change the agent loop.
+
+### How does the screenshot/action loop work?
+
+Steel screenshots the session, you send the image to the Responses API with the computer-use tool, the model returns the next action, and Steel executes it via `sessions.computer` — then the loop repeats until the task is done.
+:::
+
+
### Resources
* [OpenAI Computer Use documentation](https://platform.openai.com/docs/guides/tools-computer-use) – Official documentation from OpenAI
diff --git a/content/docs/integrations/openclaw.mdx b/content/docs/integrations/openclaw.mdx
index cb689def..9219b280 100644
--- a/content/docs/integrations/openclaw.mdx
+++ b/content/docs/integrations/openclaw.mdx
@@ -87,6 +87,31 @@ See:
* **Form-heavy workflows can still take time.** Dynamic fields, validation errors, and bot checks add retries and extra browser steps.
* **Authenticated sites work best with prepared Steel auth state.** Reusing profiles or auth context is generally more reliable than repeated interactive logins.
+### FAQ
+
+:::faq
+### Do I need to write integration code to use Steel with OpenClaw?
+
+No — the integration works through the Steel CLI. Install it with `curl -LsSf https://setup.steel.dev | sh`, run `steel login`, and once the CLI is on your `PATH` OpenClaw can run multi-step web tasks, scrape rendered pages, and work through forms without custom integration code.
+
+### How does OpenClaw connect to a Steel browser session?
+
+Via Steel CLI commands in its shell. Install the `steel-browser` skill for better command discovery — `npx skills add steel-dev/skills --skill steel-browser -a opencode -g` — then restart OpenClaw so it discovers the skill.
+
+### Does OpenClaw work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — those are properties of the Steel session, not the agent. Sessions OpenClaw starts run on the same Sessions API that supports `useProxy`, `solveCaptcha`, and `stealthConfig` at creation time; the page notes bot checks can still add retries on form-heavy flows.
+
+### When should I use `steel scrape` instead of a full OpenClaw browser session?
+
+When you only need page content — `steel scrape https://example.com` is often faster than spinning up an interactive session. Save full sessions for forms, multi-step workflows, and pages that need JavaScript rendering before the agent can reason about them.
+
+### How do I monitor or debug what OpenClaw is doing in the browser?
+
+Steel sessions return a viewer URL so you can watch the browser live — useful on form-heavy flows when a modal blocks progress or the agent needs a second attempt. For longer workflows, Steel keeps the full session history for after-the-fact inspection.
+:::
+
+
### Resources
* [OpenClaw + Steel blog post](https://steel.dev/blog/openclaw-steel-browser-let-your-ai-agent-fill-the-forms) – Case study using OpenClaw to work through a CFP submission flow
diff --git a/content/docs/integrations/playwright.mdx b/content/docs/integrations/playwright.mdx
index a956b61c..a196f87b 100644
--- a/content/docs/integrations/playwright.mdx
+++ b/content/docs/integrations/playwright.mdx
@@ -48,6 +48,27 @@ page = browser.contexts[0].new_page()
Full runnable starter: [Steel + Playwright recipe →](/cookbook/playwright)
+### FAQ
+
+:::faq
+### Do I need to change my existing Playwright code to use Steel?
+
+No — swap your local launch for `chromium.connectOverCDP()` (or `connect_over_cdp` in Python) pointed at the Steel session's `websocketUrl`. The rest of your script — `page.goto`, locators, `expect`, tracing — runs unchanged against the remote browser.
+
+### How do I connect Playwright to a Steel browser session?
+
+Create a session with `client.sessions.create()`, then connect with `chromium.connectOverCDP()` in Node or `playwright.chromium.connect_over_cdp()` in Python, passing the session's `websocketUrl` with your `apiKey` appended as a query parameter.
+
+### Does Playwright work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — those are options on `sessions.create()` (e.g. `useProxy`, `solveCaptcha`, `stealthConfig`), not Playwright settings. Playwright just sees a normal CDP browser; stealth, proxies, and the live viewer come from the Steel session.
+
+### Do I still need to run `playwright install` or have Chrome installed locally?
+
+No — the browser runs in Steel's cloud, so there's no `playwright install`, no headful display, and no Chrome on your machine. You only need the `playwright` package and a Steel API key.
+:::
+
+
### Resources
* [Playwright documentation](https://playwright.dev) – Official Playwright docs for TypeScript and Python
diff --git a/content/docs/integrations/puppeteer.mdx b/content/docs/integrations/puppeteer.mdx
index 36f05801..1955c1c0 100644
--- a/content/docs/integrations/puppeteer.mdx
+++ b/content/docs/integrations/puppeteer.mdx
@@ -34,6 +34,19 @@ const page = await browser.newPage();
Full runnable starter: [Steel + Puppeteer recipe →](/cookbook/puppeteer)
+### FAQ
+
+:::faq
+### Do I need to change my existing Puppeteer code to use Steel?
+
+No — replace `puppeteer.launch()` with `puppeteer.connect()` and pass Steel's CDP URL as `browserWSEndpoint`. `page.goto`, `page.evaluate`, `page.waitForSelector`, and the rest of the API work unchanged against the remote browser.
+
+### How do I connect Puppeteer to a Steel browser session?
+
+Create a session with `client.sessions.create()`, then call `puppeteer.connect()` with `browserWSEndpoint` set to the session's `websocketUrl` (with your `apiKey` appended) and open a tab with `browser.newPage()`.
+:::
+
+
### Resources
* [Puppeteer documentation](https://pptr.dev) – Official Puppeteer API reference
diff --git a/content/docs/integrations/pydantic-ai.mdx b/content/docs/integrations/pydantic-ai.mdx
index 84ea829a..1767c727 100644
--- a/content/docs/integrations/pydantic-ai.mdx
+++ b/content/docs/integrations/pydantic-ai.mdx
@@ -51,6 +51,23 @@ result = await agent.run("Open example.com and report the title.", deps=BrowserD
Full runnable starter: [Steel + Pydantic AI recipe →](/cookbook/pydantic-ai)
+### FAQ
+
+:::faq
+### Do I need to change my existing Pydantic AI code to use Steel?
+
+No — Steel enters through dependency injection. You connect Playwright to a Steel session over CDP and pass the resulting `Page` in via `deps=BrowserDeps(page=page)`; your agent, tools, and output models stay the same.
+
+### How do I connect Pydantic AI to a Steel browser session?
+
+Create a session with `steel.sessions.create()`, connect via `chromium.connect_over_cdp(f"{session.websocket_url}&apiKey={STEEL_API_KEY}")`, take `browser.contexts[0].pages[0]`, and pass it as `deps=BrowserDeps(page=page)` to `agent.run()`.
+
+### Does Pydantic AI work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — set them on `sessions.create()` (e.g. `use_proxy`, `solve_captcha`, `stealth_config`). The agent only sees a Playwright `Page` through `ctx.deps`, so session options never leak into tool code.
+:::
+
+
### Resources
* [Pydantic AI documentation](https://ai.pydantic.dev/) – Agents, tools, output validators, retries, and Logfire integration
diff --git a/content/docs/integrations/replit.mdx b/content/docs/integrations/replit.mdx
index 595adba9..027987dd 100644
--- a/content/docs/integrations/replit.mdx
+++ b/content/docs/integrations/replit.mdx
@@ -30,6 +30,23 @@ A good fit for prototyping, scheduled jobs, and sharing runnable examples.
Don't have an API key? Get a free key at [app.steel.dev/settings/api-keys](http://app.steel.dev/settings/api-keys).
+### FAQ
+
+:::faq
+### Do I need any local setup to run Steel automation on Replit?
+
+No — everything runs in Replit's cloud. Remix a Steel starter template, add your `STEEL_API_KEY` to the secrets pane, and hit Run.
+
+### Where do I put my Steel API key in Replit?
+
+Add `STEEL_API_KEY` in the secrets pane, found under "Tools" in the left-hand pane. Any plan works — you can get a free key at app.steel.dev/settings/api-keys.
+
+### What is the Replit integration good for?
+
+Prototyping, scheduled jobs, and sharing runnable examples — the templates give you working Steel scripts in Playwright, Puppeteer, or Selenium without configuring a local environment. A free Replit account is enough to remix and run them.
+:::
+
+
### Resources
* [Replit documentation](https://docs.replit.com) – Learn more about Replit's features
diff --git a/content/docs/integrations/selenium.mdx b/content/docs/integrations/selenium.mdx
index a0e20328..c1cbebb4 100644
--- a/content/docs/integrations/selenium.mdx
+++ b/content/docs/integrations/selenium.mdx
@@ -52,6 +52,27 @@ Each command is an HTTP round-trip, so prefer `WebDriverWait` with `expected_con
Full runnable starter: [Steel + Selenium recipe →](/cookbook/selenium)
+### FAQ
+
+:::faq
+### Do I need to change my existing Selenium code to use Steel?
+
+Mostly no — your test logic stays plain Selenium 4. The change is in the connection: point `webdriver.Remote` at Steel's WebDriver endpoint and inject the `steel-api-key` and `session-id` headers on every request via a small `RemoteConnection` subclass.
+
+### How do I connect Selenium to a Steel browser session?
+
+Steel runs a WebDriver endpoint at `http://connect.steelbrowser.com/selenium`. Create a session with `client.sessions.create(is_selenium=True)`, then pass a `RemoteConnection` subclass that adds `steel-api-key` and `session-id` headers as the `command_executor` for `webdriver.Remote`.
+
+### Does Selenium work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — those are set when you create the session (e.g. `use_proxy`, `solve_captcha`, `stealth_config` on `sessions.create`), so Selenium doesn't need to know about them. Your WebDriver commands run against the session however it was provisioned.
+
+### Why does my session need `is_selenium=True`?
+
+Because Selenium speaks the W3C WebDriver protocol over HTTP, not CDP. `is_selenium=True` provisions a WebDriver-compatible node — without it you get a CDP browser that Selenium cannot drive.
+:::
+
+
### Resources
* [Selenium Python documentation](https://selenium-python.readthedocs.io) – Official Python bindings reference
diff --git a/content/docs/integrations/stackblitz-bolt.new.mdx b/content/docs/integrations/stackblitz-bolt.new.mdx
index 8b145c18..eed87b05 100644
--- a/content/docs/integrations/stackblitz-bolt.new.mdx
+++ b/content/docs/integrations/stackblitz-bolt.new.mdx
@@ -41,6 +41,23 @@ All our StackBlitz templates can be opened in [Bolt.new](http://bolt.new/), an A
Look for the *Open in Bolt.new* button on our templates to get started with AI-assisted development.
+### FAQ
+
+:::faq
+### Do I need a StackBlitz account or any installation to run Steel templates?
+
+No — the templates run directly in your browser with no local setup, and no account is required to run or even edit them. You only need to sign in if you want to save your changes.
+
+### How do I set my Steel API key in StackBlitz?
+
+Two ways: export it in the terminal with `export STEEL_API_KEY=your_key_here`, or create a `.env` file containing `STEEL_API_KEY=your_key_here`. Then run `npm run` in the terminal to execute the script.
+
+### How does Bolt.new fit into the Steel integration?
+
+All Steel StackBlitz templates can be opened in Bolt.new, StackBlitz's AI web-development agent built on WebContainer technology. From there you can modify Steel scripts with natural-language prompts, build full-stack apps around Steel, and deploy with zero configuration — look for the "Open in Bolt.new" button on the templates.
+:::
+
+
### Resources
* [StackBlitz documentation](https://developer.stackblitz.com/) – Learn more about StackBlitz's features
diff --git a/content/docs/integrations/stagehand.mdx b/content/docs/integrations/stagehand.mdx
index 7f4369d9..cd149472 100644
--- a/content/docs/integrations/stagehand.mdx
+++ b/content/docs/integrations/stagehand.mdx
@@ -38,6 +38,23 @@ await stagehand.init();
Full runnable starter: [Steel + Stagehand recipe →](/cookbook/stagehand)
+### FAQ
+
+:::faq
+### Do I need to change my existing Stagehand code to use Steel?
+
+No — your `act`, `extract`, and `observe` calls stay the same. The only change is constructing `Stagehand` with `env: "LOCAL"` and Steel's CDP URL in `localBrowserLaunchOptions.cdpUrl`.
+
+### How do I connect Stagehand to a Steel browser session?
+
+Create a session with `client.sessions.create()`, then set `cdpUrl` inside `localBrowserLaunchOptions` to the session's `websocketUrl` with your `apiKey` appended, and call `stagehand.init()`.
+
+### Does Stagehand work with Steel's proxies, stealth mode, and CAPTCHA solving?
+
+Yes — set them when creating the Steel session (e.g. `useProxy`, `solveCaptcha`, `stealthConfig` on `sessions.create`). Stagehand connects over CDP and is unaware of how the session was provisioned.
+:::
+
+
### Resources
* [Stagehand documentation](https://docs.stagehand.dev/first-steps/introduction) – Official documentation for Stagehand
diff --git a/content/docs/integrations/x402.mdx b/content/docs/integrations/x402.mdx
index ee52aa56..f30b7c29 100644
--- a/content/docs/integrations/x402.mdx
+++ b/content/docs/integrations/x402.mdx
@@ -30,6 +30,23 @@ Rate: **$0.10/hour**
| Base (mainnet) | **0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913** |
| Solana (mainnet) | **EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v** |
+### FAQ
+
+:::faq
+### Do I need a Steel account or API key to use x402 sessions?
+
+No — x402 sessions are pay-per-use with cryptocurrency. You create and manage Steel sessions by paying with USDC on Base or Solana, with no API keys or accounts required.
+
+### How do I create a Steel session over x402?
+
+Send a request to `https://x402.steel.dev`; the server responds with `402 Payment Required`. Sign a payment authorization for the requested amount with your wallet, resend the request with the signed payment header, and you get your data with a `200 OK`.
+
+### How much do x402 Steel sessions cost?
+
+The rate is $0.10/hour, paid in USDC. All you need is a Base or Solana wallet holding USDC.
+:::
+
+
### Resources
* [x402 protocol](https://www.x402.org/) – Learn more about the x402 protocol
diff --git a/content/docs/overview/agent-traces/overview.mdx b/content/docs/overview/agent-traces/overview.mdx
index e25794ab..43ddc1b2 100644
--- a/content/docs/overview/agent-traces/overview.mdx
+++ b/content/docs/overview/agent-traces/overview.mdx
@@ -83,3 +83,14 @@ type: help
Reach out on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+### FAQ
+
+:::faq
+### What are Agent Traces in Steel?
+
+Agent Traces turn a recorded browser session into a readable timeline of agent activity, with one row per meaningful action showing a verb, target label, page URL, and timestamp. You'll find the Agent Traces tab next to Console Logs and Network when you open any recorded session in the dashboard.
+
+### Can I export an agent trace?
+
+Yes. You can copy the run as markdown, download JSON for programmatic analysis, or grab a ZIP containing markdown plus screenshots. The markdown export is structured so you can paste it into Claude Code, Codex, or Cursor and ask for a Steel script that reproduces the run.
+:::
diff --git a/content/docs/overview/authentication.mdx b/content/docs/overview/authentication.mdx
index b57f26d3..e5fcd96c 100644
--- a/content/docs/overview/authentication.mdx
+++ b/content/docs/overview/authentication.mdx
@@ -196,3 +196,19 @@ type: help
Ping us in the **#help** channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section, or email [team@steel.dev](mailto:team@steel.dev?subject=Authentication%20Help).
:::
+
+### FAQ
+
+:::faq
+### How do I authenticate requests to the Steel API?
+
+Every request uses an API key tied to your organization. Pass it as the `steel-api-key` HTTP header for the REST API, via the `STEEL_API_KEY` environment variable (or client option) for the SDKs, or as the `apiKey` query parameter on `wss://connect.steel.dev` for CDP browser connections.
+
+### Where do I get a Steel API key?
+
+Sign in at app.steel.dev, open Settings → API Keys, click Create API Key, and copy the value. The full key is shown only once at creation, so save it somewhere safe; if you lose it you must delete it and create a new one.
+
+### Why am I getting a 401 Unauthorized from Steel?
+
+A 401 means your key is invalid (typo, trailing whitespace, wrong env var, or recently deleted) or missing (`Missing API key` means no `steel-api-key` header was sent). A quick sanity check is `curl -i https://api.steel.dev/v1/sessions` with your key header: 200 means authenticated.
+:::
diff --git a/content/docs/overview/browser-tools/overview.mdx b/content/docs/overview/browser-tools/overview.mdx
index fb226eda..ba209ef9 100644
--- a/content/docs/overview/browser-tools/overview.mdx
+++ b/content/docs/overview/browser-tools/overview.mdx
@@ -237,3 +237,15 @@ type: help
### Need help with Browser Tools?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### Should I use Browser Tools or Sessions?
+
+Use Browser Tools (`/v1/scrape`, `/v1/screenshot`, `/v1/pdf`) for stateless one-shot actions against a URL; each call spins up a fresh browser and tears it down. Reach for Sessions when you need to persist cookies, click through multi-step flows, pin a proxy to a country, or reuse an authenticated profile or extension.
+
+### What formats can the scrape endpoint return?
+
+Pass `format` to get `html` (raw DOM after JS execution, the default), `cleaned_html`, `markdown` (best for feeding LLMs), or `readability` (Mozilla Readability's article object). Every response also includes `metadata` (SEO fields, status code) and a `links` array.
+:::
diff --git a/content/docs/overview/captchas-api/overview.mdx b/content/docs/overview/captchas-api/overview.mdx
index 34cfbc78..137c146f 100644
--- a/content/docs/overview/captchas-api/overview.mdx
+++ b/content/docs/overview/captchas-api/overview.mdx
@@ -380,3 +380,27 @@ type: help
### Need help building with the Captchas API?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### Does Steel solve CAPTCHAs automatically?
+
+Yes — set `solveCaptcha: true` when creating a session and Steel automatically detects and solves CAPTCHAs without interrupting your automation flow.
+
+### Can Steel detect CAPTCHAs without solving them?
+
+Yes — set `autoCaptchaSolving: false` in the session's `stealthConfig` to detect CAPTCHAs without auto-solving, then trigger solving manually via the solve endpoint (all detected CAPTCHAs, or a specific `taskId`, `url`, or `pageId`).
+
+### What CAPTCHA types does Steel's CAPTCHAs API handle?
+
+Tasks are mapped to four supported types: `recaptchaV2` (checkbox and image challenges), `recaptchaV3` (invisible scoring), `turnstile` (Cloudflare), and `image_to_text` (traditional distorted-character CAPTCHAs).
+
+### How do I check if a CAPTCHA is being solved in my Steel session?
+
+Call the status endpoint (`client.sessions.captchas.status('sessionId')`) — it returns each page's `isSolvingCaptcha` flag plus per-task statuses like `detected`, `solving`, `solved`, and `failed_to_solve`.
+
+### How does Steel solve image-based CAPTCHAs?
+
+Via the `solveImage` endpoint — you pass `imageXPath` (the CAPTCHA image element) and `inputXPath` (the answer field), both required, plus an optional `url` that defaults to the current page.
+:::
diff --git a/content/docs/overview/credentials-api/overview.mdx b/content/docs/overview/credentials-api/overview.mdx
index 35d0e990..6045e72f 100644
--- a/content/docs/overview/credentials-api/overview.mdx
+++ b/content/docs/overview/credentials-api/overview.mdx
@@ -268,3 +268,19 @@ type: help
### Need help building with the Credentials API?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### How does Steel keep stored credentials secure?
+
+With envelope encryption — each credential is protected by its own short-lived AES-256-GCM key, which is in turn encrypted with an organization-specific KMS key. The ciphertext is also bound to your org ID and credential origin as additional authenticated data (AAD), which blocks replay attacks across orgs.
+
+### Can AI agents see the passwords Steel injects?
+
+No — credentials are injected into login forms without being exposed to agents, programs, or humans viewing a live session, and each filled field is blurred immediately after input (`blurFields: true` by default) to prevent vision agents from reading PII.
+
+### Does Steel's Credentials API support 2FA / TOTP logins?
+
+Yes — include a `totpSecret` in the credential's `value` object and Steel generates a valid time-based one-time password on demand when a TOTP field is detected. The secret is securely stored and never exposed to the page.
+:::
diff --git a/content/docs/overview/extensions-api/overview.mdx b/content/docs/overview/extensions-api/overview.mdx
index 35bfd825..9e32dfc0 100644
--- a/content/docs/overview/extensions-api/overview.mdx
+++ b/content/docs/overview/extensions-api/overview.mdx
@@ -192,3 +192,23 @@ client.extensions.deleteAll()
### Need help building with the Extensions API?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### Can I use Chrome extensions in Steel browser sessions?
+
+Yes. Upload an extension as a `.zip` or `.crx` file, or directly from a Chrome Web Store URL, then attach it to any session. Note the Extensions system is currently in beta.
+
+### Do I have to upload an extension for every session?
+
+No. Extensions are stored globally against your organization, so you only upload them once and can then inject them into any session.
+
+### How do I add extensions to a session?
+
+Pass specific extension IDs via the `extensionIds` field when creating the session, or pass `all_ext` to inject every extension installed for your organization. Extensions are loaded and initialized when the session starts.
+
+### How do I update or remove an extension?
+
+Update by calling `client.extensions.update` with the `extensionId` and a new file or Chrome Web Store URL. Delete a single extension with `DELETE /v1/extensions/{extensionId}` or remove all of them with `DELETE /v1/extensions/`.
+:::
diff --git a/content/docs/overview/files-api/overview.mdx b/content/docs/overview/files-api/overview.mdx
index 16050dae..63cb62a2 100644
--- a/content/docs/overview/files-api/overview.mdx
+++ b/content/docs/overview/files-api/overview.mdx
@@ -474,3 +474,15 @@ type: help
### Need help building with the Files API?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### How do I upload a file into a website's file input from a Steel session?
+
+Upload the file to the session first, then either use CDP's `DOM.setFileInputFiles` with the session file path, or for simpler cases use standard automation methods like Playwright's `page.setInputFiles` with the session file path.
+
+### Can I download all files from a session at once?
+
+Yes. Use `client.sessions.files.downloadArchive(sessionId)` in the SDK, or hit the `files.zip` endpoint via raw HTTP, to download every file in the session as a single zip archive.
+:::
diff --git a/content/docs/overview/intro-to-steel.mdx b/content/docs/overview/intro-to-steel.mdx
index 149c7270..3604f02f 100644
--- a/content/docs/overview/intro-to-steel.mdx
+++ b/content/docs/overview/intro-to-steel.mdx
@@ -77,3 +77,23 @@ Under the hood, Steel’s cloud-native platform handles all the headaches of bro
- [Python SDK Reference](/steel-python-sdk)
- [Node SDK Reference](/steel-js-sdk)
+
+### FAQ
+
+:::faq
+### What is Steel and what does it do?
+
+Steel is an open-source browser API purpose-built for AI agents. It lets you control fleets of browser sessions in the cloud via API or Python/Node SDKs, handling JavaScript rendering, logins, proxies, CAPTCHAs, and scaling so you can focus on shipping product instead of babysitting browsers.
+
+### Why is it so hard for AI agents to browse the web?
+
+Modern sites rely on client-side rendering, multi-step navigation, CAPTCHAs, and auth walls, and many critical sites lack APIs entirely. Most of the web is deliberately anti-bot and human-friendly, which forces teams to build brittle custom scrapers and manage headless browser fleets themselves.
+
+### Can Steel bypass anti-bot measures and CAPTCHAs?
+
+Yes. Steel lets you bypass anti-bot measures with rotating proxies, stealth configs, and CAPTCHA solving, and it can access data behind logins using persistent cookies and automatic sign-in.
+
+### Does Steel help reduce LLM token costs?
+
+Yes. Steel can reduce token usage and costs by up to 80% with optimized page formats, extracting page data as cleaned HTML, markdown, PDFs, or screenshots instead of raw pages.
+:::
diff --git a/content/docs/overview/pricinglimits.mdx b/content/docs/overview/pricinglimits.mdx
index db913e2c..d2325055 100644
--- a/content/docs/overview/pricinglimits.mdx
+++ b/content/docs/overview/pricinglimits.mdx
@@ -86,3 +86,23 @@ Here's roughly\* what you'd get if you spent all of your base credits on a given
**_Enterprise plans offer even further cost efficiency with an annual commitment._**
[Talk to the founders](https://cal.com/hussien-hussien-fjxt3x/intro-chat-w-steel-founders)
+
+### FAQ
+
+:::faq
+### How much does Steel cost?
+
+Steel has a free Hobby plan ($0, with $10 in free credits) plus paid plans at $29 (Starter), $99 (Developer), and $499/month (Pro), with custom Enterprise pricing. Browser time starts at $0.10/hour and drops to $0.05/hour on Pro.
+
+### How long can a Steel browser session stay alive?
+
+Up to 24 hours on the Pro plan. Max session time scales by plan: 15 minutes on Hobby, 1 hour on Starter, 6 hours on Developer, 24 hours on Pro, and custom limits on Enterprise.
+
+### How many concurrent browser sessions can I run on Steel?
+
+5 concurrent sessions on the free Hobby plan, 10 on Starter, 20 on Developer, 100 on Pro, and custom limits on Enterprise.
+
+### How is Steel browser time billed?
+
+By the minute, rounded up. Hourly rates run from $0.10/hour on Hobby and Starter down to $0.08/hour on Developer and $0.05/hour on Pro, so $99 in Developer credits covers roughly 1,238 browser hours.
+:::
diff --git a/content/docs/overview/profiles-api/overview.mdx b/content/docs/overview/profiles-api/overview.mdx
index 554723d3..e5329b53 100644
--- a/content/docs/overview/profiles-api/overview.mdx
+++ b/content/docs/overview/profiles-api/overview.mdx
@@ -112,3 +112,15 @@ await client.profiles.update(firstSession.profileId, { userAgent: 'Mozilla/5.0 (
client.profiles.update(first_session.profile_id, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3')
```
+
+### FAQ
+
+:::faq
+### What does a Steel profile actually store?
+
+A profile stores a snapshot of the browser's User Data Directory, which includes everything stored in the browser: auth, cookies, extensions, credentials, and browser settings. You can keep a separate profile per use case, like a LinkedIn profile or a GitHub profile.
+
+### How do I create and reuse a profile across sessions?
+
+Create a session with `persistProfile: true`; after release, the profile reaches the `READY` state and the session returns a `profileId`. Pass that `profileId` when creating future sessions to start them with the same user data directory and context.
+:::
diff --git a/content/docs/overview/self-hosting/steel-local-vs-steel-cloud.mdx b/content/docs/overview/self-hosting/steel-local-vs-steel-cloud.mdx
index c5a6553d..dd1cf45f 100644
--- a/content/docs/overview/self-hosting/steel-local-vs-steel-cloud.mdx
+++ b/content/docs/overview/self-hosting/steel-local-vs-steel-cloud.mdx
@@ -27,3 +27,15 @@ type: help
### Need help running locally?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### Can I use proxies with self-hosted Steel?
+
+Yes — Steel Local supports bringing your own proxies. Steel Cloud additionally offers Steel-managed proxies on top of BYOP.
+
+### Can I load browser extensions in Steel Local?
+
+Yes — put the extensions you want in the `api/src/extensions/` folder and Steel Local will build and inject them into the session. On Steel Cloud, extensions are loaded via the Extensions API instead.
+:::
diff --git a/content/docs/overview/sessions-api/embed-sessions/live-sessions.mdx b/content/docs/overview/sessions-api/embed-sessions/live-sessions.mdx
index b867a402..3131c17b 100644
--- a/content/docs/overview/sessions-api/embed-sessions/live-sessions.mdx
+++ b/content/docs/overview/sessions-api/embed-sessions/live-sessions.mdx
@@ -150,4 +150,24 @@ If the embedded view appears blank or unresponsive:
All new sessions now run **headful by default**, streaming real-time video with WebRTC.
Use the same `debugUrl` to embed or view — Steel automatically determines the correct playback mode.
-Headless live streams remain available for legacy sessions but will be phased out over time.
\ No newline at end of file
+Headless live streams remain available for legacy sessions but will be phased out over time.
+
+### FAQ
+
+:::faq
+### Can I embed a live Steel browser session in my app?
+
+Yes. Every session created via the API returns a `debugUrl` that you can open directly in a browser or embed in your UI with an iframe, streaming the live browser in real time.
+
+### Can users interact with an embedded live session, or is it view-only?
+
+Both. The `interactive` parameter (default `true`) enables remote mouse and keyboard input for human-in-the-loop workflows; set `interactive=false` on the debug URL for a read-only, watch-only view.
+
+### Is the debug URL authenticated?
+
+No. Debug URLs are unauthenticated by design, so anyone with the URL can view or interact with that session. Add your own access controls if you embed live sessions in a user-facing product.
+
+### How does Steel's live view streaming work?
+
+New sessions run headful by default and stream real-time video over WebRTC at 25 fps using H.264, with OS-level capture and low latency. Legacy headless sessions use the same `debugUrl` but display via Chrome's screencasting, and Steel automatically picks the right playback mode.
+:::
diff --git a/content/docs/overview/sessions-api/human-in-the-loop.mdx b/content/docs/overview/sessions-api/human-in-the-loop.mdx
index 45492e7c..bf8ece84 100644
--- a/content/docs/overview/sessions-api/human-in-the-loop.mdx
+++ b/content/docs/overview/sessions-api/human-in-the-loop.mdx
@@ -141,3 +141,23 @@ Learn about session timeouts for managing interactive sessions:
Session Lifecycle
Learn how to start and release browser sessions programmatically.
+
+### FAQ
+
+:::faq
+### How do I let a user take control of an automated browser session?
+
+Embed the session's debug URL in an iframe with `interactive=true` (enables clicks, scrolling, and form input) and `showControls=true` (shows the navigation bar with URL entry and back/forward controls).
+
+### What can users do in an interactive session?
+
+With both parameters enabled, users can click page elements, scroll, fill out forms and inputs, enter new URLs in the navigation bar, and use browser-style forward/back navigation.
+
+### When is human-in-the-loop useful for browser agents?
+
+It is most useful when users need to take over a session that needs assistance, enter sensitive information like login credentials, solve CAPTCHAs, verify or correct automated actions, or demonstrate actions that will be automated.
+
+### Do actions taken in the embedded viewer affect the real session?
+
+Yes. Any actions a user takes in an interactive session affect the actual browser session and its state, so make it clear to users when they are in control. A minimum iframe height of 600px is recommended for comfortable interaction.
+:::
diff --git a/content/docs/overview/sessions-api/multi-region.mdx b/content/docs/overview/sessions-api/multi-region.mdx
index aa1d047b..7ea8fc9d 100644
--- a/content/docs/overview/sessions-api/multi-region.mdx
+++ b/content/docs/overview/sessions-api/multi-region.mdx
@@ -107,3 +107,11 @@ type: help
### Need help building with multi-region?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### How does Steel choose which region my session runs in?
+
+By default Steel automatically selects the data center closest to the client's request location for minimal latency. You can override this with the `region` parameter when creating a session.
+:::
diff --git a/content/docs/overview/sessions-api/overview.mdx b/content/docs/overview/sessions-api/overview.mdx
index 887bed54..aadb0739 100644
--- a/content/docs/overview/sessions-api/overview.mdx
+++ b/content/docs/overview/sessions-api/overview.mdx
@@ -38,3 +38,19 @@ type: help
### Need help building with the Sessions API?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev/) under the community ⭐ section.
:::
+
+### FAQ
+
+:::faq
+### What is a Steel session?
+
+A session is an isolated cloud browser instance your agent spins up on demand, like opening a fresh incognito window but running in Steel's cloud and controlled through code. Sessions are the atomic unit of the Sessions API.
+
+### Do Steel sessions keep state between steps?
+
+Yes. Each session maintains its own state, cookies, and storage, which is designed for AI agents that need to navigate the web, interact with sites, and keep context across multiple steps.
+
+### Can I control a Steel session with Puppeteer, Playwright, or Selenium?
+
+Yes. Steel has connection guides for Puppeteer, Playwright (Node and Python), and Selenium, plus Python and Node SDK references for managing sessions themselves.
+:::
diff --git a/content/docs/overview/sessions-api/quickstart.mdx b/content/docs/overview/sessions-api/quickstart.mdx
index d44cfac0..f4808911 100644
--- a/content/docs/overview/sessions-api/quickstart.mdx
+++ b/content/docs/overview/sessions-api/quickstart.mdx
@@ -111,3 +111,15 @@ type: help
### Need help building with the Sessions API?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### How long does a Steel session last by default?
+
+The default session timeout is 5 minutes. You can extend it with the `timeout` option on session create (e.g. `timeout: 1800000` for 30 minutes) and set `inactivityTimeout` to release after a period of inactivity.
+
+### Can I enable proxies and CAPTCHA solving on a session?
+
+Yes. Pass `useProxy: true` to route through Steel's residential proxy network and `solveCaptcha: true` to enable automatic CAPTCHA solving when creating the session. You can also set a custom user agent with `userAgent`.
+:::
diff --git a/content/docs/overview/sessions-api/reusing-auth-context.mdx b/content/docs/overview/sessions-api/reusing-auth-context.mdx
index c542308a..6b8f8a9d 100644
--- a/content/docs/overview/sessions-api/reusing-auth-context.mdx
+++ b/content/docs/overview/sessions-api/reusing-auth-context.mdx
@@ -181,3 +181,15 @@ Check out the full example
* **Available for Live Sessions:**
Context can only be captured from live sessions. So if you wish to re-use a context, make sure to grab the object _before_ releasing the session.
+
+### FAQ
+
+:::faq
+### How do I reuse a login across Steel sessions?
+
+Authenticate in an initial session, capture its browser state with the `GET /v1/sessions/{id}/context` endpoint (`client.sessions.context(id)` in the SDK), then pass that object to the `sessionContext` parameter when creating a new session. The new session starts already authenticated, with no second login.
+
+### Should I use the Profiles API or session context reuse?
+
+For an easier path, use the Profiles API: it reuses a complete browser profile (auth, context, cookies, extensions) automatically, not just context or cookies. Context reuse is the lower-level option when you only need to transfer cookies and local storage.
+:::
diff --git a/content/docs/overview/sessions-api/session-lifecycle.mdx b/content/docs/overview/sessions-api/session-lifecycle.mdx
index e7ae7724..096e5a75 100644
--- a/content/docs/overview/sessions-api/session-lifecycle.mdx
+++ b/content/docs/overview/sessions-api/session-lifecycle.mdx
@@ -136,3 +136,27 @@ type: help
### Need help building with the Sessions API?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### How long does a Steel browser session stay alive by default?
+
+5 minutes — after that the session is automatically released. You can change this by passing the `timeout` parameter (in milliseconds) when creating the session, e.g. `timeout: 600000` for 10 minutes.
+
+### What is the maximum length of a Steel session?
+
+Up to 24 hours, depending on your plan. Sessions are billed and metered by the minute.
+
+### Can I extend the timeout of a running Steel session?
+
+No — Steel currently doesn't support editing the timeout duration of a live session, so set the `timeout` you need at creation time.
+
+### How do I avoid paying for an idle Steel session?
+
+Set `inactivityTimeout` so the session releases itself if your side goes quiet — for example when your agent crashes, hangs, or simply stops sending commands. Without it (the default), a stalled client keeps the session alive and billed until the hard `timeout` cap; with it, any CDP command or remote input resets the timer, so normal automation is unaffected while you're protected from paying for a browser nobody is driving.
+
+### How do I end a Steel session before it times out?
+
+Call `client.sessions.release(session.id)` — releasing explicitly is best practice rather than waiting for the timeout. To clean up everything at once, `client.sessions.releaseAll()` releases all active sessions.
+:::
diff --git a/content/docs/overview/skills/index.mdx b/content/docs/overview/skills/index.mdx
index 59f1b4a4..f0faff53 100644
--- a/content/docs/overview/skills/index.mdx
+++ b/content/docs/overview/skills/index.mdx
@@ -78,3 +78,15 @@ steel scrape https://example.com
```
Restart your agent client after installing new skills so it can discover them.
+
+### FAQ
+
+:::faq
+### How do I install Steel Skills?
+
+Use `npx skills add steel-dev/skills --skill steel-browser` or the Steel CLI helper `steel skills install steel-browser`. In Claude Code you can also use the plugin marketplace: `/plugin marketplace add steel-dev/skills` then `/plugin install steel-browser@steel-skills`.
+
+### Which coding agents do Steel Skills support?
+
+Supported agent targets are Claude Code, Cursor, Codex, OpenCode, and Pi — pass the target with the `-a` flag, e.g. `npx skills add steel-dev/skills --skill steel-browser -a cursor -g`. Beyond these built-in targets, Steel Skills generally work with any agent that supports the agent skills format.
+:::
diff --git a/content/docs/overview/stealth/captcha-solving.mdx b/content/docs/overview/stealth/captcha-solving.mdx
index e791a53b..bb9e9752 100644
--- a/content/docs/overview/stealth/captcha-solving.mdx
+++ b/content/docs/overview/stealth/captcha-solving.mdx
@@ -266,3 +266,23 @@ type: help
### Need help building with captcha solving?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### How does Steel handle CAPTCHAs?
+
+With a two-pronged approach: browser fingerprinting and anti-detection systems prevent many CAPTCHAs from appearing in the first place, and when one does appear, automatic solving handles it transparently. Enable it by setting `solveCaptcha: true` when creating a session.
+
+### What CAPTCHA types does Steel support?
+
+Steel's auto-solver currently handles reCAPTCHA v2 and v3, hCaptcha, Cloudflare Turnstile, image-to-text, and slider CAPTCHAs. Systems like DataDome, Imperva, Amazon WAF, and FunCAPTCHA are detected and logged but not auto-solved, and custom or enterprise-specific implementations aren't supported.
+
+### Does Steel's CAPTCHA solver work 100% of the time?
+
+No — the system has high success rates but solving is not guaranteed to work 100% of the time, so you should implement proper error handling. Solving can also add latency, so account for it in your timeouts and waiting strategies.
+
+### How can I avoid triggering CAPTCHAs in the first place?
+
+Use Steel's automatic fingerprinting (which often bypasses avoidable CAPTCHAs by making sessions appear more human-like), reuse successful sessions while maintaining cookies, and add natural delays while avoiding rapid, repetitive actions.
+:::
diff --git a/content/docs/overview/stealth/proxies.mdx b/content/docs/overview/stealth/proxies.mdx
index 34d43c68..06a5619b 100644
--- a/content/docs/overview/stealth/proxies.mdx
+++ b/content/docs/overview/stealth/proxies.mdx
@@ -248,3 +248,27 @@ type: help
### Need help building with proxies?
Reach out to us on the #help channel on [Discord](https://discord.gg/steel-dev) under the ⭐ community section.
:::
+
+### FAQ
+
+:::faq
+### How much do Steel proxies cost?
+
+Steel-managed residential proxies are billed per GB of usage, while the default behavior (no proxy, Steel's datacenter IPs) is free and included in all plans. Bring Your Own Proxy carries no charge from Steel — you only pay your own proxy provider.
+
+### Do Steel sessions use a proxy by default?
+
+No — proxies are disabled by default (`useProxy: false` is the implicit setting), so traffic originates from Steel's own datacenter IPs at no proxy-bandwidth cost. Set `useProxy: true` to route through a Steel-managed residential IP (US-based by default).
+
+### Can I use my own proxies with Steel?
+
+Yes — Bring Your Own Proxy (BYOP) lets you pass a `server` URL in `http://`, `https://`, or `socks5://` format on any plan, including the free Hobby plan. Your proxy credentials are handled securely and never logged or stored by Steel beyond the duration of your session.
+
+### Can Steel proxies target a specific country or city?
+
+Yes — Steel-managed proxies support geographic targeting across 200+ countries (two-letter Alpha-2 codes), US states, and major global cities via the `geolocation` option. Steel recommends the broadest targeting that meets your needs, since narrower targeting means a smaller IP pool.
+
+### Which Steel plans include managed residential proxies?
+
+Developer, Pro, and Enterprise plans. The proxy pool offers hundreds of millions of residential IPs with automatic rotation, while the no-proxy default and BYOP are available on all plans including the free Hobby tier.
+:::
diff --git a/content/docs/overview/steel-cli.mdx b/content/docs/overview/steel-cli.mdx
index 23e00538..c1ba0703 100644
--- a/content/docs/overview/steel-cli.mdx
+++ b/content/docs/overview/steel-cli.mdx
@@ -337,3 +337,15 @@ NODE_ENV=test steel scrape https://example.com
- [Generated CLI Reference](https://github.com/steel-dev/cli/blob/main/docs/cli-reference.md)
- [Steel Browser Reference](https://github.com/steel-dev/cli/blob/main/docs/references/steel-browser.md)
- [Steel Skills](/overview/skills)
+
+### FAQ
+
+:::faq
+### How do I install the Steel CLI?
+
+Run `curl -fsS https://setup.steel.dev | sh`. This installs the native `steel` binary to `~/.steel/bin` and runs `steel init` to log you in, verify connectivity, and install coding-agent skills.
+
+### Can I scrape or screenshot a page from the terminal without writing code?
+
+Yes. The CLI ships one-shot API tools: `steel scrape ` (markdown-first output by default, `--raw` for full JSON), `steel screenshot --full-page`, and `steel pdf `.
+:::
diff --git a/lib/get-llm-text.ts b/lib/get-llm-text.ts
index 02a34e48..7a37e07a 100644
--- a/lib/get-llm-text.ts
+++ b/lib/get-llm-text.ts
@@ -1,6 +1,7 @@
import type { InferPageType } from 'fumadocs-core/source';
import matter from 'gray-matter';
import { source } from '@/lib/source';
+import { stripFaqFences } from '@/lib/strip-faq-fences';
export function shouldIncludeLLMPage(page: InferPageType) {
if (page.data.llm === false) return false;
@@ -16,7 +17,7 @@ export function shouldIncludeLLMPage(page: InferPageType) {
}
export async function getLLMText(page: InferPageType) {
- const processed = page.data.content;
+ const processed = stripFaqFences(page.data.content);
return `# ${page.data.title}
URL: ${page.url}
diff --git a/lib/remark-custom-directives.ts b/lib/remark-custom-directives.ts
index 1d9f24c1..4eb51190 100644
--- a/lib/remark-custom-directives.ts
+++ b/lib/remark-custom-directives.ts
@@ -11,6 +11,7 @@ import { visit } from 'unist-util-visit';
* - :::callout directive for creating Callout components
* - :::objectives directive for creating What you'll learn sections
* - :::prerequisites directive for creating Prerequisites sections
+ * - :::faq directive for creating FAQ accordions with FAQPage JSON-LD
*/
export const remarkCustomDirectives: Plugin<[], Root> = () => {
return (tree: Root, file) => {
@@ -30,6 +31,9 @@ export const remarkCustomDirectives: Plugin<[], Root> = () => {
case 'prerequisites':
transformPrerequisitesDirective(node, index, parent, file);
break;
+ case 'faq':
+ transformFaqDirective(node, index, parent, file);
+ break;
default:
// For unknown directives, transform to mdxJsxFlowElement with empty attributes
node.type = 'mdxJsxFlowElement';
@@ -410,6 +414,94 @@ function transformPrerequisitesDirective(
}
}
+/**
+ * Transform :::faq directive into FAQ accordion with FAQPage JSON-LD
+ * Expected format:
+ * :::faq
+ * ### Question text?
+ * Answer content (markdown allowed, multiple blocks supported).
+ * :::
+ */
+function transformFaqDirective(node: any, index: number | undefined, parent: any, file: any) {
+ const items: { question: string; answerNodes: any[] }[] = [];
+
+ for (const child of node.children ?? []) {
+ if (child.type === 'heading' && child.depth === 3) {
+ const question = extractInlineText(child);
+ if (!question) {
+ file.fail('faq directive question headings must contain text', child.position);
+ return;
+ }
+ items.push({ question, answerNodes: [] });
+ } else if (items.length === 0) {
+ file.fail(
+ 'faq directive content must start with a ### question heading',
+ child.position ?? node.position,
+ );
+ return;
+ } else {
+ items[items.length - 1].answerNodes.push(child);
+ }
+ }
+
+ if (items.length === 0) {
+ file.fail('faq directive must contain at least one ### question heading', node.position);
+ return;
+ }
+
+ // Build FAQPage structured data from plain-text questions and answers
+ const jsonLd = JSON.stringify({
+ '@context': 'https://schema.org',
+ '@type': 'FAQPage',
+ mainEntity: items.map((item) => ({
+ '@type': 'Question',
+ name: item.question,
+ acceptedAnswer: {
+ '@type': 'Answer',
+ text: item.answerNodes
+ .map((answerNode: any) => extractInlineText(answerNode))
+ .join(' ')
+ .trim(),
+ },
+ })),
+ });
+
+ node.type = 'mdxJsxFlowElement';
+ node.name = 'FAQ';
+ node.attributes = [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'jsonLd',
+ value: jsonLd,
+ },
+ ];
+ node.children = items.map((item) => ({
+ type: 'mdxJsxFlowElement',
+ name: 'FAQItem',
+ attributes: [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'question',
+ value: item.question,
+ },
+ ],
+ children: item.answerNodes,
+ }));
+}
+
+/**
+ * Extract plain text (including inline code and link text) from a node tree
+ */
+function extractInlineText(node: any): string {
+ if (node.type === 'text' || node.type === 'inlineCode') {
+ return node.value;
+ }
+ if (node.children) {
+ return node.children.map((child: any) => extractInlineText(child)).join('');
+ }
+ return '';
+}
+
/**
* Extract text content from a list item node
*/
diff --git a/lib/strip-faq-fences.ts b/lib/strip-faq-fences.ts
new file mode 100644
index 00000000..18887884
--- /dev/null
+++ b/lib/strip-faq-fences.ts
@@ -0,0 +1,37 @@
+// ABOUTME: Removes :::faq directive fences from raw MDX so LLM-served markdown
+// ABOUTME: exposes clean "### Question / answer" sections without directive syntax.
+
+export function stripFaqFences(content: string): string {
+ const lines = content.split('\n');
+ const out: string[] = [];
+ let depth = 0; // directive nesting depth inside a :::faq block
+
+ for (const line of lines) {
+ const trimmed = line.trim();
+
+ if (depth === 0) {
+ if (trimmed === ':::faq') {
+ depth = 1;
+ continue; // drop the opening fence
+ }
+ out.push(line);
+ continue;
+ }
+
+ // inside a :::faq block: keep nested directives intact, drop only faq's own fences
+ if (trimmed.startsWith(':::') && trimmed.length > 3) {
+ depth++;
+ out.push(line);
+ continue;
+ }
+ if (trimmed === ':::') {
+ depth--;
+ if (depth === 0) continue; // drop the faq closing fence
+ out.push(line);
+ continue;
+ }
+ out.push(line);
+ }
+
+ return out.join('\n');
+}
diff --git a/niko/faq/faq-preview.png b/niko/faq/faq-preview.png
new file mode 100644
index 00000000..86137508
Binary files /dev/null and b/niko/faq/faq-preview.png differ
diff --git a/package.json b/package.json
index 1ff4d303..c13026df 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,8 @@
"generate-llms": "bun run ./scripts/generate-llms-txt.ts",
"sync-cookbook": "bun run ./scripts/sync-cookbook.ts",
"generate-changelog-draft": "bun run ./scripts/generate-changelog-draft.ts",
- "generate": "bun run generate-openapi && bun run generate-llms"
+ "generate": "bun run generate-openapi && bun run generate-llms",
+ "test": "bun test tests/"
},
"overrides": {
"@shikijs/core": "3.3.0",
diff --git a/tests/remark-faq.test.ts b/tests/remark-faq.test.ts
new file mode 100644
index 00000000..9a089a6b
--- /dev/null
+++ b/tests/remark-faq.test.ts
@@ -0,0 +1,87 @@
+// ABOUTME: Tests for the :::faq directive transform in lib/remark-custom-directives.ts.
+// ABOUTME: Verifies FAQ/FAQItem JSX output, FAQPage JSON-LD attribute, and failure on malformed input.
+
+import { describe, expect, test } from 'bun:test';
+import remarkDirective from 'remark-directive';
+import remarkParse from 'remark-parse';
+import { unified } from 'unified';
+import { remarkCustomDirectives } from '../lib/remark-custom-directives';
+
+async function transform(md: string) {
+ const processor = unified().use(remarkParse).use(remarkDirective).use(remarkCustomDirectives);
+ const tree = processor.parse(md);
+ return processor.run(tree);
+}
+
+const SAMPLE = `
+:::faq
+### How long can a session stay alive?
+
+Up to 24 hours on the Pro plan — see [pricing](/overview/pricinglimits).
+
+### Does Steel solve CAPTCHAs automatically?
+
+Yes — set \`solveCaptcha: true\` when creating a session.
+
+It runs transparently below the CDP layer.
+:::
+`;
+
+describe(':::faq directive', () => {
+ test('transforms into FAQ element with FAQItem children', async () => {
+ const tree: any = await transform(SAMPLE);
+ const faq = tree.children.find((n: any) => n.name === 'FAQ');
+ expect(faq).toBeDefined();
+ expect(faq.type).toBe('mdxJsxFlowElement');
+
+ const items = faq.children.filter((n: any) => n.name === 'FAQItem');
+ expect(items).toHaveLength(2);
+
+ const q1 = items[0].attributes.find((a: any) => a.name === 'question');
+ expect(q1.value).toBe('How long can a session stay alive?');
+ // question headings with inline code keep the code text
+ const q2 = items[1].attributes.find((a: any) => a.name === 'question');
+ expect(q2.value).toBe('Does Steel solve CAPTCHAs automatically?');
+
+ // answers stay as mdast children so links/inline code render as MDX
+ expect(items[0].children).toHaveLength(1);
+ expect(items[0].children[0].type).toBe('paragraph');
+ expect(items[1].children).toHaveLength(2);
+ });
+
+ test('emits valid FAQPage JSON-LD with plain-text answers', async () => {
+ const tree: any = await transform(SAMPLE);
+ const faq = tree.children.find((n: any) => n.name === 'FAQ');
+ const attr = faq.attributes.find((a: any) => a.name === 'jsonLd');
+ expect(attr).toBeDefined();
+
+ const data = JSON.parse(attr.value);
+ expect(data['@context']).toBe('https://schema.org');
+ expect(data['@type']).toBe('FAQPage');
+ expect(data.mainEntity).toHaveLength(2);
+ expect(data.mainEntity[0]['@type']).toBe('Question');
+ expect(data.mainEntity[0].name).toBe('How long can a session stay alive?');
+ // link text and inline code flattened to plain text
+ expect(data.mainEntity[0].acceptedAnswer.text).toBe(
+ 'Up to 24 hours on the Pro plan — see pricing.',
+ );
+ expect(data.mainEntity[1].acceptedAnswer.text).toBe(
+ 'Yes — set solveCaptcha: true when creating a session. It runs transparently below the CDP layer.',
+ );
+ });
+
+ test('keeps question headings with inline code intact', async () => {
+ const tree: any = await transform(
+ ':::faq\n### Why reuse `browser.contexts()[0]`?\n\nBecause Steel pre-opens a context.\n:::\n',
+ );
+ const faq = tree.children.find((n: any) => n.name === 'FAQ');
+ const q = faq.children[0].attributes.find((a: any) => a.name === 'question');
+ expect(q.value).toBe('Why reuse browser.contexts()[0]?');
+ });
+
+ test('fails on a faq directive without question headings', async () => {
+ await expect(transform(':::faq\nJust a paragraph, no headings.\n:::\n')).rejects.toThrow(
+ /faq directive/,
+ );
+ });
+});
diff --git a/tests/strip-faq-fences.test.ts b/tests/strip-faq-fences.test.ts
new file mode 100644
index 00000000..bf896ee2
--- /dev/null
+++ b/tests/strip-faq-fences.test.ts
@@ -0,0 +1,29 @@
+// ABOUTME: Tests for stripFaqFences, which removes :::faq fences from raw MDX
+// ABOUTME: before it is served to LLM crawlers via getLLMText.
+
+import { describe, expect, test } from 'bun:test';
+import { stripFaqFences } from '../lib/strip-faq-fences';
+
+describe('stripFaqFences', () => {
+ test('removes faq fences but keeps questions and answers', () => {
+ const input = '## FAQ\n\n:::faq\n### A question?\n\nAn answer.\n:::\n\nAfter.';
+ expect(stripFaqFences(input)).toBe('## FAQ\n\n### A question?\n\nAn answer.\n\nAfter.');
+ });
+
+ test('leaves other directives untouched', () => {
+ const input = ':::callout\ntype: tip\nBe careful.\n:::\n';
+ expect(stripFaqFences(input)).toBe(input);
+ });
+
+ test('keeps a directive nested inside a faq block intact', () => {
+ const input = ':::faq\n### Q?\n\n:::callout\ntype: info\nNote.\n:::\n\nMore answer.\n:::\n';
+ expect(stripFaqFences(input)).toBe(
+ '### Q?\n\n:::callout\ntype: info\nNote.\n:::\n\nMore answer.\n',
+ );
+ });
+
+ test('passes through content with no faq blocks unchanged', () => {
+ const input = '# Title\n\nJust prose with ::: in a sentence? No.';
+ expect(stripFaqFences(input)).toBe(input);
+ });
+});