Files
Upload the JSONL a batch reads from, and download the JSONL it writes back.
A batch never carries its requests inline. You upload a JSONL file, hand the resulting file_ ID to
POST /v1/batches, and download the results the same way when the job finishes.
Files live in the region that received them, encrypted with SSE-KMS and Bucket Keys, and count against the same retention window as everything else on your plan.
Upload
POST /v1/files HTTP/1.1
Host: REGION.api.lararouter.com
Authorization: Bearer $LARAROUTER_API_KEY
Content-Type: multipart/form-data; boundary=----LararouterBatch
------LararouterBatch
Content-Disposition: form-data; name="purpose"
batch
------LararouterBatch
Content-Disposition: form-data; name="file"; filename="requests.jsonl"
Content-Type: application/octet-stream
{"custom_id":"ticket_4821","method":"POST","url":"/v1/chat/completions","body":{"model":"llama-3.3-70b","messages":[{"role":"user","content":"Summarize this ticket."}]}}
------LararouterBatch--multipart/form-data with two fields: the file itself and a purpose of batch. The body must be
newline-delimited JSON: one request per line, no trailing commas, no wrapping array.
Requires the files:write scope.
curl https://REGION.api.lararouter.com/v1/files \
-H "Authorization: Bearer $LARAROUTER_API_KEY" \
-F purpose=batch \
-F file=@requests.jsonl{
"id": "file_9Hs4TnQ2",
"object": "file",
"purpose": "batch",
"filename": "requests.jsonl",
"bytes": 1048221,
"line_count": 4200,
"status": "processed",
"region": "REGION",
"created": 1754150400,
"expires_at": 1761926400
}Prop
Type
status is pending while the upload is being validated, then processed or error. A file that
fails validation carries a status_details string naming the first bad line. Create the batch only
once the file reads processed.
Input format
Each line is a self-contained request. custom_id is yours and is how you match a result back to
whatever row in your database produced it.
{"custom_id":"ticket_4821","method":"POST","url":"/v1/chat/completions","body":{"model":"llama-3.3-70b","messages":[{"role":"user","content":"Summarize this ticket."}]},"metadata":{"entity":"user:usr_8421","tags":"feature=summarize"}}
{"custom_id":"ticket_4822","method":"POST","url":"/v1/chat/completions","body":{"model":"llama-3.3-70b","messages":[{"role":"user","content":"Summarize this ticket."}]},"metadata":{"entity":"user:usr_9013","tags":"feature=summarize"}}Prop
Type
Duplicate custom_id fails the whole file
Validation rejects the upload rather than silently dropping a line, because a missing result is much
harder to notice than a failed upload. Derive custom_id from a primary key and this never comes up.
Writing the file is usually a map over a query. Stream it rather than building the whole string in
memory. 50,000 lines of prompts adds up.
$handle = fopen(Storage::path('batches/requests.jsonl'), 'w');
Ticket::whereNull('summary')->lazyById()->each(function (Ticket $ticket) use ($handle) {
fwrite($handle, json_encode([
'custom_id' => "ticket_{$ticket->id}",
'method' => 'POST',
'url' => '/v1/chat/completions',
'body' => [
'model' => 'llama-3.3-70b',
'messages' => [['role' => 'user', 'content' => $ticket->body]],
],
'metadata' => ['entity' => "user:{$ticket->user_id}"],
]).PHP_EOL);
});
fclose($handle);Download
GET /v1/files/file_2Wp7Kd91/content HTTP/1.1
Host: REGION.api.lararouter.com
Authorization: Bearer $LARAROUTER_API_KEYReturns the raw JSONL rather than a JSON envelope. This is how you read a batch's output_file_id
and error_file_id.
Requires the files:read scope.
curl https://REGION.api.lararouter.com/v1/files/file_2Wp7Kd91/content \
-H "Authorization: Bearer $LARAROUTER_API_KEY" \
-o results.jsonlResult files are JSONL in the same order you uploaded, but don't rely on ordering. Match on
custom_id. See Batches for the per-line shape.
Retrieve and list
GET /v1/files/file_9Hs4TnQ2 HTTP/1.1
Host: REGION.api.lararouter.com
Authorization: Bearer $LARAROUTER_API_KEY
GET /v1/files?purpose=batch_output&limit=20 HTTP/1.1
Host: REGION.api.lararouter.com
Authorization: Bearer $LARAROUTER_API_KEYMetadata only, in the same envelope as the upload response. The list endpoint uses
cursor pagination and accepts a purpose filter.
Requires the files:read scope.
curl -G https://REGION.api.lararouter.com/v1/files \
-H "Authorization: Bearer $LARAROUTER_API_KEY" \
-d "purpose=batch_output" \
-d "limit=20"Delete
DELETE /v1/files/file_9Hs4TnQ2 HTTP/1.1
Host: REGION.api.lararouter.com
Authorization: Bearer $LARAROUTER_API_KEYRemoves the file and its content immediately. The batch that used it keeps its own record (status, counts, and cost stay readable), but the payload is gone, so download results before you delete.
Requires the files:delete or files:write scope.
curl -X DELETE https://REGION.api.lararouter.com/v1/files/file_9Hs4TnQ2 \
-H "Authorization: Bearer $LARAROUTER_API_KEY"{
"id": "file_9Hs4TnQ2",
"object": "file",
"deleted": true
}Files you don't delete are removed automatically at the end of your retention window (90 days on
Pay-as-you-go, 395 on Enterprise), which is what expires_at reports.