BatchRouter Docs

Files & large batches

Upload images, documents, and large JSONL with POST /v1/files, then reference them by file_id in items or as a batch input_file_id.

Most batches submit their work inline as an array of items. But two cases call for uploading a file first: a large JSONL request set that you'd rather not inline, and binary inputs (images, PDFs, audio) that a multimodal model needs to read. Both flow through POST /v1/files, which stores the bytes and returns a file_id you reference from your batch.

This guide covers the native file-upload endpoint, when to use a file versus inline items, and how to attach an image or document to an item.

When to use a file

Use a file instead of inline items when either is true:

  • The request set is large. Inlining thousands of items in one JSON body is awkward and memory-heavy on both ends. Upload the request set as a JSONL file and pass its id as the batch's input_file_id. See the JSONL format guide for the row shape.
  • An item needs a binary input. Images, PDFs, Office documents, audio, or video can't live inline in JSON. Upload each one, then reference its file_id inside an item's content blocks.

A batch takes its requests from either inline items or an input_file_id — not both. Binary attachments are different: you reference their file_id from inside an item (inline or in a JSONL row), so they compose with either submission style.

Upload a file

POST /v1/files takes a raw binary body — not multipart form data. You describe the file through headers:

HeaderRequiredPurpose
Content-TypeYesThe actual MIME type of the body (e.g. image/png, application/pdf, application/json).
Content-LengthYesExact size in bytes, so the upload limit is enforced before the body is stored.
X-BatchRouter-FilenameNoOriginal filename. URL-encode it if it contains spaces or non-ASCII characters.
X-BatchRouter-PurposeNoUpload purpose. Defaults to model_input.
curl -X POST https://api.batchrouter.com/v1/files \
  -H "Authorization: Bearer $BATCHROUTER_API_KEY" \
  -H "Content-Type: image/png" \
  -H "Content-Length: $(wc -c < ./chart.png)" \
  -H "X-BatchRouter-Filename: chart.png" \
  -H "X-BatchRouter-Purpose: model_input" \
  --data-binary @./chart.png

A successful upload returns 201 with the file_id and file metadata:

{
  "file_id": "file_9f3c2a...",
  "file": {
    "id": "file_9f3c2a...",
    "kind": "input_attachment",
    "content_type": "image/png",
    "size_bytes": 48210,
    "filename": "chart.png",
    "media_kind": "image",
    "created_at": "2026-06-18T10:21:00Z"
  },
  "usage": {
    "image_block": { "type": "input_image", "file_id": "file_9f3c2a..." }
  }
}

The usage block is a ready-to-paste content block for the file's media kind — image_block for images, file_block for documents — so you can drop it straight into an item.

Accepting an upload doesn't mean every model can read it. Before you enable an upload path, check the model in GET /v1/catalog/models for supports_file_uploads, input_modalities, accepted_file_types, and max_file_size. See the API reference for the full catalog fields.

Supported categories include image/*, audio/*, video/*, text/*, PDF, Word, PowerPoint, Excel, RTF, JSON, and XML. The endpoint returns 413 if the body exceeds the upload limit, 415 for an unsupported content type, and 411 if Content-Length is missing.

Reference an image or binary input in an item

For multimodal inputs, reference the file_id inside an item's message content as an input_image (for images) or input_file (for documents and other binaries) block, alongside any text:

{
  "customer_item_id": "item-1",
  "operation": "responses",
  "model": "gpt-5.4-mini",
  "input": {
    "messages": [
      {
        "role": "user",
        "content": [
          { "type": "input_text", "text": "Describe the trend in this chart." },
          { "type": "input_image", "file_id": "file_9f3c2a..." }
        ]
      }
    ]
  }
}

For a PDF or document, use an input_file block (which may carry an optional filename):

{ "type": "input_file", "file_id": "file_9f3c2a...", "filename": "report.pdf" }

These content blocks work in both inline items and JSONL rows uploaded as an input_file_id.

Submit a large request set as a file

When the requests themselves are numerous, upload them as a JSONL file (one item per line) and pass the returned id as the batch's input_file_id instead of inline items.

  1. Build the JSONL. One batch item per line, in the JSONL format. Mixed-model JSONL is supported — BatchRouter keeps one customer batch and splits execution into model-specific lanes.

  2. Upload it to POST /v1/files with Content-Type: application/json (the same upload call shown above), and keep the returned file_id.

  3. Create the batch with input_file_id set to that id — no items field:

    curl -X POST https://api.batchrouter.com/v1/batches \
      -H "Authorization: Bearer $BATCHROUTER_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{
        "input_file_id": "file_9f3c2a...",
        "sla_tier": "standard",
        "routing_mode": "cheapest"
      }'

From here the flow is identical to inline submission: the batch returns 202 with a batch_... id, and you poll and fetch results as usual. See Submit a batch for the full lifecycle.

Migrating from OpenAI Batch? The drop-in compatibility surface has its own file endpoint — POST /v1/openai/v1/files takes a multipart file + purpose: "batch" and is consumed by POST /v1/openai/v1/batches. That's separate from the native /v1/files endpoint above; pick one surface and stay on it. See the API reference for the OpenAI-compatible endpoints.

Next steps

On this page