Kimi Batch API: JSONL, Status & Cost Test

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.

Frozen protocol: POST /v1/chat/completions, model kimi-k2.6, unique custom_id values, non-empty messages and an explicit output-token bound no higher than 32,768.

Use one complete request object on each non-empty line. Never include an API key or Authorization header.

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_id exists and is unique within the file;
  • method is POST;
  • url is /v1/chat/completions;
  • body.model is one supported Batch model and is consistent across the file;
  • body.messages is 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.

RequirementCurrent implementation in our test
Input formatUTF-8 JSONL, one request object per line
Request endpoint inside each line/v1/chat/completions
HTTP method inside each linePOST
Modelkimi-k2.6 only
IDsSix unique custom_id values
Upload purposebatch
Maximum input file100 MB in the checked documentation
Completion window12h
Maximum output64 tokens per request
K2.6 thinkingDisabled for every frozen case
Automatic retries0
Replacement job after slow pollingForbidden

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_id stable 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

StageCompletion gate
1. ValidateJSONL parses locally and every ID, endpoint, method and model rule passes
2. UploadOne input file ID is returned and recorded in the ownership ledger
3. CreateOne Batch ID is returned and saved durably
4. PollA terminal state is observed for that same Batch ID
5. RetrieveEvery referenced output/error file is retained
6. ScoreRecords are joined by custom_id; accuracy and usage cost are calculated separately
7. CleanOnly 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:

  1. cap the number of polls in one invocation;
  2. use a bounded interval rather than a tight loop;
  3. save every observed status and request-count object;
  4. stop cleanly when the local window ends;
  5. resume the same Batch ID later; and
  6. 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:

  1. Transport result: Was an output or error record produced?
  2. API result: What HTTP status and response body did that record contain?
  3. 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:

  1. issue one DELETE /v1/files/{file_id};
  2. issue one later GET /v1/files/{file_id};
  3. treat HTTP 404 as verified absence; and
  4. record any other result as cleanup_pending without 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 suffixTaskFrozen oracle
001-arithmeticAdd 19 and 2342
002-categoryClassify a compiler sentenceTECHNOLOGY
003-extractionExtract a synthetic invoice IDINV-2048
004-sumSum 4, 6, 8 and 1735
005-languageIdentify a Spanish sentenceSPANISH
006-booleanDecide whether four integers are evenTRUE

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.

LayerObserved result
Provider terminal statuscompleted
SubmittedAugust 7, 2026 at 17:36:56 UTC
Terminal first observedAugust 8, 2026 at 10:30:30 UTC, about 16 hours 53 minutes later
Bounded poll count204 across resumable polling windows
Provider request counts6 completed, 0 failed, 6 total
Output/error retrievalOutput retrieved; no error file was referenced
Identifier reconciliation6 expected records; 6 unique IDs; 0 duplicate, 0 missing and 0 unexpected IDs
Record/body presence6/6 complete records with error: null and a Chat Completion body
Exact-answer accuracy6/6 frozen oracles matched
Nested status fieldresponse.status_code=0 in 6/6 rows; strict 200 conformance 0/6
Runner composite0/6 because it required both status code 200 and an exact answer
Usage258 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 cleanup2 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

SymptomLikely boundarySafe response
Upload rejectedFile format, size or upload purposeFix the local fixture; do not claim a submitted job
Batch create rejectedInput file, endpoint, model or completion windowPreserve the error and clean only the uploaded owned file
validating or in_progress for longer than expectedAsynchronous provider processingEnd the local polling window and resume the saved Batch ID
Some requests failedPer-line request or service errorRetrieve both output and error files; reconcile by custom_id
Output order differsAsynchronous completionJoin by custom_id, never array position
Cost cannot be calculatedMissing or malformed usageReport cost as unknown; do not substitute the preflight estimate
Cleanup GET is not 404File still exists or verification is uncertainMark 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_id values
  • [ ] 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.