Install
openclaw skills install @scavio-ai/scavio-extractRead any URL and get it back as clean Markdown, plain text, or raw HTML. One core endpoint, three fetch tiers (1/1/2 credits), and only a successful extraction is billed.
openclaw skills install @scavio-ai/scavio-extractRead any web page and get it back as clean Markdown, plain text, or raw HTML. This is the read-a-page primitive: one URL in, page content out.
Extract is not a platform - it is a core endpoint. There is no namespace, no per-site parser and no site-specific parameters. It works on any http(s) URL.
Use this skill when the user asks to:
This is usually the right first tool whenever a user pastes a link and asks a question about what is on it.
Get a free API key at https://scavio.dev (50 free credits to get started, no card required):
export SCAVIO_API_KEY=sk_live_your_key
Every request is a POST with a JSON body and:
Authorization: Bearer $SCAVIO_API_KEY
Base URL: https://api.scavio.dev.
| Endpoint | Credits | What it returns |
|---|---|---|
POST /api/v1/extract | 1, 1 or 2 by mode | { url, format, mode, content, content_length } |
No pagination - one URL, one response.
Extract is tier-priced, so there is no single "costs N credits" answer. The mode parameter sets the price:
mode | What it does | Credits |
|---|---|---|
normal (default) | Plain datacenter fetch | 1 |
advanced | Renders JavaScript before reading | 1 |
ultra | Heaviest fetch, for the hardest bot walls | 2 |
advanced costs the same as normal, so reach for it freely on any page that renders client-side. ultra is the only step that doubles the price - escalate to it only after normal or advanced came back empty or blocked.
Billing is charge-on-success. Only a 2xx extraction is billed. A dead link, a bot wall or a timeout costs nothing, so escalating a failed fetch to a higher tier does not stack charges for the failures.
| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | required | The page to read (1-2048 chars). http(s) only; a bare host is upgraded to https. |
format | string | markdown | html, markdown, text |
mode | string | normal | normal, advanced, ultra. This is the price-bearing parameter. |
markdown (default) - a readability extraction: the article content, cleaned of navigation and chrome, as Markdown. This is what you want for an LLM prompt or a RAG chunk.html - the raw page exactly as fetched. Use it when you intend to parse the DOM yourself.text - that same readability Markdown, flattened to plain text. The flattener is deliberately conservative about CommonMark, so snake_case identifiers, __dunders__ and inline code survive intact rather than being eaten as emphasis markers.http and https only. A bare host is upgraded to https for you. Loopback, private, link-local and cloud-metadata hosts are rejected with a 400 - the fetch happens server-side, so pointing this at localhost would only ever reach someone else's loopback, not the user's.
import os, requests
BASE = "https://api.scavio.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}"}
# 1. The common case: a page as clean Markdown, 1 credit
page = requests.post(f"{BASE}/api/v1/extract", headers=HEADERS,
json={"url": "https://example.com/pricing"}).json()
print(page["data"]["content"][:500], page["data"]["content_length"])
# 2. Plain text for an embedding pipeline, still 1 credit
plain = requests.post(f"{BASE}/api/v1/extract", headers=HEADERS,
json={"url": "https://example.com/blog/post", "format": "text"}).json()
# 3. A client-rendered page: advanced renders JavaScript and STILL costs 1 credit
spa = requests.post(f"{BASE}/api/v1/extract", headers=HEADERS,
json={"url": "https://example.com/app/docs", "mode": "advanced"}).json()
# 4. Raw HTML to parse yourself
raw = requests.post(f"{BASE}/api/v1/extract", headers=HEADERS,
json={"url": "https://example.com", "format": "html"}).json()
Escalate tiers only on failure. Failed fetches are not billed, so this ladder
costs 1 credit on success at any step and 2 only if it has to reach ultra:
def read(url, format="markdown"):
"""normal (1cr) -> advanced (1cr) -> ultra (2cr). Only the successful call is billed."""
for mode in ("normal", "advanced", "ultra"):
r = requests.post(f"{BASE}/api/v1/extract", headers=HEADERS,
json={"url": url, "format": format, "mode": mode})
if r.status_code == 200:
data = r.json()["data"]
if data["content_length"]:
return data
if r.status_code == 400:
break # bad or blocked URL - a higher tier will not fix it
return None
The envelope is { data, response_time, credits_used, credits_remaining }, and data is:
{
"url": "https://example.com/pricing",
"format": "markdown",
"mode": "normal",
"content": "# Pricing\n\nSimple, usage-based pricing...",
"content_length": 4821
}
url, format and mode echo what was actually used, so you can log which tier paid for the result. credits_used in the envelope tells you whether the call cost 1 or 2.
normal and advanced and 2 for ultra. If you tell the user what a run will cost, price it by mode.normal. Escalate to advanced for a page that renders client-side, and to ultra only when the cheaper tiers came back blocked or empty - that is the only step that doubles the cost.markdown or text for anything going into a model prompt. html is for parsing, and it burns context.content_length on a 200 usually means a bot wall or a client-rendered shell, not an empty page. Escalate the tier before reporting the page as blank.400 means the URL was rejected: a non-http(s) scheme, a malformed URL, or a loopback / private / link-local / metadata host. Not billed. A higher mode will not fix it.401 means the API key is invalid or missing. Check SCAVIO_API_KEY.404 means the page does not exist upstream. Not billed.429 means rate or usage limit exceeded. Wait before retrying. See https://scavio.dev/docs/rate-limits.502 / 503 mean the fetch failed or upstream is unavailable. Not billed - retry once, then escalate mode rather than hammering the same tier.SCAVIO_API_KEY is not set, prompt the user to export it before continuing.langchain-scavio has no extract tool - use the Scavio SDK directly. Extract is a top-level method, not a namespace: client.extract(...), never client.extract.extract(...).
pip install scavio
from scavio import ScavioClient
client = ScavioClient() # reads SCAVIO_API_KEY
page = client.extract("https://example.com/pricing")
plain = client.extract("https://example.com/blog/post", format="text")
spa = client.extract("https://example.com/app/docs", mode="advanced") # still 1 credit
JavaScript / TypeScript:
npm install scavio
import { Scavio } from "scavio";
const scavio = new Scavio(); // reads SCAVIO_API_KEY
const page = await scavio.extract({ url: "https://example.com/pricing" });
const spa = await scavio.extract({ url: "https://example.com/app/docs", mode: "advanced" });