The pitch for using a language model to extract data writes itself: describe the fields you want, point it at a page, get JSON back. The demo is magic. The problem shows up later, when the model returns a price that is not on the page, formatted perfectly, completely wrong.
A scraper that is confidently wrong is worse than one that is obviously broken. A broken selector throws an error you can catch. A hallucinated value sails straight into your database. So most of the engineering here is not about extraction. It is about refusing to return data we cannot stand behind.
The schema is a contract, not a hint
The model does not get to decide the shape of the output. Your schema does. We pass the schema as a tool definition, so the model is constrained to fill in the fields you asked for, with the types you asked for. Then we validate the result before it leaves the API.
If you asked for a number and got a string, the response is rejected. If a field is required and missing, the response is rejected. You never receive something that does not match the contract you wrote.
Null is a feature
The single most important rule: if a field is not on the page, the answer is null. Not a best guess. Not a plausible-sounding default. Null.
This sounds obvious and is surprisingly hard, because models want to be helpful. A model asked for a phone number on a page that has no phone number will happily invent a well-formatted one. We prompt and constrain explicitly against this, and we would rather return an honest gap than a convincing fabrication. You can always handle a null. You cannot handle a lie you did not know was there.
Temperature zero, every time
Extraction is not a creative task. There is a right answer on the page, and we want the same answer every time we ask. So the extraction step runs at temperature zero. Two identical requests should return identical results, and surprises in a data pipeline are bugs, not features.
What this costs you
Honesty: this discipline means you sometimes get less data. A page where the model is unsure yields nulls instead of a full row. We think that is the correct trade. A dataset where every present value is trustworthy is far more useful than one where most values are right and you have no way to tell which ones are not.
The summary
- The schema is enforced at the API boundary, not suggested to the model.
- Missing data returns null, never a guess.
- Extraction runs deterministically at temperature zero.
That is the difference between a clever demo and something you can put in production.
Try it
Give it a page and a schema and watch what comes back, including the nulls. Run the live demo, or start free.