# HTTP Request Runner – Bulk API Calls for n8n, Make & Zapier (`gazidev/http-request-runner`) Actor

Send bulk HTTP/API requests (GET, POST, PUT, PATCH, DELETE) from a list or a URL/JSON template filled from a dataset, CSV or Google Sheet. Concurrency, rate limits, retries, pagination, JSON pick. $0.50 per 1,000 successful requests; failed ones are free.

- **URL**: https://apify.com/gazidev/http-request-runner.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 successful requests

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## HTTP Request Runner – Bulk API Calls for n8n, Make & Zapier

Run **hundreds or thousands of HTTP / REST API requests in one go**: GET, POST, PUT, PATCH, DELETE, HEAD or OPTIONS, with your own headers, query parameters and JSON or raw bodies. Give it a list of requests, or **one URL/body template plus rows** from an Apify dataset, a CSV, a Google Sheet or inline JSON, and it sends one request per row.

Built for **n8n, Make (Integromat) and Zapier users** who hit loop limits, operation costs or timeouts when they call an API for every row. Here the loop runs on Apify with concurrency, a per-host rate limit, retries with backoff on 429/5xx, pagination and a JSONPath-style field picker, and you get one clean dataset row per request.

**$0.50 per 1,000 successful requests.** Failed, blocked and non-2xx/3xx requests are free.

### What you can do with it

- **Enrich a spreadsheet or dataset through any API**: one call per row (`https://api.example.com/companies/{{domain}}`), with the result joined back to the row.
- **Bulk POST / PATCH / DELETE**: create or update records in a CRM, CMS, e-commerce or ticketing system from a CSV.
- **Replace an n8n/Make "HTTP Request" loop**: send 5,000 calls in one run, then pull one dataset into your workflow, or have each result POSTed to your webhook.
- **Paginate through an API**: follow `next` URLs, cursors or GitHub-style `Link` headers.
- **Health-check endpoints** and capture status codes, timings and headers.
- **Chain Actors**: feed the output dataset of another Apify Actor into API calls.

### Features

- Methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. Custom headers, query parameters, JSON or raw body (form data, XML, GraphQL...).
- **Templating** with `{{field}}` placeholders in the URL, headers, query and body. Dot paths work (`{{address.city}}`, `{{tags.0}}`). In URLs, values are URL-encoded; use `{{{field}}}` for a raw value (for example a full URL). In a JSON body, a string that is exactly `"{{field}}"` keeps the row's native type (number, list, object).
- **Template rows** from an Apify dataset (picker), a public CSV / Google Sheets link, or inline JSON.
- **Concurrency** (1-100) and a **max requests per second per host** limit.
- **Retries** on timeouts, network errors, 408, 425, 429 and 5xx with exponential backoff; `Retry-After` is honored.
- **Pagination**: next-page path or cursor parameter in JSON, or the `Link: rel="next"` header.
- **Pick fields** from JSON responses with paths like `$.data.id` or `items[*].name`.
- **Response size cap** (stops reading after N bytes) and body storage options: dataset (parsed JSON or truncated text), key-value store (full body with a link; good for HTML, files and images) or none.
- **Webhook forward**: POST every result row to an n8n, Make or Zapier webhook.
- **SSRF protection**: requests to private, loopback, link-local, cloud-metadata (169.254.169.254) and other reserved addresses are blocked, and so are internal hostnames. DNS is resolved and checked before every connection, including redirects.

### Input examples

The prefill sends two requests to httpbin.org (a GET and a JSON POST) and finishes in a few seconds.

**One API call per row of a dataset or CSV:**

```json
{
  "urls": ["https://api.example.com/v1/companies?domain={{domain}}"],
  "headers": { "Authorization": "Bearer YOUR_API_KEY" },
  "csvUrl": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit#gid=0",
  "pick": { "name": "$.data.name", "employees": "$.data.employee_count" },
  "concurrency": 5,
  "maxRequestsPerSecondPerHost": 5
}
```

**Bulk POST with a JSON body template:**

```json
{
  "requests": [{
    "method": "POST",
    "url": "https://api.example.com/v1/contacts",
    "headers": { "Authorization": "Bearer YOUR_API_KEY" },
    "json": { "email": "{{email}}", "name": "{{first}} {{last}}", "tags": "{{tags}}" },
    "label": "contact-{{email}}"
  }],
  "templateRows": [
    { "email": "ann@example.com", "first": "Ann", "last": "Lee", "tags": ["lead"] },
    { "email": "bo@example.com", "first": "Bo", "last": "Diaz", "tags": ["customer", "vip"] }
  ]
}
```

**Cursor pagination:**

```json
{
  "urls": ["https://api.example.com/v1/orders?limit=100"],
  "maxPagesPerRequest": 50,
  "paginationNextPath": "$.meta.next_cursor",
  "paginationCursorParam": "cursor"
}
```

**GitHub-style `Link` header pagination, bodies saved as files:**

```json
{
  "urls": ["https://api.github.com/repos/apify/crawlee/issues?per_page=100&state=all"],
  "followLinkHeader": true,
  "maxPagesPerRequest": 10,
  "saveBodyTo": "kv",
  "pick": { "numbers": "$[*].number" }
}
```

### Output

One dataset row per request (and per page). The **Requests** view shows status, timing and picked fields, and the **Responses** view shows bodies.

```json
{
  "index": 1,
  "label": "post-example",
  "method": "POST",
  "url": "https://httpbin.org/post",
  "requestedAt": "2026-10-22T13:12:40+00:00",
  "durationMs": 547,
  "attempts": 1,
  "finalUrl": "https://httpbin.org/post",
  "statusCode": 200,
  "ok": true,
  "contentType": "application/json",
  "bodyBytes": 482,
  "responseTruncated": false,
  "picked": { "origin": "203.0.113.7", "echo": { "a": 1 } },
  "json": { "json": { "a": 1 }, "url": "https://httpbin.org/post" },
  "responseHeaders": { "content-type": "application/json" },
  "charged": true
}
```

Other fields: `page` (with pagination), `body` + `bodyTruncated` (non-JSON or large responses), `bodyKey` + `bodyUrl` (key-value store mode), `input` (the template row), `error`. The run summary is in the key-value store record `OUTPUT`.

### Pricing

Pay per event:

| Event | Price |
|---|---|
| `request-sent`: a request that got a **2xx or 3xx** response (each pagination page counts) | $0.0005 ($0.50 / 1,000) |
| Actor start | $0.00005 |
| Failed requests (network error, timeout, 4xx, 5xx after retries, SSRF-blocked, invalid) | free |

Retries of the same request are not charged again. Set a **maximum cost per run** and the Actor stops cleanly when it is reached, without sending requests it could not bill.

At the time of writing (October 2026), the most-used similar Actors on the Apify Store list about $0.005 per request.

### Use with n8n, Make and Zapier

1. Start the Actor with the Apify node / module / app ("Run Actor"), passing your input JSON.
2. Either wait for the run and fetch the dataset items, or set **Forward each result to webhook** to your workflow's webhook URL to receive each result as it finishes.

### Use with AI agents (Apify MCP)

Through the [Apify MCP server](https://mcp.apify.com), an agent in Claude, ChatGPT or another MCP client can call this Actor to make many API calls at once, for example *"call this endpoint for each of these 200 IDs and give me the status codes and the `price` field"*.

### FAQ

**Is my API key safe?** Headers are only sent to the URLs you enter. Use the Actor's input as you would any secret config. Apify stores the input with the run, so keep runs private. Response headers are saved to the dataset unless you turn them off.

**Which responses are charged?** Only those with a 2xx or 3xx status. 4xx/5xx responses still get a row (with status and body), but they are free.

**Why was my request blocked?** For security, the Actor can't reach private networks, localhost, cloud metadata endpoints or internal hostnames, even through redirects or DNS. Public APIs only.

**Can it send files / multipart uploads?** Not yet. JSON and raw text bodies are supported.

**How big can responses be?** Up to 50 MB per response (default cap 5 MB). Large bodies are best saved to the key-value store.

**Does it run JavaScript or bypass anti-bot protection?** No. It's a plain HTTP client (HTTP/1.1 and HTTP/2) without proxies or browsers. Use it for APIs and endpoints that you're allowed to call.

**Rate limits?** Set `maxRequestsPerSecondPerHost` and `concurrency` to stay inside the API's limits. 429 responses are retried with backoff.

### Responsible use

You decide which endpoints you call. Only call APIs and sites that you're allowed to use, and respect their terms and rate limits.

# Actor input Schema

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

One request per URL, using the shared method, headers and body below. Use `{{field}}` placeholders to fill URLs from template rows (values are URL-encoded; `{{{field}}}` inserts raw).

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

Full request objects: `{"url", "method", "headers", "query", "json" or "body", "label"}`. Missing fields fall back to the shared settings below. Placeholders work in every field.

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

Used for `urls` and for request objects without a method.

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

E.g. `{"Authorization": "Bearer YOUR_TOKEN", "Accept": "application/json"}`. Values can use `{{field}}`.

## `queryParams` (type: `object`):

Added to every URL, e.g. `{"page": 1, "q": "{{keyword}}"}`.

## `jsonBody` (type: `object`):

Sent as `application/json`. A string that is exactly `"{{field}}"` becomes the row's native value (number, list, object).

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

Raw text body (form data, XML, GraphQL...) used when no JSON body is set. Set the Content-Type header yourself; defaults to JSON if it starts with `{`/`[`, else text/plain.

## `datasetId` (type: `string`):

Send one request per dataset item, filling `{{field}}` placeholders (dot paths like `{{address.city}}` work). Several IDs can be comma-separated.

## `csvUrl` (type: `string`):

Public CSV link or a Google Sheets link (shared as 'anyone with the link'). Column names become `{{placeholders}}`.

## `templateRows` (type: `array`):

E.g. `[{"id": 1}, {"id": 2}]`. Every row x every request = one call.

## `pick` (type: `object`):

`{"outputName": "$.path"}`. Paths: `$.data.id`, `items[0].name`, `items[*].id` (list). Saved in the `picked` column.

## `saveBodyTo` (type: `string`):

Use the key-value store for large, HTML or binary responses.

## `maxBodyChars` (type: `integer`):

Longer bodies are truncated (`bodyTruncated: true`). JSON is kept parsed only if it fits.

## `includeResponseHeaders` (type: `boolean`):

Adds a `responseHeaders` object.

## `includeInputRow` (type: `boolean`):

Copies the template row into an `input` column so you can join results back.

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

Requests in flight at once.

## `maxRequestsPerSecondPerHost` (type: `number`):

Rate limit per hostname (0 = unlimited). Respect the API's limits.

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

Retries on network errors, timeouts, 408, 425, 429 and 5xx with exponential backoff; `Retry-After` is honored (max 60 s).

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

Per attempt.

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

Up to 10 redirects. Every hop is checked by the SSRF protection.

## `maxResponseBytes` (type: `integer`):

Reading stops at this size (`responseTruncated: true`).

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

Upper limit on requests (rows x request templates). Pages from pagination are extra.

## `maxPagesPerRequest` (type: `integer`):

> 1 turns on pagination. Each page is its own row and its own charge.

## `paginationNextPath` (type: `string`):

Path to the next-page URL or cursor in the JSON response, e.g. `$.next`, `$.links.next`, `$.meta.next_cursor`.

## `paginationCursorParam` (type: `string`):

If set, the value found at the path is sent in this query parameter (e.g. `cursor`, `page_token`) instead of being used as a URL.

## `followLinkHeader` (type: `boolean`):

GitHub-style pagination.

## `forwardWebhookUrl` (type: `string`):

Optional. Every result row is POSTed as JSON to this URL (e.g. an n8n / Make / Zapier webhook). Best effort, not retried.

## Actor input object example

```json
{
  "urls": [
    "https://httpbin.org/get?source=apify"
  ],
  "requests": [
    {
      "method": "POST",
      "url": "https://httpbin.org/post",
      "json": {
        "a": 1
      },
      "label": "post-example"
    }
  ],
  "method": "GET",
  "pick": {
    "origin": "$.origin",
    "echo": "$.json"
  },
  "saveBodyTo": "dataset",
  "maxBodyChars": 20000,
  "includeResponseHeaders": true,
  "includeInputRow": true,
  "concurrency": 10,
  "maxRequestsPerSecondPerHost": 5,
  "maxRetries": 3,
  "timeoutSecs": 30,
  "followRedirects": true,
  "maxResponseBytes": 5000000,
  "maxRequests": 10,
  "maxPagesPerRequest": 1,
  "followLinkHeader": false
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

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

No description

## `summary` (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 = {
    "urls": [
        "https://httpbin.org/get?source=apify"
    ],
    "requests": [
        {
            "method": "POST",
            "url": "https://httpbin.org/post",
            "json": {
                "a": 1
            },
            "label": "post-example"
        }
    ],
    "pick": {
        "origin": "$.origin",
        "echo": "$.json"
    },
    "maxRequests": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/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://httpbin.org/get?source=apify"],
    "requests": [{
            "method": "POST",
            "url": "https://httpbin.org/post",
            "json": { "a": 1 },
            "label": "post-example",
        }],
    "pick": {
        "origin": "$.origin",
        "echo": "$.json",
    },
    "maxRequests": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("gazidev/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://httpbin.org/get?source=apify"
  ],
  "requests": [
    {
      "method": "POST",
      "url": "https://httpbin.org/post",
      "json": {
        "a": 1
      },
      "label": "post-example"
    }
  ],
  "pick": {
    "origin": "$.origin",
    "echo": "$.json"
  },
  "maxRequests": 10
}' |
apify call gazidev/http-request-runner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gazidev/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/QmIxpuxIfBUFYJAcu/builds/ddWawPIzb39MsA1fc/openapi.json
