# Bulk HTTP Request Runner — Batch API Calls (`nexgensignal/bulk-http-request-runner`) Actor

Send thousands of HTTP requests in one run: GET, POST, PUT, PATCH, DELETE with your own headers and JSON bodies. Each response is a row with status, headers, timing and body, JSON parsed. Retries, redirects, concurrency. Part of NexGen Signal — data tools and official-source data products

- **URL**: https://apify.com/nexgensignal/bulk-http-request-runner.md
- **Developed by:** [NexGen Signal](https://apify.com/nexgensignal) (community)
- **Categories:** Integrations, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $33.50 / 1,000 http responses

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## 🔁 Bulk HTTP Request Runner — Batch API Calls

Send **thousands of HTTP requests in one run** (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) with your own headers and bodies, and get every response as a row: status code, reason, headers, redirects, timing, content type, size and body, with JSON bodies parsed into data.

Give a simple list of links, or full request objects with method, headers, JSON or raw body and a label. Requests run in parallel (up to 50 at once), connection errors and 429/502/503/504 answers are retried with back-off (POST and PATCH only when the connection never opened, so nothing is sent twice), and redirects are followed. Large bodies can be saved whole as files.

The unit of output is one response received, whatever its status code. Requests that get no response are listed in the run receipt and not billed.

### Who buys this and for what job

- **Developers and QA** calling an API with many parameter sets, or checking many endpoints.
- **Data teams** pulling JSON from public APIs page by page into a dataset.
- **SEO and ops teams** checking status codes, redirects and response times for URL lists.
- **No-code builders** who need a batch HTTP step in an Apify workflow.

Buyers usually search for this as **"bulk HTTP requests"**, **"batch API calls"** or **"HTTP request tool"**.

### 📊 Sample Output

![Table of responses: position, URL, status code, elapsed milliseconds and parsed JSON body.](https://api.apify.com/v2/key-value-stores/IXCaMKjxSmUTLHhmq/records/bulk-http-request-runner.png)

| position | url | status_code | elapsed_ms | body_json |
|---|---|---|---|---|
| 10 | https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson\&minmagnitude=2.9 | 200 | 189 | {"count": 1407, "maxAllowed": 20000} |
| 2 | https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson\&minmagnitude=2.1 | 200 | 206 | {"count": 2742, "maxAllowed": 20000} |
| 6 | https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson\&minmagnitude=2.5 | 200 | 207 | {"count": 1890, "maxAllowed": 20000} |
| 4 | https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson\&minmagnitude=2.3 | 200 | 209 | {"count": 2252, "maxAllowed": 20000} |
| 1 | https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson\&minmagnitude=2.0 | 200 | 233 | {"count": 3083, "maxAllowed": 20000} |

Rows from live run `xYRvCLXLxVf9QOXGK` on Apify (40 GET requests to the USGS earthquake count API, one per minimum magnitude from 2.0 to 5.9). Notice that the JSON answers are parsed, so each count can be read straight from the row.

### ✅ What you get

Every field of each record:

- `record_id`, `position`, `label` — stable id, position in your list and your label.
- `method`, `url`, `final_url`, `redirects` — the request (secret query values shown as \[redacted]) and redirects followed.
- `status_code`, `ok`, `reason`, `elapsed_ms` — the answer and its timing.
- `content_type`, `bytes`, `response_headers` — the body's type and size, and the response headers (Set-Cookie values are not shown).
- `body_text`, `body_json`, `body_truncated`, `body_file_url` — the body as text or parsed JSON (up to maxBodyKb), whether it was cut off, and the full body's link (with saveFullBody).
- `sha256`, `requested_at` — hash of the full body and when it was sent (UTC).

### ⚙️ Sample inputs

**Two GET links (the Store example)**

```json
{
  "urls": ["https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson&minmagnitude=6", "https://earthquake.usgs.gov/fdsnws/event/1/version"],
  "maxRequests": 10
}
```

Default method and headers.

**POST with a JSON body and a token**

```json
{
  "requests": [{"url": "https://postman-echo.com/post", "method": "POST", "headers": {"Authorization": "Bearer YOUR_TOKEN"}, "json": {"hello": "world"}, "label": "echo"}],
  "maxRequests": 5
}
```

The Authorization header is never written to the run receipt.

**Status check of a URL list, headers only**

```json
{
  "urls": ["https://www.federalreserve.gov/", "https://www.federalreserve.gov/this-page-does-not-exist-zz.htm"],
  "method": "HEAD",
  "maxBodyKb": 0,
  "maxRequests": 1000
}
```

Status, redirects and timing for each link.

Input fields:

| Field | Meaning |
|---|---|
| `urls` | links sent with the default method and headers. |
| `requests` | objects with url, method, headers, json or body, label. |
| `method`, `headers`, `body` | defaults for every request. |
| `parseJson` | parse JSON bodies (default true). |
| `maxBodyKb` | body kept per row (default 512 KB; 0 = none). |
| `saveFullBody` | store longer bodies whole, up to 200 MB (default false). |
| `followRedirects` | follow up to 8 redirects (default true). |
| `timeoutSecs`, `retries`, `maxConcurrency` | per-request timeout (30 s), retries (2) and parallel requests (10). |
| `maxRequests` | send at most this many requests (default 1,000). Each response received is billed. |

### 🧾 JSON sample record

```json
{
  "record_id": "38fa70d79b5d5f6cc7116bc1",
  "position": 1,
  "label": "echo",
  "method": "POST",
  "url": "https://postman-echo.com/post?api_key=[redacted]",
  "final_url": "https://postman-echo.com/post?api_key=[redacted]",
  "status_code": 200,
  "ok": true,
  "reason": "OK",
  "redirects": 0,
  "elapsed_ms": 28,
  "content_type": "application/json; charset=utf-8",
  "bytes": 815,
  "response_headers": {
    "date": "Sat, 17 Oct 2026 03:54:06 GMT",
    "content-type": "application/json; charset=utf-8",
    "transfer-encoding": "chunked",
    "connection": "keep-alive",
    "etag": "W/\"32f-hZDzd+WEgkx9PUAvGeVc63axcj4\"",
    "vary": "Accept-Encoding",
    "x-envoy-upstream-service-time": "5",
    "cf-cache-status": "DYNAMIC",
    "content-encoding": "gzip",
    "server": "cloudflare",
    "cf-ray": "a4bc5c2d0e0e86f1-IAD",
    "set-cookie": "[present, not shown]"
  },
  "body_text": null,
  "body_json": {
    "args": {
      "api_key": "dummy-not-secret"
    },
    "data": {
      "hello": "world"
    },
    "files": {},
    "form": {},
    "headers": {
      "host": "postman-echo.com",
      "content-type": "application/json",
      "authorization": "Bearer dummy-not-secret",
      "accept": "*/*",
      "x-forwarded-proto": "https",
      "user-agent": "Mozilla/5.0 (compatible; NexGenSignal/1.0; +https://apify.com/nexgensignal)",
      "content-length": "18",
      "accept-encoding": "gzip, br",
      "cookie": "__cf_bm=ibm2yTyf9BBsiylR_jnFeA_gDtdaUzMc8ZsG82t6NsU-1792209246.2340958-1.0.1.1-JFRDWbWNxmXFFywiHobYkCfvI2BHJYhhdBhnliExnh9gjPLl.1V6SCeWa9DiXFEhEm42dcGofwFybYRvjourR.5aHBjcvp1hm_7_fBBQ611txmlb_j.LSq.Lv2pVzDcB; _cfuvid=s1m_yXNBGYnVgxl75dFSqBLyPvLjHwPHtSg56o1e6AY-1792209246.2340958-1.0.1.1-gj36F5GtHH0C_35r8QuMmCNKTJJtpVSSNFNWl0JvkZY"
    },
    "json": {
      "hello": "world"
    },
    "url": "https://postman-echo.com/post?api_key=dummy-not-secret"
  },
  "body_truncated": false,
  "body_file_url": null,
  "sha256": "6958b0b65467b5accb9382caa59b00958fb3f938aec815f4abd3fd5513617e42",
  "requested_at": "2026-10-17T03:54:06.246Z"
}
```

### 🔧 How it works

Requests use one connection pool with HTTP keep-alive. Header names and values are checked (no line breaks), and Host, Content-Length, Transfer-Encoding and Connection headers are set by the client, not taken from input. Secret-looking headers (Authorization, Cookie, API keys, tokens) and secret query values are replaced with \[redacted] in the run receipt and in the URLs stored in rows.

Every run reads each host's robots.txt first: a path it disallows is skipped, listed in the run receipt and never charged. Only public internet addresses are fetched (no private, loopback or cloud-metadata addresses, checked again on every redirect). No logins, no CAPTCHA solving, no proxies.

Delivery is pushed first and charged after. The run receipt (`RUN_RECEIPT`) lists outcomes by status, every request that got no response or was skipped, the robots.txt result per host, and the delivered and charged counts.

### 💰 Pricing example

Per response delivered, no start fee, no monthly charge:

| Apify plan | Price per response |
|---|---|
| Free | $0.05 |
| Bronze | $0.045 |
| Silver | $0.04 |
| Gold, Platinum, Diamond | $0.0335 |

- 10 responses: $0.50 on the Free plan, $0.335 on Gold.
- 1,000 responses: $50.00 on Free, $33.50 on Gold.
- 10,000 responses: $500.00 on Free, $335.00 on Gold.

**Coverage and size.** Proof runs on 17 Oct 2026: 40 USGS API calls answered in about 6 seconds; a missing Federal Reserve page came back as a 404 row; api.weather.gov was skipped because its robots.txt disallows all paths.

### ⚖️ Legal & ToS

Use it only with APIs and sites you are allowed to call, within their rate limits and terms. Every run reads each host's robots.txt first: a path it disallows is skipped, listed in the run receipt and never charged. Only public internet addresses are fetched (no private, loopback or cloud-metadata addresses, checked again on every redirect). No logins, no CAPTCHA solving, no proxies.

### ❓ FAQ

**Is a 404 or 500 billed?**
Yes: it is a response and gets a row. Requests with no response at all are not billed.

**Are my tokens stored?**
Not in the run receipt or in stored URLs. They are sent to the URL you chose, and a service that echoes them back will put them in its response body.

**Can it call localhost or internal IPs?**
No. Only public internet addresses.

### 🆘 Troubleshooting

- **skipped: robots.txt disallows this path** — That host's robots.txt blocks it; the request was not sent or billed.
- **body_truncated is true** — Raise maxBodyKb or turn on saveFullBody.
- **SOURCE UNAVAILABLE** — No request got a response after retries. Nothing was charged.

### 🔗 Related Actors

- [Website Tech Stack Detector](https://apify.com/nexgensignal/website-tech-stack-api) — what a site is built with
- [Website Performance Audit](https://apify.com/nexgensignal/website-performance-audit-api) — Lighthouse scores and web vitals per page
- [Website Contact Scraper](https://apify.com/nexgensignal/bulk-website-contact-extractor) — contact details from website lists

🏢 **About NexGen Signal** — data tools and official-source data products on Apify. Browse the catalog: [apify.com/nexgensignal](https://apify.com/nexgensignal)

### ⭐ Found this useful?

If this actor saved you time, a rating on its Apify Store page helps other buyers find it.

Part of NexGen Signal — data tools and official-source data products.

# Actor input Schema

## `urls` (type: `array`):

Links to request with the default method and headers. Up to 100,000 in total with requests.

## `requests` (type: `array`):

Objects like {"url": "https://…", "method": "POST", "headers": {"Authorization": "Bearer …"}, "json": {"q": 1}, "label": "search"}. Use "body" for raw text instead of "json".

## `method` (type: `string`):

Method for links and for requests that do not set one.

## `headers` (type: `object`):

Sent with every request, e.g. {"Accept": "application/json"}. Per-request headers win. Secret-looking headers are never stored in the run receipt.

## `body` (type: `string`):

Raw body for requests that do not set one (not for GET/HEAD).

## `parseJson` (type: `boolean`):

Put JSON responses in body_json as data instead of text.

## `maxBodyKb` (type: `integer`):

Bodies longer than this are cut off in the row (0 = headers and status only).

## `saveFullBody` (type: `boolean`):

When a body is longer than the limit above, store the whole body (up to 200 MB) in the run's key-value store and link it.

## `followRedirects` (type: `boolean`):

Follow up to 8 redirects.

## `timeoutSecs` (type: `integer`):

Give up on a request after this long.

## `retries` (type: `integer`):

Retries on connection errors, and on 429/502/503/504 for GET, HEAD, OPTIONS, PUT and DELETE. POST and PATCH are retried only when the connection never opened.

## `maxConcurrency` (type: `integer`):

Requests in flight at once (a host's robots.txt crawl-delay is honoured).

## `maxRequests` (type: `integer`):

Send at most this many requests, in list order. Each response received is billed, whatever its status code; requests that get no response are not.

## Actor input object example

```json
{
  "urls": [
    "https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson&minmagnitude=6",
    "https://earthquake.usgs.gov/fdsnws/event/1/version"
  ],
  "requests": [],
  "method": "GET",
  "headers": {},
  "parseJson": true,
  "maxBodyKb": 512,
  "saveFullBody": false,
  "followRedirects": true,
  "timeoutSecs": 30,
  "retries": 2,
  "maxConcurrency": 10,
  "maxRequests": 10
}
```

# Actor output Schema

## `results` (type: `string`):

The delivered (and billed) records.

## `receipt` (type: `string`):

Per-link outcome, robots result, delivered and charged counts.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson&minmagnitude=6",
        "https://earthquake.usgs.gov/fdsnws/event/1/version"
    ],
    "maxRequests": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("nexgensignal/bulk-http-request-runner").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        "https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson&minmagnitude=6",
        "https://earthquake.usgs.gov/fdsnws/event/1/version",
    ],
    "maxRequests": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("nexgensignal/bulk-http-request-runner").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://earthquake.usgs.gov/fdsnws/event/1/count?format=geojson&minmagnitude=6",
    "https://earthquake.usgs.gov/fdsnws/event/1/version"
  ],
  "maxRequests": 10
}' |
apify call nexgensignal/bulk-http-request-runner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nexgensignal/bulk-http-request-runner"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Qz2CpGSZH0hulH2S9/builds/GeH7K5RnyImEj6tc5/openapi.json
