# HTTP Request Sender: Batch API Calls, Pagination & Retries (`larkmoor/http-request-sender`) Actor

Send hundreds of HTTP requests (GET, POST, PUT, PATCH, DELETE) to any API in one run: headers, JSON/form bodies, {{placeholders}} from a list, retries, pagination, proxy, JSON array splitting. $2.50 per 1,000 responses; network errors free.

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

## Pricing

from $2.13 / 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

## HTTP Request Sender: Batch API Calls, Pagination & Retries

Send hundreds or thousands of HTTP requests to any REST API or URL in **one run** and get every response back as a clean dataset row: status code, timing, parsed JSON (or text), errors and, if you want, response headers. GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS all work, with your own headers, query parameters and JSON, form or raw bodies.

It is built for the jobs where a no-code tool or a spreadsheet needs "just one API call per row": enrich a list through a REST API, push records into a CRM, trigger webhooks, warm a cache, check that endpoints answer, or pull every page of a paginated API into one table.

### What you can use it for

- **Clay, Make, Zapier and n8n users**: call any API without a paid HTTP plan, one run for the whole list instead of one run per call
- **Enrichment**: a URL template like `https://api.example.com/companies/{{domain}}` plus a list of rows = one request per row, results in one dataset
- **Bulk create / update**: POST or PATCH a JSON body per record (placeholders work inside the body too)
- **Pull a paginated API**: follow `page=1,2,3…` or a `next` link and split the returned array into one row per item
- **Webhooks and health checks**: fire a batch of webhooks, check that a list of endpoints answers 200, measure response times
- **Geo tests**: route requests through Apify Proxy in a chosen country

### Why this one

- **$2.50 per 1,000 responses**, half the price of the other HTTP-request actors ($5 per 1,000)
- **Network errors and timeouts are free**: you pay only when a server actually answered
- **Retries done right**: retries on 408/425/429/5xx and network errors with back-off, and it waits for the server's `Retry-After`
- **Templates**: `{{placeholders}}` from your rows in the URL, headers, query params and body; a placeholder that is the whole value keeps its type (numbers stay numbers in JSON)
- **Pagination built in**: page-number, offset (set the step) or next-link (`next`, `links.next`, `paging.next.url` …)
- **Array splitting**: `splitItemsPath: "data.items"` turns one API response into one dataset row per item — ready for CSV or Excel
- **Rate-limit friendly**: parallel requests (1–50) plus a global minimum delay between requests
- **Binary files**: download images or PDFs as base64 when you need the bytes

### Input

| Field | What it does |
|---|---|
| Requests | List of URL strings or objects `{url, method, headers, queryParams, body, bodyType, label}` |
| URLs (simple mode) | Plain URLs sent with the shared settings |
| URL template + Template rows | One request per row, `{{field}}` filled from the row |
| Shared method / headers / query params / body | Defaults for every request (a request's own values win) |
| Body type | JSON (default), form (x-www-form-urlencoded) or raw text |
| Response format | Auto, JSON, text or base64 |
| Split JSON array into rows | Dot path to an array in the response (`.` = top-level array) |
| Page parameter, start page, page step | Page-number or offset pagination |
| Next-page URL path | Follow a next link in the JSON response |
| Max pages per request | How far to paginate |
| Parallel requests, delay, timeout, retries, retry codes | Speed and politeness |
| Max requests | Hard cap per run |
| Proxy | Optional Apify Proxy with country |

Example input, three ways at once:

```json
{
  "requests": [
    {"url": "https://jsonplaceholder.typicode.com/posts", "method": "POST", "body": {"title": "Hello", "userId": 1}, "label": "create post"}
  ],
  "urlTemplate": "https://jsonplaceholder.typicode.com/users/{{id}}",
  "templateRows": [{"id": 1, "label": "first"}, {"id": 2, "label": "second"}],
  "headers": {"Accept": "application/json"},
  "maxConcurrency": 5
}
```

Pagination example (every page of a list, one row per item):

```json
{
  "urls": ["https://pokeapi.co/api/v2/pokemon?limit=100"],
  "nextUrlPath": "next",
  "splitItemsPath": "results",
  "maxPagesPerRequest": 20
}
```

### Output

One row per response (or one row per array item when you split):

```json
{
  "requestIndex": 0,
  "label": "create post",
  "method": "POST",
  "url": "https://jsonplaceholder.typicode.com/posts",
  "ok": true,
  "status": 201,
  "statusText": "Created",
  "contentType": "application/json",
  "contentLength": 47,
  "attempts": 1,
  "elapsedMs": 252,
  "json": {"title": "Hello", "userId": 1, "id": 101}
}
```

Other fields when they apply: `body` (text responses), `bodyTruncated`, `bodyBase64`, `finalUrl` (after redirects), `responseHeaders`, `page`, `pageIndex`, `itemIndex` + `item` (split mode), `error` (for example `HTTP 404` or the network error).

### How much does it cost?

Pay per event: **$0.0025 per HTTP response** (any status code) plus $0.001 per run start. Requests that fail before a server answers (DNS errors, timeouts, refused connections) are free. Higher Apify plans get tier discounts (Bronze −5%, Silver −10%, Gold and up −15%).

Worked examples (Free tier):

- **200 enrichment calls** from a URL template: 200 × $0.0025 = **$0.50** + $0.001
- **A paginated API, 30 pages of 100 items** split into 3,000 rows: 30 responses × $0.0025 = **$0.075** — you pay per page, not per item
- **5,000 webhook POSTs**: **$12.50**; at 256 MB the platform compute for such a run is a few cents

Set a maximum cost per run in Apify and the actor stops cleanly when it is reached.

### FAQ

**Is it legal?** The actor sends the requests you define, to the APIs and URLs you choose, like curl or Postman would. You are responsible for having the right to call those endpoints and for respecting their terms and rate limits (use the delay setting).

**Where do I put my API key?** In the shared headers (for example `Authorization: Bearer …`) or as a query param. Inputs are stored in your own Apify account; keep runs private if you share the account.

**Do placeholders work in the body?** Yes, in the URL, headers, query params and body, with dot paths like `{{user.id}}`.

**How are non-2xx responses handled?** They are returned with `ok: false` and `error: "HTTP 4xx"`. 429 and 5xx are retried first. Turn on "Only output successful responses" to keep them out of the dataset.

**What is the order of the rows?** Rows arrive as responses complete; sort by `requestIndex` (and `pageIndex` / `itemIndex`) to restore input order.

**Can it call localhost or cloud metadata addresses?** Link-local and metadata addresses are refused.

**Can I send files?** Bodies are JSON, form or raw text. Multipart file uploads are not supported yet.

### Tips

- Start with `maxRequests: 10` to check headers and bodies, then raise it.
- For APIs with strict rate limits, set parallel requests to 1 and a delay (for example 1,000 ms).
- For offset pagination, set the page parameter to `offset`, start page `0` and page step to your page size.

### Integrations

Run it from the Apify API, a schedule, Make, Zapier, n8n or Clay, and read the dataset as JSON, CSV or Excel. Chain it after another actor with a webhook to call an API for every row that actor found.

### Changelog

- 0.1 (Oct 2026): first release — batch requests, templates, retries with Retry-After, page/offset/next-link pagination, array splitting, base64 bodies, proxy by country.

# Actor input Schema

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

A list of requests. Each item is a URL string or an object: {"url", "method", "headers", "queryParams", "body", "bodyType", "label"}. Missing fields use the shared settings below.

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

Plain URLs. Each one is sent with the shared method, headers, query params and body.

## `urlTemplate` (type: `string`):

A URL with {{placeholders}}, e.g. https://api.example.com/users/{{id}}. One request is sent per row in 'Template rows'. Placeholders also work in headers, query params and the body.

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

Rows that fill the URL template, e.g. \[{"id": 1}, {"id": 2}]. Plain values are available as {{value}}. Dot paths like {{user.id}} work. A 'label' field is copied to the output.

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

Used when a request does not set its own method.

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

Headers added to every request, e.g. {"Authorization": "Bearer ...", "Accept": "application/json"}. A request's own headers win.

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

Query string parameters added to every URL.

## `body` (type: `object`):

Body for POST/PUT/PATCH requests that do not set their own. JSON by default.

## `bodyType` (type: `string`):

How bodies are sent.

## `responseFormat` (type: `string`):

How the response body is stored.

## `splitItemsPath` (type: `string`):

Dot path to an array in the JSON response (e.g. data.items, results, or . for a top-level array). Each array item becomes its own dataset row with the request details. You are still charged once per HTTP response, not per item.

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

Add all response headers to each row.

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

Longer text bodies are cut and marked bodyTruncated.

## `onlySuccessful` (type: `boolean`):

Non-2xx responses are left out of the dataset (they still count as a sent request).

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

Query parameter that holds the page number, e.g. page or \_page. Leave empty for no page-number pagination.

## `startPage` (type: `integer`):

First page number (use 0 for zero-based APIs).

## `pageStep` (type: `integer`):

Add this to the page parameter each time (e.g. 100 for offset-style APIs).

## `nextUrlPath` (type: `string`):

Dot path to the next page link in the JSON response, e.g. next or links.next or paging.next.url. Followed until it is empty.

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

How many pages to follow for each request. Stops early on an empty page or a non-2xx response.

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

How many requests run at the same time.

## `delayMs` (type: `integer`):

Minimum gap between two requests across the whole run. Use it to respect an API's rate limit.

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

How long to wait for one response before retrying or giving up.

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

Retries on network errors and on the status codes below. Retry-After headers are respected.

## `retryOnStatus` (type: `array`):

Responses with these status codes are retried.

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

Follow 3xx redirects to the final URL (shown as finalUrl).

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

Hard cap on requests sent in this run (pages included).

## `proxyConfiguration` (type: `object`):

Optional Apify Proxy (pick a country to call geo-specific APIs). Off by default.

## Actor input object example

```json
{
  "requests": [
    {
      "url": "https://jsonplaceholder.typicode.com/posts",
      "method": "POST",
      "body": {
        "title": "Hello",
        "userId": 1
      },
      "label": "create post"
    },
    {
      "url": "https://jsonplaceholder.typicode.com/users/1",
      "label": "get user"
    }
  ],
  "method": "GET",
  "headers": {
    "Accept": "application/json"
  },
  "bodyType": "json",
  "responseFormat": "auto",
  "includeResponseHeaders": false,
  "maxBodyChars": 200000,
  "onlySuccessful": false,
  "startPage": 1,
  "pageStep": 1,
  "maxPagesPerRequest": 1,
  "maxConcurrency": 5,
  "delayMs": 0,
  "timeoutSecs": 30,
  "maxRetries": 2,
  "retryOnStatus": [
    408,
    425,
    429,
    500,
    502,
    503,
    504
  ],
  "followRedirects": true,
  "maxRequests": 1000,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All results as JSON items in the run's default dataset (also downloadable as CSV/Excel from the dataset).

# 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/posts",
            "method": "POST",
            "body": {
                "title": "Hello",
                "userId": 1
            },
            "label": "create post"
        },
        {
            "url": "https://jsonplaceholder.typicode.com/users/1",
            "label": "get user"
        }
    ],
    "headers": {
        "Accept": "application/json"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("larkmoor/http-request-sender").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/posts",
            "method": "POST",
            "body": {
                "title": "Hello",
                "userId": 1,
            },
            "label": "create post",
        },
        {
            "url": "https://jsonplaceholder.typicode.com/users/1",
            "label": "get user",
        },
    ],
    "headers": { "Accept": "application/json" },
}

# Run the Actor and wait for it to finish
run = client.actor("larkmoor/http-request-sender").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/posts",
      "method": "POST",
      "body": {
        "title": "Hello",
        "userId": 1
      },
      "label": "create post"
    },
    {
      "url": "https://jsonplaceholder.typicode.com/users/1",
      "label": "get user"
    }
  ],
  "headers": {
    "Accept": "application/json"
  }
}' |
apify call larkmoor/http-request-sender --silent --output-dataset

```

## MCP server setup

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

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/vI6AAsC84J95jruYF/builds/dsycdaru4ehNllMuZ/openapi.json
