SFOLN

Catalogue / Web toolkit for agents

AI Web Scraper – Any URL to Structured JSON

sfoln/ai-web-scraper · AI, Agents, Developer tools On request

Turn any web page into structured JSON with AI: give URLs plus a JSON Schema or a plain list of fields and get validated data per page (schema check with one automatic repair). Reads the main content and JSON-LD. $8 per 1,000 pages; unreadable pages are free.

About

What it does

Turn any web page into structured JSON with AI. Give a list of URLs and say what you want: a JSON Schema, a plain list of fields such as price (number), or instructions in plain language. The Actor reads each page's main content (and the schema.org JSON-LD hidden in it), asks the model for exactly that data, validates the answer against your schema and, when it does not match, lets the model repair it once. You get one record per URL with data, valid and errors. Built for AI agents, enrichment pipelines and anyone who needs product, article, listing or contact data without writing a scraper per site. $8 per 1,000 pages; pages that cannot be read and pages with nothing to extract are free.

Price

Price per delivered record

EventPricePer 1,000Charged for
Page extraction
page-extraction
$0.008$8 / 1,000Charged once per URL whose page was read and from which the AI extracted data (at least one value), validated against your schema.

Apify free plan: up to 10 items per run. Paid plans have no per-run cap; set maxTotalChargeUsd to cap spend.

Run

Call it from code, an agent or the Console

This API is private on Apify for now. Email us and we will publish it for you, usually within two working days. The calls below work once it is public. contact@sfoln.com

curl · synchronous run, rows in the response
curl -X POST "https://api.apify.com/v2/actors/sfoln~ai-web-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"],"fields":["title","price (number): the price shown on the page","availability","upc: the product code"]}'
mcp.json · hosted Apify MCP server
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=sfoln/ai-web-scraper"
    }
  }
}
asynchronous run · for large batches
POST https://api.apify.com/v2/actors/sfoln~ai-web-scraper/runs?maxTotalChargeUsd=1
GET  https://api.apify.com/v2/actor-runs/{runId}            # until status is SUCCEEDED
GET  https://api.apify.com/v2/datasets/{defaultDatasetId}/items?format=json

Authenticate with an Apify API token. MCP clients can instead sign in with OAuth when prompted. Example prompt for an agent: Extract the title, price and availability from https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html as JSON.

Input

Input fields

Web page URLs plus what to extract from each: a JSON Schema, a list of fields, or plain-language instructions. Each URL gives one record with the extracted data.

FieldTypeDefaultMeaning
urls *arrayrequiredPage URLs
Web pages to extract data from, one per line (public http(s) pages; PDFs and other files are not read). Accounts on the Apify free plan can process up to 10 URLs per run.
fieldsarray—Fields to extract
The data to extract, one field per line: "name", "name: what it is" or "name (type): what it is". Types: string (default), number, integer, boolean, date, datetime, url, email, string[], number[], url[], object, object[]. A field the page does not state comes back as null. Use this or a JSON schema: when both are given, the schema is used and this list is ignored.
schemaobject—JSON Schema
Instead of fields: a JSON Schema (draft-07 or 2020-12) of the result, e.g. {"type": "object", "properties": {"name": {"type": "string"}, "price": {"type": "number"}}, "required": ["name"]}. An array schema extracts a list (for example every product on a category page). At most 4,000 characters as compact JSON; only local $ref.
instructionsstring—Instructions
Optional guidance in plain language (at most 2,000 characters), e.g. "Prices in EUR; dates as YYYY-MM-DD; ignore related products". On its own, without fields or a schema, the model decides the JSON keys.
contentModestring"main"Page content
main: the page's main content (article, product details) as markdown, navigation and footers removed. full: all visible text of the page, for data that sits in headers, sidebars or footers (a page body above 800 KB, with more than 12,000 elements or nested deeper than 128 levels is read as main content instead, with a warning). One of "main", "full".
includeContentbooleanfalseInclude page content
Add the page text that was sent to the model to each record (useful to check what the model saw).
maxUrlsinteger1000Max URLs
Safety cap on how many URLs are processed in this run; the rest are ignored and counted in the OUTPUT record. Range 1–10000.
concurrencyinteger3Concurrency
Pages processed in parallel (1–10). Pages on the same host are fetched at most 2 at a time. Range 1–10.
includeRawbooleanfalseInclude raw model replies
Attach the model's raw replies (the first answer and the repair, if any) to each record.
Example input
{
  "urls": [
    "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
  ],
  "fields": [
    "title",
    "price (number): the price shown on the page",
    "availability",
    "upc: the product code"
  ]
}

Output

One record per input item

Records land in the run's dataset; a synchronous call returns them directly. Each has a status (ok is charged; everything else is free) and a charged flag. The run's OUTPUT record totals items and charges.

FieldTypeMeaning
inputstring | objectInput
The URL as given in the input.
urlstring | nullURL
The normalized URL that was requested.
finalUrlstring | nullFinal URL
The URL that answered, after redirects.
titlestring | nullPage title
The page's headline or document title.
languagestring | nullLanguage
The page's declared language (BCP 47), when it has one.
dataobject | array | string | number | boolean | nullExtracted data
The extracted data: an object shaped by your schema or field list (an array or other value when your schema's top level is one); null when the model failed.
validboolean | nullValid
True when data validates against your schema (field lists always validate unless a value has the wrong type; instructions-only runs accept any JSON object).
errorsarray | nullValidation errors
Why data does not validate: [{ path, message }] with a JSON Pointer path (at most 20); empty when valid.
repairedboolean | nullRepaired
True when the first reply did not validate and the repair call's answer is the one returned.
contentStatusstring | nullContent status
ok | blocked | js_required | not_html | too_large | error. The model is called only for ok pages.
contentTruncatedboolean | nullContent truncated
True when the page content was cut to fit the token budget before extraction.
contentstring | nullPage content
The page text sent to the model (only with includeContent).
modelstring | nullModel
The Mistral model that produced data.
tokensUsedobject | nullTokens used
{ input, output } tokens of the model calls for this page (informational; the price per page is fixed).
statusstringStatus
ok (data extracted, charged) | not_found (nothing on the page matched, or HTTP 404; free) | error (page or model failure; free) | skipped_invalid | skipped_budget | skipped_free_plan | skipped_unmetered e.g. "ok"
chargedbooleanCharged
Whether this record incurred a pay-per-event charge.
chargedEventstringCharged event
page-extraction when charged.
chargedCountintegerEvents charged
Events actually charged when the charge came back short of the requested count (present only then).
sourcestringSource
web+mistral: the page fetched by the Actor, extraction by Mistral.
fetchedAtstringFetched at
ISO timestamp.
errorobjectError
{ code, message, provider?, status? } when status is not ok. Page codes: invalid_url, not_html, blocked, js_required, thin_content, too_large, not_found, http_error, timeout, network, dns_not_found, tls_error, blocked_url; model codes: nothing_extracted, output_too_long, invalid_model_reply, rate_limit, timeout, server, client; runtime codes: budget_exhausted, free_plan_limit, unmetered, aborted, charge_failed, record_too_large, invalid_record, dataset_push_failed.
warningsarrayWarnings
Notes about this record (content cut, schema mismatch after the repair, a failed repair).
rawobject | nullRaw model replies
The model's raw replies (only with includeRaw).
Example record
{
  "input": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
  "url": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
  "finalUrl": "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html",
  "title": "A Light in the Attic",
  "language": "en-us",
  "contentStatus": "ok",
  "contentTruncated": false,
  "data": {
    "title": "A Light in the Attic",
    "price": 51.77,
    "availability": "In stock (22 available)",
    "upc": "a897fe39b1053632"
  },
  "valid": true,
  "errors": [],
  "repaired": false,
  "model": "mistral-small-2603",
  "tokensUsed": {
    "input": 642,
    "output": 48
  },
  "status": "ok",
  "charged": true,
  "chargedEvent": "page-extraction",
  "source": "web+mistral",
  "fetchedAt": "2026-10-14T10:00:02.000Z"
}

FAQ

Questions

Schema, fields or instructions: which should I use?
A JSON Schema gives you full control (nested objects, arrays, enums, formats) and the result is validated against it. A field list ("price (number): the listed price") is the quick way: every field comes back, null when the page does not state it. Instructions alone let the model pick the keys; combine them with a schema or fields for formatting rules such as currencies or date formats.
What do valid and errors mean, and is an invalid result charged?
Every reply is validated against your schema. When it does not validate, the model gets one repair attempt with the schema and the validation errors (included in the price). valid is true when the final data matches the schema; otherwise errors lists each problem as { path, message }. A result with at least one extracted value is charged even when it is not fully valid (for example a required field the page does not contain); make such fields nullable to avoid it. Results with no value at all are returned as not_found and are free.
Which pages work?
Public HTML pages that show their content without JavaScript: product pages, articles, listings, docs, plain text and JSON endpoints. The page's main content is converted to markdown and its JSON-LD structured data is added, so prices and ratings from schema.org markup are found too. Pages that need JavaScript, sit behind bot protection or a login, and PDFs or other files are reported with contentStatus and not charged.
How much of a page does the model read?
About 4,800 tokens per page in total, of which the schema and instructions take up to 1,000; the page content (main content first) fills the rest, which is roughly 14,000 characters of text for a small schema. contentTruncated tells you when a long page was cut. For long listings, use contentMode main and ask for fewer items per page.

Machine-readable

This page for agents