GlexirDocs
Dashboard

API

Batches

The Batches endpoint runs many requests asynchronously. You upload a JSONL file of requests, start a batch, and collect the results within 24 hours. Batch requests cost 50% of the standard price. It is OpenAI-compatible and works with /v1/responses, /v1/chat/completions and /v1/embeddings.

Run a batch

Upload the input file

Build the JSONL with toBatchJSONL() and upload it with purpose batch.

TypeScript
import Glexir, { toBatchJSONL } from "@glexir/sdk";

const glexir = new Glexir({ apiKey: process.env.GLEXIR_API_KEY });

const input = await glexir.files.create({
  purpose: "batch",
  file: toBatchJSONL([
    { custom_id: "q1", url: "/v1/responses", body: { model: "gpt-5.4-mini", input: "Capital of France?", max_output_tokens: 200 } },
    { custom_id: "q2", url: "/v1/responses", body: { model: "gpt-5.4-mini", input: "Capital of Japan?", max_output_tokens: 200 } },
  ]),
});

Create the batch

TypeScript
const batch = await glexir.batches.create({
  input_file_id: input.id,
  endpoint: "/v1/responses",
  completion_window: "24h",
  metadata: { job: "capitals" },
});
console.log(batch.id, batch.status); // batch_... validating

Wait for it to finish

TypeScript
let done = await glexir.batches.retrieve(batch.id);
while (!["completed", "failed", "expired", "cancelled"].includes(done.status)) {
  await new Promise((resolve) => setTimeout(resolve, 60_000));
  done = await glexir.batches.retrieve(batch.id);
}
console.log(done.status, done.request_counts);

Download the results

TypeScript
if (done.output_file_id) {
  const text = await (await glexir.files.content(done.output_file_id)).text();
  for (const line of text.trim().split("\n")) {
    const result = JSON.parse(line);
    console.log(result.custom_id, result.response?.status_code);
  }
}

With curl, upload the file and create the batch like this:

Shell
curl https://api.glexir.com/v1/files \
  -H "Authorization: Bearer $GLEXIR_API_KEY" \
  -F purpose=batch \
  -F file=@requests.jsonl

curl https://api.glexir.com/v1/batches \
  -H "Authorization: Bearer $GLEXIR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input_file_id": "file-abc123", "endpoint": "/v1/responses", "completion_window": "24h"}'

JSONL format

The input file has one JSON request per line. Each line is a BatchRequestLine:

JSON
{"custom_id": "q1", "method": "POST", "url": "/v1/responses", "body": {"model": "gpt-5.4-mini", "input": "Capital of France?", "max_output_tokens": 200}}
{"custom_id": "q2", "method": "POST", "url": "/v1/responses", "body": {"model": "gpt-5.4-mini", "input": "Capital of Japan?", "max_output_tokens": 200}}

Glexir checks the file when you upload it. The rules:

  • The file name ends in .jsonl and the file is at most 20 MB. Split larger jobs across several batches.
  • At most 50,000 lines. Blank lines are ignored.
  • custom_id is a string, unique within the file.
  • method is "POST".
  • url is /v1/responses, /v1/chat/completions or /v1/embeddings, and every line uses the same one.
  • Every line uses the same body.model.
  • body is an object, without stream: true.
  • body passes the same model and tool checks as a live request. It can't use previous_response_id or conversation.

A line that breaks a rule fails the upload with 400, param file, and a message that names the line, for example Batch line 3: a batch may only use one model.

Output lines come back in any order, so match them to your requests by custom_id. The output and error files use OpenAI's batch output format, unchanged: each line has id, custom_id, response (status_code, request_id, body) and error.

toBatchJSONL()

TypeScript
// Exported from @glexir/sdk
declare function toBatchJSONL(lines: Array<{ custom_id: string; method?: "POST"; url: string; body: unknown }>): File;

Serializes request lines into a File named batch.jsonl (type application/jsonl), ready for files.create({ purpose: "batch" }). method defaults to "POST". Each line is written with JSON.stringify, and the file ends with a newline.

Pricing

Batch requests are billed at the batch rate, which is 50% of the standard rate for input, cached input and output tokens, and for embedding tokens. The long-context rates are halved the same way. You pay only for requests that complete. A failed batch, or one that expires with no completed requests, costs nothing. See Pricing for standard rates.

Batch holds

When you create a batch, Glexir reserves credit for the whole batch at once as a hold. OpenAI bills completed requests even if the batch later expires or is cancelled, so the full amount must be available up front.

The hold is sized from an estimate Glexir made when you uploaded the file, summed over every line, at batch rates:

  • Input: roughly 1 token per 3 bytes of request text, with fixed allowances for images and files.
  • Output: each line's max_output_tokens (Responses) or max_completion_tokens / max_tokens times n (Chat Completions). A line with no cap is counted as 32,000 output tokens.
  • Embeddings: input only.

Keep the hold small

Set max_output_tokens or max_completion_tokens on every line. Without a cap, each line reserves 32,000 output tokens, and a large batch can need far more credit than it will use.

If your available balance can't cover the hold, batches.create() fails with 402 and code credit_balance_exhausted (or spend_limit_exceeded if it would pass a monthly limit). Add credit or split the batch.

The hold lasts 26 hours, longer than the 24-hour window. When the batch reaches completed, failed, expired or cancelled, Glexir charges the actual usage reported on the batch and frees the rest. Glexir learns the final status from OpenAI's notifications, or when you call batches.retrieve() or batches.cancel(). While a hold is open it counts as one in-flight request toward your concurrency limit. See Credits & billing and Rate limits & spend limits.

The 24-hour window

completion_window must be "24h". OpenAI aims to finish within that window. Requests not finished in time are not run, and the batch ends as expired. Completed requests from an expired batch are still billed, and their results are in output_file_id.

Batch statuses (BatchStatus):

StatusMeaning
validatingThe input file is being checked.
failedValidation failed. See errors.
in_progressRequests are running.
finalizingResults are being written.
completedDone. Results are in output_file_id, failed requests in error_file_id.
expiredThe 24-hour window ran out.
cancellingCancel requested. Can last up to 10 minutes.
cancelledCancelled.

Cancel a batch

TypeScript
import Glexir from "@glexir/sdk";

const glexir = new Glexir();

const batch = await glexir.batches.cancel("batch_abc123");
console.log(batch.status); // cancelling

The batch stays cancelling for up to 10 minutes, then becomes cancelled. Requests that completed before that are still billed.

List batches

TypeScript
import Glexir from "@glexir/sdk";

const glexir = new Glexir();

for await (const batch of glexir.batches.list({ limit: 20 })) {
  console.log(batch.id, batch.status, batch.request_counts?.completed);
}

You only see batches created with your organization. See Pagination.

Methods

All methods take options?: RequestOptions as the last argument. See Client configuration.

TypeScript
import type { APIPromise, Batch, BatchCreateParams, BatchListParams, PagePromise, RequestOptions } from "@glexir/sdk";

// The methods on glexir.batches
interface Batches {
  create(body: BatchCreateParams, options?: RequestOptions): APIPromise<Batch>;
  retrieve(batchId: string, options?: RequestOptions): APIPromise<Batch>;
  list(query?: BatchListParams, options?: RequestOptions): PagePromise<Batch>;
  cancel(batchId: string, options?: RequestOptions): APIPromise<Batch>;
}

glexir.batches.create()

POST /batches.

BatchCreateParams
input_file_idstringrequired
A .jsonl file uploaded through Glexir with purpose batch. One model per file, at most 50,000 requests.
endpointBatchEndpointrequired
/v1/responses, /v1/chat/completions or /v1/embeddings. Must match the url in the file.
completion_window"24h"required
Always 24h.
metadataMetadata | nulloptional
Up to 16 string key/value pairs. Keys at most 64 characters, values at most 512.
output_expires_after{ anchor: "created_at"; seconds: number }optional
When the output and error files expire.

glexir.batches.retrieve()

GET /batches/{batch_id}.

glexir.batches.list()

GET /batches.

BatchListParams
afterstringoptional
Return batches after this batch ID. The SDK sets it for you when paging.
limitnumberoptionaldefault 20
Page size.

glexir.batches.cancel()

POST /batches/{batch_id}/cancel.

Batch object

Batch
idstringrequired
The batch ID.
object"batch"required
Always batch.
endpointstringrequired
The endpoint every request uses.
input_file_idstringrequired
The input file.
completion_windowstringrequired
24h.
statusBatchStatusrequired
See the status table above.
created_atnumberrequired
Unix timestamp in seconds.
output_file_idstringoptional
Results of successful requests. Download with files.content().
error_file_idstringoptional
Results of failed requests.
errors{ object?; data?: Array<{ code?; line?; message?; param? }> }optional
Validation errors when the batch failed.
request_counts{ total: number; completed: number; failed: number }optional
Progress.
usageobjectoptional
input_tokens, input_tokens_details.cached_tokens, output_tokens, output_tokens_details.reasoning_tokens, total_tokens. Glexir bills from this.
modelstringoptional
The model used.
metadataMetadata | nulloptional
What you sent on create.
in_progress_at, finalizing_at, completed_at, failed_at, expired_at, expires_at, cancelling_at, cancelled_atnumberoptional
Unix timestamps for each stage.
_request_idstring | nulloptional
SDK only. The x-request-id header.
BatchRequestLine<TBody = Record<string, unknown>>
custom_idstringrequired
Unique within the file. Use it to join output lines to requests.
method"POST"required
Always POST.
urlBatchEndpointrequired
The endpoint for this line.
bodyTBodyrequired
The request body, as you would send it to the endpoint.

Errors

StatusCodeWhen
400noneinput_file_id is missing, the file was not uploaded through Glexir with purpose batch, endpoint doesn't match the file, or completion_window is not 24h.
402credit_balance_exhausted, spend_limit_exceededThe hold for the whole batch can't be reserved.
403model_not_pricedThe file's model is not sold on Glexir.
404noneThe input file or batch doesn't exist or isn't yours.
429concurrency_limit_exceededToo many requests and batches in flight.

See HTTP error codes for the full list.