ExtractAPI turns an allowed public URL into structured JSON with a single REST call. The browser-backed path can render JavaScript before extracting metadata, content, JSON-LD, links, images, and an optional selector; protected or unsafe targets may still fail.
https://extractapi.appAuthorization: Bearer YOUR_API_KEY header.Start free with 50 trial requests and no card. Verify an email to unlock 500 requests/month. Your free key is displayed once: copy it directly to an environment variable, then choose an example below.
EXTRACTAPI_KEY locally. Do not commit it, paste it into browser code, or send it to support.Choose a workflow: AI-agent web extraction, JavaScript website to JSON, or competitor price monitoring.
Choose the workflow closest to your integration. Each guide includes a supported request, representative output, and the limits to plan for.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | ✅ Yes | — | The page URL to extract (HTTP/HTTPS) |
selector | string | No | — | CSS selector for custom element extraction |
waitFor | string | No | — | CSS selector to wait for before extracting |
extractImages | boolean | No | false | Include image src and alt attributes |
extractLinks | boolean | No | false | Include link href and text |
javascript | boolean | No | true | Enable JavaScript rendering |
export EXTRACTAPI_KEY="YOUR_API_KEY"
curl --request POST https://extractapi.app/v1/extract \
--header "Authorization: Bearer $EXTRACTAPI_KEY" \
--header "Content-Type: application/json" \
--data '{"url":"https://example.com"}'
const response = await fetch('https://extractapi.app/v1/extract', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.EXTRACTAPI_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ url: 'https://example.com' })
});
if (!response.ok) throw new Error(`ExtractAPI returned ${response.status}`);
const { data } = await response.json();
console.log(data.title);
import os
import requests
response = requests.post(
'https://extractapi.app/v1/extract',
headers={'Authorization': f"Bearer {os.environ['EXTRACTAPI_KEY']}"},
json={'url': 'https://example.com'},
timeout=30,
)
response.raise_for_status()
print(response.json()['data']['title'])
Keep keys in server-side environment variables. Never put a key in frontend JavaScript or a public repository.
This is a synthetic response shape. Target content, timings, and byte counts vary by page and are not a performance or availability claim.
{
"status": "ok",
"url": "https://example.com/product/123",
"data": {
"title": "Product Name — Example Store",
"metaDescription": "...",
"metaKeywords": "...",
"openGraph": { "og:title": "...", "og:image": "..." },
"h1": "Product Name",
"headings": [
{ "level": "h1", "text": "Product Name" },
{ "level": "h2", "text": "Description" }
],
"mainContent": "Full page text...",
"structuredData": [
{ "@context": "https://schema.org", "@type": "WebPage", "name": "Example Domain" }
],
"canonical": "https://example.com/product/123",
"language": "en",
"links": [
{ "text": "About", "href": "https://example.com/about" }
],
"images": [
{ "src": "https://example.com/image.png", "alt": "Example", "width": 640, "height": 480 }
],
"custom": ["$12.00"],
"pageStats": {
"textLength": 8432,
"linkCount": 47,
"imageCount": 12,
"scriptCount": 5
}
},
"responseTimeMs": 1247,
"bytesReturned": 1583,
"fetchedAt": "2026-07-22T16:30:00.000Z"
}
{
"status": "error",
"url": "https://blocked-site.example",
"error": "Target could not be extracted",
"responseTimeMs": 3021
}
{
"urls": [
"https://example.com/product/1",
"https://example.com/product/2"
]
}
{
"status": "ok",
"results": [
{
"status": "ok",
"url": "https://example.com/product/1",
"data": { "title": "Example Product" },
"responseTimeMs": 123,
"bytesReturned": 456,
"fetchedAt": "2026-01-01T00:00:00.000Z"
},
{
"status": "error",
"url": "https://example.com/product/2",
"error": "Target could not be extracted",
"responseTimeMs": 301
}
]
}
{
"apiKeyName": "My Project",
"plan": "pro",
"monthlyUsage": 1247,
"monthlyLimit": 25000,
"remaining": 23753,
"upgradeUrl": "/#pricing",
"quotaResetAt": "2026-08-01T00:00:00.000Z",
"billingPortalAvailable": true,
"recentRequests": [
{
"url": "https://example.com/product/1",
"status": "ok",
"response_time_ms": 123,
"created_at": "2026-01-01 00:00:00"
}
]
}
The free key starts with 50 trial requests. Submit the key and an email to POST /free-key-verification; the verification link unlocks 500 requests/month. The key itself is never stored in the verification record.
curl --request POST https://extractapi.app/free-key-verification \
--header "Content-Type: application/json" \
--data '{"apiKey":"YOUR_FREE_API_KEY","email":"[email protected]"}'
Paid keys can open Stripe's hosted portal for invoices, payment methods, cancellation, and plan changes:
curl --request POST https://extractapi.app/create-portal-session \
--header "Authorization: Bearer $EXTRACTAPI_KEY"
Rotate a compromised key without losing usage history or subscription state. The replacement is returned once; update your server-side secret immediately:
curl --request POST https://extractapi.app/v1/rotate-key \
--header "Authorization: Bearer $EXTRACTAPI_KEY"
All API requests require an API key passed in the Authorization header:
Choose the Free plan to receive an ext_free_ key immediately — no credit card or Stripe checkout required. The trial starts at 50 requests; verify an email to unlock 500/month. Paid-plan keys are shown on the checkout success page.
| HTTP | Meaning |
|---|---|
200 | Extraction succeeded (status: "ok") |
400 | Missing or invalid url field |
401 | Missing or invalid API key |
403 | API key is inactive or invalid |
422 | Extraction failed or URL is blocked (SSRF protection) |
429 | Rate limit or monthly limit exceeded |
500 | Internal server error |
For authentication errors, check that the server-side Authorization: Bearer value is present and has not expired. For 429, wait for the returned Retry-After; do not guess a countdown. A blocked target means the URL or a redirect failed safety validation. An empty result can mean the page rendered no readable content; try a supported selector or inspect the response ID with support. Selector errors indicate an invalid or unsupported CSS selector. Include the request ID and sanitized error category when contacting support, never the key or page contents.
Each API key is rate-limited per second and per month:
When rate-limited, you'll receive HTTP 429 with a Retry-After header. Authenticated responses also include X-Quota-Limit, X-Quota-Remaining, and X-Quota-Reset.
ExtractAPI validates all URLs for safety:
Need help? Email [email protected]. Include your target URL, language, and error message — never include your API key.