Skip to content

Providers

justcrawl.io routes scraping requests across multiple providers. You bring your own API keys.

| Provider | Auth | Auto-route by URL | Async webhook | Vendor docs | |----------|------|-------------------|---------------|-------------| | Bright Data | API Key + Zone | No (single zone per account) | No | docs.brightdata.com | | Oxylabs | Username + Password | Yes (33 verified source templates) | No (TODO 2) | developers.oxylabs.io | | Nimble Way | Bearer token | No (driver-based stealth) | No | docs.nimbleway.com | | Zyte | API Key | No (auto-extract is TODO 3) | No | docs.zyte.com | | Decodo | API Key (formerly Smartproxy) | Yes (17 verified target templates) | No (TODO 2) | help.decodo.com |

  1. Go to Settings > Provider Accounts
  2. Click Add Provider
  3. Enter your credentials
  4. Click Test Connection to verify
  5. Save

Credentials are encrypted at rest with AES-256-GCM and never returned in API responses.

In Settings > Vendor Pricing, enter what you pay per 1,000 requests for each provider. This enables:

  • Cost strategy: ranks providers by cheapest-first
  • Cost estimates: shows expected cost per workflow on the dashboard
  • Premium domains: optionally set higher rates for specific domains (e.g., social media sites)

Smart optimization (benchmarking, strategy selection, auto-tuning) requires at least 2 connected providers. With a single provider, justcrawl acts as a simple proxy with scheduling and URL management.


Every service node in a workflow accepts a shared set of settings (Country, JS rendering, Wait selector, Parse, Screenshot, Markdown, Custom Headers, Custom Cookies, Sessions, Device, Advanced JSON) plus vendor-specific extras. See Service node settings for the full reference.

The capability matrix below shows which providers support which features. Cells marked ✓ are supported and exposed in the workflow editor.

| Feature | Bright Data | Oxylabs | Nimble Way | Zyte | Decodo | |---------|:-----------:|:-------:|:----------:|:----:|:------:| | JS rendering | ✓ (always) | ✓ | ✓ | ✓ | ✓ | | Geo-targeting | ✓ | ✓ | ✓ | ✓ | ✓ | | Wait for selector | ✓ | ✓ | ✓ | ✓ | ✓ | | Parse (vendor-side structured) | — | ✓ | — | TODO 3 | ✓ | | Screenshot (PNG) | ✓ | ✓ | ✓ | ✓ | ✓ | | Markdown output | ✓ | — | ✓ | — | ✓ | | Custom headers | ✓ | ✓ | ✓ | ✓ | ✓ | | Custom cookies | — | ✓ | ✓ | ✓ | ✓ | | Sessions (sticky IP) | — | ✓ | — | ✓ | ✓ | | Device emulation | — | ✓ | ✓ | ✓ | ✓ | | Auto-route by URL | — | ✓ | — | — | ✓ | | Advanced (JSON) passthrough | ✓ | ✓ | ✓ | ✓ | ✓ |


Bright Data Web Unlocker is a synchronous proxy service that automatically renders JavaScript and handles anti-bot detection. The adapter targets the Web Unlocker product specifically. Async datasets (Web Scraper API) and SERP API are out of scope (TODO 1).

  • API Key — sent as Authorization: Bearer <key>.
  • Zone — the Web Unlocker zone name from your Bright Data dashboard (e.g., web_unlocker1). Must be Web Unlocker type, not Web Scraper, SERP, or Browser. Auto-detected from your account at connection time; pick from the dropdown.

| Feature | Notes | |---------|-------| | JS rendering | Always on. Web Unlocker can't be disabled. | | Geo-targeting | Lowercase ISO-2 (us, gb, de). Passed as-is. | | Wait for selector | Encoded in x-unblock-expect header. Requires "Manual expect elements" enabled on zone. | | Screenshot | Set screenshot: truedata_format: "screenshot". Body is base64 PNG, contentType: image/png. | | Markdown | Set markdown: truedata_format: "markdown". Body is markdown text. Screenshot wins if both set. | | Custom headers | Merged into request body headers field (preserves x-unblock-expect). Requires "Custom Headers & Cookies" enabled on zone. | | Sessions / cookies / device emulation | Not exposed by Web Unlocker. |

  • format: "json" (default): {status_code, body, headers} envelope. body contains the rendered HTML or base64 PNG / markdown if a data_format is set.
  • format: "raw": response is the body directly, no envelope.

Zone-type mismatch. If your zone is not Web Unlocker, the response shape is different and body is missing. The adapter detects this and returns errorType: 'validation' with the message Bright Data response missing 'body' field — likely zone-type mismatch (zone must be Web Unlocker, not Web Scraper / SERP / Browser).

Premium domains. Walmart, Target, Instacart, and similar require a separate premium zone in your Bright Data account. Standard zones fail.

Feature gates. customHeaders and waitForSelector only work if the corresponding feature is enabled on the zone in the Bright Data UI. Without the feature, the field is silently ignored.

When you save Bright Data credentials, ScrapeRoute calls GET /zone/get_active_zones and stores the zones (with their type) in org_provider_resources. A periodic job (sync_provider_resources, every 15 min) keeps that table in sync with the dashboard. At scrape time, the resolver picks a zone in this order:

  1. Per-node zone override (workflow editor → service node config). Non-empty value wins over everything below.
  2. Host detector — known SERP hosts route through your SERP-typed zone: google.*, bing.com, duckduckgo.com, yandex.*, and yahoo.com/search. If your account doesn't have a SERP zone, the resolver falls through.
  3. credentials.zone — your account's default Web Unlocker zone.

The workflow editor's zone dropdown includes a Use account default option (empty value) that lets a node opt out of an explicit pick and rely on the detector + credentials default.

  • Web Scraper API async (datasets/v3/trigger with snapshot polling) — TODO 1.

Oxylabs Realtime Web Scraper API. The adapter exposes 33 verified source templates (amazon_product, google_search, walmart, ebay, etsy, kroger, target, bestbuy, …) — every entry verified against developers.oxylabs.io — plus a universal fallback for any URL.

Leave the Source field empty and the adapter auto-detects the right Oxylabs source and parameter shape from your URL:

| URL | Routes to | With params | |-----|-----------|-------------| | amazon.com/dp/B08Y72CH1F | amazon_product | {query: "B08Y72CH1F", domain: "com"} | | amazon.co.uk/dp/B0XXXX | amazon_product | {query: "B0XXXX", domain: "co.uk"} | | amazon.com/s?k=laptop | amazon_search | {query: "laptop", domain: "com"} | | walmart.com/ip/widget/123456 | walmart_product | {product_id: "123456", domain: "com"} | | walmart.com/search?q=tv | walmart_search | {query: "tv"} | | google.com/search?q=test | google_search | {query: "test", domain: "com"} | | ebay.com/itm/... | ebay | {url} | | any other host | universal | {url} |

International TLDs are preserved (amazon.com.brdomain: "com.br"). Detector source: libraries/vendors/providers/src/oxylabs-source-detector.ts.

To override, set Source explicitly in the node config — that wins over auto-detection. See the legacy-row guard below if you do.

  • Username + Password — HTTP Basic auth.

| Feature | Notes | |---------|-------| | JS rendering | renderJs: truerender: "html". | | Geo-targeting | ISO-2 lowercase converted to full country name (us"United States"). | | Wait for selector | browser_instructions[0] with CSS wait. Requires JS rendering. Default timeout 10s. | | Parse | parse: true → Oxylabs returns structured JSON, contentType: application/json. | | Screenshot | screenshot: truerender: "png". Base64 PNG. Wins over renderJs. | | Custom headers | Forwarded via context: [{key: "headers", value: {...}}]. | | Custom cookies | Flat Record<string,string>context: [{key: "cookies", value: {...}}]. | | Sessions | sessionIdcontext: [{key: "session_id", value: ...}]. | | Device | desktop / mobile / tabletuser_agent_type (tablet maps to mobile). | | HTTP POST | httpMethod: "POST" + httpBodycontext: [{key: "http_method", value: "post"}, {key: "content", value: "<base64>"}]. |

Oxylabs's per-source identifier-field contract is heterogeneous, not uniform. Sending the wrong field name returns HTTP 400 with a vendor message like [product_id]: This field is missing. [query]: This field was not expected.

| Identifier field | Sources | Param shape | |------------------|---------|-------------| | url | universal, amazon, walmart, ebay, google, … | {url} | | query (+ optional domain) | amazon_product, amazon_search, walmart_search, google_search | {query, domain?} | | product_id (+ optional domain) | walmart_product | {product_id, domain?} |

If you set Source explicitly without supplying the matching field in sourceParams, the adapter returns:

errorType: 'validation'
error: "Oxylabs source 'walmart_product' requires 'product_id' in sourceParams. See https://developers.oxylabs.io/scraping-solutions/web-scraper-api/targets/walmart/product. Or clear the source field to use auto-detection from the URL."

Workaround: clear Source to use auto-detection, or populate sourceParams with the field that matches your chosen source ({query, domain} for search-style, {product_id, domain} for product-style) in Advanced (JSON).

Render timeout cap. The adapter's maxTimeoutMs is 30s; Oxylabs's upstream cap is 180s. Use Advanced (JSON) to extend (e.g., {"timeout": 120000}) up to the upstream cap.

Parsed = JSON, not HTML. When parse: true, the body is JSON.stringify-ed parsed data with contentType: application/json. Downstream HTML validators skip this content type automatically.

Oxylabs's per-source contract is heterogeneous: URL-based sources take url, search-style sources take query (+ optional domain), product-style sources take product_id (+ optional domain). Sending the wrong field name fails with HTTP 400 and a vendor message like [product_id]: This field is missing. [query]: This field was not expected. The detector picks the right field for you when you leave Source on Auto-detect from URL; the table below is the contract for explicit-source workflows.

Identifier-field legend:

  • url — pass the full URL in sourceParams.url (or leave the URL field unchanged for auto).
  • query — pass a search term in sourceParams.query.
  • product_id — pass the vendor's product id (e.g., Walmart item id, Amazon ASIN) in sourceParams.product_id.

Generated from SOURCE_REQUIRED_PARAM in libraries/vendors/providers/src/oxylabs-source-detector.ts. Run pnpm exec tsx scripts/generate-vendor-doc-tables.ts to refresh.

| Source | Group | Identifier field | Vendor docs | |---|---|---|---| | universal | General | url | link | | amazon | Amazon | url | link | | amazon_product | Amazon | query | link | | amazon_search | Amazon | query | link | | amazon_pricing | Amazon | query | link | | amazon_bestsellers | Amazon | query | link | | amazon_sellers | Amazon | query | link | | google | Google | url | link | | google_search | Google | query | link | | google_ads | Google | query | link | | google_travel_hotels | Google | query | link | | google_lens | Google | query | link | | google_shopping_search | Google | query | link | | google_shopping_product | Google | query | link | | google_trends_explore | Google | query | link | | bing | Bing | url | link | | bing_search | Bing | query | link | | walmart | Walmart | url | link | | walmart_product | Walmart | product_id | link | | walmart_search | Walmart | query | link | | ebay | eBay | url | link | | ebay_search | eBay | query | link | | etsy | E-commerce | url | link | | kroger | E-commerce | url | link | | target | E-commerce | url | link | | bestbuy | E-commerce | url | link | | lowes | E-commerce | url | link | | costco | E-commerce | url | link | | aliexpress | E-commerce | url | link | | alibaba | E-commerce | url | link | | rakuten | E-commerce | url | link | | flipkart | E-commerce | url | link | | mercadolibre | E-commerce | url | link |

  • Push-Pull async webhook — TODO 2. Realtime endpoint only today.
  • parsing_instructions UI — available via Advanced (JSON) only.

Nimble Way Web API. URL-uniform — there is no auto-routing by URL. Site-aware behavior comes from explicit driver selection (Nimble's stealth profiles).

  • API Token — sent as Authorization: Bearer <token>.

Nimble doesn't have site-specific templates. Instead, drivers tune scraping behavior:

| Driver | Use case | |--------|----------| | vx6 | Fast, no JavaScript — plain HTML only | | vx8 | Headless + JS rendering (default when renderJs: true or waitForSelector set) | | vx8-pro | Enhanced headless variant | | vx10 | Full-page rendering variant | | vx10-pro | Stealth headful — auto-selected from device: 'mobile' or device: 'tablet' | | Auto (default) | Inferred per request: device hint → JS flag → vx6 fallback |

When Driver is empty, the adapter infers:

  • device: 'mobile' | 'tablet'vx10-pro (with render enabled)
  • renderJs: true or waitForSelectorvx8
  • otherwise → backend default

| Feature | Notes | |---------|-------| | JS rendering | render: true + driver. Auto-enabled with renderJs, waitForSelector, or device: 'mobile'/'tablet'. | | Geo-targeting | Uppercase ISO-2 (US, GB, FR). Sub-country via Advanced (JSON). | | Wait for selector | browser_actions array with timeout (default 10s). Forces vx8. | | Screenshot | formats: ['html', 'screenshot']. Base64 PNG, contentType: image/png. | | Markdown | formats: ['html', 'markdown']. contentType: text/markdown. | | Custom headers | Pass-through object → body.headers. | | Custom cookies | Flat map → array of {key, value, domain}. Each cookie's domain defaults to the request URL hostname if not specified. | | Device | desktop / mobile / tablet → driver mapping above. |

device: 'desktop' alone is a no-op. Selecting Desktop without also enabling renderJs or waitForSelector does not trigger rendering — Nimble requires render: true to be paired with a driver. Combine with Render JS or a wait selector. Fixed in v0.3.4.

Auto driver fix in v0.3.4. Empty driver string previously short-circuited inference. Now device: 'mobile' still resolves to vx10-pro and renderJs: true still falls back to vx8 when Driver = Auto.

Cookies must include domain. The adapter auto-fills it from the request URL hostname; only override via Advanced (JSON) if you need a different domain (e.g., .example.com).

  • Endpoint migration — adapter currently uses sdk.nimbleway.com/v1/extract. The canonical endpoint per docs is api.webit.live/api/v1/realtime/web. Migration is on the roadmap; both currently route the same.
  • First-class sessions — no session ID; identity is per-request.
  • parsing schema UI — available via Advanced (JSON) with a CSS-selector schema.

Zyte API at /v1/extract. Two execution modes — browser and HTTP — with a strict mutual-exclusion guard at the adapter boundary.

  • API Key — HTTP Basic with key as username, empty password.

Browser mode — full headless Chrome. Triggered when you set renderJs, screenshot, or waitForSelector.

  • Sends browserHtml: true to Zyte; supports actions array (waitForSelector with timeout).
  • For headers: only Referer is honored on browser mode (other custom headers are silently dropped server-side).

HTTP mode — pure fetch. Default when no browser flag is set.

  • Sends httpResponseBody: true to Zyte. Adapter base64-decodes the body to UTF-8.
  • Supports POST body and full custom-headers passthrough.
  • Required for httpMethod: "POST" and device emulation.

Mutual exclusion. Combining browser flags (renderJs, screenshot, waitForSelector) with HTTP-mode options (httpMethod: "POST" or httpBody) returns errorType: 'validation' before reaching Zyte's API. Pick one mode.

| Feature | Notes | |---------|-------| | JS rendering | Browser mode (browserHtml: true). | | Geo-targeting | Uppercase ISO-2 → geolocation. 20 countries free; 50+ extended tier costs extra. | | Wait for selector | actions[0] with timeout — adapter converts ms → seconds. | | Screenshot | screenshot: true → base64 PNG. Adds extra cost. Browser mode only. | | Custom headers | HTTP mode: customHttpRequestHeaders (full passthrough). Browser mode: requestHeaders (only Referer honored). | | Custom cookies | requestCookies array. Each cookie's domain defaults to the request URL hostname per eng-review A2. | | Sessions | session.id (RFC 4122 v4 UUID). Free-form strings are deterministically hashed to a stable v4 UUID. | | Device | HTTP mode only. tablet maps to mobile. Silently omitted in browser mode. | | HTTP POST | HTTP mode only. UTF-8 body via httpRequestText. | | ipType | Vendor-specific node field: datacenter (default, free) or residential (KYC + per-request surcharge). |

Selector timeout unit. justcrawl uses milliseconds; Zyte uses seconds. The adapter converts (Math.round(ms / 1000)). Default 10000 ms → timeout: 10 seconds.

Screenshot is independent of renderJs. Setting screenshot: true triggers browser mode on its own without a redundant browserHtml: true. Set both if you want HTML and a screenshot in the same request.

Residential IPs require KYC. Zyte requires identity verification before allowing residential routing. Per-request surcharge applies. Defaults to datacenter.

Sessions UUID coercion. Zyte requires session.id to be a v4 UUID. The adapter SHA-1-hashes any free-form string (e.g., "user-123-cart") to a deterministic v4 UUID — same input always produces the same Zyte session.

  • Auto-extraction routing (product: true, article: true, jobPosting: true) — TODO 3. The flags can be set via Advanced (JSON), but the response routing for structured JSON is not wired into the validation pipeline yet. Use only if you control the downstream consumer.

Decodo Web Scraping API real-time mode (formerly Smartproxy). 17 verified target templates (every entry checked against help.decodo.com's llms.txt index) with auto-routing similar to Oxylabs. URLs that don't match a verified target route through universal.

Leave Target empty for automatic routing. Same shape as Oxylabs:

| URL | Routes to | With params | |-----|-----------|-------------| | amazon.com/dp/B08Y72CH1F | amazon_product | {query: "B08Y72CH1F", domain: "com"} | | amazon.com/s?k=laptop | amazon_search | {query: "laptop", domain: "com"} | | amazon other paths | amazon | {url} | | google.com/search?q=test | google_search | {query: "test", domain: "com"} | | google other paths | universal | {url} | | walmart.com/ip/widget/12345 | walmart_product | {product_id: "12345"} | | walmart.com/search?q=tv | walmart_search | {query: "tv"} | | walmart other | walmart | {url} | | ebay.* | universal | {url} (Decodo ships no per-target eBay doc page; URL passthrough) | | any other host | universal | {url} |

Detector source: libraries/vendors/providers/src/decodo-target-detector.ts. Explicit Target wins over auto-detection.

  • API Key (token) — sent as HTTP Basic username with empty password.

| Feature | Notes | |---------|-------| | JS rendering | renderJs: trueheadless: "html". | | Geo-targeting | ISO-2 → Title Case country name (e.g., us"United States"). | | Wait for selector | browser_actions[0] with default 10s timeout. | | Parse | parse: true → vendor-side parsed JSON (e.g., product details), contentType: application/json. | | Screenshot | screenshot: trueheadless: "png". Base64 PNG. Wins over markdown / renderJs. | | Markdown | markdown: true → markdown text + auto-enables headless rendering. | | Custom headers | body.headers object passthrough. | | Custom cookies | body.cookies object passthrough. | | Sessions | session_id for sticky sessions. | | Device | device_type: desktop / mobile / tablet. | | HTTP POST | http_method: "POST" + payload (base64). |

| Field | Notes | |-------|-------| | Target | Explicit override — 17 verified options grouped by site. Empty = auto-detect. | | Proxy Pool | standard (default) or premium. | | Page From / Page Count | Only meaningful for paginated targets like google_search, amazon_search. |

Decodo mirrors Oxylabs's heterogeneous identifier-field contract. Sending the wrong field name returns HTTP 400.

| Identifier field | Targets | Param shape | |------------------|---------|-------------| | url | universal, amazon, walmart, google | {url} | | query (+ optional domain) | amazon_product, amazon_search, walmart_search, google_search | {query, domain?} | | product_id | walmart_product | {product_id} |

If you set Target explicitly without supplying the matching field in targetParams:

errorType: 'validation'
error: "Decodo target 'walmart_product' requires 'product_id' in targetParams. See https://help.decodo.com/docs/web-scraping-api-walmart-product. Or clear the target field to use auto-detection from the URL."

150s server timeout. Decodo's real-time hard cap is 150s. The adapter uses maxTimeoutMs: 30000 as a conservative client guard.

Screenshot wins over markdown. If you set both, you get the PNG.

Markdown forces headless. The adapter auto-enables headless: "html" when markdown: true.

Decodo's target contract mirrors Oxylabs's: URL-based targets take url, search-style take query (+ optional domain), product-style take product_id (+ optional domain). The detector picks the right field for you when you leave Target on Auto-detect from URL; the table below is the contract for explicit-target workflows. Decodo's documented surface is narrower than Oxylabs's — only targets with a verified vendor doc page are listed; URLs that don't match any of these go through universal.

Generated from TARGET_REQUIRED_PARAM in libraries/vendors/providers/src/decodo-target-detector.ts. Run pnpm exec tsx scripts/generate-vendor-doc-tables.ts to refresh.

| Target | Group | Identifier field | Vendor docs | |---|---|---|---| | universal | General | url | link | | amazon | Amazon | url | link | | amazon_product | Amazon | query | link | | amazon_search | Amazon | query | link | | amazon_pricing | Amazon | query | link | | amazon_bestsellers | Amazon | query | link | | amazon_sellers | Amazon | query | link | | google | Google | url | link | | google_search | Google | query | link | | google_ads | Google | query | link | | google_travel_hotels | Google | query | link | | google_shopping_search | Google | query | link | | google_shopping_product | Google | query | link | | bing_search | Bing | query | link | | walmart | Walmart | url | link | | walmart_product | Walmart | product_id | link | | walmart_search | Walmart | query | link |

  • v3/task async API — TODO 2. Real-time only today.