# Send HTTP Requests - Bulk API Calls for Clay, n8n & Make (`afterstar/send-http-requests`) Actor

Send GET, POST, PUT, PATCH or DELETE requests one at a time or in bulk. JSON answers parsed, chosen fields as columns, retries with backoff, a polite rate cap. Built for Clay tables.

- **URL**: https://apify.com/afterstar/send-http-requests.md
- **Developed by:** [Afterstar](https://apify.com/afterstar) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 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.
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

**Pricing: $5.00 per 1,000 requests that reach the server. Retries are included; refused, invalid and unreachable requests are free.**

### What does Send HTTP Requests do?

It sends HTTP requests for you, **one at a time or a whole list**, and gives **one row per request**: the status code, the response headers, the **JSON answer parsed** (or the text), and the **fields you pick lifted into their own columns**. GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS, with your own headers, query parameters and body.

It is built for **Clay tables**, n8n, Make and scripts that need to call an API from a row: put the URL (and your API key in a header) into the input, and the answer comes back as data you can map to columns. It **retries with backoff** on rate limits and server errors, keeps a **polite rate cap per host**, and **never calls private or internal network addresses**.

### What you get

|  |  |
|---|---|
| **Input** | One URL (one Clay row) or a list of up to 10,000 requests, with your headers, query parameters and body |
| **Per request** | Status code, response headers, the JSON answer parsed (or text), timing, attempts, your lookup key |
| **Columns** | The JSON fields you pick, lifted into their own columns: title, data.user.email, items\[0].id |
| **Reliability** | Retries with backoff and Retry-After on 429/5xx, a polite rate cap per host, timeouts you set |
| **Safety** | Only public internet addresses (checked at every redirect); headers and bodies never logged |
| **Price** | $5.00 per 1,000 requests that reach the server; retries included, refused requests free |

### Use cases

- Enrich a Clay table from any REST API (company data, email checks, CRM lookups, your own backend) with one request per row.
- Send a list of API calls in one run: hundreds of GETs or POSTs with a lookup key on each, results joined back by that key.
- Call webhooks and internal tools from n8n or Make without writing code.
- Check what an endpoint returns (status, headers, body) from Apify's servers.

### Input

| Field | What it does |
|---|---|
| URL | The address to call. For one request (one Clay row) this is all you need |
| Method | GET (default), POST, PUT, PATCH, DELETE, HEAD or OPTIONS |
| Headers | Sent with every request, like `{"Authorization": "Bearer ..."}` or `{"X-API-Key": "..."}` |
| Query parameters | Added to the URL: `{"page": 2, "limit": 50}` |
| Body (JSON) | Sent as JSON for POST, PUT and PATCH (form-encoded if your Content-Type header says so) |
| Body (raw text) | Any text body instead, like XML or `a=1&b=2` |
| Lookup key | Anything you want back on the row, like your row ID |
| Requests (bulk) | A list of URLs, or of objects `{url, method, headers, params, data, lookupKey}`; only `url` is needed |
| Fields to lift into columns | Paths in the JSON answer to get as their own columns: `title`, `data.user.email`, `items[0].id`, or `name=path` |
| Parse JSON answers | On by default: a JSON answer is data in `json`; off: plain text in `body` |
| Max answer size | Longer answers are cut and marked `truncated` (default 512,000 bytes, up to 5,000,000) |
| Callback URL | Optional: each finished row is also POSTed there as JSON (a Clay webhook table, n8n) |
| Timeout per attempt | 1-300 seconds (default 30); numbers over 1,000 are read as milliseconds |
| Retry on failure, Max retries | On by default, 2 retries with growing waits and the server's `Retry-After` |
| Follow redirects | On by default; off gives you the 301/302 answer itself |
| Requests at once | 1-20 in parallel (default 5) |
| Max requests per second per host | A polite cap per website or API, 1-20 (default 5), retries included |
| Max requests per run | Default 1,000, up to 10,000; the rest are not sent and not charged |

In a list, the method, headers and query parameters at the top are the defaults; a request can set its own (its headers are added to, and override, the top ones).

Example input for one POST request:

```json
{
  "url": "https://api.example.com/v1/people/search",
  "method": "POST",
  "headers": { "Authorization": "Bearer YOUR_KEY" },
  "data": { "domain": "acme.com", "limit": 5 },
  "lookupKey": "row-17",
  "liftFields": ["total", "first_name=people[0].first_name"]
}
```

Example input for a list:

```json
{
  "headers": { "X-API-Key": "YOUR_KEY" },
  "requests": [
    "https://api.example.com/v1/companies/acme.com",
    { "url": "https://api.example.com/v1/companies/globex.com", "lookupKey": "row-2" },
    { "url": "https://api.example.com/v1/notes", "method": "POST", "data": { "text": "hello" } }
  ]
}
```

### Output

One row per request, in the order they finish (`index` is the position in your input):

```json
{
  "index": 0,
  "lookupKey": "row-1",
  "method": "GET",
  "url": "https://jsonplaceholder.typicode.com/posts/1",
  "statusCode": 200,
  "ok": true,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "json": { "userId": 1, "id": 1, "title": "sunt aut facere ...", "body": "quia et suscipit ..." },
  "body": null,
  "bodyType": "json",
  "contentType": "application/json; charset=utf-8",
  "bodyBytes": 292,
  "truncated": false,
  "finalUrl": "https://jsonplaceholder.typicode.com/posts/1",
  "responseHeaders": { "content-type": "application/json; charset=utf-8", "cache-control": "max-age=43200" },
  "durationMs": 62,
  "attempts": 1,
  "charged": true,
  "error": null
}
```

- `title` here is a lifted field (`liftFields: ["title"]`). A path that isn't in the answer gives `null`.
- `ok` is true for a 2xx answer. A 404 or 500 is still a real answer: you get its status, body and an `error` like `HTTP 404 Not Found`. The run itself succeeds.
- `bodyType` is `json`, `text`, `binary` (images, PDFs: the body isn't shown, `bodyBytes` says how big) or `empty`. A cut-off answer is never parsed as JSON (it would be incomplete); it stays text with `truncated: true`.
- `statusCode` is `null` when no answer came: the host doesn't resolve, the connection failed, the request was refused, or it timed out. `error` says which.
- `charged` says whether this row was charged. Query values whose names look secret (`api_key`, `token`, `secret`, `sig`...) show as `***` in `url` and `finalUrl`. Your request headers and body are never written to the results or the log.

### How much does it cost?

- **$5.00 per 1,000 requests** ($0.005 each; on paid Apify plans 10-30% less). A Clay table of 200 rows with one call each costs $1.00.
- You pay for a request that **reached the server**: it answered (with any status, 4xx and 5xx included), or it went out and the answer didn't come within the timeout. **Retries are part of the same request**: charged once.
- **Free:** requests refused for safety, invalid input rows, host names that don't resolve, connections that fail, and requests past your max or your spending limit.
- Plus Apify's start fee: $0.00005 per GB of memory per run (the default 256 MB = $0.0000125).

### Good to know

- **Safety:** only public internet addresses. Private and local networks, cloud metadata addresses, `localhost`, shorthand IP numbers and the ports web browsers refuse (mail, SSH...) are refused before anything is sent, at every redirect too. Only `http` and `https` links.
- **Be polite:** the per-host rate cap and the run cap are there so the tool can't flood a site. You are responsible for the APIs and sites you call: use your own keys, and respect their terms and rate limits.
- **Retries:** 429 and 503 answers and failed connections are retried for every method. 408, 425, 500, 502, 504, timeouts and broken connections are retried only for GET, HEAD, PUT, DELETE and OPTIONS, so a POST is never sent twice after the server may have processed it. A 4xx answer is never retried. If a server asks to wait more than 60 seconds (`Retry-After`), the row gives its answer instead of waiting.
- **Speed:** a single request usually finishes within a few seconds of the run's start, so Clay's and the API's synchronous runs work well. No proxy is used: requests come from Apify's servers.
- Answers are decompressed (gzip, deflate, brotli); the size cap counts the decompressed bytes.

### How it compares

| Instead of | With this tool |
|---|---|
| Writing and hosting a script for each API you call | Paste the URL and headers; every answer comes back as a row |
| One run per request | One run for one request or a list of 10,000, each with its own lookup key |
| Mapping a whole JSON answer by hand | Pick the fields you need and get them as their own columns |

### FAQ

**How do I use it from Clay?** Add Apify's integration to your table, pick this tool, and give the input as JSON with your table's columns in it (for example the domain column at the end of the URL), plus your API key in `headers` and the fields you want in `liftFields`. Each row's run gives one result row; map `statusCode`, `json` or your lifted columns to Clay columns.

**Are retries charged twice?** No. A request is charged once, however many attempts it took.

**Does a 404 or 500 fail the run?** No. It's a real answer: you get its status and body, and the row's `error` says `HTTP 404`. It is charged, because the server answered.

**Can I call localhost or my internal network?** No. Only public internet addresses; everything else gets a free row that says why.

**Can I send form or XML bodies?** Yes: a JSON body with the header `Content-Type: application/x-www-form-urlencoded` is form-encoded, or put any text into 'Body (raw text)' with your own Content-Type.

**Are my API keys safe?** Your headers and body are sent only to the URL you give. They are never written to the log or the results, and secret-looking query values show as `***`.

**Source:** the URLs you provide. Requests are sent with the httpx library (BSD).

### More Afterstar tools

- [Website Tech Stack Detector - CMS, Analytics & Frameworks](https://apify.com/afterstar/tech-stack-detector): Shows which technologies (shop system, CMS, analytics, frameworks) each website in your list uses, and what changed.
- [ATS Jobs Scraper - Career Site Jobs: Greenhouse, Lever, Workday](https://apify.com/afterstar/careers-jobs-ats): Paste company career-page links (Greenhouse, Lever, Ashby, Workday and 3 more) and get every open job in one table.

Found a problem or need a feature? Open an issue on the Issues tab.

# Actor input Schema

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

The address to call. For one request (one Clay row) this is all you need. Leave it empty to send only the list in 'Requests'.

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

For the URL above, and for every request in the list that doesn't name its own.

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

Sent with every request, for example {"Authorization": "Bearer ..."} or {"X-API-Key": "..."}. A request in the list can add or override its own. Never written to the log or the results.

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

Added to the URL's query string, for every request: {"page": 2, "limit": 50}. Values of names like key, token or secret show as \*\*\* in the results.

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

Body for the URL above (POST, PUT, PATCH). Sent as JSON with Content-Type application/json, or form-encoded if your headers say application/x-www-form-urlencoded.

## `bodyText` (type: `string`):

Instead of the JSON body: any text, like XML or a=1\&b=2. Set the Content-Type in Headers (default text/plain).

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

Anything you want back on the result row, like a row ID, to join answers to your own rows.

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

A list of URLs, or of objects {"url", "method", "headers", "params", "data", "lookupKey"}. Only url is needed; method, headers and params from above are the defaults. Example: \["https://api.example.com/a", {"url": "https://api.example.com/b", "method": "POST", "data": {"id": 7}, "lookupKey": "row-2"}]

## `liftFields` (type: `array`):

Paths in the JSON answer that you want as their own columns: 'title', 'data.user.email', 'items\[0].id' (or 'items.0.id'). Name the column with 'name=path'. A path that isn't there gives null.

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

On: a JSON answer goes into the 'json' field as data. Off: every answer is plain text in 'body'.

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

Longer answers are cut here and marked truncated: true (a cut JSON answer stays text). Up to 5,000,000.

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

Optional: each finished row is also POSTed here as JSON, like a Clay webhook table or an n8n webhook. Never charged.

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

How long one attempt may take, answer included. 1-300 seconds; numbers over 1,000 are read as milliseconds.

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

Retry with growing waits (and the server's Retry-After) on 429, 503 and connection failures; on 408, 425, 500, 502, 504 and timeouts too for GET, HEAD, PUT, DELETE. A 4xx answer is never retried. Retries are part of the same request: charged once.

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

Retries after the first attempt (0-5).

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

Off: a 301/302 answer is the result itself (see its 'location' header).

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

How many requests run in parallel (1-20).

## `maxRequestsPerSecondPerHost` (type: `integer`):

A polite cap per website or API (1-20), retries included. Lower it if the API limits you.

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

Requests past this are not sent (and not charged). Up to 10,000 per run.

## Actor input object example

```json
{
  "url": "https://jsonplaceholder.typicode.com/posts/1",
  "method": "GET",
  "parseJson": true,
  "maxResponseBytes": 512000,
  "responseTimeout": 30,
  "retryOnFailure": true,
  "maxRetries": 2,
  "followRedirects": true,
  "maxConcurrency": 5,
  "maxRequestsPerSecondPerHost": 5,
  "maxRequests": 1000
}
```

# Actor output Schema

## `results` (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 = {
    "url": "https://jsonplaceholder.typicode.com/posts/1"
};

// Run the Actor and wait for it to finish
const run = await client.actor("afterstar/send-http-requests").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 = { "url": "https://jsonplaceholder.typicode.com/posts/1" }

# Run the Actor and wait for it to finish
run = client.actor("afterstar/send-http-requests").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 '{
  "url": "https://jsonplaceholder.typicode.com/posts/1"
}' |
apify call afterstar/send-http-requests --silent --output-dataset

```

## MCP server setup

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

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/5HYdPxEydfABQmUZ4/builds/8HUzrFVflGbcowPHq/openapi.json
