1import http from 'node:http';
2import { Actor, log } from 'apify';
3import { executeRequest } from './http-utils.js';
4
5await Actor.init();
6
7if (Actor.config.get('metaOrigin') === 'STANDBY') {
8 startStandbyServer();
9} else {
10 await runBatchMode();
11 await Actor.exit();
12}
13
14
15
16
17
18function startStandbyServer() {
19 const port = Actor.config.get('containerPort');
20
21 const server = http.createServer(async (req, res) => {
22
23 if (req.headers['x-apify-container-server-readiness-probe']) {
24 res.writeHead(200, { 'Content-Type': 'application/json' });
25 res.end(JSON.stringify({ status: 'ready' }));
26 return;
27 }
28
29
30 if (req.method === 'GET') {
31 res.writeHead(200, { 'Content-Type': 'text/html' });
32 res.end(getLandingPageHtml());
33 return;
34 }
35
36
37 if (req.method !== 'POST') {
38 res.writeHead(405, { 'Content-Type': 'application/json' });
39 res.end(JSON.stringify({ error: 'Method not allowed. Use POST.' }));
40 return;
41 }
42
43 try {
44 const body = await readRequestBody(req);
45 let params;
46
47 res.writeHead(200, { 'Content-Type': 'application/json' });
48 res.end(JSON.stringify(result));
49 } catch (err) {
50 const statusCode = err.statusCode || 502;
51 res.writeHead(statusCode, { 'Content-Type': 'application/json' });
52 res.end(JSON.stringify({ error: err.message }));
53 }
54 });
55
56 server.listen(port, () => {
57 log.info(`HTTP API Standby server listening on port ${port}`);
58 });
59}
60
61function readRequestBody(req) {
62 return new Promise((resolve, reject) => {
63 const chunks = [];
64 req.on('data', (chunk) => chunks.push(chunk));
65 req.on('end', () => resolve(Buffer.concat(chunks).toString()));
66 req.on('error', reject);
67 });
68}
69
70
71
72
73
74async function runBatchMode() {
75 const {
76 method = 'GET',
77 endpoint,
78 body,
79 headers,
80 querystring,
81 response_headers,
82 timeoutSeconds = 30,
83 maxRetries = 3,
84 include_meta = false,
85 } = await Actor.getInput() ?? {};
86
87 if (!endpoint) {
88 await Actor.pushData({ error: 'missing_endpoint', message: 'Missing required field: endpoint. Example input: { "endpoint": "https://catfact.ninja/fact", "method": "GET" }' });
89 await Actor.exit('Skipped: Missing required field: endpoint. Example input: { "endpoint": "https://catfact.ninja/fact", "method": "GET" }');
90 return;
91 }
92
93 log.info('Starting request', { method, endpoint });
94
95 const result = await executeRequest({
96 method,
97 endpoint,
98 body,
99 headers,
100 querystring,
101 response_headers,
102 timeoutSeconds,
103 maxRetries,
104 include_meta,
105 });
106
107 await Actor.pushData(result);
108
109 if (result.status >= 400) {
110 log.warning(`Received HTTP ${result.status}`);
111 } else {
112 log.info('Request succeeded', { status: result.status });
113 }
114}
115
116
117
118
119
120function getLandingPageHtml() {
121 return `<!DOCTYPE html>
122<html lang="en">
123<head>
124<meta charset="utf-8">
125<meta name="viewport" content="width=device-width, initial-scale=1">
126<title>HTTP API — Superlative</title>
127<link rel="preconnect" href="https://fonts.googleapis.com">
128<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
129<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
130<style>
131:root{--color-bg:#0a0e14;--color-surface:#12171f;--color-border:#1e2632;--color-text:#e8eaed;--color-text-muted:#8b95a5;--color-accent:#4ade80;--color-accent-dim:rgba(74,222,128,0.15);--font-display:'Space Mono',monospace;--font-body:'DM Sans',sans-serif}
132*{margin:0;padding:0;box-sizing:border-box}
133body{font-family:var(--font-body);background:var(--color-bg);color:var(--color-text);line-height:1.6;min-height:100vh;overflow-x:hidden}
134a{color:var(--color-accent);text-decoration:none;transition:color .2s ease}
135a:hover{color:#6ee7a0}
136.grid-bg{position:fixed;top:0;left:0;width:100%;height:100%;background-image:linear-gradient(var(--color-border) 1px,transparent 1px),linear-gradient(90deg,var(--color-border) 1px,transparent 1px);background-size:60px 60px;opacity:.3;pointer-events:none;z-index:0}
137.accent-line{position:fixed;top:0;left:0;width:100%;height:2px;background:linear-gradient(90deg,transparent 0%,var(--color-accent) 50%,transparent 100%);animation:shimmer 3s ease-in-out infinite;z-index:10}
138@keyframes shimmer{0%,100%{transform:translateX(-100%)}50%{transform:translateX(100%)}}
139@keyframes fadeUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
140.container{position:relative;z-index:1;max-width:900px;margin:0 auto;padding:0 24px}
141header{padding:32px 0}
142.logo{font-family:var(--font-display);font-size:14px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--color-text);display:flex;align-items:center;gap:10px;text-decoration:none}
143.logo:hover{color:var(--color-text)}
144.logo-mark{width:28px;height:28px;background:var(--color-accent);display:flex;align-items:center;justify-content:center}
145.logo-mark svg{width:16px;height:16px}
146.hero{padding:80px 0 60px;animation:fadeUp .8s ease-out}
147.hero-label{font-family:var(--font-display);font-size:12px;font-weight:400;letter-spacing:.15em;text-transform:uppercase;color:var(--color-accent);margin-bottom:24px;display:flex;align-items:center;gap:12px}
148.hero-label::before{content:'';width:24px;height:1px;background:var(--color-accent)}
149h1{font-family:var(--font-display);font-size:clamp(28px,5vw,44px);font-weight:700;line-height:1.15;margin-bottom:20px;letter-spacing:-.02em}
150h1 .highlight{color:var(--color-accent)}
151.hero-desc{font-size:18px;color:var(--color-text-muted);max-width:540px;margin-bottom:28px;line-height:1.7}
152.hero-link{font-family:var(--font-display);font-size:13px;letter-spacing:.05em;color:var(--color-accent);display:inline-flex;align-items:center;gap:6px}
153.section{padding:60px 0;border-top:1px solid var(--color-border);animation:fadeUp .8s ease-out .2s backwards}
154.section-label{font-family:var(--font-display);font-size:11px;font-weight:400;letter-spacing:.2em;text-transform:uppercase;color:var(--color-text-muted);margin-bottom:24px}
155h2{font-family:var(--font-display);font-size:20px;font-weight:700;margin-bottom:16px;letter-spacing:-.01em}
156p,li{font-size:15px;color:var(--color-text-muted);line-height:1.7}
157li{margin-bottom:6px}
158ul{margin:.5rem 0 1rem 1.25rem}
159strong{color:var(--color-text);font-weight:600}
160pre{background:var(--color-surface);color:var(--color-text);padding:16px 20px;overflow-x:auto;margin:12px 0 16px;font-family:var(--font-display);font-size:13px;line-height:1.6;border:1px solid var(--color-border)}
161code{font-family:var(--font-display);font-size:13px}
162p code,li code,td code{background:var(--color-accent-dim);color:var(--color-accent);padding:2px 6px}
163table{width:100%;border-collapse:collapse;margin:12px 0 16px;font-size:14px}
164th,td{border:1px solid var(--color-border);padding:10px 14px;text-align:left}
165th{background:var(--color-surface);color:var(--color-text-muted);font-family:var(--font-display);font-size:12px;font-weight:400;letter-spacing:.05em;text-transform:uppercase}
166td{color:var(--color-text-muted)}
167.badge{display:inline-block;background:var(--color-accent-dim);color:var(--color-accent);padding:3px 8px;font-family:var(--font-display);font-size:11px;font-weight:400;letter-spacing:.05em}
168.product-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px;margin-top:24px}
169.product-card{background:var(--color-surface);border:1px solid var(--color-border);padding:24px;transition:all .3s ease;text-decoration:none;color:inherit;display:block}
170.product-card:hover{border-color:var(--color-accent);transform:translateY(-2px)}
171.product-name{font-family:var(--font-display);font-size:14px;font-weight:700;margin-bottom:8px;color:var(--color-text);display:flex;align-items:center;gap:8px}
172.product-name .status{font-size:9px;font-weight:400;padding:3px 6px;background:var(--color-accent-dim);color:var(--color-accent);letter-spacing:.1em}
173.product-desc{font-size:14px;color:var(--color-text-muted);line-height:1.5}
174footer{padding:48px 0;border-top:1px solid var(--color-border)}
175.footer-content{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:24px}
176.footer-text{font-size:13px;color:var(--color-text-muted)}
177.footer-links{display:flex;gap:24px}
178.footer-links a{font-family:var(--font-display);font-size:12px;color:var(--color-text-muted);letter-spacing:.05em}
179.footer-links a:hover{color:var(--color-accent)}
180@media(max-width:600px){.hero{padding:60px 0 40px}.section{padding:40px 0}.footer-content{flex-direction:column;align-items:flex-start}pre{font-size:12px;padding:12px 14px}}
181</style>
182</head>
183<body>
184<div class="grid-bg"></div>
185<div class="accent-line"></div>
186
187<div class="container">
188 <header>
189 <a href="https://apify.com/superlativetech?fpr=8e9l1" class="logo">
190 <div class="logo-mark">
191 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
192 <polyline points="4 17 10 11 4 5"></polyline>
193 <line x1="12" y1="19" x2="20" y2="19"></line>
194 </svg>
195 </div>
196 Superlative
197 </a>
198 </header>
199
200 <section class="hero">
201 <div class="hero-label">Utilities</div>
202 <h1>HTTP <span class="highlight">API</span></h1>
203 <p class="hero-desc">General-purpose HTTP request proxy. Make requests to any endpoint through Apify's infrastructure with sub-second Standby response times.</p>
204 <a href="https://apify.com/superlativetech/http-api?fpr=8e9l1" class="hero-link">View on Apify Store →</a>
205 </section>
206
207 <section class="section">
208 <div class="section-label">Features</div>
209 <h2>What this does</h2>
210 <ul>
211 <li><strong>All HTTP methods</strong> — GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS</li>
212 <li><strong>Custom headers</strong> — send any request headers as key-value pairs</li>
213 <li><strong>Request body</strong> — JSON, form data, or raw text</li>
214 <li><strong>Query strings</strong> — append query parameters to any endpoint</li>
215 <li><strong>Retries</strong> — automatic retries with configurable count</li>
216 <li><strong>Response headers</strong> — optionally capture specific response headers</li>
217 </ul>
218 </section>
219
220 <section class="section">
221 <div class="section-label">Getting Started</div>
222 <h2>Quick start</h2>
223 <p>Send a POST request to the Standby URL with a JSON body. Replace <code>YOUR_TOKEN</code> with your Apify API token.</p>
224
225 <p style="margin-top:16px"><strong>Simple GET proxy:</strong></p>
226 <pre>curl -X POST "https://superlativetech--http-api.apify.actor?token=YOUR_TOKEN" \\
227 -H "Content-Type: application/json" \\
228 -d '{
229 "endpoint": "https://catfact.ninja/fact"
230 }'</pre>
231
232 <p><strong>POST with headers and body:</strong></p>
233 <pre>curl -X POST "https://superlativetech--http-api.apify.actor?token=YOUR_TOKEN" \\
234 -H "Content-Type: application/json" \\
235 -d '{
236 "endpoint": "https://httpbin.org/post",
237 "method": "POST",
238 "headers": [
239 {"key": "X-Custom", "value": "hello"}
240 ],
241 "body": {"message": "test"}
242 }'</pre>
243 </section>
244
245 <section class="section">
246 <div class="section-label">Reference</div>
247 <h2>Request body parameters</h2>
248 <table>
249 <tr><th>Field</th><th>Required</th><th>Default</th><th>Description</th></tr>
250 <tr><td><code>endpoint</code></td><td>Yes</td><td>—</td><td>Target URL to request</td></tr>
251 <tr><td><code>method</code></td><td>No</td><td>GET</td><td>HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)</td></tr>
252 <tr><td><code>headers</code></td><td>No</td><td>—</td><td>Array of <code>{"key": "...", "value": "..."}</code> objects</td></tr>
253 <tr><td><code>body</code></td><td>No</td><td>—</td><td>Request body (object or string)</td></tr>
254 <tr><td><code>querystring</code></td><td>No</td><td>—</td><td>Array of <code>{"key": "...", "value": "..."}</code> query params</td></tr>
255 <tr><td><code>response_headers</code></td><td>No</td><td>—</td><td>Array of header names to capture from the response</td></tr>
256 <tr><td><code>timeoutSeconds</code></td><td>No</td><td>30</td><td>Request timeout in seconds</td></tr>
257 <tr><td><code>maxRetries</code></td><td>No</td><td>3</td><td>Number of retry attempts on failure</td></tr>
258 <tr><td><code>include_meta</code></td><td>No</td><td>false</td><td>Include timing and redirect metadata</td></tr>
259 </table>
260 </section>
261
262 <section class="section">
263 <div class="section-label">Response</div>
264 <h2>Response format</h2>
265 <pre>{
266 "status": 200,
267 "headers": {},
268 "body": { "fact": "Cats sleep 70% of their lives.", "length": 31 }
269}</pre>
270 </section>
271
272 <section class="section">
273 <div class="section-label">Cost</div>
274 <h2>Pricing</h2>
275 <p>Free to use — you only pay standard Apify platform usage.</p>
276 </section>
277
278 <section class="section">
279 <div class="section-label">Auth</div>
280 <h2>Authentication</h2>
281 <p>Authenticate using either method:</p>
282 <ul>
283 <li>Query parameter: <code>?token=YOUR_APIFY_TOKEN</code></li>
284 <li>Header: <code>Authorization: Bearer YOUR_APIFY_TOKEN</code></li>
285 </ul>
286 <p style="margin-top:8px">Get your token from <a href="https://console.apify.com/settings/integrations">Apify Console → Settings → Integrations</a>.</p>
287 </section>
288
289 <section class="section">
290 <div class="section-label">Superlative</div>
291 <h2>More from Superlative</h2>
292 <div class="product-grid">
293 <a href="https://apify.com/superlativetech/superclean-company-names?fpr=8e9l1" class="product-card">
294 <div class="product-name">Company Names <span class="status">Live</span></div>
295 <p class="product-desc">Normalize company names for CRM and cold email outreach.</p>
296 </a>
297 <a href="https://apify.com/superlativetech/superclean-job-titles?fpr=8e9l1" class="product-card">
298 <div class="product-name">Job Titles <span class="status">Live</span></div>
299 <p class="product-desc">Standardize job titles for outreach sequences.</p>
300 </a>
301 <a href="https://apify.com/superlativetech/superclean-person-names?fpr=8e9l1" class="product-card">
302 <div class="product-name">Person Names <span class="status">Live</span></div>
303 <p class="product-desc">Clean and format person names for personalization.</p>
304 </a>
305 <a href="https://apify.com/superlativetech/superclean-product-names?fpr=8e9l1" class="product-card">
306 <div class="product-name">Product Names <span class="status">Live</span></div>
307 <p class="product-desc">Normalize product and brand names from exports.</p>
308 </a>
309 <a href="https://apify.com/superlativetech/superclean-places?fpr=8e9l1" class="product-card">
310 <div class="product-name">Places <span class="status">Live</span></div>
311 <p class="product-desc">Parse and standardize location strings.</p>
312 </a>
313 <a href="https://apify.com/superlativetech/superclean-urls?fpr=8e9l1" class="product-card">
314 <div class="product-name">URLs <span class="status">Live</span></div>
315 <p class="product-desc">Clean and normalize URLs from lead data.</p>
316 </a>
317 <a href="https://apify.com/superlativetech/superclean-phone-numbers?fpr=8e9l1" class="product-card">
318 <div class="product-name">Phone Numbers <span class="status">Live</span></div>
319 <p class="product-desc">Format and validate phone numbers.</p>
320 </a>
321 <a href="https://apify.com/superlativetech/dns-lookup?fpr=8e9l1" class="product-card">
322 <div class="product-name">Supernet DNS Lookup <span class="status">Live</span></div>
323 <p class="product-desc">Instant DNS record lookups and reverse DNS.</p>
324 </a>
325 <a href="https://apify.com/superlativetech/supernet-domain-health?fpr=8e9l1" class="product-card">
326 <div class="product-name">Domain Health <span class="status">Live</span></div>
327 <p class="product-desc">Email reputation audit: SPF, DKIM, DMARC, blacklists, SSL.</p>
328 </a>
329 </div>
330 </section>
331
332 <footer>
333 <div class="footer-content">
334 <p class="footer-text">© 2026 Superlative</p>
335 <div class="footer-links">
336 <a href="https://apify.com/superlativetech?fpr=8e9l1">Apify Store</a>
337 </div>
338 </div>
339 </footer>
340</div>
341
342</body>
343</html>`;
344}