Skip to content

Batches (end-to-end)

This page shows a practical end-to-end flow:

  1. Create a JSONL input file
  2. Upload it with purpose: "batch"
  3. Create a Batch pointing at the uploaded file
  4. Poll Batch status
  5. Download and parse the output JSONL

See Batch JSONL input format and Batches API.

1) Create an input JSONL file

Each line is a request object with custom_id, method, url, and body.

jsonl
{"custom_id":"req-1","method":"POST","url":"/v1/responses","body":{"model":"gpt-4.1","input":"Say hi in one sentence."}}
{"custom_id":"req-2","method":"POST","url":"/v1/responses","body":{"model":"gpt-4.1","input":"What is 2+2?"}}

Save it as input.jsonl.

2) Upload the JSONL as a File (purpose=batch)

bash
curl https://api.fastapi.ai/v1/files \
  -H "Authorization: Bearer $FAST_API_KEY" \
  -F purpose="batch" \
  -F file="@input.jsonl"
javascript
import fs from "node:fs";

const form = new FormData();
form.append("purpose", "batch");
const file = new Blob([fs.readFileSync("input.jsonl")], { type: "application/jsonl" });
form.append("file", file, "input.jsonl");

const res = await fetch("https://api.fastapi.ai/v1/files", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.FAST_API_KEY}` },
  body: form,
});

console.log(await res.json());
python
import os
import requests

resp = requests.post(
  "https://api.fastapi.ai/v1/files",
  headers={"Authorization": f"Bearer {os.environ['FAST_API_KEY']}"},
  files={"file": ("input.jsonl", open("input.jsonl", "rb"), "application/jsonl")},
  data={"purpose": "batch"},
)

print(resp.json())
go
package main

import (
  "bytes"
  "fmt"
  "io"
  "mime/multipart"
  "net/http"
  "os"
)

func main() {
  var buf bytes.Buffer
  w := multipart.NewWriter(&buf)
  _ = w.WriteField("purpose", "batch")

  file, _ := os.Open("input.jsonl")
  defer file.Close()
  fw, _ := w.CreateFormFile("file", "input.jsonl")
  _, _ = io.Copy(fw, file)
  _ = w.Close()

  req, _ := http.NewRequest("POST", "https://api.fastapi.ai/v1/files", &buf)
  req.Header.Set("Authorization", "Bearer "+os.Getenv("FAST_API_KEY"))
  req.Header.Set("Content-Type", w.FormDataContentType())

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()

  body, _ := io.ReadAll(resp.Body)
  fmt.Println(string(body))
}
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;

public class Main {
  public static void main(String[] args) throws Exception {
    String boundary = "----FastAPIBoundary";
    byte[] fileBytes = Files.readAllBytes(Path.of("input.jsonl"));

    String part1 =
      "--" + boundary + "\r\n" +
      "Content-Disposition: form-data; name=\"purpose\"\r\n\r\n" +
      "batch\r\n";
    String part2Header =
      "--" + boundary + "\r\n" +
      "Content-Disposition: form-data; name=\"file\"; filename=\"input.jsonl\"\r\n" +
      "Content-Type: application/jsonl\r\n\r\n";
    String partEnd = "\r\n--" + boundary + "--\r\n";

    byte[] body = concat(part1.getBytes(), part2Header.getBytes(), fileBytes, partEnd.getBytes());

    HttpRequest req = HttpRequest.newBuilder()
      .uri(URI.create("https://api.fastapi.ai/v1/files"))
      .header("Authorization", "Bearer " + System.getenv("FAST_API_KEY"))
      .header("Content-Type", "multipart/form-data; boundary=" + boundary)
      .POST(HttpRequest.BodyPublishers.ofByteArray(body))
      .build();

    HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
    System.out.println(resp.body());
  }

  private static byte[] concat(byte[]... parts) {
    int len = 0;
    for (byte[] p : parts) len += p.length;
    byte[] out = new byte[len];
    int pos = 0;
    for (byte[] p : parts) {
      System.arraycopy(p, 0, out, pos, p.length);
      pos += p.length;
    }
    return out;
  }
}

The response includes a file_id (for example, file-abc123).

3) Create the batch

bash
curl https://api.fastapi.ai/v1/batches \
  -H "Authorization: Bearer $FAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input_file_id": "file-abc123",
    "endpoint": "/v1/responses",
    "completion_window": "24h"
  }'
javascript
const res = await fetch("https://api.fastapi.ai/v1/batches", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FAST_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    input_file_id: "file-abc123",
    endpoint: "/v1/responses",
    completion_window: "24h",
  }),
});

console.log(await res.json());
python
import os
import requests

resp = requests.post(
  "https://api.fastapi.ai/v1/batches",
  headers={
    "Authorization": f"Bearer {os.environ['FAST_API_KEY']}",
    "Content-Type": "application/json",
  },
  json={
    "input_file_id": "file-abc123",
    "endpoint": "/v1/responses",
    "completion_window": "24h",
  },
)

print(resp.json())
go
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "os"
)

func main() {
  payload := map[string]any{
    "input_file_id":    "file-abc123",
    "endpoint":         "/v1/responses",
    "completion_window": "24h",
  }
  b, _ := json.Marshal(payload)

  req, _ := http.NewRequest("POST", "https://api.fastapi.ai/v1/batches", bytes.NewReader(b))
  req.Header.Set("Authorization", "Bearer "+os.Getenv("FAST_API_KEY"))
  req.Header.Set("Content-Type", "application/json")

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    panic(err)
  }
  defer resp.Body.Close()

  body, _ := io.ReadAll(resp.Body)
  fmt.Println(string(body))
}
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
  public static void main(String[] args) throws Exception {
    String body = "{\"input_file_id\":\"file-abc123\",\"endpoint\":\"/v1/responses\",\"completion_window\":\"24h\"}";

    HttpRequest req = HttpRequest.newBuilder()
      .uri(URI.create("https://api.fastapi.ai/v1/batches"))
      .header("Authorization", "Bearer " + System.getenv("FAST_API_KEY"))
      .header("Content-Type", "application/json")
      .POST(HttpRequest.BodyPublishers.ofString(body))
      .build();

    HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
    System.out.println(resp.body());
  }
}

The response includes a batch_id (for example, batch_abc123).

4) Poll for completion

bash
curl https://api.fastapi.ai/v1/batches/batch_abc123 \
  -H "Authorization: Bearer $FAST_API_KEY"
javascript
const batchId = "batch_abc123";

while (true) {
  const res = await fetch(`https://api.fastapi.ai/v1/batches/${batchId}`, {
    headers: { Authorization: `Bearer ${process.env.FAST_API_KEY}` },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  const batch = await res.json();
  console.log("status:", batch.status);

  if (["completed", "failed", "cancelled", "expired"].includes(batch.status)) break;
  await new Promise((r) => setTimeout(r, 5000));
}
python
import os
import time
import requests

batch_id = "batch_abc123"

while True:
  resp = requests.get(
    f"https://api.fastapi.ai/v1/batches/{batch_id}",
    headers={"Authorization": f"Bearer {os.environ['FAST_API_KEY']}"},
  )
  resp.raise_for_status()
  batch = resp.json()
  print("status:", batch.get("status"))

  if batch.get("status") in ("completed", "failed", "cancelled", "expired"):
    break

  time.sleep(5)
go
package main

import (
  "encoding/json"
  "fmt"
  "io"
  "net/http"
  "os"
  "time"
)

func main() {
  batchID := "batch_abc123"

  for {
    req, _ := http.NewRequest("GET", "https://api.fastapi.ai/v1/batches/"+batchID, nil)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("FAST_API_KEY"))

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
      panic(err)
    }
    body, _ := io.ReadAll(resp.Body)
    resp.Body.Close()

    var batch map[string]any
    _ = json.Unmarshal(body, &batch)
    fmt.Println("status:", batch["status"])

    status, _ := batch["status"].(string)
    if status == "completed" || status == "failed" || status == "cancelled" || status == "expired" {
      break
    }
    time.Sleep(5 * time.Second)
  }
}
java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Main {
  public static void main(String[] args) throws Exception {
    String batchId = "batch_abc123";
    HttpClient client = HttpClient.newHttpClient();

    while (true) {
      HttpRequest req = HttpRequest.newBuilder()
        .uri(URI.create("https://api.fastapi.ai/v1/batches/" + batchId))
        .header("Authorization", "Bearer " + System.getenv("FAST_API_KEY"))
        .GET()
        .build();

      HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
      String body = resp.body();
      System.out.println("status: " + body);

      if (body.contains("\"status\":\"completed\"") ||
          body.contains("\"status\":\"failed\"") ||
          body.contains("\"status\":\"cancelled\"") ||
          body.contains("\"status\":\"expired\"")) {
        break;
      }

      Thread.sleep(5000);
    }
  }
}

When the batch is completed, the batch object typically includes output_file_id. If it failed, check error_file_id.

5) Download and parse output JSONL

See Batch outputs (download + parse JSONL) for multi-language parsing examples.

Minimal example (Python):

python
import json
import os
import requests

output_file_id = "file-OUTPUT"

resp = requests.get(
  f"https://api.fastapi.ai/v1/files/{output_file_id}/content",
  headers={"Authorization": f"Bearer {os.environ['FAST_API_KEY']}"},
)
resp.raise_for_status()

for line in resp.text.splitlines():
  row = json.loads(line)
  custom_id = row.get("custom_id")
  status = row.get("response", {}).get("status_code")
  body = row.get("response", {}).get("body")
  print(custom_id, status, body)

那年我双手插兜, 让bug稳如老狗