# HTTP Request — Bulk & Proxied API Calls (`memo23/http-request`) Actor

Send HTTP requests (GET, POST, PUT, PATCH, DELETE) to any API with custom headers, params and body. Send a whole batch in one run instead of one run per call, route through Apify proxies by country, and forward each response to a callback URL. Built for Clay, n8n and no-code stacks.

- **URL**: https://apify.com/memo23/http-request.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Integrations, Automation, AI
- **Stats:** 19 total users, 13 monthly users, 99.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 http request sents

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## HTTP Request — Bulk & Proxied API Calls

<p align="center">
  <img src="https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/http-request/logo.png" width="130" alt="HTTP Request">
</p>

Send HTTP requests (**GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS**) to any API endpoint with your own headers, query parameters and body — and get the response back as structured data.

Two things make this different from the other HTTP actors on the Store: it sends a **whole batch in one run** instead of one run per call, and it can route requests **through Apify Proxy** by country or residential group.

Built for **Clay**, **n8n**, **Make** and any no-code stack that needs real HTTP without an enterprise plan.

### Why use this actor

- **Batch in one run.** Pass an array of requests and they all go out concurrently. Other HTTP actors take a single URL, so 500 calls means 500 runs — 500 start-ups, 500 queue waits. Here it's one run.
- **Proxy routing.** Send through Apify Proxy in a chosen country to hit geo-restricted endpoints, or spread a burst across residential IPs when an API rate-limits by address. Off by default, because most API calls should go out directly.
- **Retries that make sense.** Timeouts, connection errors, 408, 429 and 5xx are retried. A 4xx is *your* request being wrong and will fail identically forever, so it is never retried and never billed twice for nothing.
- **Lookup keys.** Tag every request with an identifier and get it echoed back on the result row, so you can join responses to the source rows they came from without matching on URL.
- **Callback URL.** Each row is POSTed to your endpoint the moment it completes — the pattern Clay and n8n use to receive results.
- **Honest error rows.** A request that dies in transport still returns a row with the error on it. You always learn what happened.

### What it does

| You give it | You get back |
|---|---|
| A single URL | One row: status, headers, parsed body, timing |
| An array of requests | One row per request, sent concurrently |
| Headers / query params / JSON body | Applied per-run or overridden per-request |
| A callback URL | Each row POSTed to you as it finishes |
| A proxy config | Every request routed through it |

### Supported inputs

| Input | What it does |
|---|---|
| **URL** | The endpoint to call. All you need for a single request. |
| **HTTP method** | GET, POST, PUT, PATCH, DELETE, HEAD or OPTIONS. |
| **Headers** | Key/value pairs — `Authorization`, `X-API-Key`, anything. Applied to every request; a request can override any single key. |
| **Query parameters** | Added to whatever the URL already carries — existing query strings are preserved. |
| **Request body** | For POST/PUT/PATCH. An object is sent as JSON with a matching content-type unless you set your own. |
| **Lookup key** | Any identifier, echoed back on the row. |
| **Requests** | The batch: an array of `{ url, method, headers, params, data, lookupKey }`. Each entry inherits the run-level settings. |
| **Max concurrency** | How many go at once (1–50). Lower it if the target rate-limits you. |
| **Response timeout** | Per-attempt timeout in seconds. |
| **Retry on failure / Max retries** | Retry behaviour for transient failures. |
| **Parse JSON responses** | Parse a JSON body into a `json` field instead of a raw `body` string. |
| **Max response size** | Truncate large bodies, flagged with `truncated: true`. |
| **Treat non-2xx as an error** | Also fill the row's `error` field on a non-2xx. |
| **Callback URL** | POST each row here as it completes. |
| **Proxy configuration** | Route through Apify Proxy — pick a country or residential group. |

### Use cases

**Clay enrichment without the plan upgrade** — Clay's HTTP API column sits behind a higher tier. Call this actor instead, pass a `lookupKey` per row, and join the responses straight back to your table.

**Bulk API calls from a no-code tool** — n8n, Make and Zapier all throttle or charge per call. Hand the whole list to one run and read the dataset once.

**Geo-specific API responses** — pricing, availability and search APIs answer differently by country. Set the proxy country and see what a user there sees.

**Rate-limit spreading** — an API that limits by IP stops being a bottleneck when requests come from a residential pool.

**Webhook fan-out** — POST the same payload to many endpoints in one run, with per-endpoint headers.

**Scheduled health checks** — run on a schedule against a list of endpoints and alert on the `ok` field.

### How it works

1. You give it a URL, or an array of requests.
2. Each request is built from the run-level defaults with its own overrides merged on top — headers and query params merge key-by-key, so a request can change one header without restating the rest.
3. Requests go out concurrently, up to your **Max concurrency**, optionally through Apify Proxy.
4. Transient failures are retried; the response is read, optionally JSON-parsed, and truncated to your size cap.
5. Each result is pushed to the dataset and POSTed to your callback URL if you set one.

### Input examples

A single request:

```json
{
  "url": "https://api.github.com/repos/apify/crawlee",
  "method": "GET",
  "headers": { "Accept": "application/vnd.github+json" }
}
```

A batch, with a shared auth header and per-row lookup keys:

```json
{
  "headers": { "Authorization": "Bearer YOUR_TOKEN" },
  "requests": [
    { "url": "https://api.example.com/v1/users/1", "lookupKey": "row-1" },
    { "url": "https://api.example.com/v1/users/2", "lookupKey": "row-2" },
    { "url": "https://api.example.com/v1/users/3", "lookupKey": "row-3" }
  ],
  "maxConcurrency": 10,
  "callbackUrl": "https://hooks.example.com/inbound"
}
```

A POST through a German residential IP:

```json
{
  "url": "https://api.example.com/v1/search",
  "method": "POST",
  "data": { "query": "coffee", "limit": 25 },
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "DE" }
}
```

### Output

One row per request:

```json
{
  "lookupKey": "row-1",
  "url": "https://api.github.com/repos/apify/crawlee?per_page=1",
  "method": "GET",
  "statusCode": 200,
  "ok": true,
  "responseHeaders": { "content-type": "application/json; charset=utf-8" },
  "json": { "id": 265833387, "name": "crawlee" },
  "body": null,
  "truncated": false,
  "durationMs": 556,
  "attempts": 1,
  "error": null
}
```

#### Key output fields

| Field | Description |
|---|---|
| `lookupKey` | Whatever you passed in, echoed back. `null` if you didn't set one. |
| `statusCode` | HTTP status. **`null` means no response arrived at all** — DNS, TLS, timeout or blocked egress. |
| `ok` | `true` for a 2xx. The field to filter on. |
| `responseHeaders` | All response headers, lower-cased keys. |
| `json` | Parsed body — present only when *Parse JSON* is on and the body actually parsed. |
| `body` | Raw response text. `null` when `json` was parsed instead. |
| `truncated` | `true` if the body hit your size cap. JSON parsing is skipped when truncated, since a cut-off document would parse into a silently incomplete object. |
| `durationMs` | Total time across all attempts. |
| `attempts` | Attempts made. `1` means it worked first try. |
| `error` | Why it failed, or `null`. |

### FAQ

**Does a 404 or 500 fail the run?**
No. That's a real answer from the target API and you get the status back. Only a request that never reached the host is treated as a problem, and it's reported on the run's status message. Turn on *Treat non-2xx as an error* if you also want the row's `error` field filled.

**How am I billed?**
Per request sent, plus the actor start. A batch of 100 is billed as 100 requests — the same as 100 single runs, minus 99 start-ups.

**Are retries billed twice?**
No. Billing is per request in your input, not per attempt.

**Can I send form-encoded or XML bodies?**
Yes. Pass the body as a string and set your own `content-type` — an explicit content-type is never overridden.

**Do I need the proxy?**
Usually not. Leave it off unless you're hitting geo-restricted endpoints or an API that rate-limits by IP. Proxies add latency and will break IP-allowlisted endpoints.

**What happens to very large responses?**
They're truncated at your *Max response size* and flagged with `truncated: true`.

### 🤖 For AI Agents & LLM Apps

This actor is a clean tool-call target: one input, one structured row out, no HTML parsing. Point an agent at it to give it authenticated HTTP without handing it network access.

- Set `parseJson: true` so the agent reads `json` directly instead of parsing a string.
- Use `lookupKey` to correlate responses when the agent fans out a batch.
- `ok` and `statusCode` give the agent an unambiguous success signal.
- Cap `maxResponseBytes` to keep responses inside the model's context window.

### ⚠️ Disclaimer

Use this actor only for endpoints you are authorised to call. You are responsible for the terms of service of any API you send requests to, for the credentials you pass in headers, and for the content of the requests you make. This actor sends exactly what you tell it to and does not inspect or modify your payloads beyond the encoding described above.

### SEO Keywords

http request actor, send http requests, api request apify, clay http api, clay api enrichment, bulk http requests, rest api caller, webhook sender, post request actor, api proxy requests, no-code api calls, n8n http request, make.com http, geo-targeted api requests, batch api calls

# Actor input Schema

## `url` (type: `string`):

The endpoint to call. For a single request this is all you need. Leave it empty and use Requests below to send many in one run.

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

Method for the request above, and the default for any request in the batch that doesn't set its own.

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

Request headers as key/value pairs — e.g. Authorization or X-API-Key. Applied to every request in the run; a request can override any individual key.

## `params` (type: `object`):

Query-string parameters, added to whatever the URL already carries.

## `data` (type: `object`):

Body for POST/PUT/PATCH. An object is sent as JSON with the matching content-type (unless you set your own).

## `lookupKey` (type: `string`):

Any identifier you want echoed back on the result row — use it to join responses to the source row they came from.

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

Array of request objects: { url, method, headers, params, data, lookupKey }. Only url is required — and even that falls back to the URL above.

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

How many requests run at once in batch mode. Lower this if the target API rate-limits you.

## `responseTimeout` (type: `integer`):

How long to wait for each attempt before giving up.

## `retryOnFailure` (type: `boolean`):

Retry timeouts, connection errors, 408, 429 and 5xx responses. A 4xx is your request being wrong, so it is never retried.

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

Retries after the first attempt. Ignored when Retry on failure is off.

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

On: a JSON body is parsed into a `json` field you can address directly. Off: the raw text is returned in `body`.

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

Response bodies longer than this are truncated and flagged with `truncated: true`.

## `failOnErrorStatus` (type: `boolean`):

On: a non-2xx response also fills the row's `error` field. The row is returned and billed either way.

## `callbackUrl` (type: `string`):

Each result row is POSTed here as JSON as soon as it completes — the pattern Clay and n8n use to receive results.

## `proxy` (type: `object`):

Route requests through Apify Proxy. Pick a country for geo-specific responses, or residential groups for APIs that block datacenter IPs.

## Actor input object example

```json
{
  "url": "https://api.github.com/repos/apify/crawlee",
  "method": "GET",
  "headers": {},
  "params": {},
  "data": {},
  "requests": [
    {
      "url": "https://api.github.com/repos/apify/crawlee",
      "lookupKey": "row-1"
    },
    {
      "url": "https://api.github.com/repos/apify/apify-sdk-js",
      "lookupKey": "row-2"
    }
  ],
  "maxConcurrency": 10,
  "responseTimeout": 30,
  "retryOnFailure": true,
  "maxRetries": 2,
  "parseJson": true,
  "maxResponseBytes": 512000,
  "failOnErrorStatus": false
}
```

# Actor output Schema

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

Dataset with the status, headers, parsed body, timing and error (if any) for every request sent.

# 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 = {
    "headers": {},
    "params": {},
    "data": {},
    "requests": [
        {
            "url": "https://api.github.com/repos/apify/crawlee",
            "lookupKey": "row-1"
        },
        {
            "url": "https://api.github.com/repos/apify/apify-sdk-js",
            "lookupKey": "row-2"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/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 = {
    "headers": {},
    "params": {},
    "data": {},
    "requests": [
        {
            "url": "https://api.github.com/repos/apify/crawlee",
            "lookupKey": "row-1",
        },
        {
            "url": "https://api.github.com/repos/apify/apify-sdk-js",
            "lookupKey": "row-2",
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/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 '{
  "headers": {},
  "params": {},
  "data": {},
  "requests": [
    {
      "url": "https://api.github.com/repos/apify/crawlee",
      "lookupKey": "row-1"
    },
    {
      "url": "https://api.github.com/repos/apify/apify-sdk-js",
      "lookupKey": "row-2"
    }
  ]
}' |
apify call memo23/http-request --silent --output-dataset

```

## MCP server setup

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