Skim

將任何 URL 轉換為乾淨的 Markdown,供 AI agents 使用 — 只需一個 read(url) 工具,託管服務,無需註冊,無需 API 金鑰。

文件

Skim docs

Skim reads a URL and returns its main content as clean Markdown. Free, no signup, no key. Two ways to use it.

1. MCP (recommended for agents)

Streamable-HTTP MCP endpoint — add it as a remote server:

https://skim.perch-app.workers.dev/mcp

Tool: read

read({ url: "https://example.com/post", // required, absolute http(s) links: true, // optional, keep text; default true max_chars: 8000 // optional, truncate long content })

Returns { url, title, description, format, content, word_count, bytes, ms }.

2. HTTP

GET https://skim.perch-app.workers.dev/api/read?url=https%3A%2F%2Fexample.com%2F POST https://skim.perch-app.workers.dev/api/read {"url":"https://example.com/","links":true,"max_chars":8000}

Query/body options: format=markdown|text|data|jobs (markdown default; text also drops link markup; data = structured JSON; jobs = normalized ATS/job-board array, see below), links=0, max_chars=N.

Structured data (format=data) — for aggregators

Instead of the readable body, get the machine-readable data the page already embeds — no HTML parsing on your side:

GET https://skim.perch-app.workers.dev/api/read?url=&format=data

Returns { title, description, canonical, jsonld:[…], opengraph:{…}, twitter:{…}, meta:{…}, counts:{…} }. jsonld is the page's schema.org blocks (Product, Offer, JobPosting, Review, Article, BreadcrumbList…) parsed from every <script type="application/ld+json"> (including @graph), so a price/spec/job/product aggregator can read structured fields straight from the source. Works with render=js for SPA pages. Sites without embedded data return empty arrays (still valid JSON).

3. Read it yourself (clean reader)

Not an agent? Paste any URL into the clean reader to strip ads, popups and clutter and get a readable, shareable page: https://skim.perch-app.workers.dev/r?url=<page>.

Building a job aggregator or feed reader

Skim works well as a server-side fetch + extract layer: your aggregator or agent calls Skim, Skim fetches upstream and hands back clean content, so you never ship a browser or a scraper stack. Two patterns:

Normalized jobs (format=jobs) — one schema for every ATS. Add &format=jobs and Skim maps Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee feeds (plus any careers page carrying JSON-LD JobPosting, and a generic array fallback) into one unified array — so you write your aggregator once instead of a parser per provider:

GET https://skim.perch-app.workers.dev/api/read?url=https%3A%2F%2Fboards-api.greenhouse.io%2Fv1%2Fboards%2Fgitlab%2Fjobs&format=jobs → { "url": "...", "status": 200, "format": "jobs", "source": "greenhouse", "count": 196, "jobs": [ { "title": "Account Executive", "url": "https://.../jobs/123", "location": "Remote, Italy", "department": "Sales", "employment_type": "FullTime", "updated_at": "2026-08-10T16:52:46-04:00", "remote": true }, … ] }

Same call works over MCP (read(url, format:"jobs")) and for a JS-rendered board (add render=js). Prefer the raw feed? Omit format and Skim returns the provider JSON verbatim:

ATS JSON APIs (raw) — Skim returns JSON verbatim (the whole body, up to 5 MB), so you can parse it directly:

GET https://skim.perch-app.workers.dev/api/read?url=https%3A%2F%2Fboards-api.greenhouse.io%2Fv1%2Fboards%2F%2Fjobs

Greenhouse: https://boards-api.greenhouse.io/v1/boards/{company}/jobs

Ashby: https://api.ashbyhq.com/posting-api/job-board/{company}

Lever: https://api.lever.co/v0/postings/{company}?mode=json

Client-rendered job boards (SPA) — add render=js so Skim runs the page in a real headless browser before extracting, then returns clean Markdown:

GET https://skim.perch-app.workers.dev/api/read?url=&render=js

Skim passes the upstream HTTP status straight through — a 404 means that company/board doesn't exist on that ATS (not a Skim error), and a 403 means the site blocked the fetch. Large bodies come back whole up to 5 MB; pass max_chars=N only if you want to cap length yourself.

Batch reads — polling several boards at once? Send up to 10 URLs in one call and get an array back (each result carries its own url/status/content or error):

POST https://skim.perch-app.workers.dev/api/read-batch {"urls":["https://boards-api.greenhouse.io/v1/boards/a/jobs","https://api.lever.co/v0/postings/b?mode=json"]}

or: GET https://skim.perch-app.workers.dev/api/read-batch?urls=, → {"count":N,"results":[...]}

Call it straight from the browser — every endpoint sends Access-Control-Allow-Origin: *, so a client-side dashboard or aggregator can fetch() Skim directly with no backend and no CORS errors (Skim does the upstream fetch server-side for you):

const r = await fetch("https://skim.perch-app.workers.dev/api/read?url=" + encodeURIComponent(u) + "&format=data"); const data = await r.json(); // JSON-LD / OpenGraph / meta, from your frontend

What it handles

HTML (→ Markdown, main-content extracted), plain text, JSON and XML (returned as-is). PDFs and binaries aren't supported yet. Private/internal addresses are blocked. Rate limit: 40 reads/minute per IP.

Notes

Skim identifies itself as SkimBot/1.0 and follows redirects. It's a reader, not a scraper farm — be considerate of the sites you read.