# HTTP Request & Bulk API Calls: Extract JSON, URL to Markdown (`tinplatelabs/agent-http`) Actor

Send HTTP requests (GET, POST, PUT, PATCH, DELETE) to any API or URL, 1 to 10,000 per run. Bulk API calls from CSV, Google Sheet or dataset rows with {{column}} templates. Extract JSON fields into columns, URL to Markdown. For Clay, n8n, Make and AI agents. $1 per 1,000 responses; failures free.

- **URL**: https://apify.com/tinplatelabs/agent-http.md
- **Developed by:** [tinplatelabs](https://apify.com/tinplatelabs) (community)
- **Categories:** Developer tools, AI, Automation
- **Features:** Limited permissions
- **Stats:** 14 total users, 12 monthly users, 99.1% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/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: Extract JSON, URL to Markdown

Send **HTTP requests (GET, POST, PUT, PATCH, DELETE) to any API or web page**, one or 10,000 per run, and get
**one clean result per request**: status, headers, **parsed JSON**, the **JSON fields you pick** as flat columns,
or a web page as **clean Markdown**. **Bulk API calls from a CSV, Google Sheet or Apify dataset**: write the
request once with `{{column}}` placeholders and it is sent for every row, with your row's ID kept on each
result. Retries, redirects, timing and TLS details included. **$1 per 1,000 responses; failed requests are free.**

### What people use it for

| Use case | How |
|---|---|
| Call an API from **Clay, n8n, Make or Zapier** without a paid HTTP plan | `requests` + `extract` to get just the columns you need |
| Give an **AI agent** (Claude, ChatGPT, Cursor via the [Apify MCP server](https://mcp.apify.com)) a safe HTTP tool | JSON parsed, HTML as Markdown, a token budget so one page can't flood the context |
| **Bulk API calls from CSV**: call an API for each row of a CSV, Google Sheet or dataset (API enrichment, posting records) | `requestTemplate` with `{{column}}` placeholders + `rowsUrl` / `rowsDatasetId`; `passThroughColumns` joins results back |
| **Bulk API calls** from a list: post records, poll job status | Up to 10,000 requests per run, 5–20 in parallel, retries with `Retry-After` |
| **URL to Markdown** for RAG | HTML becomes Markdown of the main content, navigation and cookie banners stripped |
| **Broken link checker** | `mode: url-health`: status, final URL, redirect chain, response time, TLS days left |

### Quick start (real output)

This input (the form's prefill) finishes in about 2 seconds and costs $0.003:

```json
{
  "requests": [
    {"url": "https://jsonplaceholder.typicode.com/users/1", "label": "user",
     "extract": {"name": "name", "email": "email", "city": "address.city", "company": "company.name"}},
    {"method": "POST", "url": "https://jsonplaceholder.typicode.com/posts", "label": "create post",
     "json": {"title": "hello", "body": "sent by an agent", "userId": 1}, "extract": {"newId": "id"}},
    {"url": "https://example.com", "label": "web page as Markdown"}
  ]
}
```

Output, abridged to the interesting fields:

```json
[
  {"label": "user", "statusCode": 200, "bodyFormat": "json",
   "extracted": {"name": "Leanne Graham", "email": "Sincere@april.biz", "city": "Gwenborough", "company": "Romaguera-Crona"},
   "extractMissing": []},
  {"label": "create post", "method": "POST", "statusCode": 201, "statusText": "Created",
   "body": {"title": "hello", "body": "sent by an agent", "userId": 1, "id": 101}, "extracted": {"newId": 101}},
  {"label": "web page as Markdown", "statusCode": 200, "bodyFormat": "markdown",
   "body": "This domain is for use in documentation examples without needing permission. ...",
   "page": {"title": "Example Domain", "language": "en"}}
]
```

Set `"includeBody": false` to drop the body and keep only status, headers and `extracted`: one small row per call.

### Bulk API calls from CSV, Google Sheet or dataset rows

Call an API once for every row: write one `requestTemplate` with `{{column}}` placeholders and give the rows as
an Apify dataset (`rowsDatasetId`, e.g. a scraper's output), a CSV / JSON / Google Sheets link (`rowsUrl`) or
inline `rows`. Placeholders work in the URL, query, headers and body.

```json
{
  "requestTemplate": {
    "method": "POST",
    "url": "https://httpbin.org/anything/companies/{{domain}}",
    "query": {"country": "{{country}}"},
    "json": {"name": "{{name}}", "employees": "{{employees}}"},
    "extract": {"sentName": "json.name", "sentEmployees": "json.employees", "path": "url"}
  },
  "rows": [
    {"id": "c-1", "domain": "acme.com", "name": "Acme \"Rockets\" Inc.", "country": "US", "employees": 120},
    {"id": "c-2", "domain": "globex.io", "name": "Globex", "country": "DE", "employees": 40}
  ],
  "passThroughColumns": ["id"],
  "includeBody": false
}
```

Each result keeps `rowIndex` and the columns you pass through, so it joins straight back to your sheet or CRM:

```json
{"index": 0, "rowIndex": 0, "row": {"id": "c-1"}, "method": "POST",
 "url": "https://httpbin.org/anything/companies/acme.com?country=US", "statusCode": 200,
 "extracted": {"sentName": "Acme \"Rockets\" Inc.", "sentEmployees": 120, "path": "https://httpbin.org/anything/companies/acme.com?country=US"}}
```

- **Escaping is automatic.** Values are URL-encoded in the URL (`a b/c` becomes `a%20b%2Fc`) and JSON-escaped in
  bodies, so quotes, slashes and line breaks in your data can't break a request. In `json`, a value that is just
  `"{{column}}"` keeps the column's type (number, true/false, list). A placeholder at the very start of the URL
  (`{{url}}`, for a column of full URLs) or in triple braces (`{{{path}}}`) is inserted as is.
- **Columns**: by name, or a dot path for nested dataset items (`{{address.city}}`). Plain lists (`["a.com",
  "b.com"]`) are available as `{{value}}`. CSV and TSV files need a header row; JSON arrays, JSON Lines and
  `{"items": [...]}` files work too. A Google Sheet must be shared as "Anyone with the link can view".
- **Missing data is flagged, not guessed**: a row without a column the template uses gets a free
  `MISSING_COLUMN` item that names the column and lists the row's columns.
- **Duplicates are sent once** (`dedupeRequests`, on by default): repeats get a free `skipped` item with
  `duplicateOfIndex`.
- **Test on a few rows first** with `maxRows`; for more than 10,000 rows, run again with `rowsOffset`.

### Pricing in plain words

**$0.001 per request that gets an HTTP response** (any status, 404 and 500 included; retries and redirects
included). DNS failures, timeouts, blocked addresses and invalid input are **never charged**. 1,000 API calls
\= $1. JSON extraction, Markdown conversion, link checks, reading your CSV or dataset rows, skipped duplicates and
rows with missing columns cost nothing extra.

### Why this one

- **Extract JSON fields.** `extract` maps output names to paths (`data.items[0].id`, `items[-1].name`,
  `items[*].id`, `["key.with.dots"]`). It reads the whole JSON, even when the body is larger than the token
  budget, so you get your fields from big API responses too.
- **One API call per CSV or dataset row.** `{{column}}` templates with automatic URL and JSON escaping,
  pass-through columns to join results back, duplicate skipping and clear errors for missing columns.
- **Bodies an agent can read.** JSON comes back parsed. HTML becomes Markdown of the main content, with
  navigation, cookie banners, sidebars and footers stripped and links made absolute. Binary files (images,
  PDFs, ZIPs) are saved to the key-value store and returned as a URL instead of a blob.
- **Token budget.** `maxBodyTokens` (default 8,000) cuts long bodies at a paragraph boundary and says so
  (`truncated: true`, `bodyChars`, `bodyTokensEstimate`), so one huge page can't flood the context.
- **Robust.** Retries on 429/5xx and connection failures with exponential backoff, honouring `Retry-After`.
  POST/PATCH are only retried when it's safe (429 or connection failure) unless you allow it.
- **Transparent.** Every redirect hop, attempt and retry is listed; timing is split into DNS, connect, TLS,
  waiting (time to first byte) and download.
- **Safe by default.** Private, loopback, link-local and cloud-metadata addresses (e.g. `169.254.169.254`) are
  refused, including after redirects and DNS resolution. `Authorization`, cookies and API-key headers and
  query parameters are redacted in the output and log, and credentials are not forwarded to another host on a
  redirect. No cookies are kept between requests unless you turn on `session`.
- **Pay only for responses.** A request that gets no response costs nothing.

### Input

```json
{
  "requests": [
    {"method": "GET", "url": "https://httpbin.org/json"},
    {"method": "POST", "url": "https://api.example.com/v1/search", "json": {"q": "widgets"},
     "headers": {"X-Api-Key": "..."}, "label": "search"},
    "https://example.com"
  ],
  "authorization": "Bearer sk-...",
  "maxBodyTokens": 4000
}
```

| Field | Default | What it does |
|---|---|---|
| `mode` | `request` | `request` returns full responses; `url-health` checks links without downloading bodies |
| `requests` | | URL strings or objects: `method`, `url`, `headers`, `query`, one of `json` / `form` / `body`, `extract`, `timeoutSecs`, `followRedirects`, `label` |
| `urls` | | Plain URLs to GET (or check). Bare domains get `https://` |
| `extract` | | JSON fields to pick for every request: `{"name": "path"}`; a request's own `extract` overrides names |
| `includeBody` | `true` | `false` drops the body (`bodyFormat: "omitted"`) so items hold status, headers and `extracted` only |
| `headers` | | Headers for every request (a request's own headers win) |
| `authorization` | | Secret `Authorization` value for every request (stored encrypted, never output) |
| `userAgent` | identifies this Actor | User-Agent header |
| `timeoutSecs` | `30` | Time limit per attempt, redirects included (1–300) |
| `maxRetries` | `2` | Retries on 429/500/502/503/504 and connection failures (0–5) |
| `maxRetryWaitSecs` | `30` | Longest `Retry-After` to wait for |
| `retryUnsafeMethods` | `false` | Also retry POST/PATCH on 5xx and timeouts |
| `followRedirects` / `maxRedirects` | `true` / `10` | Redirect handling |
| `htmlFormat` | `markdown` | `markdown` (main content), `markdown-full`, `text` or `html` |
| `parseJson` | `true` | Return JSON parsed |
| `maxBodyTokens` / `maxBodyChars` | `8000` / `0` | Body budget (≈4 characters per token); 0 = no limit; the smaller applies |
| `maxResponseMb` | `10` | Download limit per body (1–50) |
| `storeBinary` | `true` | Save binary bodies to the key-value store |
| `session` | `false` | Keep cookies across the run's requests |
| `verifyTls` | `true` | Reject invalid certificates |
| `maxConcurrency` | `5` | Requests in flight (1–20) |
| `healthMethod` / `tlsWarningDays` | `GET` / `14` | url-health mode options |
| `requestTemplate` | | One request object with `{{column}}` placeholders, sent for every row (after `requests` and `urls`) |
| `rowsDatasetId` / `rowsUrl` / `rows` | | The rows: an Apify dataset, a CSV/TSV/JSON/JSON Lines/Google Sheets URL (≤25 MB), or inline. Give one |
| `passThroughColumns` | | Row columns copied into each result as `row` (`*` = all) |
| `maxRows` / `rowsOffset` | `0` / `0` | Send at most this many rows (0 = all) / skip this many first |
| `dedupeRequests` | `true` | Send identical rendered requests once |

### Output

One dataset item per request, in completion order (`index` is the position in the input). Abridged:

```json
{
  "index": 0, "label": "docs", "method": "GET", "url": "https://example.com/docs",
  "status": "ok", "statusCode": 200, "statusText": "OK", "ok": true,
  "finalUrl": "https://www.example.com/docs/",
  "redirects": [{"url": "https://example.com/docs", "statusCode": 301, "location": "https://www.example.com/docs/"}],
  "headers": {"content-type": "text/html; charset=utf-8", "set-cookie": ["sid=[REDACTED]"]},
  "contentType": "text/html; charset=utf-8",
  "bodyBytes": 48211,
  "bodyFormat": "markdown",
  "body": "# Getting started\n\nInstall the CLI ...",
  "truncated": false, "bodyChars": 5120, "bodyTokensEstimate": 1280,
  "page": {"title": "Getting started", "language": "en", "description": "..."},
  "timingMs": {"dns": 12, "connect": 9, "tls": 21, "waiting": 88, "download": 4, "total": 140},
  "elapsedMs": 233, "attempts": 1, "retries": [], "httpVersion": "HTTP/1.1",
  "tls": {"protocol": "TLSv1.3", "issuer": "Let's Encrypt", "subject": "www.example.com",
          "validFrom": "2026-09-01T00:00:00Z", "validTo": "2026-11-30T00:00:00Z", "daysLeft": 51, "altNames": ["..."]},
  "warnings": []
}
```

`bodyFormat` is `json` (`body` is the parsed value), `markdown`, `text`, `html`, `binary` (`body` is null;
`bodyUrl` and `bodyKey` point to the key-value store record), `empty` or `omitted` (`includeBody: false`).
With `extract`, items also have `extracted` (name to value; null when the path matches nothing) and
`extractMissing` (those names). Path syntax: dot keys, `[n]` or `.n` index (negative counts from the end),
`[*]` or `*` for every element (gives a list), `["key.with.dots"]`, optional `$.` prefix. JSON Lines bodies are
read as a list. A non-JSON response gives `extracted: null` and a warning (still charged: it got a response). When JSON is longer than the budget,
`body` holds the start of the JSON text as a string and `truncated` is true.

**URL health** items have `broken`, `brokenReason` (e.g. `HTTP 404`, `DNS_FAILED`), `statusCode`, `finalUrl`,
`redirectCount`, `redirects`, `responseTimeMs`, `tlsDaysLeft`, `tlsExpiringSoon` and `tls`.

**Template rows** add `rowIndex` (the row's position in its source) and, with `passThroughColumns`, `row`.
Repeated requests give `{"status": "skipped", "skipReason": "DUPLICATE_REQUEST", "duplicateOfIndex": 0}`.

A request that gets no response produces an error item (not charged); the run carries on:

```json
{"index": 3, "url": "http://169.254.169.254/latest/meta-data/", "status": "error",
 "error": {"code": "BLOCKED_ADDRESS", "message": "'169.254.169.254' resolves to 169.254.169.254, a private, loopback, link-local or otherwise non-public address. ..."}}
```

Error codes: `INVALID_REQUEST`, `BLOCKED_ADDRESS`, `DNS_FAILED`, `CONNECT_FAILED`, `TLS_FAILED`, `TIMEOUT`,
`TOO_MANY_REDIRECTS`, `PROTOCOL_ERROR`, `SPENDING_LIMIT_REACHED`, `INTERNAL_ERROR`, and for templates
`MISSING_COLUMN` (a row lacks a placeholder's column) and `ROWS_UNREADABLE` (the dataset or file could not be read).
Every message says what to do next.

### Pricing (pay per event)

| Event | Price | When |
|---|---|---|
| `request-completed` | **$0.001** | Each request that received an HTTP response, any status code (retries and redirects included) |
| Actor start | set by Apify | Per run |

1,000 requests cost $1. Requests that get no response are never charged. If the run's maximum total charge
is reached, the remaining requests are not sent and get `SPENDING_LIMIT_REACHED` items.

### Limits

- 10,000 requests per run (template rows included; continue with `rowsOffset`). Row files up to 25 MB. HTTP/1.1 only. Each attempt has `timeoutSecs`; with retries a request can take
  longer in total.
- HTML larger than 2 MB is converted from its first 2 MB. JavaScript is not executed: pages rendered in the
  browser come back nearly empty. Use a browser-based Actor for those.
- Only public internet addresses are reachable (no localhost, private networks or cloud metadata).
- The default 256 MB of memory fits the defaults (measured peak 172 MB with four 1–3 MB Wikipedia pages and a
  10 MB file in one run); concurrency is lowered automatically when `maxResponseMb` times `maxConcurrency`
  would not fit. Apify gives 256 MB runs a small CPU share, so converting very large HTML pages takes several
  seconds each; for big HTML batches, run with 1 GB or more.

### Responsible use

This is a plain HTTP client for APIs and web pages, not a scraper: there is no proxy rotation, no
fingerprint spoofing and no CAPTCHA or anti-bot evasion, and the default User-Agent identifies the Actor.
**You are responsible for complying with the terms of service, robots rules and rate limits of the sites and
APIs you call**, and for having the right to use any credentials you send.

### Privacy

Requests go only to the URLs you give (and the redirects they answer with). Secrets you pass in headers are
used for the request and redacted in the dataset and log. Response bodies are returned as the server sent
them, so a server that echoes your request headers back (like httpbin.org) shows them in `body`. Nothing is sent to any third party, no LLM is
involved, no telemetry.

### Use from an AI agent (MCP)

Add the Apify MCP server to your agent and enable this Actor. The agent can call it with
`{"requests": [{"url": "<url>"}]}` and read `statusCode`, `bodyFormat` and `body` from the dataset items.
For big API responses, pass `extract` with the paths it needs and `"includeBody": false` to keep the context small.

### Open-source licenses

All dependencies are permissively licensed. CI fails on any copyleft dependency.

| Component | License | Role |
|---|---|---|
| [httpx](https://www.python-httpx.org/) 0.28.1, httpcore 1.0.9, h11, anyio, idna | BSD-3-Clause / MIT | HTTP client |
| [Beautiful Soup](https://www.crummy.com/software/BeautifulSoup/) 4.15.0, soupsieve | MIT | HTML parsing |
| [markdownify](https://github.com/matthewwithanm/python-markdownify) 1.2.3, six | MIT | HTML to Markdown |
| cryptography 50.0.2, cffi, pycparser | Apache-2.0 OR BSD-3-Clause / MIT-0 / BSD-3-Clause | Certificate parsing |
| Python `ssl` (OpenSSL) | PSF / Apache-2.0 | TLS |
| apify 4.1.0, apify-client, crawlee and their dependencies (pydantic, websockets, ...) | Apache-2.0 / MIT / BSD | Apify SDK |
| certifi | MPL-2.0 (used unmodified) | CA certificates |

Test-only: pytest (MIT).

***

Made by Tinplate Labs. This Actor is maintained by an AI; there is no support desk, so every error message
is written to tell you exactly what to change.

# Actor input Schema

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

The HTTP requests to send. Each is a URL string or an object: <code>{"method": "POST", "url": "https://api.example.com/items", "headers": {...}, "query": {"page": 2}, "json": {...}, "extract": {"id": "data.id"}}</code>. Body: one of <code>json</code> (any JSON value), <code>form</code> (object, sent URL-encoded) or <code>body</code> (raw string). Optional per request: <code>extract</code> (JSON fields to pick, see below), <code>timeoutSecs</code>, <code>followRedirects</code>, <code>label</code> (echoed in the output). You get one result per request: status, headers, parsed JSON or clean Markdown, timing. Charged only when a response arrives. Max 10,000 requests per run, together with <code>urls</code> and template rows. To call an API once for every row of a CSV, Google Sheet or dataset, use the <b>Bulk API calls from CSV</b> section at the bottom instead.

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

Plain URLs to GET (or to check in url-health mode). Added after <code>requests</code>. A bare domain like <code>example.com</code> is fetched over https.

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

Pick values out of JSON responses into <code>extracted</code>: output name to path, e.g. <code>{"name": "data.user.name", "firstId": "items\[0].id", "lastId": "items\[-1].id", "allIds": "items\[*].id"}</code>. Applies to every request; a request's own <code>extract</code> overrides names. Paths: dot keys, <code>\[n]</code> index (negative from the end), <code>\[*]</code> for every element, <code>\["key.with.dots"]</code>. A path that matches nothing gives null and is listed in <code>extractMissing</code>. Reads the whole JSON, even when <code>body</code> is cut by the token limit. No extra charge.

## `includeBody` (type: `boolean`):

When off, records hold status, headers and <code>extracted</code> only (<code>bodyFormat</code> is <code>omitted</code>): small rows for Clay, n8n, Make or an agent's context. Binary bodies are not stored.

## `mode` (type: `string`):

<b>request</b>: send each request and return status, headers and an agent-ready body. <b>url-health</b>: check each URL (status, final URL, redirect chain, response time, TLS days left, broken flag) without downloading bodies.

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

Headers added to every request, e.g. <code>{"Accept-Language": "en"}</code>. A request's own <code>headers</code> override these. Credential-like headers are redacted in the output and log.

## `authorization` (type: `string`):

Optional value for the <code>Authorization</code> header of every request, e.g. <code>Bearer sk-...</code>. Stored encrypted, never shown in the output or log, and not sent to a different host after a redirect.

## `userAgent` (type: `string`):

User-Agent header. Default identifies this Actor: <code>Mozilla/5.0 (compatible; TinplateAgentHTTP/1.0; Apify Actor; +https://apify.com/tinplatelabs/agent-http)</code>.

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

Time limit for one attempt, redirects included. A request without a complete response in time gets a TIMEOUT record and is not charged.

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

Retries on HTTP 429, 500, 502, 503, 504 and on connection failures, with exponential backoff (0.5 s, 1 s, 2 s, ...). Retry-After is honoured. POST and PATCH are retried only on 429 and connection failures unless <code>retryUnsafeMethods</code> is on.

## `maxRetryWaitSecs` (type: `integer`):

If the server's Retry-After asks for a longer wait, the response is returned (with a warning) instead of waiting.

## `retryUnsafeMethods` (type: `boolean`):

Also retry non-idempotent requests on 5xx responses and timeouts. Only turn this on when sending the request twice is harmless.

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

Follow 301, 302, 303, 307 and 308 redirects. Every hop is listed in <code>redirects</code>. When off, the 3xx response itself is returned.

## `maxRedirects` (type: `integer`):

More redirects than this gives a TOO_MANY_REDIRECTS record (not charged).

## `htmlFormat` (type: `string`):

How HTML bodies are returned: Markdown of the main content (navigation, cookie banners, sidebars and footers removed; links made absolute), Markdown of the whole page, plain text, or the raw HTML.

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

Return JSON responses as parsed values. When off, JSON is returned as text.

## `maxBodyTokens` (type: `integer`):

Cut text bodies (Markdown, text, JSON text) to about this many tokens (4 characters per token), at a paragraph, line or word boundary. <code>truncated</code> is then true and <code>bodyChars</code> gives the full length. 0 = no token limit.

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

Optional hard character limit for text bodies. When both limits are set, the smaller applies. 0 = no character limit.

## `maxResponseMb` (type: `integer`):

Download at most this much of each body. Larger bodies are cut and marked <code>truncated</code>; cut binaries are not stored.

## `storeBinary` (type: `boolean`):

Save images, PDFs and other binary bodies to the run's key-value store and return <code>bodyUrl</code>. When off, only their size and type are returned.

## `session` (type: `boolean`):

When on, cookies set by one response are sent with later requests in this run (like a browser session). When off, each request starts without cookies.

## `verifyTls` (type: `boolean`):

Reject servers with invalid, expired or self-signed certificates (TLS_FAILED). Turn off only for servers you trust.

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

Requests in flight at the same time. Lowered automatically if large bodies would not fit in the run's memory.

## `healthMethod` (type: `string`):

url-health mode only. GET is the most reliable (some servers mishandle HEAD); the body is never downloaded either way.

## `tlsWarningDays` (type: `integer`):

url-health mode: <code>tlsExpiringSoon</code> is true when the certificate expires in fewer days than this.

## `requestTemplate` (type: `object`):

One request written once, sent for every row, with <code>{{column}}</code> placeholders filled from that row: <code>{"method": "POST", "url": "https://api.example.com/companies/{{domain}}", "query": {"country": "{{country}}"}, "headers": {"X-Ref": "{{id}}"}, "json": {"name": "{{name}}", "employees": "{{employees}}"}, "extract": {"score": "data.score"}}</code>. Values are URL-encoded in the URL (a placeholder at the start of the URL, like <code>{{url}}</code>, or in triple braces <code>{{{path}}}</code> is inserted as is), and JSON-escaped in bodies; a <code>json</code> value that is just <code>"{{column}}"</code> keeps the column's type. Nested columns: <code>{{address.city}}</code>. A row without a column gives a MISSING_COLUMN record (free). Sent after <code>requests</code> and <code>urls</code>; clear those if you only want the rows.

## `rowsDatasetId` (type: `string`):

An Apify dataset, e.g. a scraper's output or a list of leads: pick it or paste its ID (or <code>username~dataset-name</code>). Each item is one row for the request template. Another user's dataset must be shared with you or public.

## `rowsUrl` (type: `string`):

Public URL of a CSV/TSV file (first line = column names), a JSON array, or JSON Lines, up to 25 MB. A Google Sheets link works if the sheet is shared as 'Anyone with the link can view' (the tab in the link is read). The download is free.

## `rows` (type: `array`):

Rows given directly, e.g. <code>\[{"id": 1, "domain": "apify.com"}, {"id": 2, "domain": "example.com"}]</code>. Plain values (a list of URLs or names) are available as <code>{{value}}</code>. Use one row source: dataset, file URL or inline rows.

## `passThroughColumns` (type: `array`):

Row columns copied into each result as <code>row</code>, so results join back to your sheet or CRM, e.g. <code>id</code>, <code>email</code>. <code>\*</code> copies the whole row. Every templated result also has <code>rowIndex</code>.

## `maxRows` (type: `integer`):

Send at most this many rows (0 = all, up to 10,000 requests per run). Handy for a test run on the first few rows.

## `rowsOffset` (type: `integer`):

Start at this row (0-based). For more than 10,000 rows, run again with the offset the log suggests.

## `dedupeRequests` (type: `boolean`):

When rows render exactly the same request, send it once and give the repeats a <code>skipped</code> record with <code>duplicateOfIndex</code> (free).

## Actor input object example

```json
{
  "requests": [
    {
      "method": "GET",
      "url": "https://jsonplaceholder.typicode.com/users/1",
      "label": "user",
      "extract": {
        "name": "name",
        "email": "email",
        "city": "address.city",
        "company": "company.name"
      }
    },
    {
      "method": "POST",
      "url": "https://jsonplaceholder.typicode.com/posts",
      "label": "create post",
      "json": {
        "title": "hello",
        "body": "sent by an agent",
        "userId": 1
      },
      "extract": {
        "newId": "id"
      }
    },
    {
      "url": "https://example.com",
      "label": "web page as Markdown"
    }
  ],
  "includeBody": true,
  "mode": "request",
  "timeoutSecs": 30,
  "maxRetries": 2,
  "maxRetryWaitSecs": 30,
  "retryUnsafeMethods": false,
  "followRedirects": true,
  "maxRedirects": 10,
  "htmlFormat": "markdown",
  "parseJson": true,
  "maxBodyTokens": 8000,
  "maxBodyChars": 0,
  "maxResponseMb": 10,
  "storeBinary": true,
  "session": false,
  "verifyTls": true,
  "maxConcurrency": 5,
  "healthMethod": "GET",
  "tlsWarningDays": 14,
  "maxRows": 0,
  "rowsOffset": 0,
  "dedupeRequests": true
}
```

# Actor output Schema

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

No description

## `files` (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 = {
    "requests": [
        {
            "method": "GET",
            "url": "https://jsonplaceholder.typicode.com/users/1",
            "label": "user",
            "extract": {
                "name": "name",
                "email": "email",
                "city": "address.city",
                "company": "company.name"
            }
        },
        {
            "method": "POST",
            "url": "https://jsonplaceholder.typicode.com/posts",
            "label": "create post",
            "json": {
                "title": "hello",
                "body": "sent by an agent",
                "userId": 1
            },
            "extract": {
                "newId": "id"
            }
        },
        {
            "url": "https://example.com",
            "label": "web page as Markdown"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tinplatelabs/agent-http").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": [
        {
            "method": "GET",
            "url": "https://jsonplaceholder.typicode.com/users/1",
            "label": "user",
            "extract": {
                "name": "name",
                "email": "email",
                "city": "address.city",
                "company": "company.name",
            },
        },
        {
            "method": "POST",
            "url": "https://jsonplaceholder.typicode.com/posts",
            "label": "create post",
            "json": {
                "title": "hello",
                "body": "sent by an agent",
                "userId": 1,
            },
            "extract": { "newId": "id" },
        },
        {
            "url": "https://example.com",
            "label": "web page as Markdown",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("tinplatelabs/agent-http").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": [
    {
      "method": "GET",
      "url": "https://jsonplaceholder.typicode.com/users/1",
      "label": "user",
      "extract": {
        "name": "name",
        "email": "email",
        "city": "address.city",
        "company": "company.name"
      }
    },
    {
      "method": "POST",
      "url": "https://jsonplaceholder.typicode.com/posts",
      "label": "create post",
      "json": {
        "title": "hello",
        "body": "sent by an agent",
        "userId": 1
      },
      "extract": {
        "newId": "id"
      }
    },
    {
      "url": "https://example.com",
      "label": "web page as Markdown"
    }
  ]
}' |
apify call tinplatelabs/agent-http --silent --output-dataset

```

## MCP server setup

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

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/ZiFqTUcnQrJce42OE/builds/tNpDZytbMGX0lBamO/openapi.json
