SPB Git forge

spb/doc-api

Public
2commits 1branches 0releases
15.7 MBsize
maindefault branch
13 days agolast push
Python 88.3% TypeScript 7.6% Shell 4.1%
7.4 KB · 93 lines markdown
Rendered Raw Blame History
1# OpenAI Batch API23**Status:** `DOCUMENTED` · `LIVE_VERIFIED` (create / list / retrieve, full lifecycle to `completed`, output download) · `LIVE_DISCOVERED` (cancel on a terminal batch → 409; bogus `input_file_id` accepted then `failed`).4**Sources:** [Batch guide](https://developers.openai.com/api/docs/guides/batch) · [Batches reference](https://developers.openai.com/api/reference/resources/batches) · [Pricing](https://developers.openai.com/api/docs/pricing) · OpenAPI `openapi-master.yaml`.5**Last verified:** 2026-09-18. Twins: `generated/fragments/endpoints/openai-files-vectorstores-batch-finetuning-evals.json`, `generated/fragments/parameters/openai-batch.json`, `generated/fragments/status-lifecycles/openai-lifecycles.json` (object `batch`).67Asynchronous execution of a JSONL file of requests with a **50 % price discount**, a **separate rate-limit pool**, and a **24 h completion window**.89## 1. Endpoints1011| Method / path | SDK | Body / query | 2026-09-18 |12|---|---|---|---|13| `POST /v1/batches` | `client.batches.create` | `input_file_id` (req), `endpoint` (req), `completion_window: "24h"` (req, only value), `metadata?`, `output_expires_after? {anchor:"created_at", seconds 3600–2592000}` | 200 → `status: validating` |14| `GET /v1/batches` | `batches.list` | `after`, `limit` (1–100, default 20) | 200 |15| `GET /v1/batches/{id}` | `batches.retrieve` | | 200; 404 `No batch found with id '…'` |16| `POST /v1/batches/{id}/cancel` | `batches.cancel` | | **409** when the batch was already terminal (`failed`); documented flow `cancelling` → `cancelled` (≤ 10 min) not exercised |1718Batches cannot be deleted; they stay in the list (two test batches remain in this project's history: one `completed`, one `failed`).1920## 2. Supported endpoints (`endpoint` enum, one per batch, one model per file)2122`/v1/responses`, `/v1/chat/completions`, `/v1/embeddings`, `/v1/completions`, `/v1/moderations`, `/v1/images/generations`, `/v1/images/edits`, `/v1/videos` (POST only, JSON not multipart, assets via `file_id` / `image_url`; batch-generated videos downloadable 24 h).2324## 3. Input JSONL format2526Each line: `{"custom_id": "<unique>", "method": "POST", "url": "<same as endpoint>", "body": {…endpoint body…}}`. `stream` is rejected. Order of output lines is **not** guaranteed — join on `custom_id`.2728```jsonl29{"custom_id":"r-1","method":"POST","url":"/v1/responses","body":{"model":"gpt-5.4-nano","input":"Reply with OK.","max_output_tokens":16}}30{"custom_id":"c-1","method":"POST","url":"/v1/chat/completions","body":{"model":"gpt-4.1-nano","messages":[{"role":"user","content":"Reply with OK."}],"max_tokens":8}}31{"custom_id":"e-1","method":"POST","url":"/v1/embeddings","body":{"model":"text-embedding-3-small","input":"hello world"}}32{"custom_id":"m-1","method":"POST","url":"/v1/moderations","body":{"model":"omni-moderation-latest","input":"This is a harmless test sentence."}}33{"custom_id":"i-1","method":"POST","url":"/v1/images/generations","body":{"model":"gpt-image-1-mini","prompt":"a red square","size":"1024x1024"}}34{"custom_id":"v-1","method":"POST","url":"/v1/videos","body":{"model":"sora-2","prompt":"a paper plane","seconds":"4"}}35```36(Each example above must live in its own file — a file targets a single endpoint and a single model. Only the `/v1/responses` line was executed live; the others are `DOCUMENTED` shapes taken from the guide/spec.)3738## 4. Output / error JSONL format (verified)3940```jsonl41{"id":"batch_req_6aade973…","custom_id":"atlas-platform-agent-req-1","response":{"status_code":200,"request_id":"cbbd7532-…","body":{"id":"resp_…","object":"response","status":"completed","model":"gpt-5.4-nano-2026-03-17","output":[{"type":"message","content":[{"type":"output_text","text":"OK"}]}],"usage":{…}}},"error":null}42```43Error-file lines have `"response": null` and `"error": {"code": "…", "message": "…"}`; expired requests use `code: "batch_expired"`. `response.body` is the normal endpoint object. Output file `purpose` is `batch_output`; its `expires_at` honoured `output_expires_after` (observed `created_at + 3600`). Default deletion: 30 days after completion.4445## 5. Lifecycle (observed timings)4647`validating` → `in_progress` → `finalizing` → `completed` | `expired`; `validating` → `failed`; any active state → `cancelling` → `cancelled`.4849| t (s) | status | request_counts |50|---|---|---|51| 0 | validating | {total 0} |52| 2 (`in_progress_at`) | in_progress | {total 1, completed 0} |53| 29 (`finalizing_at`) | finalizing | |54| 30 (`completed_at`) | completed | {total 1, completed 1, failed 0}; `usage {input_tokens 10, output_tokens 5, total 15, cached 0, reasoning 0}` |5556A batch created with a non-existent `input_file_id` was **accepted (200, `validating`)** and moved to `failed` within a minute with `errors.data[0] = {code:"invalid_request", message:"Cannot find file …, or organization … does not have access to it.", param:"file_id", line:null}`. Cancelling it afterwards returned **409**.5758## 6. Batch object5960`id`, `object:"batch"`, `endpoint`, `model` (resolved snapshot, e.g. `gpt-5.4-nano-2026-03-17`), `errors {object:"list", data[] {code,message,param,line}}`, `input_file_id`, `completion_window`, `status`, `output_file_id`, `error_file_id`, timestamps `created_at`, `in_progress_at`, `expires_at` (= created + 24 h), `finalizing_at`, `completed_at`, `failed_at`, `expired_at`, `cancelling_at`, `cancelled_at`, `request_counts {total, completed, failed}`, `usage` (batches created after 2025-09-07), `metadata`.6162## 7. Limits (documented — separate from synchronous limits)6364| Limit | Value |65|---|---|66| Requests per batch | 50 000 (embeddings: also ≤ 50 000 inputs across the batch) |67| Input file size | 200 MB |68| Batch creations | 2 000 per hour |69| Queued prompt tokens per model | org-specific (Platform → Settings → Limits); not exposed by the API |70| Output tokens | no limit |7172## 8. Pricing (cite: pricing page, 2026-09-18)7374Batch = 50 % of the standard per-token price; the pricing page publishes a dedicated "Batch" table (e.g. `gpt-5.4-nano` $0.10 input / $0.01 cached / $0.625 output per 1M; `gpt-5.4` $1.25 / $0.13 / $7.50; `gpt-5.5` $2.50 / $0.25 / $15.00 for < 272K context). Fine-tuned models also have Batch rates (e.g. `gpt-4.1-nano-2025-04-14` $0.10 / $0.025 / $0.40). Requests that complete in an expired batch are billed. Test cost 2026-09-18: 15 tokens on gpt-5.4-nano ≈ $0.000004.7576## 9. Errors and edge cases7778| Situation | HTTP / state |79|---|---|80| Unknown `input_file_id` | 200 then `status: failed`, `errors.data[].code = invalid_request` |81| Cancel on terminal batch | 409 (undocumented) |82| Unknown batch id | 404 `invalid_request_error` |83| Line-level failure | appears in `error_file_id`, batch still `completed` |84| Window exceeded | `expired`; unfinished lines in error file with `batch_expired` |8586## 10. Best practices8788- One model per file; pre-validate JSON lines and unique `custom_id`s locally (validation failures fail the whole batch).89- Keep bodies small (reference images by URL/file_id; no base64 blobs).90- Poll with back-off (a 1-line batch finished in 30 s; large ones can take hours); or use webhooks `batch.completed | failed | expired | cancelled` (see the webhooks agent's docs).91- Set `output_expires_after` if you do not want outputs retained 30 days; delete input files after completion (`purpose=batch` files auto-expire in 30 d).92- Prefer Batch for evals/classification/embedding backfills; use Flex/Priority service tiers for latency-tolerant *synchronous* traffic instead.93