JSONL Format, custom_id, and File Upload
On this page
Updated November 3, 2026: With SDK
mistralai3.7.0,files.uploadexpectsfile={"file_name": …, "content": …}(passing a tuple raises aValidationErrorbefore any data is sent), and file size is available insize_bytes.
The JSONL Format: The Backbone of Batch Inference
JSONL (JSON Lines) is the standard format Mistral uses to structure batch requests. Each line in the file is a standalone JSON object representing a single request. This design is intentional: you can stream-generate a million-line file without loading it all into memory, read it back line by line, and inspect specific entries manually when a request fails. Unlike a single massive JSON array, which forces you to load the entire file just to verify one entry, JSONL lets you work at the line level.
JSONL Request Structure
Each line in your JSONL file must contain two required fields:
{
"custom_id": "req-001",
"body": {
"max_tokens": 100,
"messages": [
{"role": "user", "content": "Summarize this document in 3 points."}
]
}
}
The custom_id is a unique identifier you define for each request. It is what makes the results usable. Mistral does not guarantee processing order: the output file might start with request 8,472. Without a custom_id, you end up with a pile of responses and no way to know which document each one corresponds to. Make it a habit to derive this ID from your own reference system—doc-001, email-2024-03-15-042, product-sku-ABC123—rather than using an anonymous counter. When a response causes issues, you can trace it back to the source without running a SQL query. Two practical rules apply here: IDs must be unique within the file, or processing will return an error, and they should be reasonably short, since a 200-character identifier makes logs unreadable.
The body field requires no new learning: it contains the exact structure you would use for a synchronous API call to the target endpoint. If you are targeting /v1/chat/completions, the body is identical to what you would pass in a client.chat.complete() call. A prompt validated interactively translates directly into a batch job.
Generating a JSONL File in Python
Here is how to programmatically generate a JSONL file from a list of documents:
import json
documents = [
{"id": "doc-001", "text": "Artificial intelligence is transforming..."},
{"id": "doc-002", "text": "Deep neural networks..."},
{"id": "doc-003", "text": "Natural language processing..."},
]
with open("batch_requests.jsonl", "w", encoding="utf-8") as f:
for doc in documents:
request = {
"custom_id": doc["id"],
"body": {
"model": "mistral-small-latest",
"max_tokens": 200,
"messages": [
{
"role": "system",
"content": "You are an assistant that summarizes texts."
},
{
"role": "user",
"content": f"Summarize this text: {doc['text']}"
}
]
}
}
f.write(json.dumps(request, ensure_ascii=False) + "\n")
Note the ensure_ascii=False parameter. Without it, json.dumps escapes accented characters as \uXXXX sequences. The file remains valid, but becomes impossible for humans to read, turning any debugging on a French corpus into a decryption exercise. Make this your default from the first line of code.
Uploading the File to Mistral
In production, upload via the Python API:
from mistralai.client import Mistral
client = Mistral(api_key="your-api-key")
# Upload the JSONL file
with open("batch_requests.jsonl", "rb") as f:
batch_file = client.files.upload(
file={"file_name": "batch_requests.jsonl", "content": f},
purpose="batch"
)
print(f"Uploaded file: {batch_file.id}")
print(f"Size: {batch_file.size_bytes} bytes")
print(f"Purpose: {batch_file.purpose}")
The purpose="batch" parameter tells Mistral this file is intended for batch processing, not for other features like fine-tuning or RAG. This declaration of intent dictates what happens next: a file uploaded with the wrong purpose will be rejected by the job.
For a quick test or for non-technical teams, AI Studio offers the same service via the GUI:
- Go to
console.mistral.ai/build/files - Click “Upload”
- Select your JSONL file
- Choose the purpose “Batch Processing”
The interface returns a file_id, exactly like the API, and this is the identifier you use to create the batch job.
File Validation
A million-line batch that fails at line 300 due to a duplicate custom_id costs you a full wait cycle. A few seconds of local validation prevents this loss. The following script performs a single pass to check JSON validity, the presence of the two required fields, and ID uniqueness:
import json
errors = []
seen_ids = set()
with open("batch_requests.jsonl", "r", encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
try:
data = json.loads(line)
except json.JSONDecodeError as e:
errors.append(f"Line {line_num}: Invalid JSON - {e}")
continue
# Check required fields
if "custom_id" not in data:
errors.append(f"Line {line_num}: missing custom_id")
if "body" not in data:
errors.append(f"Line {line_num}: missing body")
# Check custom_id uniqueness
cid = data.get("custom_id")
if cid in seen_ids:
errors.append(f"Line {line_num}: duplicate custom_id '{cid}'")
seen_ids.add(cid)
if errors:
print(f"{len(errors)} error(s) found:")
for e in errors:
print(f" - {e}")
else:
print(f"File valid: {len(seen_ids)} requests ready.")
Make this a systematic step in your preparation pipeline, executed just before upload.
Limits and Constraints
File batching caps at one million lines per file, meaning you must split larger corpora into multiple submissions. Encoding must be UTF-8, and the format strictly requires one JSON object per line: a JSON object spread across multiple lines, as produced by a misplaced json.dumps(..., indent=2), breaks server-side parsing. Finally, remember the two rules already mentioned, as violating them causes the job to fail: the custom_id must be unique within the file, and the body must match the schema of the target endpoint.
Key Takeaways
- JSONL structures your batch requests with one JSON object per line
- Each request requires a unique
custom_idand abodymatching the target endpoint - Upload via the Python API (
client.files.upload) or through AI Studio - Always validate your file before submission to avoid processing errors
- Use
ensure_ascii=Falsefor French and accented characters