Migrate Websets to the Agent API
Migrates code from Exa’s Websets API (POST /websets/v0/websets, exa.websets.*) to the Agent API (POST /agent/runs, exa.agent.runs.*).
Both APIs are live; this is a contract migration — request shape, delivery model, response parsing — not a rewrite.
Do not start new collection-building work on Websets; new work goes directly to /agent (see agent.md).
Canonical Docs Links
- Agent API guide:
https://exa.ai/docs/reference/agent-api-guide
- Create a run:
https://exa.ai/docs/reference/agent-api/create-a-run
- Websets overview (legacy):
https://exa.ai/docs/reference/websets-api
When this guide and the live docs disagree on an API fact, the docs win. Never invent model availability, limits, defaults, or API behavior: if a fact is in neither this guide nor the docs, verify it against the live API — or state that you could not.
Contents
- Step 1: inventory Websets usage
- Step 2: classify each call site by integration style
- The strict-schema warning
- Request mapping
- Migration example
- Delivery and response mapping
- Top pitfalls
- Step-by-step procedure
- Verification loop
- Maintaining existing Websets integrations
Step 1: Inventory Websets Usage
Find every call site before touching code:
Turn the hits into a file-by-file checklist. Each hit is either a call site to migrate, a webhook consumer to rewrite, or intentional legacy prose to leave alone.
Step 2: Classify Each Call Site by Integration Style
Classify before touching code, and KEEP the style — do not “upgrade” raw HTTP to an SDK or swap SDKs.
- (a) Raw HTTP (
requests, fetch, curl to https://api.exa.ai/websets/v0/...):
migrate to POST https://api.exa.ai/agent/runs with the same HTTP library.
- (b) Python SDK (
exa.websets.*):
switch to exa.agent.runs.create(...) and exa.agent.runs.poll_until_finished(run_id).
Same client construction, same API key.
- (c) TypeScript SDK (
exa.websets.*):
switch to exa.agent.runs.create({...}) and exa.agent.runs.pollUntilFinished(runId).
- (d) Webhook consumers:
the Agent API has no webhooks. Replace push delivery with polling
GET /agent/runs/{id} or the SSE events stream (GET /agent/runs/{id}/events). If a downstream system needs push, keep a thin poller that forwards on terminal status.
Do NOT migrate:
- Ordinary
/search or /contents code — different surfaces, unaffected.
- Code already on the Agent API (
/agent/runs, agent.runs.*).
- The standalone Monitors API at
/monitors — a different product (monitors.md). Only Websets-owned monitor subresources under /websets/v0/monitors are affected; recreate those on the standalone Monitors API.
- Historical or factual references to Websets in prose.
The Strict-Schema Warning
THE #1 MIGRATION FAILURE: /agent/runs rejects ANY unrecognized field with HTTP 400 and no run created:
{"error": {"type": "INVALID_REQUEST", ..., "detail": "[{\"code\": \"unrecognized_keys\", \"keys\": [\"count\"], ...}]"}}
Websets fields such as count, entity, criteria, enrichments, and externalId do not exist on Agent runs.
Map every field per the table below; nothing gets carried over unmapped.
Request Mapping
Migration Example
Websets create call:
Agent API equivalent:
Delivery and Response Mapping
Websets pushes items incrementally through webhooks and events. Agent runs deliver once, at a terminal status:
- Create the run; save the returned
id (agent_run_ prefix).
- Poll
GET /agent/runs/{id} until status is completed, failed, or cancelled — or stream events with Accept: text/event-stream (replay with Last-Event-ID).
- Read
output.structured (validated against outputSchema), output.text, output.grounding for citations, and costDollars.
Always check how the run ended before reading output; only completed carries results. Exiting only on completed hangs for failed runs.
Top Pitfalls
- Leftover Websets fields → 400
unrecognized_keys. Strict schema (above). Check every request body against the mapping table.
- No webhooks on Agent. Consumers that only react to webhook delivery wait forever; rewrite to poll or stream events (Step 2d).
- Unbounded arrays.
count maps to maxItems; omitting it makes output size and enrichment cost unpredictable.
criteria semantics must move into the query text. The agent verifies against the query’s constraints; dropping verification rules silently changes results. Call out any criteria you cannot express.
- Do not paste row sets into
query. Existing rows to enrich go in input.data; records to suppress go in input.exclusion.
previousRunId must reference a completed run in the same team. A follow-up is a new create request returning a new run ID; it does not reuse the prior run’s object.
budget.maxCostDollars is compatibility-only and documented as ignored. Do not port Websets cost expectations onto it; bound cost through maxItems and effort instead.
- Websets-owned monitors are not part of the run. Recreate recurring refresh on the standalone Monitors API (
/monitors).
- Data retention changes in your favor.
/agent is a Zero Data Retention surface; Websets is not. Update compliance notes that assumed otherwise.
Step-by-Step Procedure
- Inventory with the
rg pattern above; build a checklist.
- Classify each call site by integration style (Step 2).
- Rewrite the request per the mapping table:
query absorbs entity and criteria; outputSchema absorbs enrichments and count; strip everything else.
- Rewrite delivery: webhooks and event subscriptions become polling or SSE event consumption that handles all terminal statuses (
completed, failed, and cancelled).
- Verify per the loop below.
Minimal-diff rule: keep the author’s structure, naming, and voice; do not refactor unrelated code. If a Websets feature has no Agent equivalent, remove it AND call the removal out in your migration notes; never drop user-facing output silently.
Verification Loop
- Compile or typecheck every changed file.
- Run one real request per distinct call shape: a small
maxItems, modest effort, poll to a terminal status, and confirm output.structured matches the schema. Never fabricate response JSON.
- Grep for leftovers:
websets/v0, \.websets\., externalId, enrichments, criteria. Every remaining hit must be intentional legacy prose.
Maintaining Existing Websets Integrations
Only for code that stays on Websets during a staged migration:
- Base URL is
https://api.exa.ai/websets/v0; both SDKs expose websets namespaces.
- Core objects:
Webset (container), Search (async discovery job), Item (structured result), Enrichment (async extraction job).
- The lifecycle is async and event-driven: searches find and verify candidates, matching results become items, enrichments add fields, and webhooks/events report progress. Expect seconds-to-minutes latency.
externalId provides idempotency for create calls across retries.
- Websets is not a Zero Data Retention surface.
Do not expand existing Websets integrations with new workflows; add those on /agent.Last modified on August 13, 2026