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.
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
};
}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 →
