> ## AI Agent Context > Get the complete documentation index at: https://aistudio.google.com/llms.txt > Use this file to discover all available pages. > For the full documentation in a single file, fetch: https://aistudio.google.com/llms-full.txt # Batch API {% callout type="note" %} This feature is currently only available with the generateContent API. Please follow the content on this page for more information. {% /callout %} The Gemini Batch API is designed to process large volumes of requests asynchronously at [50% of the standard cost](/docs/pricing). The target turnaround time is 24 hours, but in majority of cases, it is much quicker. Use Batch API for large-scale, non-urgent tasks such as data pre-processing or running evaluations where an immediate response is not required. ## Creating a batch job {% id="create-batch-job" %} You have two ways to submit your requests in Batch API: * **[Inline requests](/docs/batch-api#inline-requests):** A list of [`GenerateContentRequest`](https://ai.google.dev/api/batch-mode#GenerateContentRequest) objects directly included in your batch creation request. This is suitable for smaller batches that keep the total request size under 20MB. The **output** returned from the model is a list of `inlineResponse` objects. * **[Input file](/docs/batch-api#input-file):** A [JSON Lines (JSONL)](https://jsonlines.org/) file where each line contains a complete [`GenerateContentRequest`](https://ai.google.dev/api/batch-mode#GenerateContentRequest) object. This method is recommended for larger requests. The **output** returned from the model is a JSONL file where each line is either a `GenerateContentResponse` or a status object. ### Inline requests {% id="inline-requests" %} For a small number of requests, you can directly embed the [`GenerateContentRequest`](https://ai.google.dev/api/batch-mode#GenerateContentRequest) objects within your [`BatchGenerateContentRequest`](https://ai.google.dev/api/batch-mode#request-body). The following example calls the [`BatchGenerateContent`](https://ai.google.dev/api/batch-mode#google.ai.generativelanguage.v1beta.BatchService.BatchGenerateContent) method with inline requests: {% tabs %} {% tab label="Python" %} ```python from google import genai from google.genai import types client = genai.Client() # A list of dictionaries, where each is a GenerateContentRequest inline_requests = [ { 'contents': [{ 'parts': [{'text': 'Tell me a one-sentence joke.'}], 'role': 'user' }] }, { 'contents': [{ 'parts': [{'text': 'Why is the sky blue?'}], 'role': 'user' }] } ] inline_batch_job = client.batches.create( model="gemini-3.8-flash", src=inline_requests, config={ 'display_name': "inlined-requests-job-1", }, ) print(f"Created batch job: {inline_batch_job.name}") ``` {% /tab %} {% tab label="JavaScript" %} ```javascript import {GoogleGenAI} from '@google/genai'; const ai = new GoogleGenAI({}); const inlinedRequests = [ { contents: [{ parts: [{text: 'Tell me a one-sentence joke.'}], role: 'user' }] }, { contents: [{ parts: [{'text': 'Why is the sky blue?'}], role: 'user' }] } ] const response = await ai.batches.create({ model: 'gemini-3.8-flash', src: inlinedRequests, config: { displayName: 'inlined-requests-job-1', } }); console.log(response); ``` {% /tab %} {% tab label="Java" %} ```java import java.util.Arrays; import com.google.genai.Client; import com.google.genai.types.BatchJob; import com.google.genai.types.BatchJobSource; import com.google.genai.types.Content; import com.google.genai.types.CreateBatchJobConfig; import com.google.genai.types.InlinedRequest; import com.google.genai.types.Part; import java.util.List; Client client = new Client(); // A list of InlinedRequest objects List inlineRequests = Arrays.asList( InlinedRequest.builder() .contents( Arrays.asList( Content.builder() .role("user") .parts(Arrays.asList(Part.fromText("Tell me a one-sentence joke."))) .build())) .build(), InlinedRequest.builder() .contents( Arrays.asList( Content.builder() .role("user") .parts(Arrays.asList(Part.fromText("Why is the sky blue?"))) .build())) .build()); BatchJobSource src = BatchJobSource.builder().inlinedRequests(inlineRequests).build(); CreateBatchJobConfig config = CreateBatchJobConfig.builder().displayName("inlined-requests-job-1").build(); BatchJob inlineBatchJob = client.batches.create("gemini-3.8-flash", src, config); System.out.println("Created batch job: " + inlineBatchJob.name().orElse("")); ``` {% /tab %} {% tab label="REST" %} ```bash curl https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:batchGenerateContent \ -H "x-goog-api-key: $GEMINI_API_KEY" \ -X POST \ -H "Content-Type:application/json" \ -d '{ "batch": { "display_name": "my-batch-requests", "input_config": { "requests": { "requests": [ { "request": {"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}]}, "metadata": { "key": "request-1" } }, { "request": {"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}]}, "metadata": { "key": "request-2" } } ] } } } }' ``` {% /tab %} {% /tabs %} ### Input file {% id="input-file" %} For larger sets of requests, prepare a JSON Lines (JSONL) file. Each line in this file must be a JSON object containing a user-defined key and a request object, where the request is a valid [`GenerateContentRequest`](https://ai.google.dev/api/batch-mode#GenerateContentRequest) object. The user-defined key is used in the response to indicate which output is the result of which request. For example, the request with the key defined as `request-1` will have its response annotated with the same key name. This file is uploaded using the [File API](/docs/files). The maximum allowed file size for an input file is 2GB. The following is an example of a JSONL file. You can save it in a file named `my-batch-requests.json`: ```json {"key": "request-1", "request": {"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}], "generation_config": {"temperature": 0.7}}} {"key": "request-2", "request": {"contents": [{"parts": [{"text": "What are the main ingredients in a Margherita pizza?"}]}]}} ``` Similarly to inline requests, you can specify other parameters like system instructions, tools or other configurations in each request JSON. You can upload this file using the [File API](/docs/files) as shown in the following example. If you are working with multimodal input, you can reference other uploaded files within your JSONL file. {% tabs %} {% tab label="Python" %} ```python import json from google import genai from google.genai import types client = genai.Client() # Create a sample JSONL file with open("my-batch-requests.jsonl", "w") as f: requests = [ {"key": "request-1", "request": {"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}]}}, {"key": "request-2", "request": {"contents": [{"parts": [{"text": "What are the main ingredients in a Margherita pizza?"}]}]}} ] for req in requests: f.write(json.dumps(req) + "\n") # Upload the file to the File API uploaded_file = client.files.upload( file='my-batch-requests.jsonl', config=types.UploadFileConfig(display_name='my-batch-requests', mime_type='jsonl') ) print(f"Uploaded file: {uploaded_file.name}") ``` {% /tab %} {% tab label="JavaScript" %} ```javascript import {GoogleGenAI} from '@google/genai'; import * as fs from "fs"; import * as path from "path"; const ai = new GoogleGenAI({}); const fileName = "my-batch-requests.jsonl"; // Define the requests const requests = [ { "key": "request-1", "request": { "contents": [{ "parts": [{ "text": "Describe the process of photosynthesis." }] }] } }, { "key": "request-2", "request": { "contents": [{ "parts": [{ "text": "What are the main ingredients in a Margherita pizza?" }] }] } } ]; // Construct the path to file const filePath = fileName; async function writeBatchRequestsToFile(requests = [{ key: '', request: {} }], filePath = '') { try { // Use a writable stream for efficiency, especially with larger files. const writeStream = fs.createWriteStream(filePath, { flags: 'w' }); writeStream.on('error', (err = null) => { console.error(`Error writing to file ${filePath}:`, err); }); for (const req of requests) { writeStream.write(JSON.stringify(req) + '\n'); } writeStream.end(); console.log(`Successfully wrote batch requests to ${filePath}`); } catch (error) { // This catch block is for errors that might occur before stream setup, // stream errors are handled by the 'error' event. console.error(`An unexpected error occurred:`, error); } } // Write to a file. await writeBatchRequestsToFile(requests, filePath); // Upload the file to the File API. const uploadedFile = await ai.files.upload({file: 'my-batch-requests.jsonl', config: { mimeType: 'jsonl', }}); console.log(uploadedFile.name); ``` {% /tab %} {% tab label="Java" %} ```java import java.util.Arrays; import com.google.genai.Client; import com.google.genai.types.File; import com.google.genai.types.UploadFileConfig; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; Client client = new Client(); // Create a sample JSONL file List requests = Arrays.asList( "{\"key\": \"request-1\", \"request\": {\"contents\": [{\"parts\": [{\"text\": \"Describe the process of photosynthesis.\"}]}]}}", "{\"key\": \"request-2\", \"request\": {\"contents\": [{\"parts\": [{\"text\": \"What are the main ingredients in a Margherita pizza?\"}]}]}}"); Files.write(Paths.get("my-batch-requests.jsonl"), requests); // Upload the file to the File API UploadFileConfig uploadConfig = UploadFileConfig.builder() .displayName("my-batch-requests") .mimeType("jsonl") .build(); File uploadedFile = client.files.upload("my-batch-requests.jsonl", uploadConfig); System.out.println("Uploaded file: " + uploadedFile.name().orElse("")); ``` {% /tab %} {% tab label="REST" %} ```bash tmp_batch_input_file=batch_input.tmp echo -e '{"contents": [{"parts": [{"text": "Describe the process of photosynthesis."}]}], "generationConfig": {"temperature": 0.7}}\n{"contents": [{"parts": [{"text": "What are the main ingredients in a Margherita pizza?"}]}]}' > batch_input.tmp MIME_TYPE=$(file -b --mime-type "${tmp_batch_input_file}") NUM_BYTES=$(wc -c < "${tmp_batch_input_file}") DISPLAY_NAME=BatchInput tmp_header_file=upload-header.tmp # Initial resumable request defining metadata. # The upload url is in the response headers dump them to a file. curl "https://generativelanguage.googleapis.com/upload/v1beta/files" \ -D "${tmp_header_file}" \ -H "x-goog-api-key: $GEMINI_API_KEY" \ -H "X-Goog-Upload-Protocol: resumable" \ -H "X-Goog-Upload-Command: start" \ -H "X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}" \ -H "X-Goog-Upload-Header-Content-Type: ${MIME_TYPE}" \ -H "Content-Type: application/jsonl" \ -d "{'file': {'display_name': '${DISPLAY_NAME}'}}" 2> /dev/null upload_url=$(grep -i "x-goog-upload-url: " "${tmp_header_file}" | cut -d" " -f2 | tr -d "\r") rm "${tmp_header_file}" # Upload the actual bytes. curl "${upload_url}" \ -H "Content-Length: ${NUM_BYTES}" \ -H "X-Goog-Upload-Offset: 0" \ -H "X-Goog-Upload-Command: upload, finalize" \ --data-binary "@${tmp_batch_input_file}" 2> /dev/null > file_info.json file_uri=$(jq ".file.uri" file_info.json) ``` {% /tab %} {% /tabs %} The following example calls the [`BatchGenerateContent`](https://ai.google.dev/api/batch-mode#google.ai.generativelanguage.v1beta.BatchService.BatchGenerateContent) method with the input file uploaded using File API: {% tabs %} {% tab label="Python" %} ```python from google import genai # Assumes `uploaded_file` is the file object from the previous step client = genai.Client() file_batch_job = client.batches.create( model="gemini-3.8-flash", src=uploaded_file.name, config={ 'display_name': "file-upload-job-1", }, ) print(f"Created batch job: {file_batch_job.name}") ``` {% /tab %} {% tab label="JavaScript" %} ```javascript // Assumes `uploadedFile` is the file object from the previous step const fileBatchJob = await ai.batches.create({ model: 'gemini-3.8-flash', src: uploadedFile.name, config: { displayName: 'file-upload-job-1', } }); console.log(fileBatchJob); ``` {% /tab %} {% tab label="Java" %} ```java import com.google.genai.Client; import com.google.genai.types.BatchJob; import com.google.genai.types.BatchJobSource; import com.google.genai.types.CreateBatchJobConfig; Client client = new Client(); // Assumes `uploadedFileName` is the file name from the previous step String uploadedFileName = "files/my-batch-requests-id"; BatchJobSource src = BatchJobSource.builder().fileName(uploadedFileName).build(); CreateBatchJobConfig config = CreateBatchJobConfig.builder().displayName("file-upload-job-1").build(); BatchJob fileBatchJob = client.batches.create("gemini-3.8-flash", src, config); System.out.println("Created batch job: " + fileBatchJob.name().orElse("")); ``` {% /tab %} {% tab label="REST" %} ```bash # Set the File ID taken from the upload response. BATCH_INPUT_FILE='files/123456' curl https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:batchGenerateContent \ -X POST \ -H "x-goog-api-key: $GEMINI_API_KEY" \ -H "Content-Type:application/json" \ -d "{ 'batch': { 'display_name': 'my-batch-requests', 'input_config': { 'file_name': '${BATCH_INPUT_FILE}' } } }" ``` {% /tab %} {% /tabs %} When you create a batch job, you will get a job name returned. Use this name for [monitoring](/docs/batch-api#batch-job-status) the job status as well as [retrieving the results](/docs/batch-api#retrieve-batch-results) once the job completes. The following is an example output that contains a job name: ```none Created batch job from file: batches/123456789 ``` ### Batch embedding support {% id="batch-embedding" %} You can use the Batch API to interact with the [Embeddings model](/docs/embeddings) for higher throughput. To create an embeddings batch job with either [inline requests](/docs/batch-api#inline-requests) or [input files](/docs/batch-api#input-file), use the `batches.create_embeddings` API and specify the embeddings model. {% tabs %} {% tab label="Python" %} ```python from google import genai client = genai.Client() # Creating an embeddings batch job with an input file request: file_job = client.batches.create_embeddings( model="gemini-embedding-2", src={'file_name': uploaded_batch_requests.name}, config={'display_name': "Input embeddings batch"}, ) # Creating an embeddings batch job with an inline request: batch_job = client.batches.create_embeddings( model="gemini-embedding-2", # For a predefined list of requests `inlined_requests` src={'inlined_requests': inlined_requests}, config={'display_name': "Inlined embeddings batch"}, ) ``` {% /tab %} {% tab label="JavaScript" %} ```javascript // Creating an embeddings batch job with an input file request: let fileJob; fileJob = await client.batches.createEmbeddings({ model: 'gemini-embedding-2', src: {fileName: uploadedBatchRequests.name}, config: {displayName: 'Input embeddings batch'}, }); console.log(`Created batch job: ${fileJob.name}`); // Creating an embeddings batch job with an inline request: let batchJob; batchJob = await client.batches.createEmbeddings({ model: 'gemini-embedding-2', // For a predefined a list of requests `inlinedRequests` src: {inlinedRequests: inlinedRequests}, config: {displayName: 'Inlined embeddings batch'}, }); console.log(`Created batch job: ${batchJob.name}`); ``` {% /tab %} {% tab label="Java" %} ```java import java.util.Arrays; import com.google.genai.Client; import com.google.genai.types.BatchJob; import com.google.genai.types.Content; import com.google.genai.types.CreateEmbeddingsBatchJobConfig; import com.google.genai.types.EmbedContentBatch; import com.google.genai.types.EmbeddingsBatchJobSource; import com.google.genai.types.Part; import java.util.List; Client client = new Client(); String uploadedFileName = "files/my-embedding-requests-id"; // Creating an embeddings batch job with an input file request: BatchJob fileJob = client.batches.createEmbeddings( "gemini-embedding-2", EmbeddingsBatchJobSource.builder().fileName(uploadedFileName).build(), CreateEmbeddingsBatchJobConfig.builder().displayName("Input embeddings batch").build()); System.out.println("Created batch job: " + fileJob.name().orElse("")); // Creating an embeddings batch job with an inline request: EmbedContentBatch inlinedRequests = EmbedContentBatch.builder() .contents(Arrays.asList(Content.fromParts(Part.fromText("What is the meaning of life?")))) .build(); BatchJob batchJob = client.batches.createEmbeddings( "gemini-embedding-2", EmbeddingsBatchJobSource.builder().inlinedRequests(inlinedRequests).build(), CreateEmbeddingsBatchJobConfig.builder().displayName("Inlined embeddings batch").build()); System.out.println("Created batch job: " + batchJob.name().orElse("")); ``` {% /tab %} {% /tabs %} Read the Embeddings section in the [Batch API cookbook](https://github.com/google-gemini/cookbook/blob/main/quickstarts/Batch_mode.ipynb) for more examples. ### Request configuration {% id="request-config" %} You can include any request configurations you would use in a standard non-batch request. For example, you could specify the temperature, system instructions or even pass in other modalities. The following example shows an example inline request that contains a system instruction for one of the requests: {% tabs %} {% tab label="Python" %} ```python inline_requests_list = [ {'contents': [{'parts': [{'text': 'Write a short poem about a cloud.'}]}]}, {'contents': [{ 'parts': [{ 'text': 'Write a short poem about a cat.' }] }], 'config': { 'system_instruction': {'parts': [{'text': 'You are a cat. Your name is Neko.'}]}} } ] ``` {% /tab %} {% tab label="JavaScript" %} ```javascript inlineRequestsList = [ {contents: [{parts: [{text: 'Write a short poem about a cloud.'}]}]}, {contents: [{parts: [{text: 'Write a short poem about a cat.'}]}], config: {systemInstruction: {parts: [{text: 'You are a cat. Your name is Neko.'}]}}} ] ``` {% /tab %} {% tab label="Java" %} ```java import java.util.Arrays; import com.google.genai.types.Content; import com.google.genai.types.GenerateContentConfig; import com.google.genai.types.InlinedRequest; import com.google.genai.types.Part; import java.util.List; List inlineRequestsList = Arrays.asList( InlinedRequest.builder() .contents(Arrays.asList(Content.fromParts(Part.fromText("Write a short poem about a cloud.")))) .build(), InlinedRequest.builder() .contents(Arrays.asList(Content.fromParts(Part.fromText("Write a short poem about a cat.")))) .config( GenerateContentConfig.builder() .systemInstruction( Content.fromParts(Part.fromText("You are a cat. Your name is Neko."))) .build()) .build()); ``` {% /tab %} {% /tabs %} Similarly can specify tools to use for a request. The following example shows a request that enables the [Google Search tool](/docs/google-search): {% tabs %} {% tab label="Python" %} ```python inlined_requests = [ {'contents': [{'parts': [{'text': 'Who won the euro 1998?'}]}]}, {'contents': [{'parts': [{'text': 'Who won the euro 2025?'}]}], 'config':{'tools': [{'google_search': {}}]}}] ``` {% /tab %} {% tab label="JavaScript" %} ```javascript inlineRequestsList = [ {contents: [{parts: [{text: 'Who won the euro 1998?'}]}]}, {contents: [{parts: [{text: 'Who won the euro 2025?'}]}], config: {tools: [{googleSearch: {}}]}} ] ``` {% /tab %} {% tab label="Java" %} ```java import java.util.Arrays; import com.google.genai.types.Content; import com.google.genai.types.GenerateContentConfig; import com.google.genai.types.GoogleSearch; import com.google.genai.types.InlinedRequest; import com.google.genai.types.Part; import com.google.genai.types.Tool; import java.util.List; List inlinedRequests = Arrays.asList( InlinedRequest.builder() .contents(Arrays.asList(Content.fromParts(Part.fromText("Who won the euro 1998?")))) .build(), InlinedRequest.builder() .contents(Arrays.asList(Content.fromParts(Part.fromText("Who won the euro 2025?")))) .config( GenerateContentConfig.builder() .tools(Tool.builder().googleSearch(GoogleSearch.builder().build()).build()) .build()) .build()); ``` {% /tab %} {% /tabs %} You can specify [structured output](/docs/structured-output) as well. The following example shows how to specify for your batch requests. {% tabs %} {% tab label="Python" %} ```python import time from google import genai from pydantic import BaseModel, TypeAdapter class Recipe(BaseModel): recipe_name: str ingredients: list[str] client = genai.Client() # A list of dictionaries, where each is a GenerateContentRequest inline_requests = [ { 'contents': [{ 'parts': [{'text': 'List a few popular cookie recipes, and include the amounts of ingredients.'}], 'role': 'user' }], 'config': { 'response_mime_type': 'application/json', 'response_schema': list[Recipe] } }, { 'contents': [{ 'parts': [{'text': 'List a few popular gluten free cookie recipes, and include the amounts of ingredients.'}], 'role': 'user' }], 'config': { 'response_mime_type': 'application/json', 'response_schema': list[Recipe] } } ] inline_batch_job = client.batches.create( model="gemini-3.8-flash", src=inline_requests, config={ 'display_name': "structured-output-job-1" }, ) # wait for the job to finish job_name = inline_batch_job.name print(f"Polling status for job: {job_name}") while True: batch_job_inline = client.batches.get(name=job_name) if batch_job_inline.state.name in ('JOB_STATE_SUCCEEDED', 'JOB_STATE_FAILED', 'JOB_STATE_CANCELLED', 'JOB_STATE_EXPIRED'): break print(f"Job not finished. Current state: {batch_job_inline.state.name}. Waiting 30 seconds...") time.sleep(30) print(f"Job finished with state: {batch_job_inline.state.name}") # print the response for i, inline_response in enumerate(batch_job_inline.dest.inlined_responses, start=1): print(f"\n--- Response {i} ---") # Check for a successful response if inline_response.response: # The .text property is a shortcut to the generated text. print(inline_response.response.text) ``` {% /tab %} {% tab label="JavaScript" %} ```javascript import {GoogleGenAI, Type} from '@google/genai'; const ai = new GoogleGenAI({}); const inlinedRequests = [ { contents: [{ parts: [{text: 'List a few popular cookie recipes, and include the amounts of ingredients.'}], role: 'user' }], config: { responseMimeType: 'application/json', responseSchema: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { 'recipeName': { type: Type.STRING, description: 'Name of the recipe', nullable: false, }, 'ingredients': { type: Type.ARRAY, items: { type: Type.STRING, description: 'Ingredients of the recipe', nullable: false, }, }, }, required: ['recipeName'], }, }, } }, { contents: [{ parts: [{text: 'List a few popular gluten free cookie recipes, and include the amounts of ingredients.'}], role: 'user' }], config: { responseMimeType: 'application/json', responseSchema: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { 'recipeName': { type: Type.STRING, description: 'Name of the recipe', nullable: false, }, 'ingredients': { type: Type.ARRAY, items: { type: Type.STRING, description: 'Ingredients of the recipe', nullable: false, }, }, }, required: ['recipeName'], }, }, } } ] const inlinedBatchJob = await ai.batches.create({ model: 'gemini-3.8-flash', src: inlinedRequests, config: { displayName: 'inlined-requests-job-1', } }); ``` {% /tab %} {% tab label="Java" %} ```java import java.util.HashMap; import java.util.HashSet; import java.util.Arrays; import java.util.Collections; import com.google.genai.Client; import com.google.genai.types.BatchJob; import com.google.genai.types.BatchJobSource; import com.google.genai.types.Content; import com.google.genai.types.CreateBatchJobConfig; import com.google.genai.types.GenerateContentConfig; import com.google.genai.types.InlinedRequest; import com.google.genai.types.InlinedResponse; import com.google.genai.types.JobState; import com.google.genai.types.Part; import com.google.genai.types.Schema; import com.google.genai.types.Type; import java.util.List; import java.util.Map; import java.util.Set; Client client = new Client(); Map properties = new HashMap<>(); properties.put("recipeName", Schema.builder().type(Type.Known.STRING).build()); properties.put( "ingredients", Schema.builder() .type(Type.Known.ARRAY) .items(Schema.builder().type(Type.Known.STRING).build()) .build()); Schema recipeSchema = Schema.builder() .type(Type.Known.ARRAY) .items( Schema.builder() .type(Type.Known.OBJECT) .properties( properties) .required("recipeName") .build()) .build(); GenerateContentConfig jsonConfig = GenerateContentConfig.builder() .responseMimeType("application/json") .responseSchema(recipeSchema) .build(); List inlineRequests = Arrays.asList( InlinedRequest.builder() .contents( Arrays.asList( Content.builder() .role("user") .parts( Arrays.asList( Part.fromText( "List a few popular cookie recipes, and include the amounts of ingredients."))) .build())) .config(jsonConfig) .build(), InlinedRequest.builder() .contents( Arrays.asList( Content.builder() .role("user") .parts( Arrays.asList( Part.fromText( "List a few popular gluten free cookie recipes, and include the amounts of ingredients."))) .build())) .config(jsonConfig) .build()); BatchJob inlineBatchJob = client.batches.create( "gemini-3.8-flash", BatchJobSource.builder().inlinedRequests(inlineRequests).build(), CreateBatchJobConfig.builder().displayName("structured-output-job-1").build()); // Wait for the job to finish String jobName = inlineBatchJob.name().get(); System.out.println("Polling status for job: " + jobName); Set completedStates = new HashSet<>(Arrays.asList( JobState.Known.JOB_STATE_SUCCEEDED, JobState.Known.JOB_STATE_FAILED, JobState.Known.JOB_STATE_CANCELLED, JobState.Known.JOB_STATE_EXPIRED)); BatchJob batchJobInline = client.batches.get(jobName, null); while (!completedStates.contains(batchJobInline.state().get().knownEnum())) { System.out.println( "Job not finished. Current state: " + batchJobInline.state().get() + ". Waiting 30 seconds..."); Thread.sleep(30000); batchJobInline = client.batches.get(jobName, null); } System.out.println("Job finished with state: " + batchJobInline.state().get()); // Print the response List responses = batchJobInline.dest().get().inlinedResponses().orElse(Collections.emptyList()); for (int i = 0; i < responses.size(); i++) { System.out.println("\n--- Response " + (i + 1) + " ---"); InlinedResponse inlineResponse = responses.get(i); if (inlineResponse.response().isPresent()) { System.out.println(inlineResponse.response().get().text()); } } ``` {% /tab %} {% /tabs %} The following shows an example output of this job: ```json --- Response 1 --- [ { "recipe_name": "Chocolate Chip Cookies", "ingredients": [ "1 cup (2 sticks) unsalted butter, softened", "3/4 cup granulated sugar", "3/4 cup packed light brown sugar", "1 large egg", "1 teaspoon vanilla extract", "2 1/4 cups all-purpose flour", "1 teaspoon baking soda", "1/2 teaspoon salt", "1 1/2 cups chocolate chips" ] }, { "recipe_name": "Oatmeal Raisin Cookies", "ingredients": [ "1 cup (2 sticks) unsalted butter, softened", "1 cup packed light brown sugar", "1/2 cup granulated sugar", "2 large eggs", "1 teaspoon vanilla extract", "1 1/2 cups all-purpose flour", "1 teaspoon baking soda", "1 teaspoon ground cinnamon", "1/2 teaspoon salt", "3 cups old-fashioned rolled oats", "1 cup raisins" ] }, { "recipe_name": "Sugar Cookies", "ingredients": [ "1 cup (2 sticks) unsalted butter, softened", "1 1/2 cups granulated sugar", "1 large egg", "1 teaspoon vanilla extract", "2 3/4 cups all-purpose flour", "1 teaspoon baking powder", "1/2 teaspoon salt" ] } ] --- Response 2 --- [ { "recipe_name": "Gluten-Free Chocolate Chip Cookies", "ingredients": [ "1 cup (2 sticks) unsalted butter, softened", "3/4 cup granulated sugar", "3/4 cup packed light brown sugar", "2 large eggs", "1 teaspoon vanilla extract", "2 1/4 cups gluten-free all-purpose flour blend (with xanthan gum)", "1 teaspoon baking soda", "1/2 teaspoon salt", "1 1/2 cups chocolate chips" ] }, { "recipe_name": "Gluten-Free Peanut Butter Cookies", "ingredients": [ "1 cup (250g) creamy peanut butter", "1/2 cup (100g) granulated sugar", "1/2 cup (100g) packed light brown sugar", "1 large egg", "1 teaspoon vanilla extract", "1/2 teaspoon baking soda", "1/4 teaspoon salt" ] }, { "recipe_name": "Gluten-Free Oatmeal Raisin Cookies", "ingredients": [ "1/2 cup (1 stick) unsalted butter, softened", "1/2 cup granulated sugar", "1/2 cup packed light brown sugar", "1 large egg", "1 teaspoon vanilla extract", "1 cup gluten-free all-purpose flour blend", "1/2 teaspoon baking soda", "1/2 teaspoon ground cinnamon", "1/4 teaspoon salt", "1 1/2 cups gluten-free rolled oats", "1/2 cup raisins" ] } ] ``` ## Monitoring job status {% id="batch-job-status" %} Use the operation name obtained when creating the batch job to poll its status. The state field of the batch job will indicate its current status. A batch job can be in one of the following states: * `JOB_STATE_PENDING`: The job has been created and is waiting to be processed by the service. * `JOB_STATE_RUNNING`: The job is in progress. * `JOB_STATE_SUCCEEDED`: The job completed successfully. You can now retrieve the results. * `JOB_STATE_FAILED`: The job failed. Check the error details for more information. * `JOB_STATE_CANCELLED`: The job was cancelled by the user. * `JOB_STATE_EXPIRED`: The job has expired because it was running or pending for more than 48 hours. The job will not have any results to retrieve. You can try submitting the job again or splitting up the requests into smaller batches. You can poll the job status periodically to check for completion. {% tabs %} {% tab label="Python" %} ```python import time from google import genai client = genai.Client() # Use the name of the job you want to check # e.g., inline_batch_job.name from the previous step job_name = "YOUR_BATCH_JOB_NAME" # (e.g. 'batches/your-batch-id') batch_job = client.batches.get(name=job_name) completed_states = set([ 'JOB_STATE_SUCCEEDED', 'JOB_STATE_FAILED', 'JOB_STATE_CANCELLED', 'JOB_STATE_EXPIRED', ]) print(f"Polling status for job: {job_name}") batch_job = client.batches.get(name=job_name) # Initial get while batch_job.state.name not in completed_states: print(f"Current state: {batch_job.state.name}") time.sleep(30) # Wait for 30 seconds before polling again batch_job = client.batches.get(name=job_name) print(f"Job finished with state: {batch_job.state.name}") if batch_job.state.name == 'JOB_STATE_FAILED': print(f"Error: {batch_job.error}") ``` {% /tab %} {% tab label="JavaScript" %} ```javascript // Use the name of the job you want to check // e.g., inlinedBatchJob.name from the previous step let batchJob; const completedStates = new Set([ 'JOB_STATE_SUCCEEDED', 'JOB_STATE_FAILED', 'JOB_STATE_CANCELLED', 'JOB_STATE_EXPIRED', ]); try { batchJob = await ai.batches.get({name: inlinedBatchJob.name}); while (!completedStates.has(batchJob.state || '')) { console.log(`Current state: ${batchJob.state}`); // Wait for 30 seconds before polling again await new Promise(resolve => setTimeout(resolve, 30000)); batchJob = await client.batches.get({ name: batchJob.name }); } console.log(`Job finished with state: ${batchJob.state}`); if (batchJob.state === 'JOB_STATE_FAILED') { // The exact structure of `error` might vary depending on the SDK // This assumes `error` is an object with a `message` property. console.error(`Error: ${batchJob.state}`); } } catch (error) { console.error(`An error occurred while polling job ${batchJob.name}:`, error); } ``` {% /tab %} {% tab label="Java" %} ```java import java.util.HashSet; import java.util.Arrays; import com.google.genai.Client; import com.google.genai.types.BatchJob; import com.google.genai.types.JobState; import java.util.Set; Client client = new Client(); // Use the name of the job you want to check String jobName = "batches/your-batch-id"; Set completedStates = new HashSet<>(Arrays.asList( JobState.Known.JOB_STATE_SUCCEEDED, JobState.Known.JOB_STATE_FAILED, JobState.Known.JOB_STATE_CANCELLED, JobState.Known.JOB_STATE_EXPIRED)); System.out.println("Polling status for job: " + jobName); BatchJob batchJob = client.batches.get(jobName, null); while (!completedStates.contains(batchJob.state().get().knownEnum())) { System.out.println("Current state: " + batchJob.state().get()); Thread.sleep(30000); // Wait for 30 seconds before polling again batchJob = client.batches.get(jobName, null); } System.out.println("Job finished with state: " + batchJob.state().get()); if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_FAILED) { System.out.println("Error: " + batchJob.error().orElse(null)); } ``` {% /tab %} {% /tabs %} ### Polling and webhooks {% id="webhooks" %} **Tired of polling?** Gemini now supports [Webhooks](/docs/webhooks) for processing completions asynchronously. Instead of continuously calling `GET / operations`, subscribe to `batch.succeeded` directly to allow the Gemini API to push real-time notifications to your server when asynchronous or long-running operations complete. {% tabs %} {% tab label="Python" %} ```python from google import genai client = genai.Client() webhook = client.webhooks.create( name="MyBatchWebhook", subscribed_events=["batch.succeeded", "batch.failed"], uri="https://my-api.com/gemini-callback", ) print(f"Created webhook: {webhook.name}") ``` {% /tab %} {% tab label="JavaScript" %} ```javascript import { GoogleGenAI } from "@google/genai"; const client = new GoogleGenAI(); async function createWebhook() { const webhook = await client.webhooks.create({ name: "MyBatchWebhook", subscribed_events: ["batch.succeeded", "batch.failed"], uri: "https://my-api.com/gemini-callback", }); console.log(`Created webhook: ${webhook.name}`); } await createWebhook(); ``` {% /tab %} {% tab label="Java" %} ```java import java.util.Arrays; import com.google.genai.Client; import com.google.genai.gaos.models.webhooks.Webhook; import com.google.genai.gaos.models.webhooks.WebhookInput; import com.google.genai.gaos.models.webhooks.WebhookSubscribedEvent; import java.util.List; Client client = new Client(); WebhookInput input = WebhookInput.builder() .name("MyBatchWebhook") .subscribedEvents( Arrays.asList( WebhookSubscribedEvent.BATCH_SUCCEEDED, WebhookSubscribedEvent.BATCH_FAILED)) .uri("https://my-api.com/gemini-callback") .build(); Webhook webhook = client.webhooks.create(input).webhook().get(); System.out.println("Created webhook: " + webhook.name().orElse("")); ``` {% /tab %} {% tab label="REST" %} ```shell curl -X POST \ "https://generativelanguage.googleapis.com/v1/webhooks?webhook_id=my-example-webhook-123" \ -H "Content-Type: application/json" \ -H "x-goog-api-key: $GOOGLE_API_KEY" \ -d '{ "name": "My Example Webhook", "uri": "https://my-api.com/gemini-callback", "subscribed_events": ["batch.succeeded", "batch.failed"] }' ``` {% /tab %} {% /tabs %} ## Retrieving results {% id="retrieve-batch-results" %} Once the job status indicates your batch job has succeeded, the results are available in the `response` field. {% tabs %} {% tab label="Python" %} ```python import json from google import genai client = genai.Client() # Use the name of the job you want to check # e.g., inline_batch_job.name from the previous step job_name = "YOUR_BATCH_JOB_NAME" batch_job = client.batches.get(name=job_name) if batch_job.state.name == 'JOB_STATE_SUCCEEDED': # If batch job was created with a file if batch_job.dest and batch_job.dest.file_name: # Results are in a file result_file_name = batch_job.dest.file_name print(f"Results are in file: {result_file_name}") print("Downloading result file content...") file_content = client.files.download(file=result_file_name) # Process file_content (bytes) as needed print(file_content.decode('utf-8')) # If batch job was created with inline request # (for embeddings, use batch_job.dest.inlined_embed_content_responses) elif batch_job.dest and batch_job.dest.inlined_responses: # Results are inline print("Results are inline:") for i, inline_response in enumerate(batch_job.dest.inlined_responses): print(f"Response {i+1}:") if inline_response.response: # Accessing response, structure may vary. try: print(inline_response.response.text) except AttributeError: print(inline_response.response) # Fallback elif inline_response.error: print(f"Error: {inline_response.error}") else: print("No results found (neither file nor inline).") else: print(f"Job did not succeed. Final state: {batch_job.state.name}") if batch_job.error: print(f"Error: {batch_job.error}") ``` {% /tab %} {% tab label="JavaScript" %} ```javascript // Use the name of the job you want to check // e.g., inlinedBatchJob.name from the previous step const jobName = "YOUR_BATCH_JOB_NAME"; try { const batchJob = await ai.batches.get({ name: jobName }); if (batchJob.state === 'JOB_STATE_SUCCEEDED') { console.log('Found completed batch:', batchJob.displayName); console.log(batchJob); // If batch job was created with a file destination if (batchJob.dest?.fileName) { const resultFileName = batchJob.dest.fileName; console.log(`Results are in file: ${resultFileName}`); console.log("Downloading result file content..."); const fileContentBuffer = await ai.files.download({ file: resultFileName }); // Process fileContentBuffer (Buffer) as needed console.log(fileContentBuffer.toString('utf-8')); } // If batch job was created with inline responses else if (batchJob.dest?.inlinedResponses) { console.log("Results are inline:"); for (let i = 0; i < batchJob.dest.inlinedResponses.length; i++) { const inlineResponse = batchJob.dest.inlinedResponses[i]; console.log(`Response ${i + 1}:`); if (inlineResponse.response) { // Accessing response, structure may vary. if (inlineResponse.response.text !== undefined) { console.log(inlineResponse.response.text); } else { console.log(inlineResponse.response); // Fallback } } else if (inlineResponse.error) { console.error(`Error: ${inlineResponse.error}`); } } } // If batch job was an embedding batch with inline responses else if (batchJob.dest?.inlinedEmbedContentResponses) { console.log("Embedding results found inline:"); for (let i = 0; i < batchJob.dest.inlinedEmbedContentResponses.length; i++) { const inlineResponse = batchJob.dest.inlinedEmbedContentResponses[i]; console.log(`Response ${i + 1}:`); if (inlineResponse.response) { console.log(inlineResponse.response); } else if (inlineResponse.error) { console.error(`Error: ${inlineResponse.error}`); } } } else { console.log("No results found (neither file nor inline)."); } } else { console.log(`Job did not succeed. Final state: ${batchJob.state}`); if (batchJob.error) { console.error(`Error: ${typeof batchJob.error === 'string' ? batchJob.error : batchJob.error.message || JSON.stringify(batchJob.error)}`); } } } catch (error) { console.error(`An error occurred while processing job ${jobName}:`, error); } ``` {% /tab %} {% tab label="Java" %} ```java import com.google.genai.Client; import com.google.genai.types.BatchJob; import com.google.genai.types.BatchJobDestination; import com.google.genai.types.InlinedEmbedContentResponse; import com.google.genai.types.InlinedResponse; import com.google.genai.types.JobState; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; Client client = new Client(); // Use the name of the job you want to check String jobName = "batches/your-batch-id"; BatchJob batchJob = client.batches.get(jobName, null); if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_SUCCEEDED) { BatchJobDestination dest = batchJob.dest().orElse(null); // If batch job was created with a file destination if (dest != null && dest.fileName().isPresent()) { String resultFileName = dest.fileName().get(); System.out.println("Results are in file: " + resultFileName); System.out.println("Downloading result file content..."); client.files.download(resultFileName, "batch_results.jsonl", null); String fileContent = Files.readString(Paths.get("batch_results.jsonl")); System.out.println(fileContent); } // If batch job was created with inline requests else if (dest != null && dest.inlinedResponses().isPresent()) { System.out.println("Results are inline:"); List responses = dest.inlinedResponses().get(); for (int i = 0; i < responses.size(); i++) { System.out.println("Response " + (i + 1) + ":"); InlinedResponse inlineResponse = responses.get(i); if (inlineResponse.response().isPresent()) { System.out.println(inlineResponse.response().get().text()); } else if (inlineResponse.error().isPresent()) { System.out.println("Error: " + inlineResponse.error().get()); } } } // If batch job was an embedding batch with inline responses else if (dest != null && dest.inlinedEmbedContentResponses().isPresent()) { System.out.println("Embedding results found inline:"); List responses = dest.inlinedEmbedContentResponses().get(); for (int i = 0; i < responses.size(); i++) { System.out.println("Response " + (i + 1) + ":"); InlinedEmbedContentResponse inlineResponse = responses.get(i); if (inlineResponse.response().isPresent()) { System.out.println(inlineResponse.response().get()); } else if (inlineResponse.error().isPresent()) { System.out.println("Error: " + inlineResponse.error().get()); } } } else { System.out.println("No results found (neither file nor inline)."); } } else { System.out.println("Job did not succeed. Final state: " + batchJob.state().get()); batchJob.error().ifPresent(err -> System.out.println("Error: " + err)); } ``` {% /tab %} {% tab label="REST" %} ```bash BATCH_NAME="batches/123456" # Your batch job name curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME \ -H "x-goog-api-key: $GEMINI_API_KEY" \ -H "Content-Type:application/json" 2> /dev/null > batch_status.json if jq -r '.done' batch_status.json | grep -q "false"; then echo "Batch has not finished processing" fi batch_state=$(jq -r '.metadata.state' batch_status.json) if [[ $batch_state = "JOB_STATE_SUCCEEDED" ]]; then if [[ $(jq '.response | has("inlinedResponses")' batch_status.json) = "true" ]]; then jq -r '.response.inlinedResponses' batch_status.json exit fi responses_file_name=$(jq -r '.response.responsesFile' batch_status.json) curl https://generativelanguage.googleapis.com/download/v1beta/$responses_file_name:download?alt=media \ -H "x-goog-api-key: $GEMINI_API_KEY" 2> /dev/null elif [[ $batch_state = "JOB_STATE_FAILED" ]]; then jq '.error' batch_status.json elif [[ $batch_state == "JOB_STATE_CANCELLED" ]]; then echo "Batch was cancelled by the user" elif [[ $batch_state == "JOB_STATE_EXPIRED" ]]; then echo "Batch expired after 48 hours" fi ``` {% /tab %} {% /tabs %} ## Listing batch jobs {% id="listing-batch-jobs" %} You can list your recent batch jobs. {% tabs %} {% tab label="Python" %} ```python batch_jobs = client.batches.list() # Optional query config: # batch_jobs = client.batches.list(config={'page_size': 5}) for batch_job in batch_jobs: print(batch_job) ``` {% /tab %} {% tab label="JavaScript" %} ```javascript const batchJobs = await ai.batches.list(); // Optional query config: // const batchJobs = await ai.batches.list({config: {'pageSize': 5}}); for await (const batchJob of batchJobs) { console.log(batchJob); } ``` {% /tab %} {% tab label="Java" %} ```java import com.google.genai.Client; import com.google.genai.Pager; import com.google.genai.types.BatchJob; import com.google.genai.types.ListBatchJobsConfig; Client client = new Client(); Pager batchJobs = client.batches.list(null); // Optional query config: // Pager batchJobs = client.batches.list(ListBatchJobsConfig.builder().pageSize(5).build()); for (BatchJob batchJob : batchJobs) { System.out.println(batchJob); } ``` {% /tab %} {% tab label="REST" %} ```bash curl https://generativelanguage.googleapis.com/v1beta/batches \ -H "x-goog-api-key: $GEMINI_API_KEY" ``` {% /tab %} {% /tabs %} ## Cancelling a batch job {% id="canceling-batch-job" %} You can cancel an ongoing batch job using its name. When a job is canceled, it stops processing new requests. {% tabs %} {% tab label="Python" %} ```python client.batches.cancel(name=batch_job_to_cancel.name) ``` {% /tab %} {% tab label="JavaScript" %} ```javascript await ai.batches.cancel({name: batchJobToCancel.name}); ``` {% /tab %} {% tab label="Java" %} ```java import com.google.genai.Client; Client client = new Client(); String batchJobToCancelName = "batches/your-batch-id"; client.batches.cancel(batchJobToCancelName, null); ``` {% /tab %} {% tab label="REST" %} ```bash BATCH_NAME="batches/123456" # Your batch job name # Cancel the batch curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME:cancel \ -H "x-goog-api-key: $GEMINI_API_KEY" \ # Confirm that the status of the batch after cancellation is JOB_STATE_CANCELLED curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME \ -H "x-goog-api-key: $GEMINI_API_KEY" \ -H "Content-Type:application/json" 2> /dev/null | jq -r '.metadata.state' ``` {% /tab %} {% /tabs %} ## Deleting a batch job {% id="delete-batch-job" %} You can delete an existing batch job using its name. When a job is deleted, it stops processing new requests and is removed from the list of batch jobs. {% tabs %} {% tab label="Python" %} ```python client.batches.delete(name=batch_job_to_delete.name) ``` {% /tab %} {% tab label="JavaScript" %} ```javascript await ai.batches.delete({name: batchJobToDelete.name}); ``` {% /tab %} {% tab label="Java" %} ```java import com.google.genai.Client; Client client = new Client(); String batchJobToDeleteName = "batches/your-batch-id"; client.batches.delete(batchJobToDeleteName, null); ``` {% /tab %} {% tab label="REST" %} ```bash BATCH_NAME="batches/123456" # Your batch job name # Delete the batch job curl -X DELETE "https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME" \ -H "x-goog-api-key: $GEMINI_API_KEY" ``` {% /tab %} {% /tabs %} ## Generating images in batch {% id="image-generation" %} If you're using [Gemini Nano Banana](/docs/image-generation) and need to generate a lot of images, you can use the Batch API to get higher [rate limits](/docs/rate-limits) in exchange for a turnaround of up to 24 hours. You can either use [inline requests](/docs/batch-api#inline-requests-images) for small batches of requests (under 20MB) or a [JSONL input file](/docs/batch-api#input-file-images) for large batches (recommended for image generation): ### Inline requests for images {% id="inline-requests-images" %} {% tabs %} {% tab label="Python" %} ```python import time import base64 import json from google import genai from google.genai import types from PIL import Image client = genai.Client() # 1. Create batch job with inline requests inline_requests = [ { 'contents': [{'parts': [{'text': 'A big letter A surrounded by animals starting with the A letter'}]}], 'config': {'response_modalities': ['TEXT', 'IMAGE']} }, { 'contents': [{'parts': [{'text': 'A big letter B surrounded by animals starting with the B letter'}]}], 'config': {'response_modalities': ['TEXT', 'IMAGE']} } ] inline_batch_job = client.batches.create( model="gemini-3-pro-image-preview", src=inline_requests, config={ 'display_name': "inlined-image-requests-job-1", }, ) print(f"Created batch job: {inline_batch_job.name}") # 2. Monitor job status job_name = inline_batch_job.name print(f"Polling status for job: {job_name}") completed_states = set([ 'JOB_STATE_SUCCEEDED', 'JOB_STATE_FAILED', 'JOB_STATE_CANCELLED', 'JOB_STATE_EXPIRED', ]) batch_job = client.batches.get(name=job_name) # Initial get while batch_job.state.name not in completed_states: print(f"Current state: {batch_job.state.name}") time.sleep(10) # Wait for 10 seconds before polling again batch_job = client.batches.get(name=job_name) print(f"Job finished with state: {batch_job.state.name}") # 3. Retrieve results if batch_job.state.name == 'JOB_STATE_SUCCEEDED': print("Results are inline:") for i, inline_response in enumerate(batch_job.dest.inlined_responses): print(f"Response {i+1}:") if inline_response.response: for part in inline_response.response.candidates[0].content.parts: if part.text: print(part.text) elif part.inline_data: print(f"Image mime type: {part.inline_data.mime_type}") image = part.as_image() image.save(f"image_{i+1}.png") elif inline_response.error: print(f"Error: {inline_response.error}") elif batch_job.state.name == 'JOB_STATE_FAILED': print(f"Error: {batch_job.error}") ``` {% /tab %} {% tab label="JavaScript" %} ```javascript import {GoogleGenAI} from '@google/genai'; const ai = new GoogleGenAI({}); async function run() { // 1. Create batch job with inline requests const inlinedRequests = [ { contents: [{parts: [{text: 'A big letter A surrounded by animals starting with the A letter'}]}], config: {responseModalities: ['TEXT', 'IMAGE']} }, { contents: [{parts: [{text: 'A big letter B surrounded by animals starting with the B letter'}]}], config: {responseModalities: ['TEXT', 'IMAGE']} } ] const inlineBatchJob = await ai.batches.create({ model: 'gemini-3-pro-image-preview', src: inlinedRequests, config: { displayName: 'inlined-image-requests-job-1', } }); console.log(inlineBatchJob); // 2. Monitor job status let batchJob; const completedStates = new Set([ 'JOB_STATE_SUCCEEDED', 'JOB_STATE_FAILED', 'JOB_STATE_CANCELLED', 'JOB_STATE_EXPIRED', ]); try { batchJob = await ai.batches.get({name: inlineBatchJob.name || ''}); while (!completedStates.has(batchJob.state || '')) { console.log(`Current state: ${batchJob.state}`); // Wait for 10 seconds before polling again await new Promise(resolve => setTimeout(resolve, 10000)); batchJob = await ai.batches.get({ name: batchJob.name || '' }); } console.log(`Job finished with state: ${batchJob.state}`); } catch (error) { console.error(`An error occurred while polling job ${inlineBatchJob.name}:`, error); return; } // 3. Retrieve results if (batchJob.state === 'JOB_STATE_SUCCEEDED') { if (batchJob.dest?.inlinedResponses) { console.log("Results are inline:"); for (let i = 0; i < batchJob.dest.inlinedResponses.length; i++) { const inlineResponse = batchJob.dest.inlinedResponses[i]; console.log(`Response ${i + 1}:`); if (inlineResponse.response) { for (const part of inlineResponse.response?.candidates?.[0]?.content?.parts || []) { if (part.text) { console.log(part.text); } else if (part.inlineData) { console.log(`Image mime type: ${part.inlineData.mimeType}`); } } } else if (inlineResponse.error) { console.error(`Error: ${inlineResponse.error}`); } } } else { console.log("No inline results found."); } } else if (batchJob.state === 'JOB_STATE_FAILED') { console.error(`Error: ${typeof batchJob.error === 'string' ? batchJob.error : batchJob.error?.message || JSON.stringify(batchJob.error)}`); } } await run(); ``` {% /tab %} {% tab label="Java" %} ```java import java.util.HashSet; import java.util.Arrays; import java.util.Collections; import com.google.genai.Client; import com.google.genai.types.BatchJob; import com.google.genai.types.BatchJobSource; import com.google.genai.types.Content; import com.google.genai.types.CreateBatchJobConfig; import com.google.genai.types.GenerateContentConfig; import com.google.genai.types.InlinedRequest; import com.google.genai.types.InlinedResponse; import com.google.genai.types.JobState; import com.google.genai.types.Part; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.Set; Client client = new Client(); // 1. Create batch job with inline requests GenerateContentConfig imageConfig = GenerateContentConfig.builder().responseModalities("TEXT", "IMAGE").build(); List inlineRequests = Arrays.asList( InlinedRequest.builder() .contents( Arrays.asList( Content.fromParts( Part.fromText( "A big letter A surrounded by animals starting with the A letter")))) .config(imageConfig) .build(), InlinedRequest.builder() .contents( Arrays.asList( Content.fromParts( Part.fromText( "A big letter B surrounded by animals starting with the B letter")))) .config(imageConfig) .build()); BatchJob inlineBatchJob = client.batches.create( "gemini-3-pro-image-preview", BatchJobSource.builder().inlinedRequests(inlineRequests).build(), CreateBatchJobConfig.builder().displayName("inlined-image-requests-job-1").build()); System.out.println("Created batch job: " + inlineBatchJob.name().orElse("")); // 2. Monitor job status String jobName = inlineBatchJob.name().get(); System.out.println("Polling status for job: " + jobName); Set completedStates = new HashSet<>(Arrays.asList( JobState.Known.JOB_STATE_SUCCEEDED, JobState.Known.JOB_STATE_FAILED, JobState.Known.JOB_STATE_CANCELLED, JobState.Known.JOB_STATE_EXPIRED)); BatchJob batchJob = client.batches.get(jobName, null); while (!completedStates.contains(batchJob.state().get().knownEnum())) { System.out.println("Current state: " + batchJob.state().get()); Thread.sleep(10000); // Wait for 10 seconds before polling again batchJob = client.batches.get(jobName, null); } System.out.println("Job finished with state: " + batchJob.state().get()); // 3. Retrieve results if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_SUCCEEDED) { System.out.println("Results are inline:"); List responses = batchJob.dest().get().inlinedResponses().orElse(Collections.emptyList()); for (int i = 0; i < responses.size(); i++) { System.out.println("Response " + (i + 1) + ":"); InlinedResponse inlineResponse = responses.get(i); if (inlineResponse.response().isPresent()) { for (Part part : inlineResponse.response().get().parts()) { if (part.text().isPresent()) { System.out.println(part.text().get()); } else if (part.inlineData().isPresent()) { System.out.println("Image mime type: " + part.inlineData().get().mimeType().orElse("")); Files.write( Paths.get("image_" + (i + 1) + ".png"), part.inlineData().get().data().get()); } } } else if (inlineResponse.error().isPresent()) { System.out.println("Error: " + inlineResponse.error().get()); } } } else if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_FAILED) { System.out.println("Error: " + batchJob.error().orElse(null)); } ``` {% /tab %} {% tab label="REST" %} ```bash # 1. Create batch job printf -v request_data '{ "batch": { "display_name": "my-batch-image-requests", "input_config": { "requests": { "requests": [ { "request": { "contents": [{"parts": [{"text": "A big letter A surrounded by animals starting with the A letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]} }, "metadata": { "key": "request-1" } }, { "request": { "contents": [{"parts": [{"text": "A big letter B surrounded by animals starting with the B letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]} }, "metadata": { "key": "request-2" } } ] } } } }' curl https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:batchGenerateContent \ -H "x-goog-api-key: $GEMINI_API_KEY" \ -X POST \ -H "Content-Type:application/json" \ -d "$request_data" > created_batch.json BATCH_NAME=$(jq -r '.name' created_batch.json) echo "Created batch job: $BATCH_NAME" # 2. Poll job status until completion by repeating the following command # Replace $BATCH_NAME with the name returned above. curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME \ -H "x-goog-api-key: $GEMINI_API_KEY" \ -H "Content-Type:application/json" > batch_status.json echo "Current status:" jq '.' batch_status.json # 3. If state is JOB_STATE_SUCCEEDED, retrieve results from batch_status.json batch_state=$(jq -r '.state' batch_status.json) if [[ $batch_state = "JOB_STATE_SUCCEEDED" ]]; then echo "Job succeeded. Results:" jq -r '.dest.inlinedResponses' batch_status.json fi ``` {% /tab %} {% /tabs %} ### Input file for images {% id="input-file-images" %} {% tabs %} {% tab label="Python" %} ```python import json import time import base64 from google import genai from google.genai import types from PIL import Image client = genai.Client() # 1. Create and upload file file_name = "my-batch-image-requests.jsonl" with open(file_name, "w") as f: requests = [ {"key": "request-1", "request": {"contents": [{"parts": [{"text": "A big letter A surrounded by animals starting with the A letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}}}, {"key": "request-2", "request": {"contents": [{"parts": [{"text": "A big letter B surrounded by animals starting with the B letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}}} ] for req in requests: f.write(json.dumps(req) + "\n") uploaded_file = client.files.upload( file=file_name, config=types.UploadFileConfig(display_name='my-batch-image-requests', mime_type='jsonl') ) print(f"Uploaded file: {uploaded_file.name}") # 2. Create batch job file_batch_job = client.batches.create( model="gemini-3-pro-image-preview", src=uploaded_file.name, config={ 'display_name': "file-image-upload-job-1", }, ) print(f"Created batch job: {file_batch_job.name}") # 3. Monitor job status job_name = file_batch_job.name print(f"Polling status for job: {job_name}") completed_states = set([ 'JOB_STATE_SUCCEEDED', 'JOB_STATE_FAILED', 'JOB_STATE_CANCELLED', 'JOB_STATE_EXPIRED', ]) batch_job = client.batches.get(name=job_name) # Initial get while batch_job.state.name not in completed_states: print(f"Current state: {batch_job.state.name}") time.sleep(10) # Wait for 10 seconds before polling again batch_job = client.batches.get(name=job_name) print(f"Job finished with state: {batch_job.state.name}") # 4. Retrieve results if batch_job.state.name == 'JOB_STATE_SUCCEEDED': result_file_name = batch_job.dest.file_name print(f"Results are in file: {result_file_name}") print("Downloading result file content...") file_content_bytes = client.files.download(file=result_file_name) file_content = file_content_bytes.decode('utf-8') # The result file is also a JSONL file. Parse and print each line. for line in file_content.splitlines(): if line: parsed_response = json.loads(line) if 'response' in parsed_response and parsed_response['response']: for part in parsed_response['response']['candidates'][0]['content']['parts']: if part.get('text'): print(part['text']) elif part.get('inlineData'): print(f"Image mime type: {part['inlineData']['mimeType']}") data = base64.b64decode(part['inlineData']['data']) elif 'error' in parsed_response: print(f"Error: {parsed_response['error']}") elif batch_job.state.name == 'JOB_STATE_FAILED': print(f"Error: {batch_job.error}") ``` {% /tab %} {% tab label="JavaScript" %} ```javascript import {GoogleGenAI} from '@google/genai'; import * as fs from "fs"; import * as path from "path"; const ai = new GoogleGenAI({}); async function run() { // 1. Create and upload file const fileName = "my-batch-image-requests.jsonl"; const requests = [ { "key": "request-1", "request": { "contents": [{ "parts": [{ "text": "A big letter A surrounded by animals starting with the A letter" }] }], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]} } }, { "key": "request-2", "request": { "contents": [{ "parts": [{ "text": "A big letter B surrounded by animals starting with the B letter" }] }], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]} } } ]; const filePath = fileName; try { const writeStream = fs.createWriteStream(filePath, { flags: 'w' }); for (const req of requests) { writeStream.write(JSON.stringify(req) + '\n'); } writeStream.end(); console.log(`Successfully wrote batch requests to ${filePath}`); } catch (error) { console.error(`An unexpected error occurred writing file:`, error); return; } const uploadedFile = await ai.files.upload({file: fileName, config: { mimeType: 'jsonl' }}); console.log(`Uploaded file: ${uploadedFile.name}`); // 2. Create batch job const fileBatchJob = await ai.batches.create({ model: 'gemini-3-pro-image-preview', src: uploadedFile.name || '', config: { displayName: 'file-image-upload-job-1', } }); console.log(fileBatchJob); // 3. Monitor job status let batchJob; const completedStates = new Set([ 'JOB_STATE_SUCCEEDED', 'JOB_STATE_FAILED', 'JOB_STATE_CANCELLED', 'JOB_STATE_EXPIRED', ]); try { batchJob = await ai.batches.get({name: fileBatchJob.name || ''}); while (batchJob.state && !completedStates.has(batchJob.state)) { console.log(`Current state: ${batchJob.state}`); // Wait for 10 seconds before polling again await new Promise(resolve => setTimeout(resolve, 10000)); batchJob = await ai.batches.get({ name: batchJob.name || '' }); } console.log(`Job finished with state: ${batchJob.state}`); } catch (error) { console.error(`An error occurred while polling job ${fileBatchJob.name}:`, error); return; } // 4. Retrieve results if (batchJob.state === 'JOB_STATE_SUCCEEDED') { if (batchJob.dest?.fileName) { const resultFileName = batchJob.dest.fileName; console.log(`Results are in file: ${resultFileName}`); console.log("Downloading result file content..."); await ai.files.download({ file: resultFileName, downloadPath: 'result.jsonl' }); const fileContent = fs.readFileSync('result.jsonl', 'utf-8'); for (const line of fileContent.split('\n')) { if (line) { const parsedResponse = JSON.parse(line); if (parsedResponse.response) { for (const part of parsedResponse.response.candidates[0].content.parts) { if (part.text) { console.log(part.text); } else if (part.inlineData) { console.log(`Image mime type: ${part.inlineData.mimeType}`); } } } else if (parsedResponse.error) { console.error(`Error: ${parsedResponse.error}`); } } } } else { console.log("No result file found."); } } else if (batchJob.state === 'JOB_STATE_FAILED') { console.error(`Error: ${typeof batchJob.error === 'string' ? batchJob.error : batchJob.error?.message || JSON.stringify(batchJob.error)}`); } } await run(); ``` {% /tab %} {% tab label="Java" %} ```java import java.util.HashSet; import java.util.Arrays; import com.google.genai.Client; import com.google.genai.types.BatchJob; import com.google.genai.types.BatchJobSource; import com.google.genai.types.CreateBatchJobConfig; import com.google.genai.types.File; import com.google.genai.types.JobState; import com.google.genai.types.UploadFileConfig; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.Set; Client client = new Client(); // 1. Create and upload file String fileName = "my-batch-image-requests.jsonl"; List requests = Arrays.asList( "{\"key\": \"request-1\", \"request\": {\"contents\": [{\"parts\": [{\"text\": \"A big letter A surrounded by animals starting with the A letter\"}]}], \"generation_config\": {\"responseModalities\": [\"TEXT\", \"IMAGE\"]}}}", "{\"key\": \"request-2\", \"request\": {\"contents\": [{\"parts\": [{\"text\": \"A big letter B surrounded by animals starting with the B letter\"}]}], \"generation_config\": {\"responseModalities\": [\"TEXT\", \"IMAGE\"]}}}"); Files.write(Paths.get(fileName), requests); File uploadedFile = client.files.upload( fileName, UploadFileConfig.builder() .displayName("my-batch-image-requests") .mimeType("jsonl") .build()); System.out.println("Uploaded file: " + uploadedFile.name().orElse("")); // 2. Create batch job BatchJob fileBatchJob = client.batches.create( "gemini-3-pro-image-preview", BatchJobSource.builder().fileName(uploadedFile.name().get()).build(), CreateBatchJobConfig.builder().displayName("file-image-upload-job-1").build()); System.out.println("Created batch job: " + fileBatchJob.name().orElse("")); // 3. Monitor job status String jobName = fileBatchJob.name().get(); System.out.println("Polling status for job: " + jobName); Set completedStates = new HashSet<>(Arrays.asList( JobState.Known.JOB_STATE_SUCCEEDED, JobState.Known.JOB_STATE_FAILED, JobState.Known.JOB_STATE_CANCELLED, JobState.Known.JOB_STATE_EXPIRED)); BatchJob batchJob = client.batches.get(jobName, null); while (!completedStates.contains(batchJob.state().get().knownEnum())) { System.out.println("Current state: " + batchJob.state().get()); Thread.sleep(10000); // Wait for 10 seconds before polling again batchJob = client.batches.get(jobName, null); } System.out.println("Job finished with state: " + batchJob.state().get()); // 4. Retrieve results if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_SUCCEEDED) { String resultFileName = batchJob.dest().get().fileName().get(); System.out.println("Results are in file: " + resultFileName); System.out.println("Downloading result file content..."); client.files.download(resultFileName, "batch_image_results.jsonl", null); for (String line : Files.readAllLines(Paths.get("batch_image_results.jsonl"))) { if (!line.isEmpty()) { System.out.println(line); } } } else if (batchJob.state().get().knownEnum() == JobState.Known.JOB_STATE_FAILED) { System.out.println("Error: " + batchJob.error().orElse(null)); } ``` {% /tab %} {% tab label="REST" %} ```bash # 1. Create and upload file echo '{"key": "request-1", "request": {"contents": [{"parts": [{"text": "A big letter A surrounded by animals starting with the A letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}}}' > my-batch-image-requests.jsonl echo '{"key": "request-2", "request": {"contents": [{"parts": [{"text": "A big letter B surrounded by animals starting with the B letter"}]}], "generation_config": {"responseModalities": ["TEXT", "IMAGE"]}}}' >> my-batch-image-requests.jsonl # Follow File API guide to upload: https://ai.google.dev/docs/files#upload_a_file # This example assumes you have uploaded the file and set BATCH_INPUT_FILE to its name (e.g., files/abcdef123) BATCH_INPUT_FILE="files/your-uploaded-file-name" # 2. Create batch job printf -v request_data '{ "batch": { "display_name": "my-batch-file-image-requests", "input_config": { "file_name": "%s" } } }' "$BATCH_INPUT_FILE" curl https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:batchGenerateContent \ -X POST \ -H "x-goog-api-key: $GEMINI_API_KEY" \ -H "Content-Type:application/json" \ -d "$request_data" > created_batch.json BATCH_NAME=$(jq -r '.name' created_batch.json) echo "Created batch job: $BATCH_NAME" # 3. Poll job status until completion by repeating the following command: curl https://generativelanguage.googleapis.com/v1beta/$BATCH_NAME \ -H "x-goog-api-key: $GEMINI_API_KEY" \ -H "Content-Type:application/json" > batch_status.json echo "Current status:" jq '.' batch_status.json # 4. If state is JOB_STATE_SUCCEEDED, download results file batch_state=$(jq -r '.state' batch_status.json) if [[ $batch_state = "JOB_STATE_SUCCEEDED" ]]; then responses_file_name=$(jq -r '.dest.fileName' batch_status.json) echo "Job succeeded. Downloading results from $responses_file_name..." curl https://generativelanguage.googleapis.com/download/v1beta/$responses_file_name:download?alt=media \ -H "x-goog-api-key: $GEMINI_API_KEY" > batch_results.jsonl echo "Results saved to batch_results.jsonl" fi ``` {% /tab %} {% /tabs %} ## Technical details {% id="technical-details" %} * **Supported models:** Batch API supports a range of Gemini models. Refer to the [Models page](/docs/models) for each model's support of Batch API. The supported modalities for Batch API are the same as what's supported on the interactive (or non-batch) API. * **Pricing:** Batch API usage is priced at 50% of the standard interactive API cost for the equivalent model. See the [pricing page](/docs/pricing) for details. Refer to the [rate limits page](/docs/rate-limits#batch-api) for details on rate limits for this feature. * **Service level objective (SLO):** Batch jobs are designed to complete within a 24-hour turnaround time. Many jobs may complete much faster depending on their size and current system load. * **Caching:** [Context caching](/docs/caching) is supported for batch requests. Reuse cached content by specifying the `cached_content` resource name in the configuration of individual requests within your batch. If a request in your batch results in a cache hit, you pay the [standard context caching rates](/docs/pricing). ## Best practices {% id="best-practices" %} * **Use input files for large requests:** For a large number of requests, always use the file input method for better manageability and to avoid hitting request size limits for the [`BatchGenerateContent`](https://ai.google.dev/api/batch-mode#google.ai.generativelanguage.v1beta.BatchService.BatchGenerateContent) call itself. Note that there's a 2GB file size limit per input file. * **Error handling:** Check the `batchStats` for `failedRequestCount` after a job completes. If using file output, parse each line to check if it's a `GenerateContentResponse` or a status object indicating an error for that specific request. See the [troubleshooting guide](/docs/troubleshooting#error-codes) for a complete set of error codes. * **Submit jobs once:** The creation of a batch job is not idempotent. If you send the same creation request twice, two separate batch jobs will be created. * **Break up very large batches:** While the target turnaround time is 24 hours, actual processing time can vary based on system load and job size. For large jobs, consider breaking them into smaller batches if intermediate results are needed sooner. ## What's next {% id="whats-next" %} * Check out the [Batch API notebook](https://colab.research.google.com/github/google-gemini/cookbook/blob/main/quickstarts/Batch_mode.ipynb) for more examples. * The OpenAI compatibility layer supports Batch API. Read the examples on the [OpenAI Compatibility](/docs/openai#batch) page.