Lararouter
API reference

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_KEY

Returns 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.jsonl

Result 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_KEY

Metadata 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_KEY

Removes 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.

On this page