
If I had to bet on which Tabstack endpoint you will call most often, I would put my money on /extract without thinking twice. It is not the one people get excited about in the demo. Research and automate get the "wow." Extract is the one that ends up in production, running thousands of times a day, doing the unglamorous work that everything else is built on top of.
Here is the thing about extract: it is deterministic, it is cheap, and it does exactly one job per call. Those are not limitations. They are the reason it is the right default for most web data work. Before you reach for an agent, ask whether the answer is just "fetch this page and give me the clean version." A lot of the time, it is.
Extract has two methods. They solve two different problems, so let me take them one at a time.
extract.markdown: the page, minus the noise
Every web page is wrapped in stuff you do not want. Navigation, ads, cookie banners, sidebars, footers, the same boilerplate on every page of the site. When all you need is the actual content, extract.markdown fetches the URL, strips the cruft, and hands you clean markdown.
import Tabstack from '@tabstack/sdk';
const client = new Tabstack();
const result = await client.extract.markdown({
url: 'https://example.com/blog/article',
});
console.log(result.content);By default, the page's metadata (title, author, description, and so on) comes embedded as YAML frontmatter at the top of content. That is handy if you are feeding a static site generator that already expects frontmatter. For most programmatic use, though, you want the metadata as a structured object, not a string you have to parse. Add metadata: true:
const result = await client.extract.markdown({
url: 'https://example.com/blog/article',
metadata: true,
});
console.log(result.content); // pure markdown, no frontmatter
console.log(result.metadata?.title); // "Example Article Title"Now content is clean markdown and metadata is a typed object with title, author, publisher, image, site_name, type, and friends. No YAML parsing, which means no broken parse the day a title contains a colon or a quote.
Where does this earn its keep? Mostly two places. The first is anything that feeds an LLM: RAG pipelines, summarization jobs, content classification. Models work better on clean markdown than on a soup of HTML, and you are not paying for tokens you will throw away. The second is content systems: read-it-later apps, aggregators, archives, anything that wants a stable, storable version of a page instead of the live HTML.
A couple of parameters worth knowing:
effortcontrols how hard the renderer works, with valuesmin,standard(the default), andmax. Static pages are fine onstandard. Pages that build their content with JavaScript may needmax. Pages that are already plain HTML can drop tomin. This is a direct cost and speed lever, so it is worth tuning per source rather than leaving on default everywhere.nocachedefaults tofalse. Extract caches for a short window, which is a win for blog posts and docs. For breaking news or anything that changes minute to minute, setnocache: trueto force a fresh fetch.geo_targettakes a country code ({ country: 'US' }) when the page serves different content by region.
extract.json: the shape you asked for, every time
extract.markdown gives you the whole page. Often you do not want the whole page. You want three fields out of it, typed, ready to drop into your code. That is extract.json.
You pass a URL and a JSON schema. You get back data in exactly that shape.
const result = await client.extract.json({
url: 'https://news.ycombinator.com',
json_schema: {
type: 'object',
properties: {
stories: {
type: 'array',
items: {
type: 'object',
properties: {
title: { type: 'string' },
points: { type: 'number' },
},
},
},
},
},
});
// { stories: [ { title: "...", points: 342 }, ... ] }The response matches your schema. Not "close to" your schema. You get strings where you asked for strings and numbers where you asked for numbers, instead of everything arriving as text that you then coerce by hand. There is no HTML parsing on your side, no brittle selectors that break the next time the site ships a redesign.
Two behaviors worth internalizing:
First, a missing field does not throw. A single field the extractor cannot fill, on one page in a batch of a thousand, will not blow up the whole run, which is exactly what you want in production. What comes back for that field is not guaranteed to be null, though: depending on the type, you may get null or a best-effort value the extractor inferred. So validate the values you get back, do not just check for null, and mark a field required when you want the extractor to work harder to find it.
Second, your schema descriptions are doing real work. Adding a description to a property gives the extractor context about what to look for. Start with a simple schema, run it, and add descriptions to the fields that come back wrong. It is the highest-leverage tuning you can do.
json_schema: {
type: 'object',
properties: {
price: {
type: 'number',
description: 'The current sale price in USD, not the original or crossed-out price.',
},
},
}That one description is the difference between extracting the right number and the struck-through one.
Where extract is the wrong tool
This is the honest part, and it is the same boundary I drew for generate. Extract pulls what is on the page. It does not form opinions. If the field you want is "is this pricing aggressive" or "summarize this in two sentences," that is interpretation, and it belongs to /generate/json, which puts a model in the loop on purpose.
The reason to keep that line clean is cost and determinism. Extract is the cheaper, repeatable workhorse. Reach for it whenever the data is literally on the page, which is more often than you would guess. Save the AI endpoints for the jobs that actually need judgment.
If you are deciding between extract and research: extract is for when you have the URL. Research is for when you have a question and do not yet know which page holds the answer. Different problems, different endpoints.
Getting started
npm install @tabstack/sdk
# or
pip install tabstackThe SDK reads TABSTACK_API_KEY from your environment, so a first call is two lines:
from tabstack import Tabstack
client = Tabstack()
result = client.extract.markdown(url="https://example.com/blog/article")
print(result.content)Start with extract.markdown to confirm your key works and see what a clean page looks like. Then move to extract.json the moment you know which fields you actually want. Most of the web data problems you think need a pipeline are one extract call and a schema.
Ready to build? Sign up for 10,000 free credits, no card required, and make your first extract call. Full API in the docs.