Content

Extract

Clean markdown or structured JSON from any URL.

Open in…
Ask on Discord
What your agent reads
`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.
Every page: append .md to the URL. Whole site: /llms.txt · /llms-full.txt
POSThttps://api.keirolabs.cloud/api/v2/extract

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 -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

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
}

Prop

Type

Other routes

  • POST /api/web-crawler: Legacy alias for backward compatibility. Same handler, same cost.

Errors

StatusCodeCauseFix
400VALIDATION_ERRORMissing or invalid URLProvide a valid http(s) URL.
401UNAUTHORIZEDMissing or invalid API keyCheck your Authorization: Bearer header.
402INSUFFICIENT_CREDITSNot enough creditsBuy more credits or upgrade at /dashboard/billing. Nothing was charged.
403FORBIDDENURL is blocked (SSRF protection)Internal/private URLs are blocked. Use public URLs only.
429RATE_LIMIT_EXCEEDEDToo many requestsCheck X-RateLimit-Remaining header.
502SEARCH_UNAVAILABLEPage fetch or extraction failedCredits auto-refunded. Try mode="deep" for JS-heavy pages.

Rate limits

TierLimit
Free10/min
Starter30/min
Pro60/min
Startup120/min
Enterprise200/min

Over the limit you get a 429. See Rate limits for headers and backoff.

Let your agent build it

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.

Ask on Discord

On this page