Docs

Everything you need to integrate SelectorLock: register a scraper, call extract, and read the response. If something here is unclear or wrong, that's a docs bug — tell us.

Quickstart

1. Sign up and create an API key.

2. Register a scraper — a name, an optional target url, and a field map of CSS selectors. You get back a scraperId.

3. Call extract with your key, the scraperId, and your own proxy credentials:

curl https://api.selectorlock.com/v1/extract \
  -H "x-api-key: $API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "scraperId": "scr_...",
    "proxy": { "url": "http://user:pass@your-proxy-provider.com:8000" }
  }'

That's the whole loop. If the target's DOM has drifted since you registered the scraper, this same call triggers an LLM repair automatically, returns healed data, and writes the corrected selectors back to the scraper — the next call against the same scraper uses them directly, no repair, no extra cost.

Authentication

Every call to /v1/extract requires an x-api-key header with a key from the dashboard. Keys are created on your assigned plan — see rate limits below. There is no OAuth flow and no key rotation UI yet; revoke and reissue by contacting us.

Security

Every call carries your own proxy credentials — we know that's a real trust decision, so here's exactly what happens to them: they're read from the request, used in memory for that single outbound connection, and discarded when the request finishes. They are never written to disk, never stored in any database, and never included in logs — not in the scraper records (only your selectors and field types are saved there), not in the request/usage history, not in error messages. The same applies to Test a proxy in the dashboard.

API keys are hashed (SHA-256) before storage — the raw key is shown to you once, at creation, and we can't retrieve it afterward either. Scraped page content is returned to you and not retained on our side; see the extract call for the response shape.

Registering a scraper

A registered scraper is a reusable "site template": a name, a field map (CSS selector + expected type per field), and an optional pinned url. Leave the url blank to run the same field map against a different page on every call — useful for a template like "product page" you point at many URLs.

There's no public API for scraper CRUD — register one from Dashboard → Scrapers → New scraper and you'll get an id back immediately (shown inline on that page, along with a ready-to-copy extract call using it).

Pass that id as scraperId in extract calls instead of resending fields every time. When a repair fires, the healed selector is written back to the scraper record — you never see the request that paid for the repair again.

The extract call

POST /v1/extract takes a proxy (always required — this is a bring-your-own-proxy product, see proxy compatibility) and exactly one of scraperId or an inline fields map. Sending both, or neither, is a 400. A url is required unless a scraperId with a stored url is given.

The inline path (no registry, selectors resent every call) still works, for one-off scripts or migrating in gradually:

curl https://api.selectorlock.com/v1/extract \
  -H "x-api-key: $API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "url": "https://shop.example.com/product/wireless-mouse",
    "proxy": { "url": "http://user:pass@your-proxy-provider.com:8000" },
    "fields": {
      "title": { "selector": ".product-title", "schema": { "type": "string" } },
      "price": { "selector": ".price", "schema": { "type": "number" } }
    }
  }'

Worth knowing before you lean on it long-term: without a scraperId, a healed selector has nowhere to be saved, so the same drift gets repaired — and billed — again on every call that hits it, instead of once. Repairs are also the stricter of the two rate limits (10/hour on Free), so a single drifted page called repeatedly on the inline path can exhaust that budget much faster than the same drift would on a registered scraper.

A scraper registered without a url is a template — pass a different one on every call and it reuses the same field map against whatever page you point it at:

curl https://api.selectorlock.com/v1/extract \
  -H "x-api-key: $API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "scraperId": "scr_...",
    "url": "https://shop.example.com/product/wireless-mouse",
    "proxy": { "url": "http://user:pass@your-proxy-provider.com:8000" }
  }'

A successful response, healed or not, looks like:

{
  "status": "ok",
  "healed": true,
  "data": { "title": "Wireless Mouse", "price": 29.99 },
  "updatedSelectors": { "title": ".product-title", "price": ".price" },
  "scraperId": "scr_..."
}

healed is false on the common path (your selectors still matched — no LLM involved, fastest and cheapest).updatedSelectors and scraperId are only present when relevant.

Error codes

Errors are always { "status": "error", "code": ..., "message": ... }. The HTTP status is derived from the code, not the other way around.

CodeStatusMeaning
INVALID_API_KEY401The x-api-key header is missing, doesn't match a key on record, or has been revoked.
RATE_LIMIT_EXCEEDED429You've hit your plan's request or repair rate limit for the current hour.
INVALID_REQUEST_BODY400The request body failed validation — see the message for which field.
SCRAPER_NOT_FOUND404scraperId doesn't exist, belongs to another customer, or was deleted. All three look identical on purpose.
TARGET_UNREACHABLE502The target URL couldn't be reached through the proxy (timeout, DNS failure, connection refused).
PROXY_AUTH_FAILED502The proxy rejected the credentials embedded in your proxy url (HTTP 407).
PROXY_INCOMPATIBLE502The proxy responded, but not in the absolute-form-request, terminate-TLS-yourself way this product requires. See Proxy compatibility below.
LLM_REPAIR_FAILED502Both repair tiers failed to produce a response (upstream LLM error), not a data problem.
LLM_OUTPUT_UNPARSEABLE502Both repair tiers responded, but neither one's output passed your declared schema.
INTERNAL_ERROR500Unexpected server error. If this persists, it's on us — get in touch.

Proxy compatibility

proxy.url is a single string in http://user:pass@host:port form — your own forward-proxy credentials, not one we provide. A few real examples of that shape:

ProviderExample proxy.urlNotes
Websharehttp://username:password@p.webshare.io:80Rotates IP per request by default. Copy the exact endpoint from your Webshare dashboard.
Bright Datahttp://brd-customer-<id>-zone-<zone>:<password>@brd.superproxy.io:33335Host, port, and the brd-customer-…-zone-… username come from your zone's access parameters.
Self-hosted / static IPhttp://user:pass@203.0.113.10:8080Any HTTP forward proxy meeting the requirements below works — not only scraping-specific providers.

We speak to your proxy over a raw socket, not a generic HTTP client — a compatible proxy must accept the full target URL in the request line (absolute-form) and terminate TLS itself for https:// targets, handing back a plain response. We've confirmed Webshare and Bright Data (including its ISP proxy tier, no residential-plan KYC required) both work fully, on both http:// and https:// targets. It won't work with a proxy that only speaks SOCKS or that strictly requires a CONNECT tunnel without terminating TLS itself — we found IPRoyal's residential gateway proxies plain HTTP fine but does not terminate TLS for https:// targets, forwarding the request unencrypted to the origin's HTTPS port instead (which the origin correctly rejects). Provider reputation isn't a reliable predictor either way — verify each specific proxy directly.

Use Test a proxy in the dashboard before wiring any proxy into a real extract call — it runs a live request through your proxy against a known-good target for both an http:// and an https:// URL (since, as above, a proxy can support one and not the other) and reports compatible or PROXY_INCOMPATIBLE with a reason for each.

Rate limits

Limits are per plan, per hour, and reset on a fixed hourly window (not a rolling one). Repairs have their own, much stricter budget, since that's the path that calls an LLM.

PlanRequestsRepairs
Free100 / hour10 / hour
Pro2,000 / hour200 / hour
Enterprise50,000 / hour5,000 / hour

See pricing for plan details. Your plan is assigned to your account — get in touch to upgrade.