Batch Inference¶
Batch inference takes many requests in one go and answers them later. You submit the whole job, check on it from time to time, and collect the results once it has finished. FreeInference runs the requests in the background, a few at a time per model, and gives way to interactive traffic.
There are three ways to submit a batch: an inline HTTP form, OpenAI-compatible file batches and Anthropic Message Batches. The latter two work with the official OpenAI and Anthropic SDKs; no separate FreeInference SDK is needed:
Way to submit |
How |
Results |
|---|---|---|
|
||
From a file (OpenAI Batch API) |
Upload JSONL, then create a batch with its |
Download an output file and an error file |
|
Read a JSONL result stream |
A batch request can be a chat completion (/v1/chat/completions), an
embedding (/v1/embeddings), an independent Responses request
(/v1/responses), or an Anthropic message (in a Message Batch). FreeInference
runs each request itself, exactly as it runs the same request sent directly.
It does not hand the batch to a provider’s own batch service.
Availability¶
Account access: Batch creation is restricted to internal accounts and administrators unless the operators enable it for other account tiers. An account without batch access receives
403when creating a batch.
Inline chat-completion batches are the base feature. The rest of this page also covers extensions that FreeInference’s operators turn on separately:
batches from an uploaded file, and the
/v1/filesupload;Anthropic Message Batches;
embedding and Responses requests in a batch;
completion webhooks.
This page describes how these extensions behave once they are on. It does not mean they are already available on FreeInference. While one is off, calls that would create something with it are refused and the message says it is not enabled:
Call |
Answer while the extension is off |
|---|---|
Uploading a file to |
|
An inline batch whose |
|
Creating a Message Batch |
|
Setting a completion webhook |
|
If an extension is turned off again later, batches you already submitted with it keep running, and you can still read, download, cancel and delete them. Operators can find the switches and the rest of the server-side reference in the HybridInference developer documentation on batches.
When to use it¶
Use a batch for a large job that does not need its answers right away: an evaluation run, labelling or summarizing a dataset, generating synthetic data, or embedding a corpus. Compared with sending the requests yourself:
One call submits the whole job, up to 50,000 requests by default. You do not hold thousands of connections open, and you can disconnect once it is submitted.
FreeInference paces and retries the work. Batch requests run under their own concurrency limit, not your interactive one, and transient errors are retried for you, so you do not need your own rate-limit or retry logic.
The results wait for you. A finished batch and its results are kept for 7 days by default.
Send the requests yourself when you need answers within seconds or need streaming. A batch has no fixed turnaround. Its requests start when their models have room, and any that have not started when its completion window closes (24 hours by default) expire. Batch requests are not discounted either: each one counts against your daily quota exactly as the same request sent directly would.
How batches run¶
The create call does not run inference. Inline requests are checked before the batch is stored. File batches are validated in the background before any request runs; Message Batches validate each request separately. The sections below explain each protocol’s status and validation results.
Paced, behind interactive traffic. Each model runs a limited number of batch requests at once, and fewer while it is busy with interactive requests. Batches take turns, so a large batch does not hold up a smaller one submitted after it. Depending on how busy its models are, a batch can finish in minutes or take hours. Requests run in no particular order. Their results are listed in the order you submitted them.
A separate concurrency limit, shared model capacity. Batch requests do not use your interactive concurrent-request limit. They have their own per-account limit, set by your account tier (see Limits). A running batch therefore does not use up your interactive slots, and your interactive requests do not consume your personal batch quota. Batch and interactive requests still run on the same models, though. Batch work yields to interactive traffic, so a model under heavy load runs fewer batch requests, and each batch request takes model capacity like any other request.
Like your own requests. Each request is served exactly as if you had sent it to its endpoint yourself, under your account, with the same models, routing and price. Each one counts toward your daily quota, which batch and interactive requests share: a large batch can use up the quota your interactive requests also draw on.
Retried when the error may pass. A request that fails with a timeout, a
429or a5xxis retried with backoff, up to eight attempts in all. A request rejected as invalid (any other4xx) fails at once.Checked against your key every time. Before each request runs, FreeInference checks the API key that created the batch again:
if the key has been revoked or has expired, or the account was suspended or deleted, the batch fails;
if your daily quota is used up, your batches pause until it renews at 00:00 UTC;
if your account no longer has access to batch inference, your batches wait until it has again.
Keep the key you created a batch with until the batch finishes. The batch runs under that key, and the completion window keeps running while a batch is paused or waiting.
Managing a batch is a different matter. Checking on, listing, reading, downloading, cancelling and deleting your batches work with any valid API key of the same account, not only the key that created the batch. They also keep working while your daily quota is used up or your account can no longer create batches, so you can always collect and remove what you submitted. Only creating a batch (and uploading an input file) needs batch access, and only creating a batch is refused once the daily quota is used up. A batch that belongs to another account reads as not found.
Endpoints¶
Every endpoint is under the base URL https://freeinference.org/v1 and takes
your FreeInference API key, as Authorization: Bearer hyi-your-api-key (or
X-API-Key; see the API Headers Reference).
Request |
What it does |
|---|---|
|
Create a batch, inline or from a file |
|
|
|
Read its results, a page at a time |
|
Download every result of a finished batch as JSONL |
|
|
|
Delete it, with its results |
|
|
|
|
|
|
|
The examples read your key from an environment variable:
export FREEINFERENCE_API_KEY="hyi-your-api-key"
Inline batches¶
An inline batch carries its requests in the create call itself. It needs nothing but HTTP: no SDK, no file upload. The next sections show how to create one, check on it, read and download its results, and do all of that from a Python script.
Create a batch¶
POST /v1/batches with the requests inline:
curl https://freeinference.org/v1/batches \
-H "Authorization: Bearer $FREEINFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: eval-42-run-1" \
-d '{
"requests": [
{
"custom_id": "q-1",
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "glm-5.3-flash",
"messages": [{"role": "user", "content": "Summarize the plot of Hamlet in two sentences."}]
}
},
{
"custom_id": "q-2",
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "qwen3.6-35b",
"messages": [{"role": "user", "content": "Translate \"good morning\" into French."}],
"max_tokens": 50
}
}
],
"metadata": {"project": "eval-42"}
}'
The Idempotency-Key header is optional. It makes the call safe to retry; see
Submitting safely twice.
Field |
Required |
Meaning |
|---|---|---|
|
Yes |
The requests, at least one |
|
Yes |
Your name for the request: 1–512 characters, unique within the batch. Its result carries it back |
|
No |
|
|
No |
|
|
Yes |
The request body, exactly as you would send it to that endpoint. |
|
No |
Up to 16 string key-value pairs for your own use; keys up to 64 characters, values up to 512 |
One inline batch can use several models. Name each one by a model ID from
Available Models. Auto models (auto, auto-<tier>) cannot be
used in a batch. If you name a model by an alias, the request runs on the
model that alias pointed to when you created the batch.
FreeInference checks every request before it stores anything, and refuses the
whole batch with 400 if any request is invalid. The error message names the
request at fault, for example
requests[1] (custom_id 'q-2'): Model 'no-such-model' not found. As elsewhere
in the API, a model your key may not use is reported exactly like one that does
not exist.
The answer is the new batch:
{
"id": "batch_6f1c0a7e2b8d4e55a1c3f9d2e4b6a8c0",
"object": "batch",
"endpoint": "/v1/chat/completions",
"errors": null,
"input_file_id": null,
"completion_window": "24h",
"status": "validating",
"output_file_id": null,
"error_file_id": null,
"created_at": 1792929600,
"in_progress_at": null,
"expires_at": 1793016000,
"finalizing_at": null,
"completed_at": null,
"failed_at": null,
"expired_at": null,
"cancelling_at": null,
"cancelled_at": null,
"request_counts": {"total": 2, "completed": 0, "failed": 0},
"metadata": {"project": "eval-42"},
"usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0},
"cost_usd": 0.0,
"cost_unknown_attempts": 0,
"results_expires_at": null,
"error": null
}
The other endpoints take the batch’s id; the examples below read it from
$BATCH_ID. Timestamps are Unix seconds, and expires_at is when the
completion window closes. An inline batch has no files, so its file fields are
null. usage totals the tokens of the requests that have finished. Some
fields are not in OpenAI’s batch object:
cost_usd: what the batch’s finished requests have counted against your daily quota so far. It is an accounting figure, not a bill;cost_unknown_attempts: attempts whose cost could not be known, and so are not incost_usd(see Costs);results_expires_at: once the batch has finished, when it and its results will be deleted;error: why the whole batch failed.
Check on a batch¶
GET /v1/batches/{batch_id} returns the batch object again:
curl https://freeinference.org/v1/batches/$BATCH_ID \
-H "Authorization: Bearer $FREEINFERENCE_API_KEY"
Poll it until status is completed, failed, expired or cancelled, the
four final statuses:
Status |
Meaning |
|---|---|
|
Stored; no request has started yet. A batch from a file stays |
|
Requests are running |
|
Batches from a file only: every request has finished and the output files are being written. It lasts a moment |
|
Every request has finished, successfully or not. Read each one’s outcome in the results |
|
The batch stopped because the key or account that created it is no longer valid; its |
|
The completion window closed before every request had started. Those requests expire with |
|
You cancelled the batch while requests were running; they are finishing |
|
Cancelled. Requests that had not started never will |
request_counts.total is the number of requests in the batch.
request_counts.completed counts the requests that succeeded, and
request_counts.failed those that failed, including the ones a failed or
expired batch never ran. Cancelled requests are in neither.
The completion window only stops requests from starting. Requests already
running when it closes finish, so a batch can reach its final status a little
after expires_at.
Read the results¶
GET /v1/batches/{batch_id}/results pages through the batch’s requests in the
order you submitted them, each with its outcome:
curl "https://freeinference.org/v1/batches/$BATCH_ID/results?limit=100" \
-H "Authorization: Bearer $FREEINFERENCE_API_KEY"
{
"object": "list",
"data": [
{
"id": "batch_req_1041",
"custom_id": "q-1",
"status": "completed",
"response": {
"status_code": 200,
"request_id": "req_2c9f6a1d8e7b4c03a5f1e2d3c4b5a697",
"body": {
"id": "chatcmpl-9b1e",
"object": "chat.completion",
"model": "glm-5.3-flash",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Prince Hamlet ..."},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 16, "completion_tokens": 120, "total_tokens": 136}
}
},
"error": null
},
{
"id": "batch_req_1042",
"custom_id": "q-2",
"status": "failed",
"response": null,
"error": {"code": "invalid_request", "message": "...", "status": 400}
}
],
"has_more": false,
"first_id": "batch_req_1041",
"last_id": "batch_req_1042"
}
limit is 1–1000 (default 100). When has_more is true, ask for the next
page with after set to this page’s last_id:
curl "https://freeinference.org/v1/batches/$BATCH_ID/results?limit=100&after=batch_req_1042" \
-H "Authorization: Bearer $FREEINFERENCE_API_KEY"
Every request of the batch is listed, finished or not. A request that is still
queued or running has no response yet; if it has already failed once and
is being retried, its error shows what went wrong last time. So read the
results once the batch has reached a final status, or read them again later.
This endpoint works for batches from a file too.
Result |
Meaning |
|---|---|
|
Waiting to run, or waiting to be retried |
|
Running now |
|
Succeeded. |
|
Failed, or never ran because the batch failed. |
|
Never ran: the batch was cancelled |
|
Never ran: the completion window closed first |
An error has a code and a message, plus the HTTP status behind it when
there was one:
|
Meaning |
|---|---|
|
The request was rejected as invalid ( |
|
The model is gone, or your key may no longer use it ( |
|
The request was refused ( |
|
Still rate-limited after every retry ( |
|
Still timing out after every retry ( |
|
No provider could serve it after every retry ( |
|
Another server error, after every retry |
|
The response was larger than a batch result may be (4 MiB by default) |
|
Not run: the batch failed |
|
Not run: the completion window closed first |
Download the results as JSONL¶
Once a batch has reached a final status, GET /v1/batches/{batch_id}/output.jsonl downloads all of its results as one JSONL
file, one line per request in submission order, successes and failures alike.
Each line has the shape of a line of an OpenAI batch output file (id,
custom_id, response, error):
curl -o results.jsonl https://freeinference.org/v1/batches/$BATCH_ID/output.jsonl \
-H "Authorization: Bearer $FREEINFERENCE_API_KEY"
Before the batch has finished, the answer is 409.
Python example¶
This script submits an inline batch, waits for it to finish and collects every
result, using requests. It needs no
OpenAI or Anthropic SDK:
import os
import time
import requests
API = "https://freeinference.org/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['FREEINFERENCE_API_KEY']}"
def call(method, path, **kwargs):
response = session.request(method, API + path, timeout=120, **kwargs)
response.raise_for_status()
return response.json()
prompts = ["Summarize the plot of Hamlet in two sentences.", "Name three prime numbers."]
batch = call(
"POST",
"/batches",
# Reuse the same key if you retry this call, so a retry cannot submit the job twice.
headers={"Idempotency-Key": "hamlet-primes-run-1"},
json={
"requests": [
{
"custom_id": f"q-{i}",
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "glm-5.3-flash",
"messages": [{"role": "user", "content": prompt}],
},
}
for i, prompt in enumerate(prompts)
]
},
)
# Wait for a final status.
while batch["status"] not in ("completed", "failed", "expired", "cancelled"):
time.sleep(30)
batch = call("GET", f"/batches/{batch['id']}")
# Page through every result.
results, after = [], None
while True:
params = {"limit": 1000}
if after:
params["after"] = after
page = call("GET", f"/batches/{batch['id']}/results", params=params)
results.extend(page["data"])
if not page["has_more"]:
break
after = page["last_id"]
for result in results:
if result["status"] == "completed":
print(result["custom_id"], result["response"]["body"]["choices"][0]["message"]["content"])
else:
print(result["custom_id"], result["status"], result["error"])
Batches from a file (OpenAI SDK)¶
Available where file batches are enabled.
This is OpenAI’s own Batch API flow, so the official OpenAI SDKs work with it
unchanged. Write one request per line to a JSONL file, upload it with
purpose=batch, then create a batch that names the file:
{"custom_id": "q-0", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "glm-5.3-flash", "messages": [{"role": "user", "content": "Summarize the plot of Hamlet in two sentences."}]}}
{"custom_id": "q-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "glm-5.3-flash", "messages": [{"role": "user", "content": "Name three prime numbers."}]}}
The rules for the file:
each line is
{"custom_id", "method": "POST", "url", "body"}, withurlequal to the batch’sendpointand acustom_idunique in the file;every line names the same model. A file batch runs one model; use several files, or an inline batch, to mix models;
at most 50,000 lines, none longer than 4 MiB, and the file at most 32 MiB;
completion_windowmust be"24h", andendpointone of/v1/chat/completions,/v1/embeddingsor/v1/responses.
The create call answers at once with validating while FreeInference reads the
file. If any line is invalid (not JSON, a repeated custom_id, another url
or model, a body the endpoint would refuse), the whole batch is failed and
none of it runs. errors.data then lists the problems with their line numbers.
Otherwise the batch moves to in_progress. Once every request has finished, it
is finalizing for a moment while its result files are written. Then it
reaches its final status with:
output_file_id: one line per request that succeeded;error_file_id: one line per request that failed, was cancelled or expired.
Either one is null when it would be empty. A batch in which everything
succeeded has no error file, one in which nothing succeeded has no output file,
and a batch that failed validation has neither. Check before downloading.
A complete script with the official OpenAI Python SDK:
import json
import os
import time
from openai import OpenAI
client = OpenAI(
base_url="https://freeinference.org/v1",
api_key=os.environ["FREEINFERENCE_API_KEY"],
)
# 1. Write the requests, one per line. Every line uses the same model.
prompts = ["Summarize the plot of Hamlet in two sentences.", "Name three prime numbers."]
with open("requests.jsonl", "w", encoding="utf-8") as f:
for i, prompt in enumerate(prompts):
line = {
"custom_id": f"q-{i}",
"method": "POST",
"url": "/v1/chat/completions",
"body": {"model": "glm-5.3-flash", "messages": [{"role": "user", "content": prompt}]},
}
f.write(json.dumps(line) + "\n")
# 2. Upload the file and create the batch.
with open("requests.jsonl", "rb") as f:
upload = client.files.create(file=f, purpose="batch")
batch = client.batches.create(
input_file_id=upload.id,
endpoint="/v1/chat/completions",
completion_window="24h",
metadata={"project": "eval-42"},
# The SDK retries failed calls on its own; the key keeps a retry from creating a second batch.
extra_headers={"Idempotency-Key": f"eval-42-{upload.id}"},
)
# 3. Wait for a final status.
while batch.status not in ("completed", "failed", "expired", "cancelled"):
time.sleep(30)
batch = client.batches.retrieve(batch.id)
print(batch.status, batch.request_counts)
if batch.errors and batch.errors.data: # the input file was invalid; nothing ran
for problem in batch.errors.data:
print(f"line {problem.line}: {problem.message}")
def read_lines(file_id):
"""Download a result file, or nothing when the batch has none (file_id is None)."""
if file_id is None:
return []
text = client.files.content(file_id).text
return [json.loads(line) for line in text.splitlines() if line]
# 4. Successes, then everything else.
for line in read_lines(batch.output_file_id):
print(line["custom_id"], line["response"]["body"]["choices"][0]["message"]["content"])
for line in read_lines(batch.error_file_id):
# A request that got an HTTP error carries it in response.body.error;
# one that never ran (cancelled, expired, batch failed) carries it in error.
problem = line["error"] or line["response"]["body"]["error"]
print(line["custom_id"], problem["code"], problem["message"])
The paged /results endpoint and the
JSONL export also work for a batch from a
file, if you prefer them to the two files.
Batch files¶
/v1/files holds only batch files. It is not a general Files API: a file
cannot be referenced from a chat, Messages or Responses request.
Request |
What it does |
|---|---|
|
Upload a JSONL input file ( |
|
List your files: uploads ( |
|
Return one file object |
|
Download its bytes |
|
Delete it for good |
Retention. An uploaded input file is kept for 30 days by default. The output and error files of a batch are kept as long as the batch itself, 7 days after it finishes by default, and go when the batch is deleted.
Fixed bytes. A result file has the same bytes every time you download it. Its size and SHA-256 (sent as the
ETag) are fixed when the batch ends.Deleting. Deleting an input file does not affect a batch already created from it. A deleted output or error file is not written again: the batch keeps naming it, but downloading it then answers
404. The results themselves stay readable through/resultsand the JSONL export until the batch is deleted.Limits. You may keep up to 512 MiB of uploaded files and upload up to 60 files per hour, by default. Over the storage limit an upload answers
413; over the hourly limit,429.
Anthropic Message Batches¶
Available where Anthropic Message Batches are enabled.
/v1/messages/batches follows Anthropic’s Message Batches API, so the official
Anthropic SDKs work against it. The same routes are also served under
/anthropic, as /anthropic/v1/messages/batches, matching the base URL used
for Anthropic-compatible clients.
A complete script with the official Anthropic Python SDK:
import os
import time
import anthropic
client = anthropic.Anthropic(
base_url="https://freeinference.org/anthropic",
api_key=os.environ["FREEINFERENCE_API_KEY"],
)
prompts = ["Summarize the plot of Hamlet in two sentences.", "Name three prime numbers."]
batch = client.messages.batches.create(
requests=[
{
"custom_id": f"q-{i}",
"params": {
"model": "glm-5.3-flash",
"max_tokens": 512,
"messages": [{"role": "user", "content": prompt}],
},
}
for i, prompt in enumerate(prompts)
],
# The SDK retries failed calls on its own; the key keeps a retry from creating a second batch.
extra_headers={"Idempotency-Key": "hamlet-primes-msgbatch-1"},
)
# Wait until the batch has ended; results are available only then.
while batch.processing_status != "ended":
time.sleep(30)
batch = client.messages.batches.retrieve(batch.id)
print(batch.request_counts)
for entry in client.messages.batches.results(batch.id):
result = entry.result
if result.type == "succeeded":
text = "".join(block.text for block in result.message.content if block.type == "text")
print(entry.custom_id, text)
elif result.type == "errored":
print(entry.custom_id, "errored:", result.error.error.type, result.error.error.message)
else: # "canceled" or "expired": the request never ran
print(entry.custom_id, result.type)
How Message Batches differ from the OpenAI-shaped batches above:
The
anthropic-versionheader is required. The SDK sends it for you.2023-06-01and2026-10-21are accepted. A batch keeps the version it was created with, and each of its requests is checked against that version’s rules.anthropic-betais refused.Checking. Some things are checked at once, and a problem with any of them refuses the whole batch: each
custom_idmust be 1–64 letters, digits,_or-, and unique, and the batch must stay within the request-count and body-size limits. Each request’sparamsis then checked on its own. A request FreeInference cannot run as written, including one naming a model you may not use, becomes anerroredresult while the others still run. A parameter is never silently dropped: when a model’s route cannot carry a parameter (extended thinking, cache control or structured output on a non-Anthropic backend, for example), that request errors instead.Progress.
request_countsshow every request asprocessinguntil the whole batch hasended;results_urlappears then. Results can be read only once the batch has ended.Cancelling and deleting. Cancelling makes the batch
cancelinguntil its running requests finish. A Message Batch can be deleted only once it has ended (400before).One turn per request. A result that asks for a tool call is for your program to carry out and send back in a new request; FreeInference never runs your tools. Server tools, Files API sources, MCP servers and containers are refused.
Separate listing.
GET /v1/messages/batcheslists your Message Batches. An ID from one API reads as not found on the other.Your account, no workspaces. Message Batches belong to your account, like every batch here.
Embeddings and Responses in a batch¶
Available where embedding and Responses batches are enabled.
/v1/embeddings and /v1/responses can be batched like chat completions,
inline or from a file. Set url (inline) or endpoint (file) to the endpoint,
and give each request the body you would send to it. One batch targets one
endpoint.
A batched embedding request accepts what /v1/embeddings accepts. A batched
Responses request must be independent: it runs once, on its own, and is not
stored. So:
omit
storeor set it tofalse;leave out
previous_response_id,conversation,backgroundandstream;hosted tools, file inputs and any other field FreeInference would have to drop are refused.
Submitting safely twice¶
A network error during a create call leaves you not knowing whether the batch
was created. Send an Idempotency-Key header with the create call (any of the
three APIs) to make it safe to retry:
the same key with the same request returns the batch the first call created, instead of creating another. This works even if your daily quota has since run out or your account has lost batch access;
the same key with a different request answers
409;after that batch has been deleted, the key answers
410.
A key is 1–255 printable characters of your choosing, such as a job name and a
run number. It belongs to your account, so any of your API keys can replay it,
and a replay never changes which key the batch runs under. A key is remembered
until a day after its batch ends. The OpenAI and Anthropic SDKs do not send
this header for you: pass it with extra_headers, as in the examples above.
Without it, an SDK’s automatic retry of a create call can submit the batch
twice.
Costs¶
Batch requests cost the same as the same requests sent directly. There is no
batch discount. Every attempt at a request is recorded with what it cost, at the
same point and price as an interactive request, and a batch is never charged a
second time on top. cost_usd adds up the attempts whose cost is known.
Sometimes an attempt’s result is lost after the request was sent, for example
when the request timed out. The provider may still have run it. Its cost is
then unknown: it is counted in cost_unknown_attempts and left out of
cost_usd rather than counted as zero. A retried request may therefore have
run more than once. A batch whose cost_unknown_attempts is not 0 may have
cost somewhat more than cost_usd says.
Cancel a batch¶
POST /v1/batches/{batch_id}/cancel stops a batch and returns it:
curl -X POST https://freeinference.org/v1/batches/$BATCH_ID/cancel \
-H "Authorization: Bearer $FREEINFERENCE_API_KEY"
Requests that have not started are cancelled; running ones finish and keep
their results. The batch is cancelling until they do, then cancelled. A
batch from a file that is cancelled while its file is still being checked ends
cancelled with nothing run. Cancelling a batch that has already finished, or
is finalizing, returns it unchanged. With the SDKs, call
client.batches.cancel(batch_id) or client.messages.batches.cancel(batch_id).
Delete a batch¶
DELETE /v1/batches/{batch_id} deletes a batch with its requests and results,
and with the output and error files of a batch from a file:
curl -X DELETE https://freeinference.org/v1/batches/$BATCH_ID \
-H "Authorization: Bearer $FREEINFERENCE_API_KEY"
{"id": "batch_6f1c0a7e2b8d4e55a1c3f9d2e4b6a8c0", "object": "batch.deleted", "deleted": true}
A batch can be deleted in any status, but not while one of its requests is
running: the answer is then 409. Cancel the batch, wait for its running
requests to finish, then delete it. A Message Batch can be deleted only once it
has ended (DELETE /v1/messages/batches/{batch_id}).
FreeInference also deletes a finished batch on its own, 7 days after it
finished by default (results_expires_at says when), so collect its results
before then.
List your batches¶
GET /v1/batches lists your batches, inline and from files, newest first:
curl "https://freeinference.org/v1/batches?limit=20" \
-H "Authorization: Bearer $FREEINFERENCE_API_KEY"
limit is 1–100 (default 20). The answer has the shape of a page of results,
with batch objects in data; for the next page, pass after set to the
previous page’s last_id. Message Batches are listed by
GET /v1/messages/batches.
Batches in the console¶
The Batches page of the console, linked from your dashboard, lists your batches from all three APIs with their progress, tokens and known cost. From it you can cancel and delete batches, download results and, where webhooks are enabled, set up your completion webhook.
Completion webhooks¶
Available where batch webhooks are enabled.
Instead of polling, you can have FreeInference call an HTTPS endpoint of yours when a batch ends. Register one endpoint for all your batches on the console’s Batches page, or with the API:
curl -X PUT https://freeinference.org/v1/batch_webhook \
-H "Authorization: Bearer $FREEINFERENCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/freeinference-batches"}'
The first time you set a webhook, the answer shows its signing secret,
whsec_…, once; store it. Changing the URL later keeps the secret. Send
"rotate_secret": true with the URL to get a new secret.
GET /v1/batch_webhook shows the webhook, DELETE /v1/batch_webhook removes
it, and GET /v1/batch_webhook/events lists recent deliveries.
When a batch ends, after its results can be read, FreeInference POSTs an
event: batch.completed, batch.failed, batch.expired or batch.cancelled
for an OpenAI-shaped batch, message_batch.ended for a Message Batch. The body
names the batch only, never a prompt or a result:
{"id": "evt_…", "object": "event", "type": "batch.completed", "created_at": 1793016000, "data": {"id": "batch_6f1c…"}}
Fetch the results with your API key. When you receive a delivery:
verify its signature. Deliveries are signed following the Standard Webhooks specification (
webhook-id,webhook-timestampandwebhook-signatureheaders), so its libraries can verify them with yourwhsec_…secret;answer quickly with a
2xx. A delivery that does not get one is retried with backoff, up to eight attempts;deduplicate on
webhook-id. The same event can arrive more than once.
The URL must be HTTPS and reach a public address; private, loopback and link-local addresses are refused.
Limits¶
Limit |
Default |
|---|---|
Requests in one batch |
50,000 |
Size of the create request’s body, and of an input file |
32 MiB |
One line of an input file |
4 MiB |
One request’s stored result |
4 MiB |
Unfinished batches per account ( |
10 |
Unfinished requests across all your batches |
100,000 |
Batch requests of yours running at once, by account tier |
free 8, pro 16, internal 64 |
Completion window: how long a batch’s requests have to start |
24 hours |
How long a finished batch and its results are kept |
7 days |
How long an uploaded input file is kept |
30 days |
Uploaded files you may keep, and upload per hour |
512 MiB; 60 |
These are the defaults, and FreeInference may change them. Split a larger job
into several batches. The role-based concurrency limit is counted per gateway
process; several processes can run more requests in total, subject to the
shared per-model limits. The completion window of a batch from a file and of a
Message Batch is always 24 hours. For an inline batch it is FreeInference’s
setting, and a completion_window you send is ignored.
Errors¶
Errors from creating a batch (and uploading a file):
Status |
When |
|---|---|
|
The body is not valid JSON or not a batch; a request is invalid, uses an unknown, unavailable or auto model, or repeats a |
|
The API key is missing, invalid or expired |
|
Your account may not create batches; or Message Batches are not enabled |
|
Batch inference is unavailable at the moment |
|
The |
|
The batch this |
|
The body or file is larger than the size limit, or an upload would exceed your file storage |
|
You already have the most unfinished batches or batch requests allowed; your daily quota is used up until it renews at 00:00 UTC; or you have uploaded the most files allowed this hour |
Errors from managing batches:
Status |
When |
|---|---|
|
Reading results: |
|
The API key is missing, invalid or expired |
|
The batch or file does not exist, was deleted, or belongs to another account; or batch inference is unavailable at the moment. The message says which |
|
Deleting: one of the batch’s requests is still running. Downloading |
Checking on, reading, downloading, cancelling and deleting a batch never answer
429 for a used-up daily quota.
Coming from OpenAI’s or Anthropic’s batch APIs¶
Requests run on FreeInference, not a provider’s batch service. Each one is served like an interactive request, at the same price: there is no batch discount, and turnaround depends on how busy the models are, not on a provider’s batch queue.
The limits are FreeInference’s (see Limits), lower than the providers’ own: 50,000 requests and 32 MiB per batch by default.
The completion window stops requests from starting. Requests already running when it closes finish, so a batch can end after
expires_at.Extras of FreeInference’s own: the inline form, the paged
/v1/batches/{batch_id}/results, the JSONL export,DELETE /v1/batches/{batch_id},Idempotency-Key, and the fieldscost_usd,cost_unknown_attempts,results_expires_atanderror.OpenAI file batches run one model per file. Only
purpose=batchfiles exist, and they serve batches only.Responses requests in a batch are independent:
store: true,previous_response_id,conversation,backgroundand hosted tools are refused.Message Batches belong to your account; there are no workspaces.
anthropic-betais refused, as are server tools, Files API sources, MCP servers and containers.
See also¶
Available Models — the model IDs a batch can use
API Headers Reference — authentication headers
IDE & Coding Agent Integrations — send chat requests directly with the OpenAI SDK or curl
Quick Start — create an API key
HybridInference developer documentation: Batch Inference — the server-side reference behind this page