exa-contents — Claude Code skill: pull page text from known URLs
This skill shows the agent how to call Exa Contents over cURL and get clean text, excerpts or summaries for URLs it already has, without running a new search.
npx skills add exa-labs/agent-skills --skill exa-contents -a claude-codeWhat exa-contents does
Once the links are in hand, a fresh search is wasted work. The skill documents a POST to https://api.exa.ai/contents with an x-api-key header, returning page content in a shape a model can use: full markdown text, query-driven excerpts, or a per-page summary. For agent context it points at highlights, which costs fewer tokens than pulling whole documents.
How stale a page may be is set by maxAgeHours: 0 forces a live crawl, -1 reads cache only, and a positive number accepts cached content up to that age. livecrawlTimeout caps the crawl in milliseconds. Linked pages come in through subpages and subpageTarget, while extras collects outbound links and image URLs from each page.
You need an Exa key in EXA_API_KEY. The part that bites is the response: the request can answer HTTP 200 while individual URLs fail, so per-URL statuses deserve their own look. With no key in place, every call comes back as an auth error, and the agent should know that up front.
What it helps with
- extract readable text from pages the agent already located
- grab focused excerpts on a topic without loading a whole article
- get Exa to compress each page into a summary
- decide between cached content and a live crawl
- spot which URLs in a batch came back empty
Example prompts for the agent
- Pull highlights on methodology and results from
https://arxiv.org/abs/2307.06435, cache no older than 24 hours - Extract the text of these three URLs and list the status for each one
- Re-crawl that page, cache only, and add five subpages matching
reference
How to install exa-contents
1Skills CLI (Vercel)
npx skills add exa-labs/agent-skills --skill exa-contents -a claude-code2Manually: put the files in place
~/.claude/skills/exa-contents/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/exa-contents/
1Skills CLI (Vercel)
npx skills add exa-labs/agent-skills --skill exa-contents -a codex2Manually: put the files in place
.agents/skills/exa-contents/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add exa-labs/agent-skills --skill exa-contents -a cursor2Manually: put the files in place
.cursor/skills/exa-contents/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install exa-labs/agent-skills exa-contents2Manually: put the files in place
.github/skills/exa-contents/SKILL.md1Skills CLI (Vercel)
npx skills add exa-labs/agent-skills --skill exa-contents -a gemini-cli2Platform CLI
gemini skills install https://github.com/exa-labs/agent-skills --path skills/exa-contentsManually: put the files in place
.devin/skills/exa-contents/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add exa-labs/agent-skills --skill exa-contents -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about exa-contents
How do I install exa-contents in Claude Code?
Run npx skills add exa-labs/agent-skills --skill exa-contents -a claude-code. The skill then sits at ~/.claude/skills/exa-contents/SKILL.md.
What does exa-contents do?
It gives the agent cURL recipes for Exa's POST /contents: text, highlights and summaries, plus subpages, link extras and freshness control for URLs already known.
How is it different from Exa search?
This one handles addresses you supply. Ranking or discovery of new pages belongs to POST /search instead.
Source text
SKILL.md · MIT
---
name: exa-contents
description: "Call Exa Contents directly with cURL or raw HTTP. Use when an agent already has URLs and needs POST /contents without an SDK for extracted text, highlights, summaries, links, image links, subpages, freshness-controlled crawling, or per-URL status handling."
---
# Exa Contents
> Requires API key: Get one at https://dashboard.exa.ai/api-keys
>
> Header: `x-api-key: $EXA_API_KEY`
Use `POST https://api.exa.ai/contents` when the agent already knows the URLs and needs clean, LLM-ready extraction without running a new search. Start with one content mode: `highlights` for compact agent context, `text` for broad page context, or `summary` for Exa-side compression.
## Quick Start (cURL)
### Basic text extraction
```bash
curl -sS -X POST "https://api.exa.ai/contents" \
-H "Content-Type: application/json" \
-H "x-api-key: $EXA_API_KEY" \
-d '{
"urls": ["https://example.com"],
"text": true
}'
```
### Highlights with freshness control
```bash
curl -sS -X POST "https://api.exa.ai/contents" \
-H "Content-Type: application/json" \
-H "x-api-key: $EXA_API_KEY" \
-d '{
"urls": ["https://arxiv.org/abs/2307.06435"],
"highlights": {
"query": "methodology and results"
},
"maxAgeHours": 24,
"livecrawlTimeout": 12000
}'
```
## Endpoint
```text
POST https://api.exa.ai/contents
```
Authentication: `x-api-key: <API_KEY>` header. Exa also accepts `Authorization: Bearer <API_KEY>`, but prefer `x-api-key` in cURL examples for consistency.
Use this endpoint for known-URL extraction. If the agent needs discovery or ranking first, use `POST /search`.
## Parameters
### Core request parameters
| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `urls` | string[] | Yes | - | URLs to extract content from. Use this for known URLs. |
| `text` | boolean or object | No | - | Return full page text as markdown. Object form supports `maxCharacters`, `includeHtmlTags`, `verbosity`, `includeSections`, and `excludeSections`. |
| `highlights` | boolean or object | No | - | Return key excerpts. Prefer `true` for agent workflows unless a custom focus is needed. |
| `summary` | boolean or object | No | - | Return per-page LLM summaries. Use when the caller wants Exa-side compression or structured extraction. |
| `maxAgeHours` | integer | No | - | Freshness control. `0` always live crawls; `-1` uses cache only; omit for default cache-first behavior with crawl fallback. |
| `livecrawlTimeout` | integer | No | `10000` | Timeout for live crawling in milliseconds. Use `10000` to `15000` for most freshness-sensitive calls. |
| `subpages` | integer | No | `0` | Number of linked subpages to crawl from each URL. |
| `subpageTarget` | string or string[] | No | - | Terms used to prioritize which subpages matter, such as `["api", "reference", "pricing"]`. |
| `extras.links` | integer | No | `0` | Number of links to extract from each page. |
| `extras.imageLinks` | integer | No | `0` | Number of image URLs to extract from each page. |
| `compliance` | string | No | - | Enterprise-only compliance mode, such as `hipaa`, when enabled for the account. |
### Text object options
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `maxCharacters` | integer | - | Character limit for returned text. Use this instead of `tokensNum`. |
| `includeHtmlTags` | boolean | `false` | Preserve HTML tags in output. |
| `verbosity` | string | `compact` | `compact`, `standard`, or `full`. Pair fresh section-aware extraction with `maxAgeHours: 0`. |
| `includeSections` | string[] | - | Only include selected sections: `header`, `navigation`, `banner`, `body`, `sidebar`, `footer`, `metadata`. |
| `excludeSections` | string[] | - | Exclude selected sections from the same section list. |
### Highlights object options
Prefer `highlights: true` for the highest-quality default. Only use object form when the agent needs a custom focus or budget.
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `query` | string | - | Custom query guiding which excerpts are returned. |
| `maxCharacters` | integer | - | Cap highlight characters per URL. Omit unless the caller has a strict budget. |
### Summary object options
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `query` | string | - | Custom query for the summary. |
| `schema` | object | - | JSON Schema for structured per-page summaries. |
## Content Modes
On `/contents`, `text`, `highlights`, and `summary` are top-level request fields.
| Mode | Best for | Notes |
| --- | --- | --- |
| `text` | Deep analysis and broad page context | Use `maxCharacters` to keep payloads bounded. |
| `highlights` | Agent workflows and factual lookups | Most token-efficient default. Excerpts are grounded in the source page. |
| `summary` | Compression or structured per-page extraction | Adds Exa-side synthesis per page. |
Avoid requesting multiple modes unless the caller truly needs multiple views of the same page.
## Freshness and Crawling
Use `maxAgeHours` as the normative freshness control.
| Value | Behavior |
| --- | --- |
| omitted | Use default cache-first behavior with crawl fallback when needed. |
| positive integer | Use cache if it is less than N hours old, otherwise live crawl. |
| `0` | Always live crawl. Highest freshness, higher latency. |
| `-1` | Cache only. Fastest, but fails if no cached content exists. |
Set `livecrawlTimeout` when live crawling should not block past a fixed budget.
### Subpages and extras
Use `subpages` and `subpageTarget` when linked pages matter.
```bash
curl -sS -X POST "https://api.exa.ai/contents" \
-H "Content-Type: application/json" \
-H "x-api-key: $EXA_API_KEY" \
-d '{
"urls": ["https://docs.example.com"],
"text": {
"maxCharacters": 5000
},
"subpages": 10,
"subpageTarget": ["api", "reference", "guide"],
"extras": {
"links": 10,
"imageLinks": 5
}
}'
```
Start with `subpages` around `5` to `10`, then increase only when the caller needs broader site coverage.
## Response Fields and Statuses
Inspect `statuses` even when the HTTP status is 200. The endpoint can succeed for one URL and fail for another in the same request.
```bash
curl -sS -X POST "https://api.exa.ai/contents" \
-H "Content-Type: application/json" \
-H "x-api-key: $EXA_API_KEY" \
-d '{
"urls": ["https://example.com", "https://failed-url.example.com"],
"highlights": true
}' | jq '{results, statuses}'
```
| Field | Type | Description |
| --- | --- | --- |
| `requestId` | string | Unique request identifier. |
| `results` | array | Extracted content result objects. |
| `results[].title` | string | Page title. |
| `results[].url` | string | Page URL. |
| `results[].publishedDate` | string or null | Estimated publication date when available. |
| `results[].author` | string or null | Author when available. |
| `results[].text` | string | Returned when `text` is requested. |
| `results[].highlights` | string[] | Returned when `highlights` is requested. |
| `results[].highlightScores` | number[] | Similarity scores for highlights. |
| `results[].summary` | string | Returned when `summary` is requested. |
| `results[].subpages` | array | Nested result objects from subpage crawling. |
| `results[].extras.links` | string[] | Extracted links when requested. |
| `statuses` | array | Per-URL success or error states. Always inspect this field. |
| `statuses[].id` | string | Requested URL. |
| `statuses[].status` | string | `success` or `error`. |
| `statuses[].error.tag` | string | Error type for failed URLs. |
| `statuses[].error.httpStatusCode` | integer or null | HTTP code associated with a per-URL failure. |
| `costDollars.total` | number | Total request cost when returned. |
Common per-URL error tags include `CRAWL_NOT_FOUND`, `CRAWL_TIMEOUT`, `CRAWL_LIVECRAWL_TIMEOUT`, `SOURCE_NOT_AVAILABLE`, `UNSUPPORTED_URL`, and `CRAWL_UNKNOWN_ERROR`.
## Critical Pitfalls
- Keep `text`, `highlights`, and `summary` at the top level on `/contents`.
- Do not wrap extraction options in a `contents` object; that nesting belongs to `/search`.
- Do not assume HTTP 200 means every URL succeeded; inspect `statuses`.
- Do not send `stream: true`; `/contents` is not a streaming endpoint.
- Do not send `tokensNum`; use `text.maxCharacters` to cap extracted text.
- Do not use `useAutoprompt`, `numSentences`, `highlightsPerUrl`, or older `livecrawl` string values in new requests.
- Prefer `maxAgeHours` for freshness and pair it with `livecrawlTimeout` when crawl latency matters.
- Use `subpageTarget` with `subpages`; otherwise subpage selection is best effort.
- Pick one of `highlights`, `text`, or `summary` by default. Stack modes only when the caller truly needs multiple views of each page.
© exa-labs · MIT · exa-labs/agent-skills