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