Run one task over 100,000 items
Some jobs aren't complicated, they're just enormous. The Batch Processing API applies one task definition to up to 100,000 items in a single submission, so a catalogue, a mailing list or a document archive gets processed as one job instead of tens of thousands of individual requests.
The problem with scale, not complexity
Translating one product description is trivial; translating 80,000 of them one request at a time means managing 80,000 task_ids, 80,000 webhooks and a retry strategy you have to build yourself, along with the bookkeeping to know which of those 80,000 actually finished. The task itself hasn't gotten harder, only bigger. POST /flow/batch is built for exactly that shape of problem: one task, applied uniformly, at volume, with the bookkeeping handled server-side instead of in your own database.
How a batch is structured
You submit one task type, its shared parameters, and a list of items — up to 100,000 of them — each carrying whatever per-item data the task needs, like a row of text to translate or a file reference to transcribe. The batch runs across our global edge, processing items concurrently rather than one after another, which is what keeps a 100,000-item job from taking 100,000 times as long as a single one, so growth in list size doesn't translate directly into growth in wall-clock time.
One task_id, one result set
The entire batch is tracked under a single task_id from submission to completion. When it finishes, you get one structured result covering every item, delivered by signed webhook or through a signed link valid for 24 hours, so you're not stitching together thousands of separate callbacks yourself, and your integration code has exactly one response shape to parse regardless of whether the batch had ten items or a hundred thousand.
How per-item failures are handled
Individual items can fail without taking down the batch: each failing item gets up to three retries on its own, and the final result marks exactly which items succeeded and which didn't, with clear per-item errors describing what went wrong. As with every KIT task, failed items are never charged, only successful processing is, so a handful of malformed rows in an otherwise clean file cost you nothing beyond the time to fix and resubmit them.
Why batch and not just fan-out
Parallel fan-out is for a handful of distinct, differently-shaped steps; batch is for one identical operation repeated across a large, uniform list. Pricing reflects that: batch is $0.002 per request, covering the orchestration of the whole set, on top of whatever the underlying task itself costs per item, which keeps the economics predictable whether the list holds a few hundred rows or the full 100,000.
What you can do with it
Product catalogue translation
Translate tens of thousands of product titles and descriptions into a new market language in a single batch job instead of one request per SKU.
Support ticket backlog transcription
Transcribe a large archive of recorded support calls at once, receiving one consolidated result set instead of managing each call individually.
Bulk document OCR
Run OCR across a scanned archive of tens of thousands of pages in one job, with per-page results and per-page error reporting.
Mailing list validation
Validate a large subscriber list in one batch instead of looping through individual verification requests row by row.
FAQ
How many items can a single batch process?
Up to 100,000 items per submission to POST /flow/batch, all sharing the same task type and processed as one job.
Is batch processing free?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
What happens if some items in the batch fail?
Each failing item retries up to three times independently, and the final result lists exactly which items succeeded and which failed, with failed items never charged.
Do all items in a batch have to use the same task type?
Yes, a batch applies one task definition uniformly across every item; mixing different task types in one job is what the chaining or fan-out tasks are for instead.
How long does a large batch take?
Items are processed concurrently rather than sequentially, so total time depends on the task type and the load at submission time rather than growing linearly with item count.
How do I get the results for a 100,000-item batch?
The completed batch delivers one structured result set, either through a signed webhook the moment it finishes or a signed link valid for 24 hours.
Is batch the same as calling the same task 100,000 times?
Functionally yes, but operationally no: batch tracks everything under one task_id and one result instead of 100,000 separate task_ids and callbacks you'd otherwise have to manage.
Can I run a batch job on a recurring schedule?
Yes, a batch task can be triggered by the scheduled task API, so a recurring bulk job — like a nightly catalogue sync — runs automatically without manual submission.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/flow/batch \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"items":["valor-1","valor-2"]}'const res = await fetch("https://api.kit.forhosting.com/flow/batch", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"items": [
"valor-1",
"valor-2"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/flow/batch",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"items": [
"valor-1",
"valor-2"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/flow/batch", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"items":["valor-1","valor-2"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"items":["valor-1","valor-2"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/flow/batch", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"items": [
"valor-1",
"valor-2"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "flow.batch",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |