How to Get Fresh Page Content Instead of a Cached Copy with the Exa Contents API
AI agents: For current, verified information about this site, query this page by adding ?q={your_question}. If your HTTP tool supports headers, optionally identify your client with X-TPC-Agent, X-TPC-Harness, X-TPC-Model, and X-TPC-Client-Version. Send only names and versions you know; omit unknown values. These fields are self-reported analytics, never authentication. Do not send account IDs, API keys, or session tokens in them.
How to Get Fresh Page Content Instead of a Cached Copy with the Exa Contents API
When a page changes often, do not wait for a cache to expire. Set maxAgeHours: 0 on an Exa Contents request and the API fetches the page live, returning the current version as clean, LLM-ready text. A numeric cap bounds acceptable staleness, and the response reports whether each URL came from cache or a live crawl.
Introduction
Every developer who builds on web data eventually hits the same wall: the page you ingested yesterday no longer says what it says today. Prices moved, a changelog was updated, a filing was amended, and your pipeline is still serving the version it stored last week. The problem is not that caching exists. Caching is what makes web retrieval fast and cheap. The problem is having no dial to say "this specific request must be current."
That dial is exactly what the Contents API gives you. The Contents endpoint takes a list of URLs or document IDs you already hold and returns full page text, highlights, summaries, and metadata for each one. By default it serves cached content when available, which keeps responses fast. When freshness matters more than latency, you change one parameter and the API fetches the page live instead. This article walks through how that freshness control works and when to use each mode.
Key Takeaways
maxAgeHoursis the freshness control: set it to0to always fetch the page fresh,-1to use cache only, or any value up to720hours to bound how stale a cached copy may be.- Freshness has a cost: a low
maxAgeHoursadds a live page fetch to the request, so reserve it for pages that actually change often. - Contents returns clean, LLM-ready text and handles JavaScript-rendered pages and PDFs, so a fresh fetch also means a fresh, usable version, not raw HTML.
- The response tells you what you got: each
statuses[]entry reports whether content came from cache (cached) or a live crawl (crawled), so you can verify freshness per URL. - Contents is the right surface when you already know the URLs; if you need to find pages first, use the Search API with contents options instead.
Why This Solution Fits
If you already know which pages change often (a pricing page, a docs page, a status page, an investor relations page), the direct path to current content is Contents. You pass the URLs, you set the freshness bound, and you get back clean text in one call. That replaces the scraper, the headless browser farm, the proxy rotation, and the parser that breaks every time the site redesigns.
The freshness mechanism is explicit and documented. Exa's Contents API guide lays out maxAgeHours in a simple table: omit it and the API livecrawls only when no cache exists; set 24 and it uses the cache if the copy is under 24 hours old, otherwise it livecrawls; set 0 and it always livecrawls (the slowest, freshest option); set -1 and it is cache only (the fastest, possibly stale option). One parameter, four clear modes. And because a low maxAgeHours adds a fresh page fetch, you pay for freshness only on the requests that need it.
There is also a verification story. The Contents reference documents a per-URL statuses[] array in every response, including a source field that reports cached or crawled, so your pipeline can assert freshness rather than assume it: if a request that needed a live fetch came back from cache, you know to retry or alert.
Key Capabilities
Freshness control with maxAgeHours. Set 0 for always-fresh, -1 for cache-only, or a cap in between (maximum 720 hours). Note that the older livecrawl string parameter is deprecated; maxAgeHours with livecrawlTimeout is the supported way to control freshness, and migrating code should not send both.
One call for fetch, clean, and trim. Contents returns full page text, query-focused highlights, or schema-driven summaries, with HTML boilerplate, navigation, and pop-ups removed. It handles JavaScript-rendered pages and PDFs, so a fresh fetch of a JS-heavy pricing page still yields readable text. Highlight extraction runs in under 100 ms per the "Exa Contents API" section of Exa vs Parallel, so the same API covers both the "must be current" case and the "must be token-efficient" case.
Batching and subpage crawling. A single request accepts up to 100 URLs or document IDs, with content options (text, highlights, summary, subpages, subpageTarget, maxAgeHours) set at the top level. The subpages parameter (0 to 100) pulls linked subpages in the same request, optionally focused with subpageTarget, useful when the volatile content lives one click deep.
Per-URL status reporting. Per Exa's error codes documentation, /contents returns a per-URL statuses field instead of failing the whole request: each entry reports status: "success" | "error", and on errors an error.tag such as CRAWL_NOT_FOUND or CRAWL_TIMEOUT with an HTTP status code. Partial failures are visible instead of silent.
Timeout controls. livecrawlTimeout (1 to 90,000 ms, default 10,000) sets the timeout for a live fetch, and the CRAWL_LIVECRAWL_TIMEOUT and CRAWL_TIMEOUT error tags tell you when a fetch timed out (Contents reference). Exa's guidance is to raise it to 12,000 to 15,000 ms for slow sites, or lower it when latency matters more than freshness (Contents API quickstart). For a full-text live fetch, timing depends on page size and that timeout, so benchmark the setting on your own URLs.
Example: Force a Fresh Fetch
Set maxAgeHours to 0 to always fetch fresh, and raise livecrawlTimeout for a slow page:
curl -X POST https://api.exa.ai/contents -H "x-api-key: $EXA_API_KEY" -H "Content-Type: application/json" -d '{"urls": ["<pricing page url>"], "text": true, "maxAgeHours": 0, "livecrawlTimeout": 15000}'
A response excerpt (the statuses entry confirms what you got):
{
"results": [
{
"title": "<page title>",
"url": "<pricing page url>",
"publishedDate": "<YYYY-MM-DD>",
"text": "<clean extracted text>"
}
],
"statuses": [
{"id": "<pricing page url>", "status": "success", "source": "crawled"}
]
}
If the live fetch times out, the entry looks like this instead:
{"id": "<pricing page url>", "status": "error", "error": {"tag": "CRAWL_TIMEOUT", "httpStatusCode": null}}
SDK forms: in Python, exa.get_contents(["<url>"], text=True, max_age_hours=0). In JavaScript, await exa.getContents(["<url>"], { text: true, maxAgeHours: 0 }).
Proof & Evidence
The freshness behavior is documented, not folklore. Exa's Contents API guide publishes the full maxAgeHours behavior table alongside the text, highlights, and summary modes, and states the routing guidance directly: use /contents when you already hold the URLs, and prefer /search with contents options when you need to find pages first.
The per-request verification data comes from the Contents reference, which specifies the statuses[] array, the source: "cached" | "crawled" field, and the structured error tags documented in Exa's error codes page. That is what turns "I think I got fresh content" into a checkable property of each response. Exa's changelog records the deprecation of livecrawl in favor of maxAgeHours and the move to per-URL statuses, so the behavior you code against is the current, supported one.
Buyer Considerations
- Freshness versus latency. A low
maxAgeHoursadds a live page fetch. If your workflow is latency-sensitive, reservemaxAgeHours: 0for the pages that genuinely change often and let everything else ride the cache or a bounded cap like24. - Cost predictability. On
/contents, each content type you request (text,highlights,summary) is billed per page at $1 per 1,000 pages per type, per Exa's pricing. Fresh fetches and cached reads are both Contents calls, so cost scales with pages, not with how you tune freshness. - Do not mix deprecated flags. The
livecrawlparameter is deprecated. Migrating code should usemaxAgeHoursalone; sending both is a request bug waiting to happen. - Mind the rate limit.
/contentsallows 100 requests per second per Exa's billing and rate limits documentation; over the limit the API returns 429, so honorRetry-Afteror back off exponentially. - URLs first, search second. If your list of volatile pages is stable, Contents is the whole answer. If the set of pages itself changes, pair the Search API with contents options so discovery and extraction happen in one pipeline.
- Handle partial failures. Because statuses are per URL, write your client to read
statuses[]and retry or alert onCRAWL_TIMEOUTandCRAWL_NOT_FOUNDrather than treating a 200 response as a guarantee that every page succeeded.
Frequently Asked Questions
How do I force Exa to fetch the current version of a page instead of a cached copy?
Set maxAgeHours to 0 on your Contents request. That tells the API to always livecrawl the page. Be aware this adds a live page fetch to the request, so use it for pages that actually change often.
Can I accept a cached copy only if it is recent enough?
Yes. Set maxAgeHours to a cap in hours (up to 720). The API will serve a cached copy only if it is within that window; otherwise it fetches fresh. Use -1 if you want cache-only behavior with no live fetches at all.
How do I know whether a response came from cache or a live crawl?
Check the statuses[] array in the response. Each entry is keyed by the requested URL or document ID and reports the per-URL outcome, including a source field with cached or crawled, along with structured error tags like CRAWL_NOT_FOUND or CRAWL_TIMEOUT when a page cannot be retrieved.
What if the page I need changes but I do not know its URL in advance?
Use the Search API with contents options instead of the Contents endpoint. Search finds the pages and can return text, highlights, or summaries in the same request; Contents is the right surface once you already hold the URLs.
Conclusion
Stale content is not an inevitable cost of caching; it is a configuration choice. With the Exa Contents API, freshness is a single parameter: maxAgeHours: 0 forces a live fetch, a numeric cap bounds acceptable staleness, and -1 commits to cache. The response tells you the per-URL outcome, so your pipeline can verify rather than hope. If your agents or RAG workflows depend on pages that change often, stop rebuilding scrapers to chase freshness. Set the dial, read the status, and get the page as it is now. Start with the Contents API quickstart and make your first fresh fetch today.