We build custom applications 5x faster and cheaper 🚀
Book a Free Consultation
Stuck on an error? Book a 30-minute call with an engineer and get a direct fix + next steps. No pressure, no commitment.
Integrate Tavily Web Search with OpenClaw by building a small connector skill that calls Tavily’s REST API (using either an API key or OAuth), hosting any long‑lived pieces outside the agent (OAuth callback endpoint, token storage, webhooks, schedulers), configuring the skill and secrets through ClawHub, and ensuring runtime code handles authentication (including token refresh), request validation, and error/logging so that agents can invoke the skill reliably. Test with real API responses, validate webhook signatures, and debug by inspecting logs, HTTP responses, and token scopes.
// server.js
// Minimal connector that proxies a search request to Tavily using an API key.
// Replace TAVILY_API_BASE and process.env.TAVILY_API_KEY with your values.
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
// POST /search -> { query: "..." }
app.post('/search', async (req, res) => {
// // validate input
const q = (req.body && req.body.query) || '';
if (!q) return res.status(400).json({ error: 'missing query' });
try {
const r = await fetch('https://api.tavily.example/v1/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `ApiKey ${process.env.TAVILY_API_KEY}` // // send API key in header
},
body: JSON.stringify({ q })
});
const payload = await r.json();
// // forward or transform fields as needed
return res.status(r.status).json(payload);
} catch (err) {
console.error('search error', err);
return res.status(500).json({ error: 'upstream error' });
}
});
app.listen(3000, () => console.log('connector listening 3000'));
// skillHandler.js
// This runs inside the agent as a stateless handler.
// It calls your external connector which holds the real API key.
const fetch = require('node-fetch');
module.exports = async function handleInvocation(input) {
// // input: { query: "..." }
const resp = await fetch(process.env.CONNECTOR_URL + '/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: input.query })
});
const payload = await resp.json();
return { results: payload };
};
// oauth.js
// Very small flow: redirect to authorization, callback exchanges code for tokens.
// You'll store refresh_token securely (database or secret store).
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.get('/auth/start', (req, res) => {
const params = new URLSearchParams({
response_type: 'code',
client_id: process.env.TAVILY_CLIENT_ID,
redirect_uri: process.env.OAUTH_REDIRECT_URI,
scope: 'search.read', // // pick scopes Tavily requires
state: 'opaque_state_value'
});
res.redirect(`https://auth.tavily.example/authorize?${params.toString()}`);
});
app.get('/auth/callback', express.urlencoded({ extended: true }), async (req, res) => {
const { code, state } = req.query;
// // exchange code for tokens
const tokenResp = await fetch('https://auth.tavily.example/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: process.env.OAUTH_REDIRECT_URI,
client_id: process.env.TAVILY_CLIENT_ID,
client_secret: process.env.TAVILY_CLIENT_SECRET
})
});
const tokenJson = await tokenResp.json();
// // store tokenJson.access_token and tokenJson.refresh_token securely
// // respond with success page
res.json(tokenJson);
});
// // refresh helper
async function refreshAccessToken(refreshToken) {
const r = await fetch('https://auth.tavily.example/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: process.env.TAVILY_CLIENT_ID,
client_secret: process.env.TAVILY_CLIENT_SECRET
})
});
return r.json();
}
// server-search-oauth.js
// Uses stored access_token and refresh logic before calling the API.
async function callWithToken(accessToken, q) {
// // call Tavily search API with Bearer token
const r = await fetch('https://api.tavily.example/v1/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({ q })
});
if (r.status === 401) {
// // token expired: trigger refresh (not shown here) and retry
}
return r.json();
}
// webhook.js
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.raw({ type: '*/*' }));
app.post('/webhook', (req, res) => {
const signature = req.headers['x-tavily-signature'];
const secret = process.env.TAVILY_WEBHOOK_SECRET;
const computed = crypto.createHmac('sha256', secret).update(req.body).digest('hex');
if (!signature || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computed))) {
return res.status(401).send('invalid signature');
}
const payload = JSON.parse(req.body.toString());
// // handle webhook payload
res.status(200).send('ok');
});
Speak one‑on‑one with a senior engineer about your no‑code app, migration goals, and budget. In just half an hour you’ll leave with clear, actionable next steps—no strings attached.
1
Put Tavily secrets into your connectors.yml as environment‑variable references, then set those environment variables in the Claw Agent runtime (or your secret manager) so the agent reads them at startup. Ensure the agent has permissions to read the variables and that any OAuth flow or API key rotation is handled by an external service or startup hook.
connectors:
tavily:
type: rest
base_url: https://api.tavily.example
api_key: ${TAVILY_API_KEY}
client_id: ${TAVILY_CLIENT_ID}
client_secret: ${TAVILY_CLIENT_SECRET}
export TAVILY_API_KEY="sk_..."
export TAVILY_CLIENT_ID="your-client-id"
export TAVILY_CLIENT_SECRET="your-secret"
2
Direct answer: Provide an Elasticsearch-style index template that types Tavily fields (url, title, snippet, content, published_at, source, language, tags, score) and an ingest pipeline that normalizes dates/language, extracts domain, computes a content_hash, and calls an external encoder for vectors; below is a compact, realistic example you can adapt for OpenClaw-backed search storage.
// index template
const indexTemplate = {
index_patterns: ["tavily-*"],
mappings: {
properties: {
url: { type: "keyword" },
title: { type: "text", analyzer: "standard" },
snippet: { type: "text", analyzer: "standard" },
content: { type: "text", analyzer: "english" },
published_at: { type: "date" },
source: { type: "keyword" },
language: { type: "keyword" },
tags: { type: "keyword" },
score: { type: "float" },
domain: { type: "keyword" },
content_hash: { type: "keyword" },
content_vector: { type: "dense_vector", dims: 768 }
}
}
}
// ingest pipeline
const ingestPipeline = {
description: "Normalize Tavily docs",
processors: [
{ // extract domain
"script": { "source": "ctx.domain = ctx.url != null ? new URL(ctx.url).hostname : null" }
},
{ // normalize date
"date": { "field": "published_at", "target_field": "published_at", "formats": ["ISO8601","yyyy-MM-dd"] }
},
{ // compute fingerprint
"set": { "field": "content_hash", "value": "{{{content}}}" } // replace with actual fingerprinting step in production
},
{ // call external vector encoder via webhook (handled outside runtime)
"set": { "field": "content_vector", "value": null } // populate with encoder output before indexing
}
]
}
3
Implement retries/backoff, bulk batching and a circuit breaker inside your OpenClaw skill’s HTTP wrapper: detect 429/503, honor Retry-After, apply exponential backoff with jitter, send records in configurable bulk sizes, limit concurrency, and store circuit-breaker state outside the agent (Redis or DB) so it survives restarts.
Use env vars for params, log every decision, and keep state external. Key points:
4
Start by checking the Claw Agent logs for connector errors, confirm the connector sync job completed successfully, force or verify an index refresh, inspect shard allocation/cluster health for unassigned shards, and resolve any mapping conflicts (reindex with corrected mappings if needed). Ensure credentials and permissions are correct.
This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â