# HTTP Request: Bulk API Calls, curl Import, JSON & Markdown (`ctriolab/http-request`) Actor

Send one or thousands of HTTP requests (GET, POST, PUT, PATCH, DELETE, HEAD) from a list, pasted curl commands, or a URL template with rows. Get status, timing, JSON fields as columns, or clean Markdown. Failed requests are free.

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

## Pricing

from $0.35 / 1,000 http responses

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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: Bulk API Calls, curl Import, JSON & Markdown

Send HTTP requests from Apify, one or thousands at a time, and get one clean result row per request.

- **Three ways to give requests**: a JSON list (`url`, `method`, `headers`, `query`, `body`), pasted **curl commands** ("Copy as cURL" from browser tools or API docs), or a **URL template** with rows (`https://api.example.com/users/{{id}}` + `[{"id": 1}, {"id": 2}]`).
- **Every method**: GET, POST, PUT, PATCH, DELETE, HEAD. JSON, form, text or raw bodies.
- **Useful output**: status, ok, final URL after redirects, response time, content type, size, and the body as JSON, text, or **clean Markdown** for HTML pages. Or pick **JSON fields** by dot path (`data.items.0.name`) and get them as columns.
- **Reliable**: retries on 429, 5xx and network errors with back-off, respects `Retry-After`, timeouts, parallel requests, body size limit.
- **Safe**: private, loopback, link-local and cloud metadata addresses (for example `127.0.0.1`, `10.0.0.0/8`, `169.254.169.254`, `::1`) are always blocked, checked again after DNS and on every redirect.
- **Pay only for answers**: **$0.50 per 1,000 responses**. DNS failures, timeouts, blocked addresses, invalid input and unchanged responses are free.

### How to use it

1. Put requests in **Requests**, **curl commands**, or **URL template** + **Template rows** (you can mix them). Leave everything empty to try a small public sample API.
2. Optional: headers for all requests, retries, timeout, requests at a time, response format, JSON fields to extract.
3. Run. Each request becomes one row in the **Responses** table. The **Run summary** has counts by status and the list of invalid input.

### Input examples

A list of requests:

```json
{
  "requests": [
    { "url": "https://jsonplaceholder.typicode.com/todos/1" },
    { "url": "https://jsonplaceholder.typicode.com/posts", "method": "POST", "body": { "title": "hello", "userId": 1 } },
    { "url": "https://jsonplaceholder.typicode.com/comments", "query": { "postId": 1 }, "label": "comments of post 1" }
  ],
  "headers": { "Accept": "application/json" }
}
```

One request per row, with fields as columns:

```json
{
  "urlTemplate": "https://jsonplaceholder.typicode.com/users/{{id}}",
  "templateRows": [{ "id": 1 }, { "id": 2 }, { "id": 3 }],
  "extractFields": ["name", "email", "city=address.city"],
  "responseFormat": "none"
}
```

A pasted curl command:

```json
{
  "curlCommands": "curl -X POST 'https://jsonplaceholder.typicode.com/posts' -H 'Content-Type: application/json' --data-raw '{\"title\":\"t\",\"userId\":3}'"
}
```

A web page as Markdown (for LLMs and AI agents):

```json
{ "requests": ["https://httpbin.org/html"], "responseFormat": "markdown" }
```

### Output

```json
{
  "index": 0,
  "label": null,
  "method": "GET",
  "url": "https://jsonplaceholder.typicode.com/users/1",
  "ok": true,
  "status": 200,
  "finalUrl": "https://jsonplaceholder.typicode.com/users/1",
  "redirects": 0,
  "responseTimeMs": 182,
  "contentType": "application/json; charset=utf-8",
  "sizeBytes": 509,
  "bodyTruncated": false,
  "bodyFormat": null,
  "body": null,
  "fields": { "name": "Leanne Graham", "email": "Sincere@april.biz", "city": "Gwenborough" },
  "error": null,
  "attempts": 1,
  "charged": true,
  "userData": { "id": 1 }
}
```

Requests without a response (DNS failure, timeout, blocked address, invalid input) also get a row, with `error` set and `charged: false`. `index` is the position in your input, so you can sort results back into order. `userData` carries your template row (or the request's `userData`) so you can join results to your table.

### Use it from Clay, n8n, Make, Zapier or an AI agent

- **n8n, Make, Zapier**: call the Apify "Run Actor synchronously and get dataset items" endpoint: `POST https://api.apify.com/v2/acts/ctriolab~http-request/run-sync-get-dataset-items?token=YOUR_TOKEN` with the input JSON as the body. The response is the list of result rows. Or use the official Apify app in n8n, Make or Zapier and choose this Actor.
- **Clay**: use the Apify integration (or an HTTP API column with the URL above) and map your table columns into `templateRows` or a `requests` item; `userData` comes back with each row.
- **Webhooks**: set **Callback URL** and every result is also POSTed to it as soon as it is ready.
- **AI agents (MCP)**: the Actor is available through the Apify MCP server (`https://mcp.apify.com`) as `ctriolab/http-request`. Use `responseFormat: "markdown"` to give a model readable pages and `extractFields` to keep answers small.

### Only changed responses (monitoring)

Turn on **Only changed responses** and schedule the run. A response is saved, and charged, only when its status or body is different from the last run with the same **Monitor name**. The first run saves everything (`changeStatus: "new"`), later runs save only what changed (`changeStatus: "changed"`).

### Pricing

Pay per event:

| Event | Free plan | Bronze | Silver | Gold and up |
|---|---|---|---|---|
| HTTP response (any status, 4xx and 5xx included) | $0.0005 | $0.00045 | $0.0004 | $0.00035 |
| Actor start | $0.00005 | $0.00005 | $0.00005 | $0.00005 |

That is **$0.50 per 1,000 responses** on the Free plan. Retries and redirects of one request count as one response. Not charged: DNS failures, timeouts, blocked addresses, invalid input, unchanged responses in monitoring mode, and anything after your run's maximum charge. Apify platform usage (compute, proxy) is billed separately by Apify; a 256 MB run with a few requests uses a fraction of a cent. Set a maximum cost per run in the run options to cap spending.

### Safety and limits

- Addresses that are never called: private networks (10/8, 172.16/12, 192.168/16, 100.64/10, fc00::/7), loopback (127/8, ::1), link-local and cloud metadata (169.254/16, fe80::/10), and other reserved ranges, including IPv4-mapped IPv6 and NAT64 forms. Host names are checked after DNS, and every redirect is checked again.
- Only `http` and `https`. No file uploads (`-F`), no `@file` bodies.
- Bodies are cut at **Max body size** (default 1 MB, up to 8 MB); JSON fields can only be read from a body that fits.
- The Actor does not log in, solve CAPTCHAs, or bypass blocking. It sends exactly the requests you give it.

### FAQ

**Are failed requests charged?** No. Only requests that got an HTTP response are charged, and 4xx or 5xx responses count as responses (you asked for them and got an answer). DNS errors, timeouts and blocked addresses are free.

**Can I send API keys?** Yes, in `headers` (for example `Authorization`). They are sent only to the URL you give; when a redirect goes to another host, `Authorization` and `Cookie` are dropped. Keep keys in your own integration and avoid putting them in shared or public task inputs.

**How fast is it?** Requests run in parallel (default 5, up to 50). Thousands of requests usually finish in a few minutes, depending on the API.

**What about rate limits?** On 429 the Actor waits (using `Retry-After` when the API sends it) and pauses all requests to that host, then retries up to **Max retries** times. Lower **Requests at a time** for strict APIs.

**Can I call internal or localhost services?** No. Private and local addresses are always blocked for security, also behind redirects.

**Is the response saved if it is HTML?** Yes, as text, or as Markdown if you choose **Markdown**. The `title` column holds the page title.

### Your responsibility

You choose the URLs and send the requests. Use them in line with the target service's terms and rate limits, and only with credentials you are allowed to use.

Questions or a missing feature: open an issue on the Actor page or write to help@ctriolab.com.

# Actor input Schema

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

A JSON list of requests. Each item: {"url": "...", "method": "GET|POST|PUT|PATCH|DELETE|HEAD", "headers": {...}, "query": {...}, "body": "text" or {JSON}, "bodyType": "json|form|text|raw", "label": "...", "userData": {...}}. Only url is required. A plain string is read as a GET URL. Leave all request fields empty to try a small public sample API.

## `curlCommands` (type: `string`):

Paste one or more curl commands (for example "Copy as cURL" from browser developer tools or API docs). Each command starts with the word curl. Supported: -X, -H, -d, --data-raw, --data-binary, --data-urlencode, --json, -u, -A, -e, -b, -G, -I, --url. Ignored: -L, -s, -k, --compressed and output options. Not supported: -F (file upload) and -d @file.

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

A URL with {{field}} placeholders, sent once per row of "Template rows". Values are URL-encoded; a placeholder at the very start (for example {{url}}) is inserted as is. Dot paths work: {{company.domain}}.

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

JSON list of objects, for example \[{"id": 1}, {"id": 2}]. Each row is also returned in the result as userData, so you can join results back to your table.

## `templateMethod` (type: `string`):

HTTP method for template requests.

## `templateHeaders` (type: `object`):

Headers for template requests. Values may use {{field}}.

## `templateBody` (type: `string`):

Body for template requests, with {{field}} placeholders. With body type JSON, text values are escaped for use inside JSON strings, for example {"name": "{{name}}", "age": {{age}}}.

## `templateBodyType` (type: `string`):

Sets the Content-Type of the template body.

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

Sent with every request, for example {"Authorization": "Bearer ...", "Accept": "application/json"}. A request's own headers win.

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

How a request body is sent when the request does not say. Object bodies are always sent as JSON unless the request sets bodyType.

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

The whole request, including redirects and reading the body. Timed-out requests are not charged.

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

Retries on HTTP 429, 5xx and network errors, with back-off. Retry-After is respected and pauses every request to that host. Note: a retried POST may repeat its effect on the server; set 0 for requests that must run once.

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

How many requests run in parallel. Lower it for APIs with strict rate limits.

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

Follow up to 10 redirects. Each hop is checked against blocked addresses. Authorization and Cookie headers are dropped when the host changes.

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

Optional. Send requests through Apify Proxy (choose a country for geo-specific answers). Off by default. Proxy traffic is billed by Apify as platform usage.

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

Auto: JSON responses are saved as JSON, everything else as text. Markdown: HTML pages become clean Markdown (scripts, styles, navigation and footers removed, links made absolute). None: no body, only status, timing and extracted fields.

## `extractFields` (type: `array`):

Dot paths read from JSON responses into the fields column, for example data.id, items.0.name, items\[0].price. Rename with name=path, for example city=address.city. Missing paths give null. The body must fit in Max body size to be parsed.

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

Add all response headers to each result.

## `maxBodyBytes` (type: `integer`):

Bodies longer than this are cut (bodyTruncated: true) and the rest is not downloaded. Max 8,000,000.

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

Optional. Each result is also POSTed as JSON to this URL as soon as it is ready (for example an n8n, Make or Zapier webhook).

## `onlyChanged` (type: `boolean`):

For scheduled runs: save (and pay for) a response only when its status or body differs from the last run with the same monitor name. Unchanged responses are skipped and not charged.

## `monitorName` (type: `string`):

Keeps the memory of "Only changed responses" separate per watch list.

## Actor input object example

```json
{
  "requests": [
    {
      "url": "https://jsonplaceholder.typicode.com/todos/1"
    },
    {
      "url": "https://jsonplaceholder.typicode.com/posts",
      "method": "POST",
      "body": {
        "title": "hello",
        "body": "from Apify",
        "userId": 1
      }
    }
  ],
  "templateMethod": "GET",
  "templateBodyType": "json",
  "bodyType": "raw",
  "timeoutSecs": 30,
  "maxRetries": 3,
  "maxConcurrency": 5,
  "followRedirects": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "responseFormat": "auto",
  "includeResponseHeaders": false,
  "maxBodyBytes": 1000000,
  "onlyChanged": false,
  "monitorName": "default"
}
```

# Actor output Schema

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

One item per request: status, timing, body or extracted fields, error.

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

Counts by status, charged responses, failures, blocked addresses, unchanged responses and invalid input.

# 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"
        },
        {
            "url": "https://jsonplaceholder.typicode.com/posts",
            "method": "POST",
            "body": {
                "title": "hello",
                "body": "from Apify",
                "userId": 1
            }
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ctriolab/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 = { "requests": [
        { "url": "https://jsonplaceholder.typicode.com/todos/1" },
        {
            "url": "https://jsonplaceholder.typicode.com/posts",
            "method": "POST",
            "body": {
                "title": "hello",
                "body": "from Apify",
                "userId": 1,
            },
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("ctriolab/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 '{
  "requests": [
    {
      "url": "https://jsonplaceholder.typicode.com/todos/1"
    },
    {
      "url": "https://jsonplaceholder.typicode.com/posts",
      "method": "POST",
      "body": {
        "title": "hello",
        "body": "from Apify",
        "userId": 1
      }
    }
  ]
}' |
apify call ctriolab/http-request --silent --output-dataset

```

## MCP server setup

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