Extract
Clean markdown or structured JSON from any URL.
`POST https://api.keirolabs.cloud/api/v2/extract`
Cost: 2 credits · Latency: 1s - 5s
Extract clean markdown/text content from a specific URL. Steps up through progressively heavier rendering when a page blocks simple requests, so it gets past the defenses that stop a plain HTTP client. Supports semantic truncation by objective, optional vector embeddings, and structured JSON extraction via a user-supplied JSON Schema.
**When to use it.** Use Extract when you have a specific URL and need its clean content as markdown. Supports JS-rendered pages (deep mode), structured extraction (JSON Schema), and semantic filtering (objective).
**When not to.** Don't use Extract if you need to search first (use Search or Search+Content — they search AND extract in one call).
## Request
**cURL**
```bash
curl -X POST https://api.keirolabs.cloud/api/v2/extract \
-H "Authorization: Bearer $KEIRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://stripe.com/pricing", "mode": "lite"}'
```
**Python**
```python
import os, requests
res = requests.post(
"https://api.keirolabs.cloud/api/v2/extract",
headers={"Authorization": f"Bearer {os.environ['KEIRO_API_KEY']}"},
json={
"url": "https://stripe.com/pricing",
"mode": "lite"
},
)
print(res.json())
```
**Node.js**
```js
const res = await fetch("https://api.keirolabs.cloud/api/v2/extract", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KEIRO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"url": "https://stripe.com/pricing",
"mode": "lite"
}),
});
console.log(await res.json());
```
## Parameters
| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes | The fully-qualified URL to extract. Must be http(s); SSRF-blocked internal targets return 403. |
| `mode` | enum | no | Extraction mode. Options: "medium" (default - full extraction + RAG enrichment), "deep" (renders JavaScript-heavy pages for sites that need it), "heavy" (maximum-effort rendering for the hardest pages, at full price). |
| `objective` | string | no | Semantic objective. Only text relevant to this goal is kept; everything else is dropped to reduce noise. |
| `json_schema` | object | no | JSON Schema for structured extraction. Only honored when mode is not "light". No extra charge. |
| `expand_content` | boolean | no | Clicks "Read More"/"Show more" links before extracting. Default: false. |
| `max_chars` | integer | no | Alias for max_chars_source. Max characters extracted. Default: 7,000. |
| `max_chars_source` | integer | no | Max characters per source. Default: 7,000. |
## Response
```json title="200 OK"
{
"data": {
"results": [
{
"url": "https://news.ycombinator.com",
"status": 200,
"size_bytes": 28412,
"time_taken": 1.83,
"text": "**Hacker News**\n\n1. Show HN: I built a distributed lock service in Rust...",
"json_schema": {
"data": {
"title": "Hacker News",
"items": [
{
"rank": 1,
"title": "Show HN: I built a distributed lock service in Rust",
"url": "..."
}
]
}
}
}
],
"latencyMs": 1830
},
"creditsRemaining": 1137,
"credits_used": 2
}
```
| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `data.results` | array | no | List of extraction results (one per URL). |
| `data.results[].url` | string | no | The URL that was extracted. |
| `data.results[].status` | integer | no | HTTP status code from the fetch (200 = success). |
| `data.results[].text` | string | no | Clean markdown content extracted from the page. |
| `data.results[].json_schema` | object | no | Structured data extracted using your JSON Schema (if provided). |
| `data.results[].size_bytes` | integer | no | Size of extracted content in bytes. |
| `data.results[].time_taken` | number | no | Extraction time in seconds. |
| `data.latencyMs` | integer | no | Total request latency. |
| `credits_used` | number | no | Credits charged: 2, json_schema included. |
| `creditsRemaining` | number | no | Your remaining credit balance. |
## Other routes
- `POST /api/web-crawler`: Legacy alias for backward compatibility. Same handler, same cost.
## Errors
| Status | Code | Cause | Fix |
| --- | --- | --- | --- |
| `400` | `VALIDATION_ERROR` | Missing or invalid URL | Provide a valid http(s) URL. |
| `401` | `UNAUTHORIZED` | Missing or invalid API key | Check your Authorization: Bearer header. |
| `402` | `INSUFFICIENT_CREDITS` | Not enough credits | Buy more credits or upgrade at /dashboard/billing. Nothing was charged. |
| `403` | `FORBIDDEN` | URL is blocked (SSRF protection) | Internal/private URLs are blocked. Use public URLs only. |
| `429` | `RATE_LIMIT_EXCEEDED` | Too many requests | Check X-RateLimit-Remaining header. |
| `502` | `SEARCH_UNAVAILABLE` | Page fetch or extraction failed | Credits auto-refunded. Try mode="deep" for JS-heavy pages. |
## Rate limits
| Tier | Limit |
| --- | --- |
| Free | 10/min |
| Starter | 30/min |
| Pro | 60/min |
| Startup | 120/min |
| Enterprise | 200/min |
Over the limit you get a `429`. See [Rate limits](/rate-limits) for headers and backoff.https://api.keirolabs.cloud/api/v2/extractExtract clean markdown/text content from a specific URL. Steps up through progressively heavier rendering when a page blocks simple requests, so it gets past the defenses that stop a plain HTTP client. Supports semantic truncation by objective, optional vector embeddings, and structured JSON extraction via a user-supplied JSON Schema.
When to use it. Use Extract when you have a specific URL and need its clean content as markdown. Supports JS-rendered pages (deep mode), structured extraction (JSON Schema), and semantic filtering (objective).
When not to. Don't use Extract if you need to search first (use Search or Search+Content — they search AND extract in one call).
Request
curl -X POST https://api.keirolabs.cloud/api/v2/extract \
-H "Authorization: Bearer $KEIRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://stripe.com/pricing", "mode": "lite"}'Parameters
Prop
Type
Response
{
"data": {
"results": [
{
"url": "https://news.ycombinator.com",
"status": 200,
"size_bytes": 28412,
"time_taken": 1.83,
"text": "**Hacker News**\n\n1. Show HN: I built a distributed lock service in Rust...",
"json_schema": {
"data": {
"title": "Hacker News",
"items": [
{
"rank": 1,
"title": "Show HN: I built a distributed lock service in Rust",
"url": "..."
}
]
}
}
}
],
"latencyMs": 1830
},
"creditsRemaining": 1137,
"credits_used": 2
}Prop
Type
Other routes
POST /api/web-crawler: Legacy alias for backward compatibility. Same handler, same cost.
Errors
| Status | Code | Cause | Fix |
|---|---|---|---|
400 | VALIDATION_ERROR | Missing or invalid URL | Provide a valid http(s) URL. |
401 | UNAUTHORIZED | Missing or invalid API key | Check your Authorization: Bearer header. |
402 | INSUFFICIENT_CREDITS | Not enough credits | Buy more credits or upgrade at /dashboard/billing. Nothing was charged. |
403 | FORBIDDEN | URL is blocked (SSRF protection) | Internal/private URLs are blocked. Use public URLs only. |
429 | RATE_LIMIT_EXCEEDED | Too many requests | Check X-RateLimit-Remaining header. |
502 | SEARCH_UNAVAILABLE | Page fetch or extraction failed | Credits auto-refunded. Try mode="deep" for JS-heavy pages. |
Rate limits
| Tier | Limit |
|---|---|
| Free | 10/min |
| Starter | 30/min |
| Pro | 60/min |
| Startup | 120/min |
| Enterprise | 200/min |
Over the limit you get a 429. See Rate limits for headers and backoff.
Paste one prompt into Claude Code, Cursor, Codex or Windsurf. It installs Keiro MCP, reads these docs and writes the integration against the real API.