Why use Exa for enrichments?
1. Superior Search
Faster, more relevant, and more comprehensive than alternatives
2. Find, don't buy
3rd party enrichment services rely on purchasing stale data. Search across the web in real time instead.
3. Configurable
Exa's model parameters can dynamically be adjusted for any use case
Pipeline Summary
Query records → Call Exa Deep Search for each → Get structured data → Update database
Exa Monitor fires weekly → Delivers fresh structured data to webhook → Push updates to database
Daily cron queries new accounts → Exa Deep Search enriches → Auto-sync on ingest
Exa Deep Search API Format
Exa Deep Search performs a thorough web search and returns structured data via outputSchema in a single API call. Define your JSON Schema and get back exactly the fields you need—no separate LLM step required.
import Exa from 'exa-js';
const exa = new Exa(process.env.EXA_API_KEY);
// Define your output schema (JSON Schema format)
const companySchema = {
type: 'object',
properties: {
industry: {
type: 'string',
description: 'Company sector (e.g., "Financial Services", "Developer Tools")'
},
annualRevenue: {
type: 'string',
description: 'Estimated annual revenue or ARR (e.g., "$50M ARR", "$1.2B")'
},
employeeCount: {
type: 'string',
description: 'Approximate number of employees (e.g., "8,000", "~500")'
},
ownership: {
type: 'string',
description: 'Ownership status: Private, Public, or Subsidiary'
},
latestFunding: {
type: 'string',
description: 'Most recent funding round (e.g., "Series C - $150M")'
},
recentNews: {
type: 'string',
description: 'Most notable recent news or announcement'
},
description: {
type: 'string',
description: 'Brief company description (1-2 sentences)'
}
},
required: ['industry', 'description']
};
// Call Exa Deep Search with structured output
const response = await exa.search(
'Company profile and information for Stripe',
{
type: 'deep',
category: 'company',
outputSchema: companySchema,
}
);
// Parse structured output
const enrichment = JSON.parse(response.output.content);
console.log(enrichment);
// {
// "industry": "Financial Services",
// "annualRevenue": "$14.3B revenue",
// "employeeCount": "8,000",
// "ownership": "Private",
// "latestFunding": "Series I - $6.5B at $50B valuation",
// "recentNews": "Launched Stripe Billing 2.0",
// "description": "Payment infrastructure for the internet"
// }
// Grounding citations for transparency
const sources = response.output.grounding.flatMap(
g => g.citations.map(c => c.url)
);
console.log(sources);
// ["https://stripe.com/about", "https://techcrunch.com/...", ...]required for must-have fields and enum for fields with fixed options. Add description to guide extraction.Phase 1: Initial Backfill
One-time enrichment of your existing company records
Query your company records
Query your database to get the company names and domains you want to enrich, along with the record IDs for syncing data back. This example uses a generic CRM, but works with any database.
// Query all accounts with websites
const result = await crm.query(`
SELECT Id, Name, Website FROM Account WHERE Website != null
`);
const accounts = result.records.map(acc => ({
id: acc.Id,
name: acc.Name,
domain: new URL(acc.Website).hostname,
}));Enrich with Exa Deep Search
For each company, call Exa Deep Search with your outputSchema. This single API call performs a thorough web search and extracts exactly the fields you need—no separate LLM step required.
import Exa from 'exa-js';
const exa = new Exa(process.env.EXA_API_KEY);
async function enrichCompany(companyName, domain) {
const response = await exa.search(
`Company profile and information for ${companyName} (${domain})`,
{
type: 'deep',
category: 'company',
outputSchema: companySchema,
}
);
const enrichment = JSON.parse(response.output.content);
const sources = response.output.grounding.flatMap(
g => g.citations.map(c => c.url)
);
return { data: enrichment, sources };
}Update your database with enriched data
Push the structured data back to your database, mapping fields to your schema.
async function updateCRMAccount(accountId, enrichedData) {
await crm.sobjects.Account.update({
Id: accountId,
Industry: enrichedData.industry,
NumberOfEmployees: enrichedData.employeeCount,
Description: enrichedData.description,
Last_Enriched__c: new Date().toISOString(),
});
}Run the complete backfill pipeline
Orchestrate all the steps with concurrency control and error handling.
// Process accounts with concurrency limit
for (const account of accounts) {
const { data } = await enrichCompany(account.name, account.domain);
await updateCRMAccount(account.id, data);
}Phase 2: Weekly Refresh with Exa Monitors
Automated weekly re-enrichment using Exa Monitors — no cron jobs needed
Create a Monitor for each company
Use the Monitors API to set up a recurring search with your enrichment schema. Exa handles scheduling, execution, and deduplication automatically.
import Exa from 'exa-js';
const exa = new Exa(process.env.EXA_API_KEY);
const monitor = await exa.monitors.create({
name: 'Stripe enrichment',
search: {
query: 'Company profile and information for Stripe (stripe.com)',
numResults: 5,
},
outputSchema: companySchema,
trigger: {
interval: 'weekly',
},
webhook: {
url: 'https://your-app.com/api/enrichment-webhook',
},
});
// Store the webhook secret — only returned on creation
console.log(monitor.webhookSecret);Set up your webhook endpoint
Create an endpoint to receive structured enrichment data when the monitor fires.
app.post('/api/enrichment-webhook', async (req, res) => {
res.status(200).send('OK');
const { output, metadata } = req.body;
if (output?.results) {
for (const result of output.results) {
const enrichment = JSON.parse(result.output.content);
const sources = result.output.grounding.flatMap(
g => g.citations.map(c => c.url)
);
await updateCRMAccount(metadata.accountId, enrichment);
}
}
});Create monitors for all accounts
Loop through your accounts and create a monitor for each one. Use metadata to link monitor results back to your database records.
for (const account of accounts) {
await exa.monitors.create({
name: `${account.name} enrichment`,
search: {
query: `Company profile and information for ${account.name} (${account.domain})`,
numResults: 5,
},
outputSchema: companySchema,
trigger: { interval: 'weekly' },
webhook: { url: 'https://your-app.com/api/enrichment-webhook' },
metadata: { accountId: account.id },
});
}Manage your monitors
Pause, update, or trigger monitors on demand as needed.
// Pause a monitor
await exa.monitors.update(monitor.id, { status: 'paused' });
// Trigger a run immediately (works for active or paused monitors)
await exa.monitors.trigger(monitor.id);
// List all monitors
const monitors = await exa.monitors.list({ status: 'active' });
// Delete a monitor
await exa.monitors.delete(monitor.id);Phase 3: New Record Ingestion
Automatically enrich new records as they're added to your database
Set up a daily cron job
Schedule a daily job to detect and enrich new records added in the last 24 hours.
import cron from 'node-cron';
// Run daily at 7 AM
cron.schedule('0 7 * * *', async () => {
// New account enrichment logic here
});Query new records
Fetch accounts created today that haven't been enriched yet.
const newAccounts = await crm.query(
'SELECT Id, Name, Website FROM Account WHERE CreatedDate = TODAY'
);Enrich and sync each new record
Call Exa Deep Search for each new account and push the enriched data back.
for (const account of newAccounts.records) {
const { data } = await enrichCompany(account.Name, account.Website);
await updateCRMAccount(account.Id, data);
}Alternative: Real-time webhook
For instant enrichment, set up a webhook that triggers when a new account is created.
// Webhook endpoint for real-time enrichment
app.post('/webhook/new-account', async (req, res) => {
res.status(200).send('OK');
const { data } = await enrichCompany(req.body.name, req.body.website);
await updateCRMAccount(req.body.id, data);
});That's it!
You now have a complete enrichment pipeline: initial backfill for existing records, weekly refresh with Exa Monitors to keep data current, and automatic enrichment for new accounts. Exa handles all the web search, scheduling, and data extraction for you.