Batches
A batch is a set of up to 50,000 requests that you submit in one go and collect later. Syntic runs them when capacity is free, finishes within 24 hours (usually much sooner), and charges 50% of the normal token price. Use it for work nobody is waiting on.
When to use it
- Classifying, tagging or summarising a large backlog.
- Running evaluations over a test set.
- Generating product descriptions, translations or embeddings-style metadata overnight.
Do not use batches for anything a person is waiting to see.
How it works
Prepare a JSONL file
One request per line, each with your own custom_id.
Upload it and create the batch
Upload with the Files API (purpose: "batch"), then POST /v1/batches. Small batches can be sent inline instead.
Wait
Poll GET /v1/batches/{id} or register a webhook for batch.completed.
Download results
Fetch the results file. Lines come back in any order; match them by custom_id.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /v1/batches | Create a batch. |
GET | /v1/batches | List batches, newest first (limit, after_id, before_id). |
GET | /v1/batches/{batch_id} | Get status and counts. |
POST | /v1/batches/{batch_id}/cancel | Stop starting new requests. |
GET | /v1/files/{output_file_id}/content | Download results. |
Base URL https://app.syntic.ai/v1 (the paths above already include /v1), Authorization: Bearer sk_live_....
Input file
Each line is one JSON object:
{"custom_id": "ticket-1001", "endpoint": "/v1/chat/completions", "body": {"model": "Amara Quick", "max_tokens": 20, "messages": [{"role": "user", "content": "Label this ticket: My parcel never arrived."}]}}
{"custom_id": "ticket-1002", "endpoint": "/v1/chat/completions", "body": {"model": "Amara Quick", "max_tokens": 20, "messages": [{"role": "user", "content": "Label this ticket: How do I change my password?"}]}}| Field | Details |
|---|---|
custom_id | Your id, 1 to 64 characters, unique within the batch. |
endpoint | /v1/chat/completions or /v1/messages. |
body | A normal request body. stream must be absent or false. Every feature works except server tools that need a live connection (MCP connector). |
Create request
| Field | Type | Required | Details |
|---|---|---|---|
input_file_id | string | One of these two | A batch file. |
requests | array | One of these two | Up to 1,000 inline lines in the same shape as the file. |
completion_window | string | No | Only "24h" today. |
metadata | object | No | Up to 16 string pairs, returned unchanged. |
The batch object
{
"id": "batch_01JBXF3P8K",
"object": "batch",
"status": "in_progress",
"input_file_id": "file_01JBXF1Z",
"output_file_id": null,
"error_file_id": null,
"request_counts": { "total": 5000, "succeeded": 3120, "failed": 4, "canceled": 0 },
"created_at": 1791820800,
"started_at": 1791820860,
"ends_by": 1791907200,
"completed_at": null,
"metadata": { "job": "october-tickets" }
}status | Meaning |
|---|---|
validating | Checking the file. Bad lines fail the whole batch here, with details in errors. |
in_progress | Running. |
canceling | Cancel requested; running requests finish. |
completed | All lines done. output_file_id is set. |
expired | The 24 hours ran out; finished lines are in the output, the rest are expired. |
canceled | Cancelled; finished lines are in the output. |
failed | Validation failed. Nothing ran or was billed. |
Results file
One line per request, any order:
{"custom_id": "ticket-1001", "result": {"type": "succeeded", "status_code": 200, "body": {"id": "chatcmpl-...", "choices": [{"index": 0, "message": {"role": "assistant", "content": "delivery"}, "finish_reason": "stop"}], "usage": {"prompt_tokens": 21, "completion_tokens": 2, "total_tokens": 23}}}}
{"custom_id": "ticket-1002", "result": {"type": "errored", "status_code": 400, "error": {"type": "invalid_request", "code": "max_tokens_too_large", "message": "...", "param": "max_tokens"}}}result.type is succeeded, errored, canceled or expired. Only succeeded lines are billed. Results files expire 30 days after the batch ends.
Examples
# 1. Upload
curl https://app.syntic.ai/v1/files -H "Authorization: Bearer $SYNTIC_API_KEY" \
-F purpose=batch -F file=@tickets.jsonl
# 2. Create
curl https://app.syntic.ai/v1/batches \
-H "Authorization: Bearer $SYNTIC_API_KEY" -H "Content-Type: application/json" \
-d '{"input_file_id":"file_01JBXF1Z","completion_window":"24h","metadata":{"job":"october-tickets"}}'
# 3. Check
curl https://app.syntic.ai/v1/batches/batch_01JBXF3P8K -H "Authorization: Bearer $SYNTIC_API_KEY"
# 4. Download
curl https://app.syntic.ai/v1/files/file_01JBXG7Q/content -H "Authorization: Bearer $SYNTIC_API_KEY" -o results.jsonlErrors
Unified envelope for the batch endpoints. Per-request errors appear inside the results file.
| Status | code | Cause |
|---|---|---|
| 400 | invalid_batch_file | A line is not valid JSON or misses a field. message gives the line number. |
| 400 | duplicate_custom_id | Two lines share a custom_id. |
| 400 | streaming_not_allowed | A body has stream: true. |
| 400 | too_many_requests_in_batch | More than 50,000 lines or 500 MB. |
| 404 | batch_not_found | Unknown id or another workspace’s batch. |
| 409 | batch_not_cancelable | Already finished. |
| 429 | batch_queue_full | Too many queued requests for your tier. |
Limits and pricing
| Limit | Value |
|---|---|
| Requests per batch | 50,000 |
| Input file | 500 MB |
| Queued requests per organisation | 500,000 (higher on upper tiers) |
| Window | 24 hours |
| Price | 50% of the standard input, cached input and output rates. Server-tool fees are not discounted. |
Batch traffic does not count towards per-minute rate limits.
Build notes for the platform team
- Tables:
batches,batch_items(status, attempt, result offset). Workers pull items in off-peak capacity first. - Bill per succeeded item at the batch rate with
usage.service_tier = "batch". - Fire
batch.completedthrough the shared webhook system. - Depends on Files API and workspaces.