DenchClaw HTTP Proxy: External API Calls Without CORS

How DenchClaw's built-in HTTP proxy lets your custom apps call external APIs without CORS errors — no backend required.

Mark Rachapoom
Mark Rachapoom
·6 min read
DenchClaw HTTP Proxy: External API Calls Without CORS

Custom Dench Apps are web applications running in a browser context. Normally, that means CORS restrictions block direct API calls to external services — you'd need a backend server to proxy requests. DenchClaw's built-in HTTP proxy eliminates this problem: use dench.http.fetch() to call any external URL from your app, and DenchClaw's local server makes the request on your behalf.

The CORS Problem (and Why It Matters)#

When a web app running at localhost:3100 tries to fetch https://api.apollo.io/v1/contacts, the browser blocks the request with a CORS error — unless Apollo's API explicitly allows localhost:3100 as an origin. Most APIs don't.

Traditional solution: build a backend server that proxies requests. Your frontend calls your backend; your backend calls the API. This works but requires server infrastructure you have to maintain.

DenchClaw's solution: the local DenchClaw server (already running at localhost:3100) acts as the proxy. Your app calls dench.http.fetch(), the DenchClaw server receives the request server-side (where CORS doesn't apply), calls the external API, and returns the response.

Using dench.http.fetch()#

The API mirrors the standard Fetch API:

// Basic GET request
const response = await dench.http.fetch("https://api.example.com/data");
const data = await response.json();
 
// POST with headers and body
const response = await dench.http.fetch("https://api.apollo.io/v1/contacts/search", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": "your-apollo-api-key"
  },
  body: JSON.stringify({
    q_keywords: "software engineer",
    page: 1,
    per_page: 10
  })
});
 
const { contacts } = await response.json();

The return value is a Response-like object with .json(), .text(), .status, and .headers — identical to the standard Fetch API.

Storing API Keys Securely#

API keys should never be hardcoded in your app's HTML/JS files. DenchClaw provides a secure env/secrets API:

// Store an API key (from your app's settings UI or via AI)
await dench.store.set("apollo_api_key", "your-key-here", { secret: true });
 
// Retrieve it in your app code
const apiKey = await dench.store.get("apollo_api_key");
 
// Use it in requests
const response = await dench.http.fetch("https://api.apollo.io/...", {
  headers: { "X-Api-Key": apiKey }
});

Secret values are stored encrypted at rest in your local workspace. They're accessible only by your apps running in DenchClaw — not visible in plaintext in any file.

Practical Examples#

Apollo.io Lead Enrichment#

async function enrichLead(email) {
  const apiKey = await dench.store.get("apollo_api_key");
 
  const response = await dench.http.fetch(
    "https://api.apollo.io/v1/people/match",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Api-Key": apiKey
      },
      body: JSON.stringify({ email })
    }
  );
 
  if (!response.ok) return null;
 
  const { person } = await response.json();
  return {
    name: person.name,
    title: person.title,
    company: person.organization_name,
    linkedin: person.linkedin_url,
    phone: person.phone_numbers?.[0]?.sanitized_number
  };
}

Clearbit Company Enrichment#

async function enrichCompany(domain) {
  const apiKey = await dench.store.get("clearbit_api_key");
 
  const response = await dench.http.fetch(
    `https://company.clearbit.com/v2/companies/find?domain=${domain}`,
    {
      headers: {
        "Authorization": `Bearer ${apiKey}`
      }
    }
  );
 
  if (!response.ok) return null;
  const company = await response.json();
 
  return {
    name: company.name,
    description: company.description,
    employees: company.metrics?.employees,
    revenue: company.metrics?.annualRevenue,
    industry: company.category?.industry,
    logo: company.logo
  };
}

Talk to Sales

Tell us about your team and we'll show you how Dench fits your workflow.

By submitting this form you agree to be contacted by our team. See our Privacy Policy and Terms of Service.

Weather for Location-Based Features#

async function getWeather(city) {
  // wttr.in is public API, no key needed
  const response = await dench.http.fetch(
    `https://wttr.in/${encodeURIComponent(city)}?format=j1`
  );
  const data = await response.json();
  return data.current_condition[0];
}

Sending Emails via External SMTP API#

async function sendEmail(to, subject, body) {
  const apiKey = await dench.store.get("sendgrid_api_key");
 
  const response = await dench.http.fetch(
    "https://api.sendgrid.com/v3/mail/send",
    {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        personalizations: [{ to: [{ email: to }] }],
        from: { email: "you@yourdomain.com" },
        subject,
        content: [{ type: "text/plain", value: body }]
      })
    }
  );
 
  return response.status === 202;  // SendGrid returns 202 for success
}

Error Handling#

try {
  const response = await dench.http.fetch("https://api.example.com/data");
 
  if (!response.ok) {
    const errorText = await response.text();
    console.error(`API error ${response.status}: ${errorText}`);
    return null;
  }
 
  return await response.json();
} catch (error) {
  console.error("Network or proxy error:", error);
  return null;
}

Request Logging#

All HTTP proxy requests are logged in DenchClaw's request log (viewable in developer tools). This is useful for debugging — you can see exactly what was sent, what was received, and how long the request took.

Rate Limiting#

The HTTP proxy doesn't add artificial rate limits, but it passes through any rate limit responses from the external API (429 status codes). Handle these in your app:

if (response.status === 429) {
  const retryAfter = response.headers.get("Retry-After") || 60;
  await new Promise(r => setTimeout(r, retryAfter * 1000));
  return enrichLead(email);  // Retry
}

Security Considerations#

The HTTP proxy makes requests from your local machine (or your DenchClaw server if hosted). This means:

  • Requests use your IP address
  • API keys are stored on your local machine
  • No third-party proxy service ever sees your API keys or response data

Compared to cloud-based proxy services, DenchClaw's local proxy is more private — your API calls and responses never leave your local infrastructure.

See also: DenchClaw Store API for secure secret storage, and DenchClaw Webhooks for receiving external callbacks.

Frequently Asked Questions#

Can I use dench.http.fetch() for streaming APIs?#

Streaming response support is on the roadmap. Currently, dench.http.fetch() returns the full response when complete. For streaming APIs (like OpenAI's streaming completions), use dench.chat.send() which handles streaming natively.

Is there a timeout for proxy requests?#

Default timeout is 30 seconds. Override per-request: dench.http.fetch(url, { timeout: 60000 }). For long-running requests, consider using the AI agent instead of a direct API call — the agent handles timeouts and retries gracefully.

Can I use dench.http.fetch() to download large files?#

Yes, but large files are buffered in memory on the DenchClaw server before being sent to your app. For very large files (>50MB), stream the download directly to a filesystem path instead: dench.http.download(url, localPath).

Does the proxy support file uploads?#

Yes. Pass a FormData object as the body for multipart file uploads. DenchClaw's proxy forwards the multipart body to the destination server correctly.

Can I call localhost URLs for local development services?#

Yes. dench.http.fetch("http://localhost:8080/api/data") works and is useful for integrating with other local development services during app development.

Ready to try DenchClaw? Install in one command: npx denchclaw. Full setup guide →

Related articles

Keep reading

View all