Skip to main content
Upload a JSONL file of chat-completion requests, create a batch over it, and collect the results when it finishes. Every completed request is billed at half the model’s synchronous rate. The API is the OpenAI Batch API, so the official OpenAI SDKs work unchanged once you point them at https://api.morphllm.com/v1. Available on morph-glm53flash, morph-dsv4flash, morph-glm53-744b, and morph-kimik3. Per-token rates are on the pricing page.

When to use it

Batch fits work where nobody is waiting on an individual response: eval runs, dataset generation, nightly summarization, re-indexing, backfills. You trade latency for price. Results land within 24 hours, usually much sooner, and there is no per-request rate limit to work around. For work that needs a response now, use the synchronous endpoint. For background traffic that still needs each answer within seconds, use Standby instead.

Quick Start

Write one request per line in a JSONL file. Each line names a custom_id you choose, the endpoint, and the same body you would send synchronously.
requests.jsonl
Then upload the file, create the batch, poll until it reaches a terminal status, and download the output and error files.

Input file format

One JSON object per line. Blank lines are not allowed. Every line in a batch must name the same model. A file that mixes models, repeats a custom_id, or targets another endpoint fails validation and the batch goes to failed with the reasons in errors.

Output file format

One line per request the model answered, in completion order rather than input order. Join back to your requests on custom_id.
response.body is the full Chat Completions response, usage included. error is always null in this file.
Model errors are output lines, not error lines. When the model returns a non-2xx for a request (a 400 for a bad parameter, a 413 for an oversized prompt, a 500), the line is written to the output file with that status_code and the error envelope in response.body, and error stays null. Check status_code on every output line rather than assuming the file only holds successes. OpenAI routes these to the error file; Morph does not.

Error file format

One line per request that never got a model response. response is always null here.
Lines in the error file are not billed.

Statuses

Poll GET /v1/batches/{batch_id} every 30 to 60 seconds. There is no webhook.

Cancel and expiry

  • Cancel with POST /v1/batches/{batch_id}/cancel while the batch is validating or in_progress. It moves to cancelling, then cancelled once in-flight requests drain. Cancelling a terminal batch returns 400; cancelling one already cancelling is a no-op.
  • Expiry happens when the 24-hour completion_window closes. The batch moves to expired.
  • In both cases partial output is kept. Whatever completed is in the output file and billed. Whatever did not run is in the error file and not billed. OpenAI discards results on cancel; Morph keeps them.

Limits

Pricing

Every request that completes is billed at 50% of the model’s synchronous rate, input and output alike. Requests in the error file are free. Cancel and expiry do not refund requests that already completed. Rates per model are on the pricing page.

Retention

  • Input files, output files, and error files are deleted 30 days after they are written. Download what you need before then, or set a shorter window with expires_after on upload and output_expires_after on batch creation (1 hour to 30 days, anchored to when the file is created).
  • Zero-data-retention accounts: files and outputs are deleted after 24 hours. Poll and download promptly.
  • DELETE /v1/files/{file_id} removes a file immediately.

Pitfalls

Check the error file too. The two files together cover every custom_id. If the batch is expired or cancelled, the missing lines are in the error file as batch_expired or batch_cancelled.
That is the model rejecting that one request (a bad parameter, an oversized prompt). Read response.body for the reason, fix the line, and resubmit it in a new batch. The rest of the batch is unaffected.
after on GET /v1/files and GET /v1/batches is an integer offset, not an object id, so the SDK’s auto-paginator (which passes the last id) gets a 400. Pass after and limit yourself and increment after by limit.
Expected: lines are written as requests complete. Join on custom_id.

See Also