# HTTP Request Runner (`deriverge/http-request-runner`) Actor

Sends the HTTP requests you define to any API and returns each response as a row with status, timing, JSON or text and the fields you pick. Fills URL and body templates from rows, follows pagination and retries 429 and 5xx with backoff.

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

## Pricing

from $0.15 / 1,000 requests

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

## HTTP Request Runner

HTTP Request Runner sends the HTTP requests you define and returns one row per response: status code, timing, the body as JSON or text, fields you pick with JSON paths, and the error when something failed. Use it to call a REST API in bulk from a schedule, a workflow or another run. Requests can be filled from rows of data (`{⁠{id}⁠}` placeholders), follow pagination and retry after 429 and 5xx. Requests go from Apify's servers without a proxy, so sites that block data centers block this actor too. It costs $0.30 per 1,000 requests on the Free plan ($0.15 on Business), and the $5 of monthly credit in Apify's Free plan covers about 16,000 requests.

### What data does it return?

| Field | Description |
|---|---|
| `requestIndex`, `page` | Position of the request in the input after templates are filled, starting at 0, and the page number within a paginated request. |
| `label`, `userData` | Your own label and data from the request, copied to the row so you can match responses to inputs, such as `"label": "user {⁠{id}⁠}"`. |
| `templateRowIndex` | Which template row filled the request, starting at 0; `null` without a template. |
| `method`, `url`, `finalUrl` | Method and URL sent, and the URL after redirects. Values of secret query parameters show as `REDACTED`. |
| `status`, `statusText`, `ok` | HTTP status such as `200`, the reason phrase, and `true` when the status is in **Success status codes** (2xx by default). |
| `responseTimeMs`, `totalTimeMs`, `attempts`, `redirects` | Time of the last attempt, total time with retries and waits, number of attempts and redirects followed. |
| `contentType`, `bodyBytes` | Content type of the response and body size in bytes after decompression. |
| `json` | The parsed body when the response is JSON. |
| `text` | The body as text when it is not JSON, such as HTML, XML or CSV; `null` for images, PDFs and other binary files. |
| `extracted` | Values found at the JSON paths in **Fields to extract**, such as `{"name": "Example", "ids": [1, 2]}`. |
| `itemCount` | Number of items in the list at **Items path**, or in a response that is itself a list. |
| `responseHeaders` | Response headers without `set-cookie`, only with **Save response headers** on. Request headers are never saved. |
| `error`, `errorType` | What went wrong, and its type: `status`, `network`, `dns`, `timeout`, `too_large`, `tls`, `blocked`, `bot_protection`, `skipped`, `template` or `invalid_input`. |
| `bodyTruncated`, `requestedAt` | `true` when a body over 4 MB was cut, and when the request started (UTC). |

### How to call an API in bulk

1. Add requests to **Requests** as a JSON list. Each one needs `url` and can have `method`, `headers`, `query`, `json` (a JSON body), `body` (a text body), `form` (form fields), `label` and `userData`. For plain GET requests, paste URLs into **URLs** instead.
2. Put API keys and tokens in **Secret headers**, such as `{"Authorization": "Bearer ..."}`, or in **Secret query parameters**, such as `{"api_key": "..."}`. Both are stored encrypted. Headers, query parameters and a body for every request go into **Headers**, **Query parameters** and **Body**.
3. To send one request per row, write `{⁠{field}⁠}` in the URL, headers, query, body or label and list the rows in **Template rows**, or pick a dataset in **Template rows from a dataset**. A value that is the whole JSON field, such as `"userId": "{⁠{id}⁠}"`, keeps its type (number, list, object).
4. For lists spread over pages, choose **Pagination** and fill **Next page path**, **Items path** or the page parameter, and set **Maximum pages**.
5. Pick fields with **Fields to extract**, set **Save the response body** to keep only those, and click **Start**. Rows appear in the **Output** tab as they finish, ready to export as JSON, CSV or Excel. The `SUMMARY` record counts responses by status, retries and failures.

An input that looks up three users and follows a cursor on each:

```json
{
  "requests": [
    {
      "url": "https://api.example.com/v1/users/{⁠{id}⁠}/orders",
      "query": { "limit": 100 },
      "label": "orders of {⁠{id}⁠}"
    }
  ],
  "secretHeaders": { "Authorization": "Bearer YOUR_TOKEN" },
  "templateRows": [{ "id": 101 }, { "id": 102 }, { "id": 103 }],
  "paginationMode": "cursor",
  "nextPath": "meta.next_cursor",
  "cursorParam": "cursor",
  "itemsPath": "data",
  "extract": { "orderIds": "$.data[*].id" }
}
```

### Example output

One row from a run on 11 October 2026 that sent a templated request to a public test API (JSONPlaceholder) with a secret query parameter, which shows as `REDACTED` in `finalUrl`.

```json
{
  "requestIndex": 0,
  "page": 1,
  "label": "post 1",
  "userData": {
    "row": 1
  },
  "templateRowIndex": 0,
  "method": "GET",
  "url": "https://jsonplaceholder.typicode.com/posts/1",
  "finalUrl": "https://jsonplaceholder.typicode.com/posts/1?api_key=REDACTED",
  "status": 200,
  "statusText": "OK",
  "ok": true,
  "attempts": 1,
  "redirects": 0,
  "responseTimeMs": 78,
  "totalTimeMs": 79,
  "contentType": "application/json; charset=utf-8",
  "bodyBytes": 292,
  "itemCount": null,
  "json": {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  "text": null,
  "bodyTruncated": false,
  "extracted": {
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "id": 1
  },
  "error": null,
  "errorType": null,
  "requestedAt": "2026-10-11T15:38:41.209Z"
}
```

### How much does it cost to send HTTP requests?

| | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| 1,000 requests | $0.30 | $0.24 | $0.195 | $0.15 |

You pay only for the events in the table. There is no start fee, and compute time and proxies are included.

10,000 requests cost $3.00 on the Free plan and $1.50 on Business. A request is charged once when the server answers with any status code, and each page of a paginated request is one request. Retries, requests that got no answer, addresses the actor refuses, template errors and bot protection pages are not charged.

### Limits

- At most 50 requests run in parallel and at most 20 per second go to one host; the default is 10 in parallel and 5 per second. In a test on 11 October 2026, 1,000 requests to three public APIs took 62 seconds with 30 in parallel and 20 per second per host, 30 of them retried once.
- Up to 10 retries with waits of 1, 2, 4, 8 seconds and so on, or as long as the `Retry-After` header asks, up to 300 seconds. POST and PATCH are retried after 429 and 503 only unless you turn on **Retry POST and PATCH on any error**.
- Responses are read up to **Maximum response size** (5 MB by default, 20 MB at most), and up to 4 MB of a body is saved per row. Binary files are counted but not saved.
- There is no proxy and no browser. Requests leave from Apify's data center addresses with the user agent `deriverge-http-request-runner/1.0` unless you set your own `User-Agent`.
- When a host answers with a bot protection page (Cloudflare, DataDome, Imperva, AWS WAF, PerimeterX, a captcha), the actor sends nothing more to that host in the run, marks the rest as `skipped` and does not try to get around it.
- Private, local and reserved addresses (10.x, 192.168.x, 127.x, 169.254.x, IPv6 local ranges), cloud metadata addresses, host names such as `localhost` and non-HTTP ports are refused, also after a redirect or DNS lookup.
- Pagination goes up to 1,000 pages per request. Page and offset pagination stop at the first page with an empty list, so the last request returns an empty page. Cursors are sent as a query parameter only.
- JSON paths support dots, `[0]`, `[-1]`, `[*]`, `['name']` and `..`; filter expressions such as `[?(@.price > 10)]` are not supported.
- Template rows from a dataset are read up to 100,000 rows. Cookies are not kept between requests; send a `Cookie` header if an API needs one.

### Keeping API keys safe

**Secret headers** and **Secret query parameters** are encrypted fields: Apify stores them encrypted with a key that only runs of this actor under your account can use. The actor never writes them to the log or to the output, never saves request headers at all, and replaces their values (6 characters or longer) with `REDACTED` wherever they appear in a URL or a response body, for example when an echo API returns your headers. When a redirect leads to another host, the secret headers, `Authorization` and `Cookie` are dropped before the next request.

### FAQ

#### Is it legal to send requests with this actor?

The actor sends only the requests you define, to the addresses you give, and does not get around logins, rate limits or bot protection. You are responsible for having the right to call each API, for its terms of use and rate limits, and for any personal data in the responses.

#### Can I call the Apify API or my own backend with it?

Yes, any public HTTPS or HTTP address works, with your token in **Secret headers**. Servers on a private network, such as a database in your office or a service on localhost, cannot be reached, because the actor refuses private addresses.

#### Why does a row have status 403 or 429?

The server refused the request: 403 often means a missing or wrong API key, 429 that you went over its rate limit. Lower **Requests per second per host**, raise **Retries**, and check `errorType`; `bot_protection` means the site blocks automated traffic and the actor stopped calling it.

### Related scrapers

- [Article Extractor](https://apify.com/deriverge/article-extractor)
- [PDF Text Extractor](https://apify.com/deriverge/pdf-text-extractor)
- [Tech Stack Detector](https://apify.com/deriverge/tech-stack-detector)

### Support

This actor is built and maintained by deriverge s.r.o., a software company based in the Czech Republic. If a run fails or a field you need is missing, please open an issue in the **Issues** tab or write to us at info@deriverge.com. We respond in English and Czech. Runs can be scheduled in Apify Console or started from the **API** tab, which has examples for Python, JavaScript and cURL and works with Make, Zapier, n8n and the Apify MCP server. If the actor saves you time, a short review helps other people find it.

# Changelog

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

# Actor input Schema

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

A JSON list of requests. Each one is an object with "url" and optionally "method", "headers", "query", "json" (a JSON body), "body" (a text body), "form" (form fields), "label" and "userData" (copied to the output row). {{field}} placeholders are filled from Template rows. A plain URL string also works.

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

A quicker way to add requests: one URL per line, sent with the method, headers, query parameters and body below.

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

Method for URLs and for requests that do not set their own.

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

Headers for every request, such as {"Accept": "application/json"}. Put API keys and Authorization in Secret headers instead, where they are stored encrypted.

## `secretHeaders` (type: `object`):

Headers with credentials, such as {"Authorization": "Bearer ..."} or {"X-API-Key": "..."}. Stored encrypted, never logged and never written to the output. They are dropped when a redirect leads to another host.

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

Query parameters added to every URL, such as {"limit": 100, "lang": "en"}. A list value repeats the parameter.

## `secretQueryParams` (type: `object`):

Query parameters with credentials, such as {"api_key": "..."}. Stored encrypted and added to every URL when it is sent; their values show as REDACTED in the output.

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

Body for URLs and for requests without their own. Valid JSON is sent as application/json, other text as text/plain unless you set Content-Type in Headers.

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

A JSON list of objects, such as \[{"id": 1}, {"id": 2}]. A request with {{id}} in it is sent once per row; requests without placeholders are sent once. Nested fields work as {{user.id}}.

## `templateDatasetId` (type: `string`):

A dataset whose items are used as template rows, after the ones above. The run can read only the dataset you pick.

## `paginationMode` (type: `string`):

How to find the next page.

## `nextPath` (type: `string`):

JSON path to the next page URL (for Next page URL) or the next cursor (for Cursor), such as next, links.next or meta.next_cursor.

## `cursorParam` (type: `string`):

Query parameter that carries the cursor, such as cursor, after or starting_after. Defaults to cursor.

## `pageParam` (type: `string`):

Query parameter for page numbers or offsets, such as page, offset or skip. Defaults to page, or offset in offset mode.

## `pageStart` (type: `integer`):

Value of the page or offset parameter on the first request when the URL does not have it. Defaults to 1 for pages and 0 for offsets.

## `offsetStep` (type: `integer`):

How much the offset grows per page, usually your page size. Leave it empty to use the number of items on the previous page.

## `itemsPath` (type: `string`):

JSON path to the list of results in each response, such as data, results or items. Pagination stops at a page where this list is empty. Needed for page and offset pagination unless the response itself is a list.

## `maxPages` (type: `integer`):

Most pages to request for each request in the input, the first one included.

## `extract` (type: `object`):

Output names and JSON paths to copy from each JSON response into the extracted field, such as {"name": "$.data.name", "ids": "$.items\[*].id"}. Paths support dots, \[0], \[-1], \[*] and ..

## `saveBody` (type: `string`):

What to keep from each response. Bodies over 4 MB are cut and marked with bodyTruncated.

## `saveResponseHeaders` (type: `boolean`):

Adds the response headers (without set-cookie) to each row. Request headers are never saved.

## `successStatuses` (type: `array`):

Status codes that count as success (ok is true), as codes (404), ranges (200-299) or classes (2xx). Other codes still return a row with error filled in, and they end pagination.

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

How many requests run at the same time across all hosts.

## `requestsPerSecondPerHost` (type: `number`):

Highest rate for any one host, 20 at most. Lower it to stay inside the rate limit of the API you call.

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

How many times to retry after HTTP 429, a 5xx status, a timeout or a dropped connection. Waits 1, 2, 4, 8 s and so on, or as long as the Retry-After header asks, up to 300 s.

## `retryNonIdempotent` (type: `boolean`):

POST and PATCH requests are retried after 429 and 503 only, because a server error or a dropped connection may mean the server already acted on them. Turn this on to retry them like the other methods.

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

Time for one attempt, redirects included.

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

Follow up to 10 redirects. When off, a 3xx response is returned as it is.

## `ignoreTlsErrors` (type: `boolean`):

Accept expired or self-signed certificates. Only for servers you trust.

## `maxResponseMegabytes` (type: `integer`):

Responses larger than this are stopped and reported as too_large.

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

Stop after this many requests, pages included, so the cost of a run stays predictable. Leave it empty for no limit.

## Actor input object example

```json
{
  "requests": [
    {
      "url": "https://jsonplaceholder.typicode.com/todos/1",
      "label": "todo"
    },
    {
      "url": "https://registry.npmjs.org/-/package/apify/dist-tags",
      "label": "npm"
    },
    {
      "url": "https://pokeapi.co/api/v2/pokemon?limit=3",
      "label": "pokemon"
    }
  ],
  "method": "GET",
  "paginationMode": "none",
  "maxPages": 10,
  "saveBody": "full",
  "saveResponseHeaders": false,
  "successStatuses": [
    "2xx"
  ],
  "maxConcurrency": 10,
  "requestsPerSecondPerHost": 5,
  "maxRetries": 3,
  "retryNonIdempotent": false,
  "timeoutSecs": 30,
  "followRedirects": true,
  "ignoreTlsErrors": false,
  "maxResponseMegabytes": 5
}
```

# Actor output Schema

## `responses` (type: `string`):

One row per request or page: status, timing, the JSON or text body, extracted fields and the error, if any.

## `summary` (type: `string`):

Requests sent, responses by status code, retries, pages, failures by type and hosts that answered with bot protection.

# 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": "todo"
        },
        {
            "url": "https://registry.npmjs.org/-/package/apify/dist-tags",
            "label": "npm"
        },
        {
            "url": "https://pokeapi.co/api/v2/pokemon?limit=3",
            "label": "pokemon"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("deriverge/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 = { "requests": [
        {
            "url": "https://jsonplaceholder.typicode.com/todos/1",
            "label": "todo",
        },
        {
            "url": "https://registry.npmjs.org/-/package/apify/dist-tags",
            "label": "npm",
        },
        {
            "url": "https://pokeapi.co/api/v2/pokemon?limit=3",
            "label": "pokemon",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("deriverge/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 '{
  "requests": [
    {
      "url": "https://jsonplaceholder.typicode.com/todos/1",
      "label": "todo"
    },
    {
      "url": "https://registry.npmjs.org/-/package/apify/dist-tags",
      "label": "npm"
    },
    {
      "url": "https://pokeapi.co/api/v2/pokemon?limit=3",
      "label": "pokemon"
    }
  ]
}' |
apify call deriverge/http-request-runner --silent --output-dataset

```

## MCP server setup

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