Quickstart
Upload a batch, collect the results, and read back what it cost.
Five steps. By the end you'll have run a real batch, attributed each line to a user, and pulled the cost back out of the billing API.
Every invocation on Lararouter runs as a batch. You upload a JSONL file, create a job against it, and collect results inside the completion window: 24 hours on Pay-as-you-go, 4 hours on Enterprise.
Check your key
GET /v1/me is the cheapest possible sanity check. One call proves your key is valid, tells you which
region answered, and confirms which plan and completion window you're on.
curl https://REGION.api.lararouter.com/v1/me \
-H "Authorization: Bearer $LARAROUTER_API_KEY"{
"object": "identity",
"key": { "id": "key_7Fq2", "name": "production", "last4": "9c41", "scopes": ["batches:write", "files:write", "usage:read"] },
"project": { "id": "prj_2mK9", "name": "Acme API", "plan": "pay_as_you_go" },
"region": "REGION",
"permissions": ["batches:write", "files:write", "usage:read"],
"retention": { "requests_days": 90, "conversations_days": 90 },
"batches": { "completion_window": "24h", "concurrent": 8, "max_lines_per_batch": 50000 }
}If this returns 401, the key is wrong or revoked. See Authentication.
Write the input file
One JSON object per line, no wrapping array. body is exactly the request an OpenAI-compatible client
would have sent, so anything you already build for chat completions drops in unchanged.
{"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=support-summary"}}
{"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=support-summary"}}custom_id is yours and is how you match a result back to the row that produced it. metadata is
what turns "we spent $4,000 on inference" into "this customer cost us $12.40 last month". See
Attribution.
Now upload it with a purpose of batch.
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",
"line_count": 2,
"status": "processed"
}Wait for status to read processed before moving on. See Files for the size
ceilings and validation rules.
Create the batch
Hand over the file ID and say which endpoint the lines target.
curl https://REGION.api.lararouter.com/v1/batches \
-H "Authorization: Bearer $LARAROUTER_API_KEY" \
-H "Idempotency-Key: summaries-2026-08-03" \
-H "Content-Type: application/json" \
-d '{
"input_file_id": "file_9Hs4TnQ2",
"endpoint": "/v1/chat/completions",
"completion_window": "24h"
}'{
"id": "batch_5Rj8Lm2Q",
"object": "batch",
"status": "validating",
"request_counts": { "total": 2, "completed": 0, "failed": 0 },
"cost_usd": 0,
"expires_at": 1754236800
}Always send an idempotency key
A retried create without one submits the file a second time and bills it twice. See Idempotency.
Collect the results
Poll GET /v1/batches/{id} on a schedule. A minute between checks is plenty for a job measured in
hours.
curl https://REGION.api.lararouter.com/v1/batches/batch_5Rj8Lm2Q \
-H "Authorization: Bearer $LARAROUTER_API_KEY"
curl https://REGION.api.lararouter.com/v1/files/file_2Wp7Kd91/content \
-H "Authorization: Bearer $LARAROUTER_API_KEY" \
-o results.jsonl{"custom_id":"ticket_4821","response":{"status_code":200,"request_id":"req_01JD8XK4M2","body":{"choices":[{"message":{"role":"assistant","content":"The customer is locked out following a password reset…"},"finish_reason":"stop"}],"usage":{"prompt_tokens":284,"completion_tokens":96,"cost_usd":0.00042}}},"error":null}Lines fail individually, so a completed batch can still have a non-zero request_counts.failed.
Read error_file_id as well as output_file_id. Batches covers every status.
Use the Billing API
Now ask what that user has spent. The same endpoint answers for one entity or for the intersection of two.
curl -G https://REGION.api.lararouter.com/v1/usage/entities \
-H "Authorization: Bearer $LARAROUTER_API_KEY" \
-d "entity=user:usr_8421" \
-d "start=2026-08-01" \
-d "group_by=model"{
"object": "usage",
"entities": ["user:usr_8421"],
"totals": { "tokens_input": 284, "tokens_output": 96, "cost_usd": 0.00042, "invocations": 1 },
"groups": [
{
"key": "llama-3.3-70b",
"tokens_input": 284,
"tokens_output": 96,
"cost_usd": 0.00042,
"invocations": 1
}
]
}Call the API from your language
Call the API with HTTP (cURL, PHP, TypeScript, or Python), or with the OpenAI SDK. The OpenAI SDK
uploads files and creates batches. The Laravel AI SDK and LangChain cannot submit batch jobs; they
can still produce the body object you write onto each JSONL line.
Put the base URL and key in configuration once:
export LARAROUTER_API_KEY=lr_sk_…
export LARAROUTER_URL=https://REGION.api.lararouter.com/v1The body on each JSONL line is an ordinary OpenAI chat, embeddings, or rerank request. Build that
object in your language, write the file, then upload and create the batch with the HTTP or OpenAI SDK
calls in the steps above.
Where to go next
Batches
Every status a job passes through, and what to do about each one.
Attribution
What an entity is, how tags differ, and how to model your own billing hierarchy.
Limits
Cap what any one user can spend before they spend it.
Chat completions
The request body that goes inside each line, including tools and structured outputs.