Steel

Scrape, screenshot, and PDF — three fixed browser-automation actions behind a closed field policy

View as Markdown

Fronts three single-shot actions on Steel’s Browser Tools REST API (https://api.steel.dev) — each one page load, one managed browser, one response:

POST /steel/scrape → https://api.steel.dev/v1/scrape
POST /steel/screenshot → https://api.steel.dev/v1/screenshot
POST /steel/pdf → https://api.steel.dev/v1/pdf

Steel is a full browser-automation platform, not just these three actions — Sessions, a CDP relay for driving a live browser, and account-level Files, Credentials, Profiles, and Extensions all sit behind the same account key every NativePort tenant shares. Only the three Browser Tools actions above are stateless and single-call, so only those are mounted.

Sessions, the CDP relay, profiles, credentials, files, extensions, and CAPTCHA/proxy configuration are not exposed. Any other Steel path draws the gateway’s own 404. Those surfaces are either stateful, cross-tenant resources on the one shared Steel account, or cost-expanding channels this gateway has no ownership-tracking layer for.

Like Browserless, /steel runs a closed field policy ahead of the upstream call — a fixed allow-set of JSON body fields, everything else rejected before Steel is ever touched. Unlike Browserless, Steel’s Browser Tools accept no query parameters at all: auth and every control travel in the steel-api-key header or the JSON body, so the query-string policy here is simpler — any query parameter present is rejected outright.

Credential handling

  • steel-api-key is injected server-side on every call; Authorization is dropped before the request goes upstream.
  • There’s no forced timeout field to strip — Steel’s only timing knob, delay, is a body field the policy already bounds directly (see below).

Field policy

Every JSON body field is explicitly admitted or rejected — there’s no passthrough for anything the list below doesn’t name.

Admitted on all three routes: url (required, http:// or https://), delay (integer milliseconds, 010000, to wait after navigation before capturing).

Extra fields per route:

  • /scrape also admits format (an array — html, cleaned_html, markdown, readability), plus screenshot and pdf (booleans) to bundle a hosted screenshot or PDF alongside the scraped content in the same call.
  • /screenshot also admits fullPage (boolean) — capture the entire scrollable page instead of just the viewport.
  • /pdf admits nothing beyond the common set.

Denied outright — a 400 before the request reaches Steel: useProxy (Steel’s residential-proxy toggle), and any other body field or query parameter the policy doesn’t name above. Default-deny, not default-allow.

A rejected call answers 400 with {"error": "Rejected: <reason>."} and is never forwarded — Steel never sees it, and it’s never billed.

Scrape

POST /steel/scrape — loads url and returns page content. The response always includes content, metadata, and links; format controls which content variants come back (html if omitted).

$curl -X POST "$GATEWAY/steel/scrape" \
> -H "Authorization: Bearer $TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"url": "https://example.com", "format": ["markdown", "readability"]}'

Response: application/json — Steel’s own scrape result, relayed as-is.

1{
2 "content": {
3 "markdown": "# Example Domain\n\n..."
4 },
5 "metadata": {
6 "title": "Example Domain",
7 "statusCode": 200
8 },
9 "links": []
10}

Add screenshot: true or pdf: true to bundle a hosted file alongside the scraped content:

$curl -X POST "$GATEWAY/steel/scrape" \
> -H "Authorization: Bearer $TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"url": "https://example.com", "format": ["markdown"], "screenshot": true}'

Screenshot

POST /steel/screenshot — loads url and returns a hosted PNG. Use fullPage: true to capture the entire scrollable page instead of just the viewport.

$curl -X POST "$GATEWAY/steel/screenshot" \
> -H "Authorization: Bearer $TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"url": "https://example.com", "fullPage": true}'

Response: application/json, a hosted, public, durable URL:

1{ "url": "https://files.steel.dev/v1/static/example.png" }

PDF

POST /steel/pdf — loads url and returns a hosted PDF. Same shape as screenshot: a JSON body with one hosted URL.

$curl -X POST "$GATEWAY/steel/pdf" \
> -H "Authorization: Bearer $TOKEN" \
> -H "Content-Type: application/json" \
> -d '{"url": "https://example.com"}'

Response: application/json:

1{ "url": "https://files.steel.dev/v1/static/example.pdf" }

Billing

Billed a flat 5,000 microcredits ($0.005) per accepted, forwarded call, charged synchronously before the request reaches Steel — Steel bills each Browser Tools call flat regardless of outcome, so the charge lands the moment the gateway commits to forwarding and applies the same whether the call succeeds, Steel answers a non-2xx, or the upstream request fails outright. A call the field policy rejects (see above) is never charged.

There’s no idempotency key on this route: a retry after a failed or timed-out call opens its own new Steel Browser Tools call and is billed as its own, independent attempt.

Field-level request and response detail for the three admitted actions lives in Steel’s Browser Tools docs; the gateway modifies neither beyond the field policy described above.