Search
Ask the web a question and get ranked results back. They are read live off a real search engine through a residential exit, never a reseller's cache you are not allowed to redistribute.
One call
curl -X POST https://api.scrapa.app/v1/search \
-H "authorization: Bearer sc_live_your_key" \
-H "content-type: application/json" \
-d '{"query": "quanto custa abrir um MEI", "proxy": {"country": "br"}}'{
"data": {
"query": "quanto custa abrir um MEI",
"topic": "general",
"engine": "brave",
"pool": "residential",
"bytes": 311402,
"cached": false,
"answer": null,
"results": [
{
"position": 1,
"url": "https://www.gov.br/empresas-e-negocios/pt-br/empreendedor",
"title": "Portal do Empreendedor",
"snippet": "A formalização como MEI é gratuita. O empreendedor paga apenas o DAS…",
"publishedAt": null,
"image": null
}
]
},
"meta": { "cost": 4, "creditsRemaining": 983, "requestId": "0f3c…" }
}A hit is a position, a URL, the title and the snippet the index holds. There is no relevance score, because no search engine publishes one. A number here would be invented, and you would build on it.
The order is the ranking: results[0] is what the engine put first. Read it as a list, not as a bag.
Which country's web
The exit country picks the market too, so a Brazilian exit returns what somebody in Brazil sees, in Portuguese, with Brazilian sites ranked where Brazil ranks them. It is `us` unless you say otherwise. The same two letters that pick the exit for a scrape pick it here.
News, and the only dates we trust
{
"query": "eleições",
"topic": "news",
"timeRange": "week",
"proxy": { "country": "br" }
}Ask for news and results come from a news index: dated journalism, freshest first, narrowed by `timeRange` to the last day, week or month. This is the topic that reliably fills `publishedAt`. On the general web it is usually null, and null is the honest answer. We never guess a date off a page.
Pictures
{
"query": "bolo de cenoura",
"topic": "images",
"proxy": { "country": "br" }
}{
"position": 1,
"url": "https://receitas.example.com/bolo-de-cenoura",
"title": "Bolo de cenoura com cobertura de chocolate",
"snippet": "",
"publishedAt": null,
"image": {
"url": "https://cdn.example.com/fotos/bolo-de-cenoura.jpg",
"thumbnailUrl": "https://ts2.mm.bing.net/th?id=OIP.example&pid=15.1",
"width": 1272,
"height": 764
}
}Ask for images and each hit is a page that shows a picture. `url` is that page. The picture is in `image`: its own address, a small copy that always loads, and its size in pixels. A picture's own address can be gone or refuse a download, so keep the small copy as a fallback.
We do not know who owns a picture, and no hit says. Credit the page in `url`, and ask its owner before you publish the picture.
Pictures the engine marks as adult are left out, so a page can hold fewer results than you asked for. `includeDomains` takes one host here, not ten. Two or more is a 400, and it costs nothing.
Only your hosts
{
"query": "engenheiro de dados",
"includeDomains": ["linkedin.com", "github.com"],
"limit": 20
}Pass up to ten bare hosts and the answer is on that list or it is not there. `linkedin.com` covers `br.linkedin.com` and never `notlinkedin.com`. We ask the search engine for it and enforce it on the way out, so what you get back is what you asked for whatever the engine did with the operator.
From a result to a page
import { Scrapa } from "@scrapa/sdk";
const scrapa = new Scrapa(process.env.SCRAPA_API_KEY!);
const { results } = await scrapa.search({ query: "melhores CRMs para PME" });
const pages = await Promise.all(
results.slice(0, 3).map((hit) => scrapa.scrape({ url: hit.url })),
);A search hands you URLs and a sentence each. That is enough to choose, rarely enough to work with. Read the ones worth reading with scrape. We keep them two calls rather than one so a page that walls you is one failure you can see and retry, not eleven hidden inside a request you are waiting on.
A written answer
answer: true adds a short paragraph written over the results, in the language the query was asked in, with nothing in it that is not in the snippets. It costs one extra credit and it is off by default: if your own model is going to read the results anyway, this is a round trip you do not need.
What it costs
Four credits for a page of ten results. A limit above ten reads a second page and costs a second row, which is the only honest way to price a second fetch. One more credit for the written answer.
A search every engine refused costs nothing, and so does anything we broke. A search that ran and found nothing is charged. The index answered, and "there is nothing" is an answer. Asking the same thing again within a few minutes is free and says so with cached: true.
The shell spelling
curl "https://api.scrapa.app/v1/search?query=pncp&topic=news&domains=gov.br" \
-H "authorization: Bearer sc_live_your_key"The same operation as a GET, for when a JSON body is in the way: the domain list is a comma string and the proxy block flattens into query parameters. One capability, two spellings. The SDK and the MCP tool have only one.