Overview
Base URL:https://api.exa.ai/monitors
Auth: Pass your API key via the x-api-key header. Get one at https://dashboard.exa.ai/api-keys
Monitors are scheduled, recurring Exa searches. You define a search query and a cron schedule, and the system runs the search automatically and delivers results to your webhook. Each run automatically deduplicates against previous results so you only see new content.
Installation
Minimal Working Example
- Python
- JavaScript
- cURL
Endpoints
POST /monitors — Create a Monitor
Creates a monitor and returns it with a one-time webhookSecret.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Display name for the monitor. |
search | object | Yes | Search configuration. See Search Parameters. |
trigger | object | No | Cron schedule. See Trigger. Omit for manual-only monitors. |
outputSchema | object | No | JSON Schema for structured output. See Output Schema. |
metadata | object | No | Arbitrary key-value pairs for your own tracking. |
webhook | object | Yes | Webhook configuration. See Webhook. |
webhookSecret field (string). Store this secret immediately — it is only returned once and is needed for webhook signature verification.
GET /monitors — List Monitors
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
status | string | — | Filter by status: active, paused, or disabled. |
cursor | string | — | Pagination cursor from a previous response’s nextCursor. |
limit | integer | 50 | Results per page (1-100). |
{ "data": [Monitor, ...], "hasMore": boolean, "nextCursor": string | null }
GET /monitors/{id} — Get a Monitor
Response: A Monitor object.
PATCH /monitors/{id} — Update a Monitor
All fields are optional. For search, you can send a partial object (only the fields you want to change). Set trigger to null to remove the schedule.
Request body:
| Field | Type | Description |
|---|---|---|
name | string | Updated name. |
status | string | active or paused. |
search | object | Partial search params to merge. |
trigger | object or null | New cron trigger, or null to remove. |
outputSchema | object or null | New output schema, or null to remove. See Output Schema. |
metadata | object or null | New metadata, or null to remove. |
webhook | object | Partial webhook params to merge. |
DELETE /monitors/{id} — Delete a Monitor
Response: The deleted Monitor object.
POST /monitors/{id}/trigger — Trigger a Run
Starts a run immediately, regardless of the cron schedule. Works for active and paused monitors.
Response: { "triggered": true }
GET /monitors/{id}/runs — List Runs
Query params:
| Param | Type | Default | Description |
|---|---|---|---|
cursor | string | — | Pagination cursor. |
limit | integer | 50 | Results per page (1-100). |
{ "data": [Run, ...], "hasMore": boolean, "nextCursor": string | null }
GET /monitors/{id}/runs/{runId} — Get a Run
Response: A Run object.
Search Parameters
Nested undersearch in the create/update request.
| Parameter | Type | Default | Description |
|---|---|---|---|
query | string | (required) | The search query to run. Supports natural language descriptions. |
numResults | integer | 10 | Number of results per run (1-100). |
contents | object | — | Content extraction options. See Contents Parameters. |
Contents Parameters
Nested undersearch.contents. All fields are optional.
| Parameter | Type | Description |
|---|---|---|
text | boolean or object | Return full page text as markdown. Object form: { maxCharacters, includeHtmlTags, verbosity, includeSections, excludeSections }. |
highlights | boolean or object | Return key excerpts. Object form: { query, maxCharacters }. |
summary | boolean or object | Return LLM-generated summary. Object form: { query, maxTokens }. |
extras | object | Extract links and media: { links, imageLinks, richImageLinks, richLinks, codeBlocks } (all integers 0-1000). |
context | boolean or object | Return surrounding context. Object form: { maxCharacters }. |
livecrawl | string | Crawl strategy: "never", "always", "fallback", "auto", or "preferred". |
livecrawlTimeout | integer | Livecrawl timeout in ms (0-90000). |
maxAgeHours | integer | Max age of cached content in hours. 0 = always livecrawl. -1 = never livecrawl. |
filterEmptyResults | boolean | Filter out results with no content. |
subpages | integer | Number of subpages to crawl per result (0-100). |
subpageTarget | string or string[] | Keywords to prioritize when selecting subpages. |
Text Object Options
| Parameter | Type | Description |
|---|---|---|
maxCharacters | integer | Character limit for returned text. |
includeHtmlTags | boolean | Preserve HTML tags in output. |
verbosity | string | "compact", "standard", or "full". |
includeSections | string[] | Only include these page sections: header, navigation, banner, body, sidebar, footer, metadata. |
excludeSections | string[] | Exclude these page sections. Same options as above. |
Highlights Object Options
| Parameter | Type | Description |
|---|---|---|
query | string | Custom query to direct highlight selection. |
maxCharacters | integer | Maximum characters for all highlights combined. |
Summary Object Options
| Parameter | Type | Description |
|---|---|---|
query | string | Custom query for the summary. |
maxTokens | integer | Maximum tokens for the summary. |
Trigger
Nested undertrigger in the create/update request.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Must be "cron". |
expression | string | Yes | Standard 5-field Unix cron expression. Minimum interval is 1 hour. |
timezone | string | No | IANA timezone identifier. Defaults to "Etc/UTC". |
Webhook
Nested underwebhook in the create/update request.
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS URL. Must be a public endpoint (no localhost or private IPs). |
events | string[] | No | Which events to deliver. Omit to receive all events. |
Webhook Events
| Event | Fired When |
|---|---|
monitor.created | A new monitor is created |
monitor.updated | A monitor is updated |
monitor.deleted | A monitor is deleted |
monitor.run.created | A new run starts |
monitor.run.completed | A run finishes (success or failure) |
Webhook Payload
data field contains the full monitor or run object depending on event type.
Webhook Signature Verification
Webhook signature verification lets you confirm that incoming webhook requests actually came from Exa and haven’t been tampered with. Without verification, any external party that discovers your webhook URL could send fake payloads to your endpoint. Use thewebhookSecret returned from the create endpoint to verify signatures on every incoming request.
Every webhook delivery includes an Exa-Signature header:
- Extract
t(timestamp) andv1(signature) from the header - Construct the signed payload:
{t}.{request_body} - Compute HMAC-SHA256 of the signed payload using your webhook secret
- Compare the computed signature with
v1using constant-time comparison
- Python
- JavaScript
Object Schemas
Monitor Object
| Field | Type | Description |
|---|---|---|
id | string | Unique monitor identifier. |
name | string or null | Display name. |
status | string | "active", "paused", or "disabled". See Monitor Statuses. |
search | object | The search configuration. |
trigger | object or null | The cron trigger, or null if manual-only. |
outputSchema | object or null | JSON Schema for structured output. |
metadata | object or null | Your custom key-value pairs. |
webhook | object | { url, events }. |
nextRunAt | string or null | ISO 8601 timestamp of the next scheduled run. null if no trigger. |
createdAt | string | ISO 8601 creation timestamp. |
updatedAt | string | ISO 8601 last-update timestamp. |
Run Object
| Field | Type | Description |
|---|---|---|
id | string | Unique run identifier. |
monitorId | string | Parent monitor ID. |
status | string | "pending", "running", "completed", "failed", or "cancelled". |
output | object or null | Search results and output. null until completed. |
output.results | array | Array of search result objects (title, url, publishedDate, etc.). |
output.content | any | Structured output when outputSchema is set. |
output.grounding | array | Field-level citations with confidence. See Grounding. |
failReason | string or null | Why the run failed. See Fail Reasons. |
startedAt | string or null | ISO 8601 timestamp when execution began. |
completedAt | string or null | ISO 8601 timestamp when execution finished. |
failedAt | string or null | ISO 8601 timestamp if the run failed. |
cancelledAt | string or null | ISO 8601 timestamp if the run was cancelled. |
durationMs | integer or null | Total execution time in milliseconds. |
createdAt | string | ISO 8601 creation timestamp. |
updatedAt | string | ISO 8601 last-update timestamp. |
Grounding
Each entry inoutput.grounding provides source citations for a field in the output:
| Field | Type | Description |
|---|---|---|
field | string | The output field path (e.g. "content", "results[0].title"). |
citations | array | Sources: { url, title }. Duplicate URLs are deduplicated. |
confidence | string | "low", "medium", or "high". |
Monitor Statuses
Monitors have three possible statuses. Anactive monitor runs on its cron schedule and accepts manual triggers. A paused monitor stops running on schedule but still accepts manual triggers via the trigger endpoint — useful for temporarily halting a monitor without deleting it. A disabled monitor does not run at all; this status is set automatically by the system and cannot be set via the API.
Fail Reasons
| Reason | Description | Action |
|---|---|---|
api_key_invalid | API key is invalid or revoked. | Update your API key. Monitor auto-disables after 10 consecutive failures with this reason. |
insufficient_credits | Not enough credits. | Add credits to your account. |
invalid_params | Search parameters are invalid. | Fix the monitor’s search configuration. |
rate_limited | Too many concurrent requests. | Reduce monitor frequency or wait. |
search_unavailable | Exa search backend is temporarily down. | Retries on next scheduled run. |
search_failed | Search execution failed. | Check search parameters. Contact support if persistent. |
internal_error | Unexpected error. | Contact support if persistent. |
Output Schema
outputSchema controls how the search synthesizes results into structured output. It supports two modes:
Text mode (default when no schema is provided)
WhenoutputSchema is omitted or set to { "type": "text" }, the run output’s content field contains a plain text summary synthesized from the search results.
description field guides the synthesis. When outputSchema is omitted entirely, the system generates a text summary based on the search query.
Object mode
Whentype is "object", you provide a JSON Schema that defines the structure of the output. The search extracts and organizes information from results to match your schema.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | "text" or "object". |
description | string | No | Guides the synthesis. Useful for both modes. |
properties | object | When type: "object" | JSON Schema properties definition. |
required | string[] | No | Which properties are required in the output. |
additionalProperties | boolean | No | Whether extra fields are allowed. Defaults to false. |
outputSchema is set, completed runs include:
output.contentshaped to your schemaoutput.groundingwith field-level citations and confidence scores
Automatic Deduplication
Monitors deduplicate results across runs using two layers: Date-based filtering. Each run only fetches content published or crawled since the last run. The system uses the cron schedule to compute a time window with a 2x overlap buffer, so content published between runs is captured even with slight timing variations. Semantic deduplication. The system tracks outputs from the last 5 runs and uses them to focus on new developments. This prevents the same stories or data points from appearing repeatedly.Error Handling
| HTTP Status | Meaning |
|---|---|
| 400 | Bad request. Invalid parameters or invalid cron expression. |
| 401 | Invalid or missing API key. |
| 404 | Monitor or run not found. |
| 422 | Validation error. Check parameter types and constraints. |
| 429 | Rate limit exceeded. |
Common Mistakes
Patterns and Gotchas
- Do not set a
typefield on search params. Monitors handle this internally. Runs typically take 5-60 seconds. - Store
webhookSecretimmediately. It is only returned in the create response and is needed for webhook signature verification. It cannot be retrieved later. - Use
triggerfor automation, manual trigger for testing. You can create a monitor without a trigger and usePOST /monitors/{id}/triggerto run it on demand. This is useful for testing before adding a schedule. - Paused monitors still accept manual triggers. Set status to
pausedto stop the cron schedule while keeping the monitor available for on-demand runs. outputSchemacontrols structured output. See Output Schema for details ontype: "text"vstype: "object".- Python SDK response attributes use snake_case. Access response fields with snake_case:
monitor.webhook_secret,monitor.next_run_at,run.fail_reason. Request dicts use camelCase keys matching the API (e.g.,{"numResults": 10}). Alternatively, use typed Pydantic models (CreateSearchMonitorParams,UpdateSearchMonitorParams) with snake_case field names. - Webhook events default to all. If you omit
eventsin the webhook config, all event types are delivered. - Overlap prevention. If a run is still in progress when the next scheduled time arrives, the in-progress run is cancelled.
SDK Auto-Pagination Helpers
Both SDKs provide helpers that handle pagination automatically when listing monitors or runs.- Python
- JavaScript
| SDK | List (single page) | Iterate all (auto-paginate) | Collect all |
|---|---|---|---|
| Python | exa.monitors.list() | exa.monitors.list_all() | exa.monitors.get_all() |
| Python (runs) | exa.monitors.runs.list(id) | exa.monitors.runs.list_all(id) | exa.monitors.runs.get_all(id) |
| JavaScript | exa.monitors.list() | exa.monitors.listAll() | exa.monitors.getAll() |
| JavaScript (runs) | exa.monitors.runs.list(id) | exa.monitors.runs.listAll(id) | exa.monitors.runs.getAll(id) |
Complete Examples
Monitor with structured output and contents
Manual-only monitor (no schedule)
Full lifecycle
- Python
- JavaScript

