Get your dream built 10x faster

How to integrate Tavily Web Search with OpenClaw

We build custom applications 5x faster and cheaper 🚀

Book a Free Consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Stuck on an error? Book a 30-minute call with an engineer and get a direct fix + next steps. No pressure, no commitment.

How to integrate Tavily Web Search with OpenClaw

 

Direct answer

 

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.

 

Prerequisites and high‑level architecture

 
  • Tavily credentials: obtain an API key or OAuth client_id/client_secret and documented scopes from Tavily’s developer console.
  • ClawHub setup: register the skill in ClawHub and add configuration entries (API keys, OAuth client secrets, webhook secrets, environment variables). Keep secrets in ClawHub’s secret store or your secret manager; never embed them in code.
  • Runtime components that must be external: a small web server for OAuth callbacks and webhooks, a secure token store (database or secrets service) for refresh tokens, and optional schedulers/queues if you need background polling or heavy throughput. The agent runtime should remain stateless where possible.
  • Networking: a reachable HTTPS endpoint for OAuth redirect URIs and webhooks (use a stable host for production; tunneling services only for dev).

 

Authentication options and recommended flows

 
  • API key (simplest): If Tavily supports API keys, store the key as a secret and send it in a header (for example, Authorization: ApiKey or x-api-key). This is simplest for server-to-server or single-account setups.
  • OAuth 2.0 (recommended for user-level access): Implement the authorization_code flow with a redirect URI hosted by your connector. After exchanging the code for tokens, store the access_token and refresh_token securely. Implement refresh_token handling and validate requested scopes.
  • Webhook verification: If Tavily sends webhooks (e.g., for indexed results or link updates), verify signatures (HMAC or provided header) and respond quickly (200 status). Use the webhook secret stored in ClawHub.

 

Connector implementation pattern (server + skill)

 
  • Server responsibilities (external): handle OAuth redirects & token exchange, persist refresh tokens, receive and verify webhooks, optionally perform polling jobs, expose a small internal API the skill calls if you want separation.
  • Skill responsibilities (in-agent code): receive invocation payloads from agents, validate input, call Tavily via your server or directly to Tavily’s REST API (if stateless and safe), format results for the agent, and return. Keep it short-lived and idempotent.
  • Secrets & config: provide the skill with only the minimal secrets it needs (short-lived tokens or a service token). Store long-term secrets externally.

 

Concrete examples (generic REST + Node.js)

 
  • Example: Search using an API key
// 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'));
  • How the OpenClaw skill calls it: the skill handler makes a single HTTP request to your connector’s /search endpoint and returns the result to the agent.
// 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 };
};
  • Example: OAuth 2.0 authorization\_code (server side refresh)
// 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();
}
  • Calling Tavily with OAuth token from your connector
// 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 verification example (HMAC)

 
  • Verify signature header to prevent spoofing: compute HMAC of the request body with your webhook secret and compare to header.
// 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');
});

 

Testing, deployment, and debugging checklist

 
  • Unit and integration tests: mock Tavily endpoints in tests; assert your connector handles success, 4xx, 5xx, and refresh flows.
  • Local dev: use a tunnel (ngrok) for OAuth redirect during development, but use a stable HTTPS endpoint in production.
  • Logging: log request/response status, error bodies, token refresh attempts, and webhook verification failures. Redact secrets from logs.
  • Inspect HTTP: when something fails, capture the full HTTP request/response (headers and body) from your server to Tavily. Verify status codes, error messages, and remaining rate-limit headers.
  • Credentials/scopes: confirm the token has required scopes and that the refresh\_token is present; if API key is failing, ensure it’s not restricted by IP or domain.
  • Skill invocation: verify the agent actually calls the skill by checking agent runtime logs and your connector access logs; validate payload shapes.

 

Operational and security considerations

 
  • Least privilege: request the minimal scopes needed from Tavily.
  • Secret rotation: plan for rotating API keys and client secrets—store metadata so you can update without downtime.
  • Rate limits: respect Tavily’s rate limits; implement backoff and retries in your connector, not inside the agent runtime.
  • Persistence: store long-lived tokens and any user-specific configuration outside the agent in a proper datastore.
  • Monitoring: emit metrics (request rate, error rate, latency, token refresh failures) to your monitoring system to detect problems early.

Book Your Free 30‑Minute Migration Call

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.

Book a Free Consultation

Troubleshooting Tavily Web Search and OpenClaw Integration

1

Configure Tavily credentials in connectors.yml and Claw Agent

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.

 

Connector YAML example

 
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}

 

Set env in Claw Agent

 
export TAVILY_API_KEY="sk_..."
export TAVILY_CLIENT_ID="your-client-id"
export TAVILY_CLIENT_SECRET="your-secret"
  • Validate by checking agent logs and a test request.
  • Rotate keys outside the agent; reload or restart to pick up changes.

2

Map Tavily Web Search schema to OpenClaw index mapping using index template and ingest pipeline

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 + ingest pipeline

 
// 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
    }
  ]
}
  • Pipeline notes: run encoder as an external service (skill calls) and populate content_vector before final bulk index; compute deterministic content_hash in a worker; ensure API keys via env vars.

3

Configure retry/backoff bulk size circuit breaker in OpenClaw ingest pipeline for 429/503

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.

 

Implementation pattern

 

Use env vars for params, log every decision, and keep state external. Key points:

  • Retry: exponential backoff + jitter, stop on non-retryable status.
  • Bulk: batch by size/time, semaphore for concurrency.
  • Circuit breaker: track failures and cooldown in Redis; short-circuit when open.
```js // Node.js skill wrapper example const axios = require('axios') // Redis client assumed async function callWithRetry(url,payload){ const max=5; for(let i=0;isetTimeout(r,wait)) } } throw new Error('max retries') } ```

4

Troubleshoot Tavily documents not searchable: Claw Agent logs, connector sync job, index refresh, shard allocation, mapping conflicts

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.

 

Troubleshooting checklist

 
  • Claw Agent logs: look for auth failures, 4xx/5xx, exceptions or timeouts in the agent runtime logs.
  • Connector sync job: check last run, errors, backoff or queueing in ClawHub/connector UI.
  • Index refresh: confirm automatic refresh or trigger manual refresh so new docs become searchable.
  • Shard allocation: check cluster health (yellow/red) and unassigned shards; fix node/disk issues.
  • Mapping conflicts: identify field-type mismatches; resolve by reindexing with correct mappings or renaming fields.
Book a Free Consultation

Still stuck?
Copy this prompt into ChatGPT and get a clear, personalized explanation.

This prompt helps an AI assistant understand your setup and guide you through the fix step by step, without assuming technical knowledge.

AI AI Prompt


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

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.Â