#!/usr/bin/env python3 """Generate generated/fragments/endpoints/openai-files-vectorstores-batch-finetuning-evals.json from the OpenAPI index (tmp/platform-spec/_index.json), the SDK map and the observed live results (2026-09-18).""" from __future__ import annotations import json, re from pathlib import Path ROOT = Path(__file__).resolve().parent.parent IDX = json.load(open(ROOT / "tmp/platform-spec/_index.json")) SDK = json.load(open(ROOT / "tmp/platform-spec/_sdk_map.json")) RET = "2026-09-18" REF = "https://developers.openai.com/api/reference/resources/" GUIDE = "https://developers.openai.com/api/docs/guides/" DEPREC = "https://developers.openai.com/api/docs/deprecations" FAM = [("/fine_tuning/alpha/graders", "graders"), ("/fine_tuning", "fine_tuning"), ("/files", "files"), ("/uploads", "uploads"), ("/vector_stores", "vector_stores"), ("/batches", "batches"), ("/evals", "evals")] DEPRECATED = {"fine_tuning": "Self-serve fine-tuning is winding down: no new orgs since 2026-05-07, job creation ends 2027-01-06 (inference on existing ft: models continues until base-model deprecation).", "graders": "Graders are deprecated together with the Evals platform (API shutdown 2026-11-30) and the fine-tuning platform (2027-01-06).", "evals": "Evals platform deprecated 2026-06-03: read-only 2026-10-31, dashboard + API shut down 2026-11-30."} SRC_PAGE = {"files": "files", "uploads": "uploads", "vector_stores": "vector_stores", "batches": "batches", "fine_tuning": "fine_tuning", "graders": "graders", "evals": "evals"} GUIDES = {"files": [GUIDE + "file-inputs"], "uploads": [], "vector_stores": [GUIDE + "retrieval", GUIDE + "tools-file-search"], "batches": [GUIDE + "batch"], "fine_tuning": [GUIDE + "supervised-fine-tuning", GUIDE + "direct-preference-optimization", GUIDE + "reinforcement-fine-tuning", GUIDE + "model-optimization"], "graders": [GUIDE + "graders"], "evals": [GUIDE + "evals"]} # observed live results, keyed by "METHOD /path" (spec path form) V = lambda result, http, note, method="live_api": {"method": method, "verified_at": RET, "result": result, "http_status": http, "request_note": note} # noqa: E731 LIVE = { "POST /files": V("success", 200, "multipart purposes user_data (+expires_after 3600s -> expires_at set), batch (default expires_at = +30d), evals, assistants; 200-byte txt and 1-line jsonl"), "GET /files": V("success", 200, "?purpose=user_data&limit=5 -> list envelope {object,data,first_id,last_id,has_more}"), "GET /files/{file_id}": V("success", 200, "200 for own file; 404 'No such File object' after delete"), "DELETE /files/{file_id}": V("success", 200, "{object:'file', id, deleted:true} for all 7 created files"), "GET /files/{file_id}/content": V("success", 200, "200 (raw jsonl) for purpose batch_output; 400 'Not allowed to download files of purpose: user_data' for user_data"), "POST /uploads": V("success", 200, "purpose user_data accepted although the OpenAPI enum lists only assistants|batch|fine-tune|vision; status pending; expires_at = created_at + 3600"), "POST /uploads/{upload_id}/parts": V("success", 200, "multipart field 'data' -> {object:'upload.part'}; 400 after cancel: 'Upload status is already in a cancelled state'"), "POST /uploads/{upload_id}/complete": V("success", 200, "part_ids -> status completed, file.purpose user_data, file.bytes == declared bytes"), "POST /uploads/{upload_id}/cancel": V("success", 200, "status cancelled"), "POST /vector_stores": V("success", 200, "name+description+metadata+expires_after(last_active_at,1d) -> status completed, usage_bytes 0"), "GET /vector_stores": V("success", 200, "limit=5"), "GET /vector_stores/{vector_store_id}": V("success", 200, "usage_bytes 1203 after a 200-byte file (index overhead); 404 for bogus id"), "POST /vector_stores/{vector_store_id}": V("success", 200, "rename + metadata replace"), "DELETE /vector_stores/{vector_store_id}": V("success", 200, "{object:'vector_store.deleted', deleted:true}"), "POST /vector_stores/{vector_store_id}/files": V("success", 200, "static chunking 100/20 + attributes {string,number,boolean} -> in_progress, completed after ~7.6s"), "GET /vector_stores/{vector_store_id}/files": V("success", 200, "?filter=completed"), "GET /vector_stores/{vector_store_id}/files/{file_id}": V("success", 200, "polled in_progress -> completed (7.6s)"), "POST /vector_stores/{vector_store_id}/files/{file_id}": V("success", 200, "attributes replaced wholesale (not merged)"), "DELETE /vector_stores/{vector_store_id}/files/{file_id}": V("success", 200, "{object:'vector_store.file.deleted', deleted:true}"), "GET /vector_stores/{vector_store_id}/files/{file_id}/content": V("success", 200, "{object:'vector_store.file_content.page', data:[{type:'text',text}], has_more, next_page}"), "POST /vector_stores/{vector_store_id}/search": V("success", 200, "string query + and/eq/gte filters + rewrite_query (search_query rewritten) + ranking_options; array query + ranker none; non-matching filter -> empty data"), "POST /vector_stores/{vector_store_id}/file_batches": V("success", 200, "files[] form with per-file attributes + chunking_strategy auto -> object 'vector_store.file_batch' (spec says vector_store.files_batch), in_progress -> completed ~4s"), "GET /vector_stores/{vector_store_id}/file_batches/{batch_id}": V("success", 200, "polled to completed"), "GET /vector_stores/{vector_store_id}/file_batches/{batch_id}/files": V("success", 200, "returned chunking_strategy.type 'static' for a file added with type auto (auto resolves to static 800/400)"), "POST /vector_stores/{vector_store_id}/file_batches/{batch_id}/cancel": V("failure", 500, "cancel on an already-completed batch returned HTTP 500 'The server had an error processing your request' (not tested on an in-progress batch)"), "POST /batches": V("success", 200, "1-request /v1/responses gpt-5.4-nano; status validating -> in_progress (2s) -> finalizing -> completed at 30s; usage populated; output file purpose batch_output with expires_at honoring output_expires_after. NOTE: a bogus input_file_id is accepted with 200 (fails later during validation)"), "GET /batches": V("success", 200, "limit=3"), "GET /batches/{batch_id}": V("success", 200, "404 for bogus id"), "POST /batches/{batch_id}/cancel": V("failure", 409, "cancel on a batch that had just transitioned validating -> failed returned 409 Conflict (behaviour not documented); cancel on an in-progress batch not exercised because the 1-request test batch completed in 30s"), "GET /fine_tuning/jobs": V("success", 200, "empty list {object,data,has_more}; metadata[owner]=x filter accepted"), "POST /fine_tuning/jobs": V("restricted", 403, "validation-only call with bogus training_file -> 403 code training_not_available: 'OpenAI is winding down the fine-tuning platform and your organization is no longer able to create new fine-tuning training jobs'"), "GET /fine_tuning/jobs/{fine_tuning_job_id}": V("success", 404, "bogus id -> 404 code fine_tune_not_found (endpoint reachable)"), "POST /fine_tuning/jobs/{fine_tuning_job_id}/cancel": V("success", None, "not called (no job)", method="docs_only"), "POST /fine_tuning/jobs/{fine_tuning_job_id}/pause": V("success", 404, "bogus id -> 404 fine_tune_not_found"), "POST /fine_tuning/jobs/{fine_tuning_job_id}/resume": V("success", None, "not called (no job)", method="docs_only"), "GET /fine_tuning/jobs/{fine_tuning_job_id}/events": V("success", 404, "bogus id -> 404 fine_tune_not_found"), "GET /fine_tuning/jobs/{fine_tuning_job_id}/checkpoints": V("success", 404, "bogus id -> 404 fine_tune_not_found"), "GET /fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions": V("restricted", 401, "401 'Missing scopes: api.fine_tuning.checkpoints.read' (requires org Owner role / admin scope)"), "POST /fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions": V("success", None, "not called (admin-level write)", method="docs_only"), "DELETE /fine_tuning/checkpoints/{fine_tuned_model_checkpoint}/permissions/{permission_id}": V("success", None, "not called (admin-level write)", method="docs_only"), "POST /fine_tuning/alpha/graders/validate": V("success", 200, "string_check + python graders -> {grader}; invalid operation 'contains' -> 400 type invalid_value param operation"), "POST /fine_tuning/alpha/graders/run": V("success", 200, "string_check reward 1.0 (0.4ms), text_similarity fuzzy_match 0.756, python 1.0 (2.3s sandbox), multi with calculate_output -> sub_rewards; no model grader run (cost)"), "POST /evals": V("success", 201, "custom data_source_config (item_schema + include_sample_schema) with string_check/text_similarity/python criteria -> 201; logs config + label_model criterion -> 201; returned schema wraps {item, sample} with sample.required=[model, choices]"), "GET /evals": V("success", 200, "list is eventually consistent: empty right after create, present 3s later"), "GET /evals/{eval_id}": V("success", 200, "404 after delete"), "POST /evals/{eval_id}": V("success", 200, "rename + metadata"), "DELETE /evals/{eval_id}": V("success", 200, "{object:'eval.deleted', deleted:true, eval_id}"), "POST /evals/{eval_id}/runs": V("success", 201, "jsonl file_content and jsonl file_id (purpose evals) with pre-filled sample {model, choices, output_text} -> 201 queued; sample without model/choices -> 400 'model is a required property'; responses source without model/input_messages -> 400"), "GET /evals/{eval_id}/runs": V("success", 200, "?status=completed returned 0 items right after completion (index lag)"), "GET /evals/{eval_id}/runs/{run_id}": V("success", 200, "queued -> in_progress -> completed in ~4s for 3 items, no sampling; result_counts + per_testing_criteria_results (with testing_criteria_id) populated"), "POST /evals/{eval_id}/runs/{run_id}": V("success", 200, "cancel on a completed run -> 200, status stays completed"), "DELETE /evals/{eval_id}/runs/{run_id}": V("success", 200, "{object:'eval.run.deleted', deleted:true, run_id}"), "GET /evals/{eval_id}/runs/{run_id}/output_items": V("success", 200, "3 items, status pass|fail, ?status=fail filter works; results[] {name(id), type:null, score, passed}; extra fields _datasource_item_content_hash, available_includes"), "GET /evals/{eval_id}/runs/{run_id}/output_items/{output_item_id}": V("success", 200, "single item"), } STATUS_FOR_RESULT = {"success": "LIVE_VERIFIED", "restricted": "ACCOUNT_RESTRICTED", "failure": "FAILED_VERIFICATION"} def family(path): for p, f in FAM: if path.startswith(p): return f def clean_sdk(s): return re.sub(r"([^<]+)", r"\1", s) if s else None records = [] for key, op in IDX.items(): method, path = key.split(" ", 1) fam = family(path) ver = LIVE.get(key, V("success", None, "not called", method="docs_only")) status = ["DOCUMENTED"] if ver["method"] == "live_api": status.append(STATUS_FOR_RESULT[ver["result"]]) if fam in DEPRECATED: status.append("DEPRECATED") if fam == "graders": status.append("BETA") # alpha path if key == "POST /uploads": status.append("LIVE_DISCOVERED") # user_data purpose not in spec enum if key == "POST /batches/{batch_id}/cancel": status = ["DOCUMENTED", "LIVE_DISCOVERED"] # 409 on terminal batch: reachable, undocumented semantics paged = any(p in ("after", "limit") for p in op["params"]) and method == "GET" and not path.endswith(("/content",)) resp = op["responses"] ok = next(iter(resp.values()), {}) sdk = SDK.get(f"{method} /v1{path}", {}) rec = { "provider": "openai", "api_family": fam, "method": method, "path": f"/v1{path}", "name": op["operationId"], "description": op["summary"] or "", "status": status, "auth": "Bearer API key (project or user key)", "beta_header": None, "request": {"content_type": op["body_ct"], "body_ref": op.get("body_ref")}, "response": {"content_type": ok.get("content_type"), "body_ref": ok.get("ref"), "success_codes": sorted(int(c) for c in resp if c.isdigit() and int(c) < 400)}, "streaming": {"supported": False, "events_ref": None}, "pagination": ({"style": "cursor", "params": [p for p in op["params"] if p in ("after", "before", "limit", "order", "order_by", "filter", "status", "purpose", "metadata", "project_id")]} if paged else None), "idempotency": "safe" if method == "GET" else ("idempotent" if method == "DELETE" else "not idempotent"), "sdk": {"python": clean_sdk(sdk.get("python")), "node": clean_sdk(sdk.get("node"))}, "deprecation": DEPRECATED.get(fam), "verification": ver, "sources": [{"url": REF + SRC_PAGE[fam], "retrieved_at": RET}] + [{"url": u, "retrieved_at": RET} for u in GUIDES[fam]] + ([{"url": DEPREC, "retrieved_at": RET}] if fam in DEPRECATED else []), } records.append(rec) records.sort(key=lambda r: (r["api_family"], r["path"], r["method"])) out = ROOT / "generated/fragments/endpoints/openai-files-vectorstores-batch-finetuning-evals.json" out.parent.mkdir(parents=True, exist_ok=True) out.write_text(json.dumps(records, indent=1, ensure_ascii=False) + "\n") from collections import Counter print(len(records), Counter(s for r in records for s in r["status"]))