The Kimi Batch API accepts multiple Chat Completions requests in a JSONL file, processes them asynchronously and exposes output and error files when the job reaches a terminal state. It is useful for offline workloads that do not need an immediate response, but it adds responsibilities that a normal API call does not: every line must be independently valid, request IDs must remain unique, status polling must be bounded, and uploaded files must be cleaned up only after processing and evidence retrieval are complete.
This guide includes a private browser validator and a documented six-request kimi-k2.6 test. The one submitted Batch reached provider status completed; all six expected records and complete response bodies were present, and all six answer strings matched their frozen oracles. A separate status check did not pass: every output row carried a nested response.status_code value of 0, while the checked official example shows 200. We therefore report answer accuracy as 6/6, strict status-field conformance as 0/6, and the runner’s transport-gated composite as 0/6. These are different measurements.
Cleanup is also not complete under our protocol. Both owned files returned successful DELETE responses, but the single allowed verification GET for each returned HTTP 500 rather than the required 404. We recorded cleanup_pending and did not retry.
Kimi AI Guide is independent and is not affiliated with Moonshot AI or Kimi.
Validate a Kimi Batch JSONL file
Validation only
Check a Kimi Batch JSONL file
Validate every non-empty line before a separate reviewed upload. This tool never submits, uploads, saves or logs a batch.
Validation result
- Non-empty lines
- Parsed objects
- Locally valid rows
- Issues
Readiness:
Models:
Endpoints:
Line-by-line issues
The validator should run locally in the visitor’s browser. It should parse one JSON object per non-empty line and report structural errors without sending the file, prompts or API credentials to this website or Kimi.
For each line, check at least:
custom_idexists and is unique within the file;methodisPOST;urlis/v1/chat/completions;body.modelis one supported Batch model and is consistent across the file;body.messagesis a non-empty array;- the completion limit is a positive integer;
- unsupported or conflicting model parameters are absent; and
- the whole upload is non-empty and no larger than the documented 100 MB file limit.
Local validation reduces avoidable failures; it cannot prove that the live API will accept a file, that a model is still available to a particular account or that each model answer will be correct.
Current Batch requirements
Kimi’s Batch API guide and Batch creation reference were checked on August 7, 2026.
| Requirement | Current implementation in our test |
|---|---|
| Input format | UTF-8 JSONL, one request object per line |
| Request endpoint inside each line | /v1/chat/completions |
| HTTP method inside each line | POST |
| Model | kimi-k2.6 only |
| IDs | Six unique custom_id values |
| Upload purpose | batch |
| Maximum input file | 100 MB in the checked documentation |
| Completion window | 12h |
| Maximum output | 64 tokens per request |
| K2.6 thinking | Disabled for every frozen case |
| Automatic retries | 0 |
| Replacement job after slow polling | Forbidden |
The Batch-specific guide and pricing pages checked for this test listed K2.6 and K2.5. Broader Kimi model pages or live Chat/API inventory may list K2.7, but that does not by itself establish Batch eligibility. We tested only K2.6, which the Batch service accepted. Treat K2.7 as a source-specific documentation discrepancy and check the current Batch documentation and your live account before submitting it.
JSONL request format
This is the structure of one synthetic line in our valid fixture:
{"custom_id":"batch8-valid-001-arithmetic","method":"POST","url":"/v1/chat/completions","body":{"model":"kimi-k2.6","messages":[{"role":"system","content":"Return only the requested answer token. Do not explain."},{"role":"user","content":"Add 19 and 23. Return digits only."}],"thinking":{"type":"disabled"},"max_completion_tokens":64}}
JSONL is not a JSON array:
- do not wrap the file in
[and]; - do not add commas between lines;
- keep each request as one complete JSON object on one physical line; and
- keep
custom_idstable because it is the safest way to join an output record back to its source task.
The response order may not be the same as the input order. Score and reconcile by custom_id, not line position.
The safe Batch lifecycle
| Stage | Completion gate |
|---|---|
| 1. Validate | JSONL parses locally and every ID, endpoint, method and model rule passes |
| 2. Upload | One input file ID is returned and recorded in the ownership ledger |
| 3. Create | One Batch ID is returned and saved durably |
| 4. Poll | A terminal state is observed for that same Batch ID |
| 5. Retrieve | Every referenced output/error file is retained |
| 6. Score | Records are joined by custom_id; accuracy and usage cost are calculated separately |
| 7. Clean | Only owned file IDs are deleted, then each GET verification returns 404 |
The important rule is that a polling timeout does not authorize resubmission. If the create request returned a Batch ID, save it durably and resume that exact ID. A replacement can duplicate spend and produce two result sets for the same work.
Upload and create the job
With the API key stored server-side in MOONSHOT_API_KEY, the lifecycle begins with a file upload:
upload_file="requests.jsonl"
curl https://api.moonshot.ai/v1/files \
-H "Authorization: Bearer $MOONSHOT_API_KEY" \
-F "purpose=batch" \
-F "file=@${upload_file}"
Save the returned file ID privately, then create one Batch:
curl https://api.moonshot.ai/v1/batches \
-H "Authorization: Bearer $MOONSHOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input_file_id": "FILE_ID_FROM_UPLOAD",
"endpoint": "/v1/chat/completions",
"completion_window": "12h"
}'
Never put a real key, file ID, Batch ID or private prompt in browser JavaScript, a public screenshot or a downloadable evidence file.
Poll without creating a duplicate
Retrieve the saved job with GET /v1/batches/{batch_id}. A safe monitor should:
- cap the number of polls in one invocation;
- use a bounded interval rather than a tight loop;
- save every observed status and request-count object;
- stop cleanly when the local window ends;
- resume the same Batch ID later; and
- create no replacement merely because processing is slow.
Kimi’s guide says Batch processing can extend well beyond an interactive request. Application timeouts should therefore be shorter than the provider completion window while the durable run state remains resumable.
Retrieve output and error files
After a terminal status, inspect the returned output_file_id and error_file_id. Retrieve every referenced file before deleting anything:
GET /v1/files/{output_file_id}/content
GET /v1/files/{error_file_id}/content
An output record should be joined to the input by custom_id. Keep three result layers separate:
- Transport result: Was an output or error record produced?
- API result: What HTTP status and response body did that record contain?
- Answer score: Did the model content match the frozen oracle?
A completed Batch can contain failed individual requests. Likewise, six HTTP 200 records do not prove six correct answers.
Clean up only this run’s files
Our runner records an ownership ledger for file IDs returned during this run. Cleanup is deliberately blocked until the Batch is terminal and all referenced results have been retrieved.
For each owned input, output or error file:
- issue one
DELETE /v1/files/{file_id}; - issue one later
GET /v1/files/{file_id}; - treat HTTP 404 as verified absence; and
- record any other result as
cleanup_pendingwithout deleting an unrelated ID or retrying blindly.
The input file must not be deleted while its Batch is non-terminal. If a deliberately invalid fixture uploads successfully but Batch creation returns a known validation rejection, only that uploaded input becomes eligible for cleanup.
Frozen live test design
The valid test contains six synthetic, deterministic prompts with precomputed answers:
custom_id suffix | Task | Frozen oracle |
|---|---|---|
001-arithmetic | Add 19 and 23 | 42 |
002-category | Classify a compiler sentence | TECHNOLOGY |
003-extraction | Extract a synthetic invoice ID | INV-2048 |
004-sum | Sum 4, 6, 8 and 17 | 35 |
005-language | Identify a Spanish sentence | SPANISH |
006-boolean | Decide whether four integers are even | TRUE |
Every case uses kimi-k2.6, disabled thinking, a 64-token maximum and an exact-output oracle. The prompts contain no personal, proprietary or live customer data.
We also ran a separate deliberately invalid fixture once. It contained a wrong method, a wrong request URL and unsupported/mixed model selection. The local validator reported METHOD_NOT_POST, URL_NOT_CHAT_COMPLETIONS, UNSUPPORTED_BATCH_MODEL and MIXED_MODELS. The live upload returned HTTP 400 before an input file or Batch was created, so no model request ran and there was nothing to delete. We did not correct and resubmit it.
Live result: completed Batch, exact answers and a status-field anomaly
Run batch-api-valid-20260807173655 was submitted once. No replacement Batch or corrective request rerun was created.
| Layer | Observed result |
|---|---|
| Provider terminal status | completed |
| Submitted | August 7, 2026 at 17:36:56 UTC |
| Terminal first observed | August 8, 2026 at 10:30:30 UTC, about 16 hours 53 minutes later |
| Bounded poll count | 204 across resumable polling windows |
| Provider request counts | 6 completed, 0 failed, 6 total |
| Output/error retrieval | Output retrieved; no error file was referenced |
| Identifier reconciliation | 6 expected records; 6 unique IDs; 0 duplicate, 0 missing and 0 unexpected IDs |
| Record/body presence | 6/6 complete records with error: null and a Chat Completion body |
| Exact-answer accuracy | 6/6 frozen oracles matched |
| Nested status field | response.status_code=0 in 6/6 rows; strict 200 conformance 0/6 |
| Runner composite | 0/6 because it required both status code 200 and an exact answer |
| Usage | 258 prompt, 31 completion, 289 total and 0 cached tokens |
| Usage-derived Batch cost | $0.00022146 using the dated K2.6 Batch formula below |
| Owned-file cleanup | 2 DELETE responses at HTTP 200; the two one-time verification GETs returned HTTP 500, not 404; cleanup_pending, 0 retries |
The provider’s completed status and complete response bodies establish that records were produced. The six exact answer matches establish accuracy only for these six synthetic cases. They do not turn the nested status marker into HTTP 200: the retained rows literally contain 0, which differs from the official Batch output example. The public evidence therefore preserves three separate layers—record presence, answer accuracy and strict status-field conformance—instead of collapsing them into one pass rate.
The 16-hour-53-minute figure is the interval from submission to our first terminal observation, not a measured provider processing duration. There was no continuous poll between the last non-terminal observation and the final resume, so the exact completion time is unknown.
Download the sanitized evidence
Download the Kimi Batch API evidence ZIP (4,738 bytes; SHA-256 5ad6ac3907d3db79898a48f2204d7196f338ac07530dba86608b3d43c4bed0c3).
The package contains only a structurally whitelisted six-row result file, a machine-readable evaluation, abstract run and cleanup outcomes, a manifest and a scan report. It excludes raw output, raw errors, headers, reasoning fields, credentials, provider file/Batch IDs, account data and private runner state. A fresh download from the Media Library URL above matched the listed byte count and SHA-256 exactly.
Cost: conservative estimate and observed usage
The dated Batch pricing used by our guard applies a 0.6 multiplier to K2.6 token charges. With the August 7, 2026 K2.6 prices frozen for this run, the calculation is:
estimated Batch cost = 0.6 x (
uncached_input_tokens x $0.95 / 1,000,000
+ cached_input_tokens x $0.16 / 1,000,000
+ output_tokens x $4.00 / 1,000,000
)
Our conservative local estimator reserved $0.00184158 for the six valid cases. That figure assumed every case would reach its 64-token output ceiling and was only a guard estimate. The retrieved rows reported 258 prompt tokens, 31 completion tokens and no cached tokens; applying the same dated formula gives an observed usage-derived cost of $0.00022146. This is token arithmetic from retained usage fields, not a statement about the account’s final invoice or balance.
Prices and eligible models can change. Check the current Kimi Batch pricing and your console before submission. For general model-rate arithmetic, use the Kimi API pricing guide.
Common Batch failures
| Symptom | Likely boundary | Safe response |
|---|---|---|
| Upload rejected | File format, size or upload purpose | Fix the local fixture; do not claim a submitted job |
| Batch create rejected | Input file, endpoint, model or completion window | Preserve the error and clean only the uploaded owned file |
validating or in_progress for longer than expected | Asynchronous provider processing | End the local polling window and resume the saved Batch ID |
| Some requests failed | Per-line request or service error | Retrieve both output and error files; reconcile by custom_id |
| Output order differs | Asynchronous completion | Join by custom_id, never array position |
| Cost cannot be calculated | Missing or malformed usage | Report cost as unknown; do not substitute the preflight estimate |
| Cleanup GET is not 404 | File still exists or verification is uncertain | Mark cleanup pending; do not delete IDs outside the ownership ledger |
Use the Kimi API Error Decoder for documented API error types and bounded retry decisions.
Limits of this test
- Six simple synthetic prompts cannot measure production reliability, throughput or model quality broadly.
- A single Batch cannot establish an average completion time or service-level guarantee.
- The retained evidence does not explain why all six nested status markers were
0. It could reflect undocumented response semantics, an implementation issue or another layer not exposed by this trace; we report the observation without assigning a cause. - The runner’s 0/6 transport-gated composite is not an answer-accuracy score. The exact answers matched 6/6.
- The successful DELETE responses did not pass our 404 verification gate, so cleanup remains pending and protocol completion is false.
- The invalid fixture was rejected during upload. It did not test a later Batch-creation rejection or a failed per-line model request.
- The valid job does not test oversized files, cancellation, concurrent Batches or K2.7 Batch eligibility.
- The calculated $0.00022146 is usage-derived token arithmetic, not an audited invoice charge.
Pre-submission checklist
- [ ] One complete JSON object per line
- [ ] Unique, stable
custom_idvalues - [ ] One supported model and one endpoint throughout the file
- [ ] Prompt data synthetic or approved for vendor processing
- [ ] Output-token ceiling and worst-case spend calculated
- [ ] API key stored server-side
- [ ] Upload response and Batch ID saved before the next mutation
- [ ] Poll count, interval and elapsed time bounded
- [ ] No automatic replacement or corrective rerun
- [ ] Output and error files retained before cleanup
- [ ] Cleanup restricted to the run’s ownership ledger and verified with 404
Start with the Kimi API setup guide if you do not yet have a working server-side key. Use Kimi API Streaming for interactive responses rather than asynchronous offline jobs.
Official sources
Vendor documentation supports the lifecycle and dated availability statements. The run status, fixture, score, usage, cost and cleanup claims on this page come from the structurally whitelisted artifacts for the single valid run and the separate one-time invalid upload test.
