# Bulk HTTP Request — API Calls, JSON & Headers (`tagadanar/bulk-http-request`) Actor

Send a list of HTTP requests (GET, POST, PUT, PATCH, DELETE) in one run: one row per request with status, response headers, parsed JSON or text body and timing. $1.00 per 1,000 requests answered + $0.001/run.

- **URL**: https://apify.com/tagadanar/bulk-http-request.md
- **Developed by:** [Tagada Data](https://apify.com/tagadanar) (community)
- **Categories:** Developer tools, Integrations, Automation
- **Security:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 78.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 http request answereds

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: API calls from a list, JSON and headers

Send many HTTP requests in one run and get **one dataset row per request**: status, response headers, the body as parsed JSON (or text), timing and redirects. Built for no-code and automation work (Clay, n8n, Make, Zapier, spreadsheets) where you have a list of ids or URLs and an API to call for each.

### What you get per request

| field | meaning |
|---|---|
| `label`, `method`, `url` | your input, so rows match your records |
| `finalUrl`, `redirects` | where the request ended after redirects, and how many hops |
| `status`, `statusText`, `ok` | the HTTP answer (`ok` = 2xx) |
| `headers` | response headers as an object |
| `json` | the body as real data when the response is JSON (`parseJson` on) |
| `text` | the body as text otherwise (or when `parseJson` is off) |
| `contentType`, `bytes`, `truncated` | body type, bytes read, and `true` when it was cut at `maxBodyKb` |
| `timingMs`, `attempts` | time to the full body, and tries used |
| `error`, `errorMessage` | a named reason when no HTTP answer came back (see below) |

Request headers are never copied into the output.

### Input

- **Requests**: a list of `{url, method, headers, body, label}` objects. Only `url` is required. An object `body` is sent as JSON.
- **URLs**: a plain list that shares the Method, Headers and Body fields. Both lists can be used together (up to 10,000 requests per run).
- Method: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS (default GET).
- Follow redirects (default on, max 10 hops), timeout per request (default 30 s), retries (default 1), concurrency (default 5, max 20), parse JSON (default on), max stored body (default 1,024 KB).

Retries happen only after a network error, HTTP 429 or a 5xx answer, with back-off; a `Retry-After` header is honoured up to 10 s. Other 4xx answers are never retried. A retried POST can reach the server twice, so set Retries to 0 for calls that must not repeat.

### Errors you can see in a row (no HTTP answer)

`TIMEOUT`, `DNS_ERROR`, `CONNECTION_REFUSED`, `CONNECTION_RESET`, `TLS_ERROR`, `NETWORK_ERROR`, `TOO_MANY_REDIRECTS`, `SSRF_REFUSED`, `SCHEME_NOT_ALLOWED`, `INVALID_URL`, `INVALID_ROW`. These rows are free.

### Safety

- **Public addresses only.** A request whose host resolves to a private, loopback, link-local or cloud-metadata address (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1, fc00::/7, fe80::/10, 0.0.0.0 and similar) is refused with `SSRF_REFUSED`, checked after DNS resolution and again on every redirect hop. Only `http` and `https` are allowed.
- **Secrets stay out of the log.** Authorization, Cookie, `*key*`, `*token*` and similar header values are masked in logs, and URLs are logged without their query string.
- Requests go out directly from the Apify platform. There is no proxy option.

### Pricing

Pay per event, we pay the compute:

- **$1.00 per 1,000 requests that got an HTTP answer** (any status code, including 404 and 500: you got an answer). Cheaper on higher Apify plans.
- $0.001 per run.
- **Not charged:** network failures, timeouts, refused private addresses, invalid rows.

If the run's charge limit is reached the run stops cleanly, says so in its status message, and the rows already delivered are kept.

### Run status

The run ends with a message such as `DELIVERED: 18 of 20 request(s) answered: 15 x 2xx, 3 x 4xx; the 2 that got no answer were not charged.` A run where every request failed at network level ends FAILED with the reason; invalid input ends SUCCEEDED with an `Input error: ...` message and no request is sent.

### Example

```json
{
  "method": "GET",
  "headers": { "Accept": "application/json" },
  "urls": [
    "https://jsonplaceholder.typicode.com/todos/1",
    "https://jsonplaceholder.typicode.com/todos/2"
  ]
}
```

Limits: responses are read up to `maxBodyKb`; binary content types (images, PDFs, archives) are counted in `bytes` but not stored. No cookies are kept between requests. Use only on services you are allowed to call.

# Changelog

This Actor's version history is a separate document: https://apify.com/tagadanar/bulk-http-request/changelog.md

# Actor input Schema

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

One object per request: {"url": "https://api.example.com/items/1", "method": "POST", "headers": {"Authorization": "Bearer ..."}, "body": {"name": "x"}, "label": "my id"}. Only "url" is required; method, headers and body fall back to the shared settings below. An object body is sent as JSON. "label" is copied to the output row so you can match results to your own records. Up to 10,000 requests per run, from this list and the URL list together.

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

A plain list of URLs that all use the shared Method, Headers and Body below. Handy for calling one API endpoint with many ids.

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

HTTP method for the URL list and for any request that has no method of its own.

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

Headers sent with every request, e.g. {"Accept": "application/json", "Authorization": "Bearer YOUR_TOKEN"}. A request's own headers win. Header values are never written to the log or the output rows.

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

Request body for POST, PUT, PATCH and DELETE. Text is sent as is; text that is valid JSON gets Content-Type: application/json unless you set one. For a different body per request use the Requests list.

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

Follow up to 10 redirects (each hop is checked again). Off: you get the 3xx answer itself, with its Location header.

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

Give up on an attempt after this long (connect plus full body).

## `maxRetries` (type: `integer`):

Extra attempts after a network error, HTTP 429 or a 5xx answer, with back-off (a Retry-After header is honoured, up to 10 s). 4xx answers other than 429 are never retried. Note a retried POST can be processed twice by the server: set 0 for non-idempotent calls.

## `concurrency` (type: `integer`):

Requests in flight at once (max 20). Lower it for APIs with strict rate limits.

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

Put JSON answers in the "json" column as real data instead of text. Off: the raw text is always in "text".

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

Response bodies are cut at this size and flagged truncated: true (a cut JSON body is kept as text). Binary content types (images, PDFs, archives) are counted but not stored.

## Actor input object example

```json
{
  "requests": [
    {
      "url": "https://jsonplaceholder.typicode.com/todos/1",
      "label": "get-json"
    },
    {
      "url": "https://httpbin.org/post",
      "method": "POST",
      "body": {
        "hello": "world"
      },
      "label": "post-json"
    },
    {
      "url": "https://httpbin.org/status/404",
      "label": "not-found"
    }
  ],
  "method": "GET",
  "followRedirects": true,
  "timeoutSecs": 30,
  "maxRetries": 1,
  "concurrency": 5,
  "parseJson": true,
  "maxBodyKb": 1024
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# 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 = {
    "requests": [
        {
            "url": "https://jsonplaceholder.typicode.com/todos/1",
            "label": "get-json"
        },
        {
            "url": "https://httpbin.org/post",
            "method": "POST",
            "body": {
                "hello": "world"
            },
            "label": "post-json"
        },
        {
            "url": "https://httpbin.org/status/404",
            "label": "not-found"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tagadanar/bulk-http-request").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 = { "requests": [
        {
            "url": "https://jsonplaceholder.typicode.com/todos/1",
            "label": "get-json",
        },
        {
            "url": "https://httpbin.org/post",
            "method": "POST",
            "body": { "hello": "world" },
            "label": "post-json",
        },
        {
            "url": "https://httpbin.org/status/404",
            "label": "not-found",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("tagadanar/bulk-http-request").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 '{
  "requests": [
    {
      "url": "https://jsonplaceholder.typicode.com/todos/1",
      "label": "get-json"
    },
    {
      "url": "https://httpbin.org/post",
      "method": "POST",
      "body": {
        "hello": "world"
      },
      "label": "post-json"
    },
    {
      "url": "https://httpbin.org/status/404",
      "label": "not-found"
    }
  ]
}' |
apify call tagadanar/bulk-http-request --silent --output-dataset

```

## MCP server setup

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

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/ZhqjrvdafCXb5OKfv/builds/j3gqzjf4SbMBqU3mj/openapi.json
