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%
59.7 KB · 517 lines python
Raw Blame History
1#!/usr/bin/env python32"""Generator for the OpenAI media/safety fragments (images, video, embeddings, moderation, safety, provenance).3Run from repo root: .venv/bin/python tmp-live/gen_media.py  -> writes generated/fragments/**/openai-*.json4All facts come from sources/openai/** (retrieved 2026-09-18) plus live probes logged in reports/live-requests.jsonl.5"""6import json7from pathlib import Path89ROOT = Path(__file__).resolve().parent.parent10RET = "2026-09-18"11TODAY = "2026-09-18"12P = "openai"1314REF = "https://developers.openai.com/api/reference/resources/"15GUIDE = "https://developers.openai.com/api/docs/guides/"16SPEC = "https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml"17PRICING = "https://developers.openai.com/api/docs/pricing"18DEPREC = "https://developers.openai.com/api/docs/deprecations"19MODELS = "https://developers.openai.com/api/docs/models/"202122def src(*urls):23    return [{"url": u, "retrieved_at": RET} for u in urls]242526def ver(method="live_api", result="success", status=200, note="", at=TODAY):27    return {"method": method, "verified_at": at, "result": result, "http_status": status, "request_note": note}282930DOCS_ONLY = ver("docs_only", "not_run", None, "documented only; not called in this run")3132GPT_IMAGE_ALL = ["gpt-image-2.5-sunburst", "gpt-image-2.5-sunburst-2026-09-08", "gpt-image-2.5-flare",33                 "gpt-image-2.5-flare-2026-09-08", "gpt-image-2", "gpt-image-2-2026-04-21", "gpt-image-1.5",34                 "gpt-image-1", "gpt-image-1-mini", "chatgpt-image-latest"]35GPT_IMAGE_25 = GPT_IMAGE_ALL[:4]36GPT_IMAGE_2X = GPT_IMAGE_ALL[:6]37GPT_IMAGE_LEGACY = ["gpt-image-1.5", "gpt-image-1", "gpt-image-1-mini", "chatgpt-image-latest"]38DALLE = ["dall-e-2", "dall-e-3"]  # RETIRED 2026-05-12 (live: "The model 'dall-e-3' does not exist.")39IMAGE_MODELS_GEN = GPT_IMAGE_ALL + DALLE40IMAGE_MODELS_EDIT = GPT_IMAGE_ALL + ["dall-e-2"]41VIDEO_MODELS = ["sora-2", "sora-2-pro", "sora-2-2025-10-06", "sora-2-pro-2025-10-06", "sora-2-2025-12-08"]42EMB_MODELS = ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]43MOD_MODELS = ["omni-moderation-latest", "omni-moderation-2024-09-26"]44MOD_LEGACY = ["text-moderation-latest", "text-moderation-stable", "text-moderation-007"]4546# ----------------------------------------------------------------------------------------------------------------47# ENDPOINTS48# ----------------------------------------------------------------------------------------------------------------49IMG_SRC = src(REF + "images", GUIDE + "image-generation", SPEC)50VID_SRC = src(REF + "videos", GUIDE + "video-generation", SPEC, DEPREC)51VIDEO_DEPRECATION = ("Videos API + all Sora 2 models: deprecation notice 2026-03-24, shutdown 2026-09-24 "52                     "(deprecations page). Still reachable on 2026-09-18.")535455def ep(method, path, name, description, status, *, family, sdk_py, sdk_node, content_type="application/json",56       body_ref=None, response=None, streaming=None, pagination=None, verification=None, sources=None, notes=None,57       auth="Bearer API key", idempotency="none documented", beta_header=None):58    return {59        "provider": P, "api_family": family, "method": method, "path": path, "name": name,60        "description": description, "status": status, "auth": auth, "beta_header": beta_header,61        "request": {"content_type": content_type, "body_ref": body_ref},62        "response": response or {},63        "streaming": streaming or {"supported": False, "events_ref": None},64        "pagination": pagination, "idempotency": idempotency,65        "sdk": {"python": sdk_py, "node": sdk_node},66        "verification": verification or DOCS_ONLY, "sources": sources or [], "notes": notes,67    }686970ENDPOINTS = [71    ep("POST", "/v1/images/generations", "Create image",72       "Creates one or more images from a text prompt. GPT image models return base64 (b64_json) plus token usage; "73       "supports streaming of partial images (stream=true, partial_images 0-3).",74       ["DOCUMENTED", "LIVE_VERIFIED"], family="images", sdk_py="client.images.generate(**params)",75       sdk_node="client.images.generate({...})", body_ref="CreateImageRequest",76       response={"content_type": "application/json", "object": "ImagesResponse",77                 "live_note": "data[0] contained b64_json AND an undocumented generation_id (LIVE_DISCOVERED); usage present"},78       streaming={"supported": True, "events_ref": "openai-images streaming events (image_generation.partial_image, image_generation.completed)",79                  "transport": "SSE, `event:` + `data:` lines"},80       verification=ver(note="gpt-image-1-mini, 1024x1024, quality=low, output_format=jpeg, prompt 'a plain white square' -> 200 in 5.7 s; usage input 10 text tokens / output 272 image tokens; est. $0.0022. Invalid size -> 400 image_generation_user_error/invalid_value. style / response_format on gpt-image-1-mini -> 400 unknown_parameter. dall-e-3 -> 400 \"The model 'dall-e-3' does not exist.\""),81       sources=IMG_SRC, notes="Defaults to dall-e-2 per docs unless a GPT-image-only parameter is used, but DALL-E models were removed 2026-05-12: always pass model explicitly. Org verification may be required for GPT Image models."),82    ep("POST", "/v1/images/edits", "Create image edit",83       "Creates an edited or extended image from 1-16 source images (multipart `image`/`image[]` or JSON `images[]` with file_id/image_url), an optional PNG mask (alpha=0 marks editable area, applied to the first image) and a prompt. Supports input_fidelity, streaming, background, output_format.",84       ["DOCUMENTED", "UNVERIFIED"], family="images", sdk_py="client.images.edit(**params)",85       sdk_node="client.images.edit({...})", content_type="multipart/form-data | application/json",86       body_ref="CreateImageEditRequest",87       response={"content_type": "application/json", "object": "ImagesResponse"},88       streaming={"supported": True, "events_ref": "openai-images streaming events (image_edit.partial_image, image_edit.completed)"},89       verification=ver("docs_only", "not_run", None, "not called (paid); parameters cross-checked against OpenAPI CreateImageEditRequest and the JSON body documented in the reference page"),90       sources=IMG_SRC, notes="Spec default model = gpt-image-1.5. Mask must be PNG < 4 MB with alpha channel, same size as the first image (guide: image + mask < 50 MB). gpt-image-2 always processes inputs at high fidelity (input_fidelity not settable)."),91    ep("POST", "/v1/images/variations", "Create image variation (legacy)",92       "Creates variations of a square PNG (< 4 MB). Only dall-e-2 was ever supported.",93       ["DOCUMENTED", "LEGACY", "RETIRED", "FAILED_VERIFICATION"], family="images",94       sdk_py="client.images.create_variation(**params)", sdk_node="client.images.createVariation({...})",95       content_type="multipart/form-data", body_ref="CreateImageVariationRequest",96       response={"content_type": "application/json", "object": "ImagesResponse"},97       verification=ver(result="failure", status=404, note="multipart model=dall-e-2 n=1 size=256x256 with a 1x1 PNG -> HTTP 404 with EMPTY body (no JSON error). Consistent with dall-e-2 removal on 2026-05-12."),98       sources=IMG_SRC + src(DEPREC), notes="Still present in reference + OpenAPI, but non-functional for our key: treat as RETIRED."),99    # ---- videos100    ep("POST", "/v1/videos", "Create video",101       "Starts an asynchronous Sora video render job (status queued -> in_progress -> completed|failed). Accepts JSON or multipart (multipart allows uploading input_reference bytes).",102       ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.create(**params) / client.videos.create_and_poll(...)",103       sdk_node="client.videos.create({...})", content_type="application/json | multipart/form-data",104       body_ref="CreateVideoJsonBody | CreateVideoMultipartBody",105       response={"content_type": "application/json", "object": "Video"},106       verification=ver("docs_only", "not_run", None, "not called (cost $0.10-$0.70 per second). List/retrieve verified instead."),107       sources=VID_SRC, notes=VIDEO_DEPRECATION + " Also supported through the Batch API (JSON only, url /v1/videos). Guide documents a `characters: [{id}]` array that is absent from the OpenAPI spec."),108    ep("GET", "/v1/videos", "List videos", "Lists video jobs of the current project (cursor pagination).",109       ["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], family="videos", sdk_py="client.videos.list(**params)",110       sdk_node="client.videos.list({...})", content_type=None,111       response={"content_type": "application/json", "object": "list of Video", "live_note": "items carried undocumented `edited_from_video_id` and `extended_from_video_id` fields (LIVE_DISCOVERED)"},112       pagination={"style": "cursor", "params": ["after", "limit", "order"], "fields": ["first_id", "last_id", "has_more"]},113       verification=ver(note="GET /v1/videos?limit=2 -> 200, 1 completed sora-2 job from 2026-07 (expired assets)"),114       sources=VID_SRC, notes=VIDEO_DEPRECATION),115    ep("GET", "/v1/videos/{video_id}", "Retrieve video", "Fetches the latest metadata/status of a video job.",116       ["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], family="videos", sdk_py="client.videos.retrieve(video_id)",117       sdk_node="client.videos.retrieve(videoID)", content_type=None,118       response={"content_type": "application/json", "object": "Video"},119       verification=ver(note="existing id -> 200 (status completed, progress 100); unknown id -> 404 invalid_request_error \"Video with id '...' not found.\" (code null)"),120       sources=VID_SRC, notes=VIDEO_DEPRECATION),121    ep("DELETE", "/v1/videos/{video_id}", "Delete video", "Permanently deletes a completed or failed video and its stored assets.",122       ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.delete(video_id)",123       sdk_node="client.videos.delete(videoID)", content_type=None,124       response={"content_type": "application/json", "object": "VideoDeleteResponse {id, deleted, object:'video.deleted'}"},125       verification=ver("docs_only", "not_run", None, "destructive; not called"), sources=VID_SRC, notes=VIDEO_DEPRECATION),126    ep("GET", "/v1/videos/{video_id}/content", "Download video content",127       "Streams the rendered MP4 (default) or a derived asset: ?variant=thumbnail (image/webp) | spritesheet (jpg).",128       ["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], family="videos", sdk_py="client.videos.download_content(video_id, variant=...)",129       sdk_node="client.videos.downloadContent(videoID, {variant})", content_type=None,130       response={"content_type": "video/mp4 | image/webp | image/jpeg | application/json (error)", "object": "binary"},131       verification=ver(result="failure", status=404, note="variant=thumbnail on a 2-month-old completed job -> 404 invalid_request_error \"The video is no longer available. Downloads expire after 48 hours.\" (endpoint reachable; asset expired)"),132       sources=VID_SRC, notes="Assets expire (expires_at; live message says 48 h; batch guide says 24 h after batch completion). " + VIDEO_DEPRECATION),133    ep("POST", "/v1/videos/{video_id}/remix", "Create video remix (being replaced by edits)",134       "Creates a new job re-rendering a completed video with a refreshed prompt. Changelog 2026-03-12: replaced by POST /v1/videos/edits, deprecated in 6 months.",135       ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.remix(video_id, prompt=...)",136       sdk_node="client.videos.remix(videoID, {prompt})", body_ref="{prompt}",137       response={"content_type": "application/json", "object": "Video (remixed_from_video_id set)"},138       sources=VID_SRC + src("https://developers.openai.com/api/docs/changelog"), notes=VIDEO_DEPRECATION),139    ep("POST", "/v1/videos/edits", "Create video edit",140       "Edits an existing generated video (JSON `video: {id}`) or an uploaded MP4 (multipart `video` file + explicit `model`; upload path limited to eligible customers). Model is inferred from the source video id.",141       ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.edit(**params)",142       sdk_node="client.videos.edit({...})", content_type="application/json | multipart/form-data",143       body_ref="CreateVideoEditJsonBody | CreateVideoEditMultipartBody",144       response={"content_type": "application/json", "object": "Video (live list shows edited_from_video_id)"},145       sources=VID_SRC, notes=VIDEO_DEPRECATION),146    ep("POST", "/v1/videos/extensions", "Create video extension",147       "Continues a completed video: generates the next segment (`seconds` 4-20 per extension, up to 6 extensions / 120 s total) stitched to the source. No characters or image references.",148       ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.extend(**params)",149       sdk_node="client.videos.extend({...})", content_type="application/json | multipart/form-data",150       body_ref="CreateVideoExtendJsonBody | CreateVideoExtendMultipartBody",151       response={"content_type": "application/json", "object": "Video (seconds = stitched total; live list shows extended_from_video_id)"},152       sources=VID_SRC, notes=VIDEO_DEPRECATION),153    ep("POST", "/v1/videos/characters", "Create character",154       "Creates a reusable non-human character (cameo) from a short MP4 (2-4 s, 16:9 or 9:16, 720p-1080p). Human likeness blocked by default. Reference the returned id in `characters: [{id}]` and mention the name verbatim in the prompt (max 2 characters per video).",155       ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.create_character(video=..., name=...)",156       sdk_node="client.videos.createCharacter({...})", content_type="multipart/form-data", body_ref="CreateVideoCharacterBody",157       response={"content_type": "application/json", "object": "VideoCharacterResource {id, name, created_at}"},158       verification=ver(result="failure", status=400, note="GET /v1/videos/characters?limit=1 (probe for an undocumented list op) -> 400 invalid_value \"Invalid 'video_id': 'characters'. Expected an ID that begins with 'video'.\" => no list operation; path is routed as /videos/{video_id} for GET"),159       sources=VID_SRC, notes=VIDEO_DEPRECATION),160    ep("GET", "/v1/videos/characters/{character_id}", "Retrieve character", "Fetches a character by id.",161       ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.get_character(character_id)",162       sdk_node="client.videos.getCharacter(characterID)", content_type=None,163       response={"content_type": "application/json", "object": "VideoCharacterResource"}, sources=VID_SRC, notes=VIDEO_DEPRECATION),164    # ---- embeddings165    ep("POST", "/v1/embeddings", "Create embeddings",166       "Returns embedding vectors for a string, an array of up to 2048 strings, a token array or an array of token arrays. Max 8192 tokens per input and 300,000 tokens summed per request.",167       ["DOCUMENTED", "LIVE_VERIFIED"], family="embeddings", sdk_py="client.embeddings.create(**params)",168       sdk_node="client.embeddings.create({...})", body_ref="CreateEmbeddingRequest",169       response={"content_type": "application/json", "object": "CreateEmbeddingResponse {object:'list', data[Embedding], model, usage{prompt_tokens,total_tokens}}"},170       verification=ver(note="input 'OK': 3-small -> 1536 floats, 3-large -> 3072, ada-002 -> 1536 (response model 'text-embedding-ada-002-v2'); usage prompt_tokens=1 each; L2 norms ~1.000. dimensions=16 + encoding_format=base64 -> 88-char base64 = 64 bytes = 16 little-endian float32, still unit-norm. dimensions on ada-002 -> 400 'This model does not support specifying dimensions.' Token-array input [[11380]] -> 200."),171       sources=src(REF + "embeddings", GUIDE + "embeddings", SPEC, PRICING),172       notes="Also available via Batch API (all three models) and regional endpoints. Observed header x-ratelimit-limit-requests: 10000 (account-specific)."),173    # ---- moderation174    ep("POST", "/v1/moderations", "Create moderation",175       "Classifies text and/or image inputs against 13 harm categories. Free of charge. omni-moderation accepts text + image_url (URL or data URL, images <= 20 MB); no audio.",176       ["DOCUMENTED", "LIVE_VERIFIED"], family="moderations", sdk_py="client.moderations.create(**params)",177       sdk_node="client.moderations.create({...})", body_ref="CreateModerationRequest",178       response={"content_type": "application/json", "object": "CreateModerationResponse {id, model, results[Moderation]}"},179       verification=ver(note="omni-moderation-latest text 'Reply with OK.' -> 200 flagged=false, 13 categories, all category_applied_input_types=['text']. Multimodal text + 1x1 PNG data URL -> 200; image-capable categories reported ['text','image']. text-moderation-latest -> 400 invalid_request_error param=model (removed 2025-10-27)."),180       sources=src(REF + "moderations", GUIDE + "moderation", SPEC, PRICING, DEPREC),181       notes="Inline alternative: pass top-level `moderation: {model}` in /v1/responses or /v1/chat/completions to get scores for input and output (changelog)."),182    # ---- safety183    ep("GET", "/v1/safety/alerts/{id}", "Get project safety alert",184       "Returns a misalignment-monitoring alert (safety.alert object) belonging to the authenticated API project. Alert ids arrive via the `safety.alert.created` / `safety.org_alert.created` webhooks (pattern ^alert_[0-9a-f]{32}$).",185       ["DOCUMENTED", "LIVE_VERIFIED"], family="safety", sdk_py="client.safety.alerts.retrieve(id)",186       sdk_node="client.safety.alerts.retrieve(id)", content_type=None,187       response={"content_type": "application/json", "object": "SafetyAlertResource"},188       verification=ver(result="failure", status=404, note="GET /v1/safety/alerts/salert_atlasprobe -> 404 {type: invalid_request_error, code: safety_alert_not_found, message: 'Safety alert not found.'} => endpoint reachable with a standard project key; we simply have no alerts"),189       sources=src(GUIDE + "safety-checks/misalignment-monitoring", SPEC),190       notes="Reference page for this resource is not in the downloaded docs set; documented in the misalignment-monitoring guide and the OpenAPI spec (operationId Getprojectsafetyalert, group safety-alerts)."),191    ep("GET", "/v1/safety/cases/{id}", "Get safety case",192       "Returns a safety case (warning or deactivation issued for a safety identifier). Case ids arrive via `safety.warning_issued` / `safety.deactivation_issued` webhooks (example id 'C-abc123').",193       ["LIVE_DISCOVERED", "ACCOUNT_RESTRICTED"], family="safety", sdk_py="not in python-sdk-api.md (spec-only)",194       sdk_node="not in node-sdk-api.md (spec-only)", content_type=None,195       response={"content_type": "application/json", "object": "SafetyCaseResource"},196       verification=ver(result="restricted", status=403, note="GET /v1/safety/cases/C-atlasprobe -> 403 with a NON-standard body: {\"error\": \"You have insufficient permissions for this operation. Missing scopes: api.safety.read. ...\"} (error is a string, not an object). Requires scope api.safety.read."),197       sources=src(SPEC), notes="Spec-only (operationId Getsafetycase, group safety-cases); no guide/reference page in the downloaded docs."),198    ep("POST", "/v1/content_provenance_checks", "Create content provenance check",199       "Synchronously checks an image (PNG/JPEG/WebP) or audio file (MP3/Opus/AAC/FLAC/WAV/PCM, <= 60 s) for OpenAI provenance signals: C2PA Content Credentials (images) and SynthID watermark (images + audio). One file per request, <= 50 MiB.",200       ["DOCUMENTED", "LIVE_VERIFIED"], family="content_provenance", sdk_py="client.content_provenance_checks.create(file=...)",201       sdk_node="client.contentProvenanceChecks.create({file})", content_type="multipart/form-data", body_ref="CreateContentProvenanceBody {file}",202       response={"content_type": "application/json", "object": "ProvenanceResource {object:'content_provenance_check', created_at, results[C2PA|SynthID]}"},203       verification=ver(note="fresh gpt-image-1-mini JPEG -> 200: c2pa outcome=detected validation_state=trusted issuer='OpenAI OpCo, LLC' model='API / gpt-image' generated_at set; synthid outcome=not_detected. 1x1 hand-made PNG -> c2pa not_detected/not_present, synthid not_detected."),204       sources=src(GUIDE + "content-provenance", REF + "content_provenance_checks/methods/create", SPEC),205       notes="Not eligible for Zero Data Retention. Strict rate limits (429 rate_limit_exceeded, honor Retry-After); 400 for malformed/unsupported/blocked file; 404 for orgs without access. Requires SDK python>=2.52.0."),206]207208# ----------------------------------------------------------------------------------------------------------------209# PARAMETERS210# ----------------------------------------------------------------------------------------------------------------211212213def prm(endpoint, name, typ, desc, *, location="body", required=False, default=None, minimum=None, maximum=None,214        enum=None, models=None, status=None, source=None, notes=None):215    return {216        "provider": P, "endpoint": endpoint, "parameter": name, "location": location, "type": typ,217        "required": required, "default": default, "minimum": minimum, "maximum": maximum, "enum": enum,218        "description": desc, "compatible_models": models, "beta_header": None,219        "status": status or ["DOCUMENTED"], "source": source or REF, "notes": notes,220    }221222223IMG_REF = REF + "images"224GEN = "POST /v1/images/generations"225EDT = "POST /v1/images/edits"226VAR = "POST /v1/images/variations"227LV = ["DOCUMENTED", "LIVE_VERIFIED"]228SIZE_DESC = ("WIDTHxHEIGHT. gpt-image-2 / 2.5 accept arbitrary sizes: both edges multiples of 16, aspect ratio between 1:3 and 3:1, max edge 3840, total pixels 655,360-8,294,400; above 2560x1440 is experimental. "229             "Other GPT image models: 1024x1024, 1536x1024, 1024x1536, auto. dall-e-2: 256x256, 512x512, 1024x1024. dall-e-3: 1024x1024, 1792x1024, 1024x1792.")230231PARAMS_IMAGES = [232    prm(GEN, "prompt", "string", "Text description. Max 32000 chars for GPT image models, 1000 for dall-e-2, 4000 for dall-e-3.", required=True, models=IMAGE_MODELS_GEN, status=LV, source=IMG_REF),233    prm(GEN, "model", "string", "Image model id. Docs: defaults to dall-e-2 unless a GPT-image-only parameter is present; DALL-E removed 2026-05-12 so always set it.", default="dall-e-2 (documented; effectively required)", enum=IMAGE_MODELS_GEN, models=IMAGE_MODELS_GEN, status=LV, source=IMG_REF, notes="Live: dall-e-3 -> 400 \"The model 'dall-e-3' does not exist.\" chatgpt-image-latest is in the edits enum and its model page lists generations as supported."),234    prm(GEN, "n", "integer|null", "Number of images. 1-10; dall-e-3 only n=1.", default=1, minimum=1, maximum=10, models=IMAGE_MODELS_GEN, status=LV, source=IMG_REF),235    prm(GEN, "quality", "string|null", "auto (default) | low | medium | high (GPT image) | xhigh, max (gpt-image-2.5 only) | standard, hd (dall-e-3) | standard (dall-e-2).", default="auto", enum=["auto", "low", "medium", "high", "xhigh", "max", "standard", "hd"], models=IMAGE_MODELS_GEN, status=LV, source=IMG_REF, notes="Live: quality=low accepted by gpt-image-1-mini and echoed in the response."),236    prm(GEN, "size", "string|null", SIZE_DESC, default="auto (spec) / 1024x1024 (edits spec)", enum=["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "1792x1024", "1024x1792", "WIDTHxHEIGHT (gpt-image-2/2.5)"], models=IMAGE_MODELS_GEN, status=LV, source=IMG_REF, notes="Live gpt-image-1-mini size=123x456 -> 400 image_generation_user_error param=size code=invalid_value 'Supported sizes are 1024x1024, 1024x1536, 1536x1024, and auto.'"),237    prm(GEN, "background", "string|null", "transparent | opaque | auto (default). GPT image models only. gpt-image-2.5 fully supports transparent; gpt-image-2 transparent is PREVIEW. With transparent use output_format png or webp.", default="auto", enum=["transparent", "opaque", "auto"], models=GPT_IMAGE_ALL, source=IMG_REF, notes="Response echoes background ('opaque' observed live)."),238    prm(GEN, "output_format", "string|null", "png (default) | jpeg | webp. GPT image models only (DALL-E used response_format).", default="png", enum=["png", "jpeg", "webp"], models=GPT_IMAGE_ALL, status=LV, source=IMG_REF),239    prm(GEN, "output_compression", "integer|null", "Compression 0-100 for jpeg/webp output. GPT image models only.", default=100, minimum=0, maximum=100, models=GPT_IMAGE_ALL, source=IMG_REF),240    prm(GEN, "moderation", "string|null", "Content-moderation strictness: auto (default) or low (less restrictive). GPT image models only.", default="auto", enum=["low", "auto"], models=GPT_IMAGE_ALL, source=IMG_REF),241    prm(GEN, "stream", "boolean|null", "Stream partial images as SSE events (image_generation.partial_image / .completed). GPT image models only.", default=False, models=GPT_IMAGE_ALL, source=IMG_REF),242    prm(GEN, "partial_images", "integer|null", "Number of partial images to emit while streaming (0-3). 0 = final image only in one event. Each partial image costs +100 image output tokens.", default=0, minimum=0, maximum=3, models=GPT_IMAGE_ALL, source=IMG_REF),243    prm(GEN, "response_format", "string|null", "url | b64_json. dall-e-2/3 only (URLs valid 60 min). GPT image models always return b64_json.", default="url", enum=["url", "b64_json"], models=DALLE, status=["DOCUMENTED", "RETIRED"], source=IMG_REF, notes="Live: on gpt-image-1-mini -> 400 unknown_parameter param=response_format."),244    prm(GEN, "style", "string|null", "vivid (default) | natural. dall-e-3 only.", default="vivid", enum=["vivid", "natural"], models=["dall-e-3"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF, notes="Live: on gpt-image-1-mini -> 400 unknown_parameter param=style."),245    prm(GEN, "user", "string", "Stable end-user identifier for abuse monitoring (see safety identifiers).", models=IMAGE_MODELS_GEN, source=IMG_REF),246    # edits247    prm(EDT, "image", "file | file[] (multipart)", "Image(s) to edit, multipart field `image` or repeated `image[]`. GPT image models: up to 16 images, each png/webp/jpg < 50 MB. dall-e-2: one square PNG < 4 MB.", required=True, maximum=16, models=IMAGE_MODELS_EDIT, source=IMG_REF, notes="Required in the OpenAPI multipart schema (with prompt)."),248    prm(EDT, "images", "array<{file_id?, image_url?}> (JSON)", "JSON-body alternative to multipart `image`: up to 16 references, each with exactly one of file_id (Files API) or image_url (URL or base64 data URL).", maximum=16, models=GPT_IMAGE_ALL, source=IMG_REF),249    prm(EDT, "prompt", "string", "Desired edit. Max 32000 chars (GPT image), 1000 (dall-e-2).", required=True, models=IMAGE_MODELS_EDIT, source=IMG_REF),250    prm(EDT, "mask", "file (multipart) | {file_id?, image_url?} (JSON)", "PNG whose fully transparent pixels (alpha 0) mark the area to edit; applied to the first image; must have an alpha channel and the same size/format as the image. Masking with GPT Image is prompt-guided (not pixel-exact).", models=IMAGE_MODELS_EDIT, source=IMG_REF),251    prm(EDT, "model", "string|null", "Edit model. Spec default gpt-image-1.5.", default="gpt-image-1.5", enum=IMAGE_MODELS_EDIT, models=IMAGE_MODELS_EDIT, source=IMG_REF),252    prm(EDT, "input_fidelity", "string|null", "high | low (default). How strongly input details (esp. faces) are preserved. Supported for gpt-image-1, gpt-image-1.5 and later; NOT gpt-image-1-mini; gpt-image-2 always uses high (parameter rejected/ignored).", default="low", enum=["high", "low"], models=["gpt-image-1", "gpt-image-1.5", "chatgpt-image-latest", "gpt-image-2.5-sunburst", "gpt-image-2.5-flare"], source=IMG_REF, notes="Changelog: a bug made gpt-image-1.5/chatgpt-image-latest use high fidelity even with low; fixed."),253    prm(EDT, "background", "string|null", "transparent | opaque | auto. GPT image models only (gpt-image-2 transparent = preview).", default="auto", enum=["transparent", "opaque", "auto"], models=GPT_IMAGE_ALL, source=IMG_REF),254    prm(EDT, "quality", "string|null", "auto | low | medium | high; xhigh, max for gpt-image-2.5; standard for dall-e-2.", default="auto", enum=["auto", "low", "medium", "high", "xhigh", "max", "standard"], models=IMAGE_MODELS_EDIT, source=IMG_REF),255    prm(EDT, "size", "string|null", SIZE_DESC + " Edits spec default 1024x1024.", default="1024x1024", enum=["auto", "1024x1024", "1536x1024", "1024x1536", "256x256", "512x512", "WIDTHxHEIGHT (gpt-image-2/2.5)"], models=IMAGE_MODELS_EDIT, source=IMG_REF),256    prm(EDT, "n", "integer|null", "1-10 edited images.", default=1, minimum=1, maximum=10, models=IMAGE_MODELS_EDIT, source=IMG_REF),257    prm(EDT, "moderation", "string|null", "low | auto. GPT image models.", default="auto", enum=["low", "auto"], models=GPT_IMAGE_ALL, source=IMG_REF),258    prm(EDT, "output_format", "string|null", "png | jpeg | webp (GPT image models).", default="png", enum=["png", "jpeg", "webp"], models=GPT_IMAGE_ALL, source=IMG_REF),259    prm(EDT, "output_compression", "integer|null", "0-100 for jpeg/webp.", default=100, minimum=0, maximum=100, models=GPT_IMAGE_ALL, source=IMG_REF),260    prm(EDT, "response_format", "string|null", "url | b64_json, dall-e-2 only.", default="url", enum=["url", "b64_json"], models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF),261    prm(EDT, "stream", "boolean|null", "Stream image_edit.partial_image / image_edit.completed events.", default=False, models=GPT_IMAGE_ALL, source=IMG_REF),262    prm(EDT, "partial_images", "integer|null", "0-3 partial images while streaming (+100 output tokens each).", default=0, minimum=0, maximum=3, models=GPT_IMAGE_ALL, source=IMG_REF),263    prm(EDT, "user", "string", "End-user identifier.", models=IMAGE_MODELS_EDIT, source=IMG_REF),264    # variations265    prm(VAR, "image", "file (multipart)", "Square PNG < 4 MB.", required=True, models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF),266    prm(VAR, "model", "string|null", "Only dall-e-2.", default="dall-e-2", enum=["dall-e-2"], models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF),267    prm(VAR, "n", "integer|null", "1-10.", default=1, minimum=1, maximum=10, models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF),268    prm(VAR, "response_format", "string|null", "url | b64_json.", default="url", enum=["url", "b64_json"], models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF),269    prm(VAR, "size", "string|null", "256x256 | 512x512 | 1024x1024.", default="1024x1024", enum=["256x256", "512x512", "1024x1024"], models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF, notes="Live 2026-09-18: whole endpoint returns 404 with empty body."),270    prm(VAR, "user", "string", "End-user identifier.", models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF),271]272273VREF = REF + "videos"274VG = GUIDE + "video-generation"275DEP = ["DOCUMENTED", "DEPRECATED"]276PARAMS_VIDEOS = [277    prm("POST /v1/videos", "prompt", "string", "Text prompt (1-32000 chars) describing subject, camera, lighting, motion.", required=True, minimum=1, maximum=32000, models=VIDEO_MODELS, status=DEP, source=VREF),278    prm("POST /v1/videos", "model", "string", "sora-2 (default) | sora-2-pro | dated snapshots. sora-2 alias -> sora-2-2025-12-08; sora-2-pro -> sora-2-pro-2025-10-06.", default="sora-2", enum=VIDEO_MODELS, models=VIDEO_MODELS, status=DEP, source=VREF),279    prm("POST /v1/videos", "seconds", "string", "Clip duration as a string. Spec enum '4' | '8' | '12' (default '4'); guide/changelog (2026-03-12): both models also accept '16' and '20'; extensions doc lists 4, 8, 12, 16, 20.", default="4", enum=["4", "8", "12", "16 (guide)", "20 (guide)"], models=VIDEO_MODELS, status=DEP, source=VREF, notes="Spec enum lags the guide."),280    prm("POST /v1/videos", "size", "string", "Resolution WIDTHxHEIGHT. Spec enum: 720x1280 (default), 1280x720, 1024x1792, 1792x1024. Guide + pricing add 1080x1920 / 1920x1080 (sora-2-pro only, $0.70/s). 1024p and 1080p are sora-2-pro only.", default="720x1280", enum=["720x1280", "1280x720", "1024x1792", "1792x1024", "1080x1920 (pro, guide)", "1920x1080 (pro, guide)"], models=VIDEO_MODELS, status=DEP, source=VREF),281    prm("POST /v1/videos", "input_reference", "file (multipart) | {file_id?, image_url?} (JSON)", "Optional first-frame image reference. Multipart: upload bytes (`-F input_reference=@img.jpeg;type=image/jpeg`). JSON/Batch: object with exactly one of file_id or image_url (data URL allowed, <= 20 MiB). Images with human faces are rejected.", models=VIDEO_MODELS, status=DEP, source=VREF),282    prm("POST /v1/videos", "characters", "array<{id: string}>", "Reusable character ids from POST /v1/videos/characters (max 2 per video); mention the character name verbatim in the prompt. Combinable with input_reference; not supported by extensions.", maximum=2, models=VIDEO_MODELS, status=["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], source=VG, notes="Documented only in the guide; ABSENT from the OpenAPI spec bodies (CreateVideoJsonBody/MultipartBody)."),283    prm("GET /v1/videos", "after", "string", "Cursor: id of the last item of the previous page.", location="query", status=DEP, source=VREF),284    prm("GET /v1/videos", "limit", "integer", "Page size.", location="query", status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], source=VREF, notes="limit=2 accepted live."),285    prm("GET /v1/videos", "order", "string", "asc | desc by timestamp.", location="query", enum=["asc", "desc"], status=DEP, source=VREF),286    prm("GET /v1/videos/{video_id}", "video_id", "string", "Video job id (must start with 'video').", location="path", required=True, status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], source=VREF, notes="Live: a path segment not starting with 'video' -> 400 invalid_value param=video_id."),287    prm("DELETE /v1/videos/{video_id}", "video_id", "string", "Video job id.", location="path", required=True, status=DEP, source=VREF),288    prm("GET /v1/videos/{video_id}/content", "video_id", "string", "Video job id.", location="path", required=True, status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], source=VREF),289    prm("GET /v1/videos/{video_id}/content", "variant", "string", "video (default, MP4) | thumbnail (webp) | spritesheet (jpg).", location="query", default="video", enum=["video", "thumbnail", "spritesheet"], status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], source=VREF, notes="variant=thumbnail accepted live (asset expired -> 404)."),290    prm("POST /v1/videos/{video_id}/remix", "video_id", "string", "Completed source video id.", location="path", required=True, status=DEP, source=VREF),291    prm("POST /v1/videos/{video_id}/remix", "prompt", "string", "Updated prompt for the remix.", required=True, status=DEP, source=VREF),292    prm("POST /v1/videos/edits", "video", "{id: string} (JSON) | file (multipart)", "Completed video to edit (id) or an uploaded MP4 (eligible customers only). Model is inferred from the id.", required=True, status=DEP, source=VREF),293    prm("POST /v1/videos/edits", "prompt", "string", "How to edit the source video (1-32000 chars). Keep to one clear adjustment.", required=True, minimum=1, maximum=32000, status=DEP, source=VREF),294    prm("POST /v1/videos/edits", "model", "string", "Required only when uploading a new video (multipart).", enum=VIDEO_MODELS, status=DEP, source=VG),295    prm("POST /v1/videos/extensions", "video", "{id: string} (JSON) | file (multipart)", "Completed video to extend.", required=True, status=DEP, source=VREF),296    prm("POST /v1/videos/extensions", "prompt", "string", "How the scene continues (1-32000 chars).", required=True, minimum=1, maximum=32000, status=DEP, source=VREF),297    prm("POST /v1/videos/extensions", "seconds", "string", "Length of the new segment: 4, 8, 12, 16, 20 (spec enum only lists 4/8/12). Up to 6 extensions, 120 s total.", required=True, enum=["4", "8", "12", "16", "20"], status=DEP, source=VREF),298    prm("POST /v1/videos/characters", "video", "file (multipart, video/mp4)", "Short clip (2-4 s, 16:9 or 9:16, 720p-1080p) of a non-human subject.", required=True, status=DEP, source=VREF),299    prm("POST /v1/videos/characters", "name", "string", "Display name (1-80 chars); must be used verbatim in prompts.", required=True, minimum=1, maximum=80, status=DEP, source=VREF),300    prm("GET /v1/videos/characters/{character_id}", "character_id", "string", "Character id.", location="path", required=True, status=DEP, source=VREF),301]302303EREF = REF + "embeddings"304PARAMS_EMB = [305    prm("POST /v1/embeddings", "input", "string | string[] | integer[] | integer[][]", "Text(s) or token array(s) to embed. Non-empty; <= 8192 tokens per input; arrays 1-2048 items; <= 300,000 tokens summed per request. Tokenizer cl100k_base.", required=True, minimum=1, maximum=2048, models=EMB_MODELS, status=LV, source=EREF, notes="Live: string 'OK' and token array [[11380]] both accepted."),306    prm("POST /v1/embeddings", "model", "string", "text-embedding-3-small | text-embedding-3-large | text-embedding-ada-002.", required=True, enum=EMB_MODELS, models=EMB_MODELS, status=LV, source=EREF, notes="Live: ada-002 responds with model 'text-embedding-ada-002-v2'."),307    prm("POST /v1/embeddings", "dimensions", "integer", "Output dimensions (Matryoshka truncation, re-normalized by the API). text-embedding-3-* only; 1..native size (1536 small / 3072 large).", minimum=1, maximum=3072, models=["text-embedding-3-small", "text-embedding-3-large"], status=LV, source=EREF, notes="Live: dimensions=16 on 3-small -> 16 unit-norm floats; on ada-002 -> 400 'This model does not support specifying dimensions.'"),308    prm("POST /v1/embeddings", "encoding_format", "string", "float (default, JSON numbers) | base64 (little-endian float32 bytes, smaller payload).", default="float", enum=["float", "base64"], models=EMB_MODELS, status=LV, source=EREF, notes="Live: base64 of 16 dims = 64 bytes = 88 base64 chars."),309    prm("POST /v1/embeddings", "user", "string", "End-user identifier for abuse monitoring.", models=EMB_MODELS, source=EREF),310]311312MREF = REF + "moderations"313PARAMS_MOD = [314    prm("POST /v1/moderations", "input", "string | string[] | array<{type:'text', text} | {type:'image_url', image_url:{url}}>", "Content to classify: one string, many strings, or a multimodal array. Images: URL or base64 data URL, <= 20 MB; only omni-moderation models accept images.", required=True, models=MOD_MODELS, status=LV, source=MREF),315    prm("POST /v1/moderations", "input[].type", "string", "text | image_url (multimodal array items).", enum=["text", "image_url"], models=MOD_MODELS, status=LV, source=MREF),316    prm("POST /v1/moderations", "input[].text", "string", "Text to classify (type=text).", models=MOD_MODELS, status=LV, source=MREF),317    prm("POST /v1/moderations", "input[].image_url.url", "string (uri | data URL)", "Image URL or data:image/...;base64,... (type=image_url).", models=MOD_MODELS, status=LV, source=MREF, notes="Live: 1x1 PNG data URL accepted."),318    prm("POST /v1/moderations", "model", "string", "omni-moderation-latest (default) | omni-moderation-2024-09-26. text-moderation-latest/stable/007 were removed 2025-10-27.", default="omni-moderation-latest", enum=MOD_MODELS + MOD_LEGACY, models=MOD_MODELS, status=LV, source=MREF, notes="Live: text-moderation-latest -> 400 invalid_request_error param=model 'Invalid value for model'."),319]320321MM = GUIDE + "safety-checks/misalignment-monitoring"322SBP = GUIDE + "safety-best-practices"323PARAMS_SAFETY = [324    prm("GET /v1/safety/alerts/{id}", "id", "string", "Project safety alert id (maxLength 38; webhook pattern ^alert_[0-9a-f]{32}$; guide examples use 'salert_123').", location="path", required=True, maximum=38, status=LV, source=MM, notes="Live: unknown id -> 404 code safety_alert_not_found."),325    prm("GET /v1/safety/cases/{id}", "id", "string", "Safety case id (maxLength 128; webhook example 'C-abc123').", location="path", required=True, maximum=128, status=["LIVE_DISCOVERED", "ACCOUNT_RESTRICTED"], source=SPEC, notes="Live: 403 missing scope api.safety.read."),326    prm("POST /v1/content_provenance_checks", "file", "file (multipart; set the part's media type)", "Image (image/png, image/jpeg, image/webp) or audio (MP3, Opus as audio/ogg, AAC, FLAC, WAV, PCM; <= 60 s decoded). <= 50 MiB, one file per request. Do not add a separate `type` field.", required=True, status=LV, source=GUIDE + "content-provenance"),327    prm("POST /v1/responses", "safety_identifier", "string|null", "Stable, privacy-preserving end-user id (hash of username/email), max 64 chars. Lets OpenAI block an abusive end user instead of the whole org; blocked ids get an 'identifier blocked' error. Replaces the legacy `user` field together with prompt_cache_key.", maximum=64, status=["DOCUMENTED"], source=SBP, notes="Owned by the Responses agent for the full endpoint; recorded here for the safety cross-reference."),328    prm("POST /v1/chat/completions", "safety_identifier", "string|null", "Same semantics as on /v1/responses (max 64 chars).", maximum=64, status=["DOCUMENTED"], source=SBP),329    prm("POST /v1/realtime/client_secrets", "OpenAI-Safety-Identifier", "string", "Header carrying the safety identifier for Realtime sessions (also on direct WebSocket/WebRTC connection requests from a trusted backend). Identifiers do not carry over between APIs or sessions.", location="header", status=["DOCUMENTED"], source=GUIDE + "safety-checks"),330    prm("POST /v1/responses", "moderation", "{model: string}", "Top-level object requesting moderation scores for the input and the generated output in the same response (omni-moderation-latest). Scores arrive after the full output when streaming; may contain an error object if moderation failed.", status=["DOCUMENTED"], source=GUIDE + "moderation", notes="Also accepted by POST /v1/chat/completions (changelog). Full endpoint owned by other agents."),331]332333# ----------------------------------------------------------------------------------------------------------------334# STREAMING EVENTS335# ----------------------------------------------------------------------------------------------------------------336COMMON_IMG_FIELDS = {"b64_json": "string (base64 image bytes)", "background": "transparent|opaque|auto", "created_at": "integer (unix s)",337                     "output_format": "png|webp|jpeg", "quality": "low|medium|high|xhigh|max|auto",338                     "size": "WIDTHxHEIGHT string | 1024x1024 | 1024x1536 | 1536x1024 | auto"}339USAGE_SCHEMA = {"input_tokens": "integer", "input_tokens_details": {"image_tokens": "integer", "text_tokens": "integer"},340                "output_tokens": "integer (image output tokens)", "total_tokens": "integer"}341342343def sev(event, api, desc, schema, example, source):344    return {"provider": P, "api": api, "direction": "server→client", "event": event, "description": desc,345            "schema": schema, "example": example, "source": source, "status": ["DOCUMENTED", "UNVERIFIED"],346            "transport": "SSE: `event: <name>` line followed by `data: <json>`; only with stream=true on GPT image models"}347348349STREAM_EVENTS = [350    sev("image_generation.partial_image", "POST /v1/images/generations",351        "A partial (progressively refined) image is available. Emitted up to `partial_images` times (0-3); may be fewer if the final image is ready sooner. Each partial image bills +100 image output tokens.",352        {**COMMON_IMG_FIELDS, "partial_image_index": "integer (0-based)", "type": "image_generation.partial_image"},353        {"type": "image_generation.partial_image", "b64_json": "...", "partial_image_index": 0}, REF + "images/generation-streaming-events"),354    sev("image_generation.completed", "POST /v1/images/generations",355        "Final image is available; carries the full usage block.",356        {**COMMON_IMG_FIELDS, "type": "image_generation.completed", "usage": USAGE_SCHEMA},357        {"type": "image_generation.completed", "b64_json": "...", "usage": {"total_tokens": 100, "input_tokens": 50, "output_tokens": 50, "input_tokens_details": {"text_tokens": 10, "image_tokens": 40}}}, REF + "images/generation-streaming-events"),358    sev("image_edit.partial_image", "POST /v1/images/edits",359        "Partial edited image during streaming edits.",360        {**COMMON_IMG_FIELDS, "partial_image_index": "integer (0-based)", "type": "image_edit.partial_image"},361        {"type": "image_edit.partial_image", "b64_json": "...", "partial_image_index": 0}, REF + "images/edit-streaming-events"),362    sev("image_edit.completed", "POST /v1/images/edits",363        "Final edited image with usage.",364        {**COMMON_IMG_FIELDS, "type": "image_edit.completed", "usage": USAGE_SCHEMA},365        {"type": "image_edit.completed", "b64_json": "...", "usage": {"total_tokens": 100, "input_tokens": 50, "output_tokens": 50, "input_tokens_details": {"text_tokens": 10, "image_tokens": 40}}}, REF + "images/edit-streaming-events"),366]367368# ----------------------------------------------------------------------------------------------------------------369# OBJECTS370# ----------------------------------------------------------------------------------------------------------------371372373def obj(name, api, desc, fields, *, status, source, example=None, notes=None):374    return {"provider": P, "object": name, "api_family": api, "description": desc, "fields": fields, "status": status,375            "example": example, "notes": notes, "sources": src(*([source] if isinstance(source, str) else source))}376377378OBJECTS = [379    obj("ImagesResponse", "images", "Response of generations/edits/variations.",380        {"created": "integer unix s", "data": "Image[]", "background": "transparent|opaque (GPT image)", "output_format": "png|webp|jpeg",381         "quality": "low|medium|high|xhigh|max", "size": "string", "usage": "ImageUsage (GPT image models)"},382        status=LV, source=REF + "images",383        example={"created": 1789782224, "background": "opaque", "output_format": "jpeg", "quality": "low", "size": "1024x1024",384                 "data": [{"b64_json": "<39383 bytes of JPEG, base64>", "generation_id": "<opaque id>"}],385                 "usage": {"input_tokens": 10, "input_tokens_details": {"image_tokens": 0, "text_tokens": 10}, "output_tokens": 272,386                           "output_tokens_details": {"image_tokens": 272, "text_tokens": 0}, "total_tokens": 282}},387        notes="Live example (gpt-image-1-mini, low, 1024x1024). `created` present; no `revised_prompt`/`url` for GPT image models."),388    obj("Image", "images", "One generated image.",389        {"b64_json": "string (default for GPT image; dall-e only with response_format=b64_json)", "url": "string (dall-e only, 60-min validity; unsupported for GPT image)",390         "revised_prompt": "string (dall-e-3 only)", "generation_id": "string — LIVE_DISCOVERED, undocumented (observed on gpt-image-1-mini generations)"},391        status=["DOCUMENTED", "LIVE_VERIFIED", "LIVE_DISCOVERED"], source=REF + "images"),392    obj("ImageUsage", "images", "Token usage for GPT image models (docs text still says 'gpt-image-1 only' but present for all GPT image models).",393        {"input_tokens": "int", "input_tokens_details": {"image_tokens": "int", "text_tokens": "int"}, "output_tokens": "int",394         "output_tokens_details": {"image_tokens": "int", "text_tokens": "int"}, "total_tokens": "int"}, status=LV, source=REF + "images"),395    obj("Video", "videos", "Video generation job.",396        {"id": "string (video_...)", "object": "'video'", "model": "VideoModel", "status": "queued|in_progress|completed|failed",397         "progress": "integer 0-100", "created_at": "unix s", "completed_at": "unix s|null", "expires_at": "unix s|null (asset expiry, ~48 h after completion observed)",398         "prompt": "string|null", "size": "VideoSize", "seconds": "string (stitched total for extensions)", "remixed_from_video_id": "string|null",399         "error": "VideoCreateError|null", "edited_from_video_id": "string|null — LIVE_DISCOVERED (not in docs/spec)",400         "extended_from_video_id": "string|null — LIVE_DISCOVERED (not in docs/spec)", "quality": "'standard' — appears in a doc example only"},401        status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED", "LIVE_DISCOVERED"], source=REF + "videos",402        example={"id": "video_6a5e...a57a", "object": "video", "created_at": 1784590069, "status": "completed", "completed_at": 1784590117,403                 "edited_from_video_id": None, "error": None, "expires_at": 1784762917, "extended_from_video_id": None, "model": "sora-2",404                 "progress": 100, "prompt": "A simple blue geometric cube rotating on a white background.", "remixed_from_video_id": None,405                 "seconds": "4", "size": "720x1280"}, notes="Live example from GET /v1/videos (2026-09-18). expires_at - completed_at = 172,800 s = 48 h."),406    obj("VideoCreateError", "videos", "Why a job failed.",407        {"code": "string", "message": "string", "misalignment": {"error_type": "potentially_unintended_data_transfer|potentially_unintended_data_access|potentially_unintended_destructive_activity|other|<future>",408                                                                 "detailed_explanation": "string", "steer": {"message": "string"}}},409        status=["DOCUMENTED", "DEPRECATED"], source=REF + "videos"),410    obj("VideoList", "videos", "Paginated list.", {"object": "'list'", "data": "Video[]", "first_id": "string|null", "last_id": "string|null", "has_more": "boolean"},411        status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], source=REF + "videos"),412    obj("VideoDeleteResponse", "videos", "Deletion confirmation.", {"id": "string", "deleted": "boolean", "object": "'video.deleted'"}, status=["DOCUMENTED", "DEPRECATED"], source=REF + "videos"),413    obj("VideoCharacterResource", "videos", "Character (cameo) created from an uploaded clip.", {"id": "string|null", "name": "string|null", "created_at": "unix s"}, status=["DOCUMENTED", "DEPRECATED"], source=REF + "videos"),414    obj("WebhookEvent video.completed / video.failed", "videos", "Webhook events emitted when a video job reaches a terminal state; payload carries only the job id (fetch details with GET /v1/videos/{id}).",415        {"id": "evt_...", "object": "'event'", "created_at": "unix s", "type": "video.completed|video.failed", "data": {"id": "video_..."}},416        status=["DOCUMENTED", "DEPRECATED"], source=[GUIDE + "video-generation", REF + "webhooks"],417        example={"id": "evt_abc123", "object": "event", "created_at": 1758941485, "type": "video.completed", "data": {"id": "video_abc123"}}),418    obj("CreateEmbeddingResponse", "embeddings", "Embeddings list.", {"object": "'list'", "data": "Embedding[] (same order as input; index field)", "model": "string (e.g. text-embedding-ada-002-v2)", "usage": {"prompt_tokens": "int", "total_tokens": "int"}},419        status=LV, source=REF + "embeddings", example={"object": "list", "data": [{"object": "embedding", "index": 0, "embedding": ["<1536 floats>"]}], "model": "text-embedding-3-small", "usage": {"prompt_tokens": 1, "total_tokens": 1}}),420    obj("Embedding", "embeddings", "One vector.", {"object": "'embedding'", "index": "int", "embedding": "float[] (encoding_format=float) | base64 string of little-endian float32 (encoding_format=base64)"},421        status=LV, source=REF + "embeddings", notes="Live lengths: 1536 (3-small, ada-002), 3072 (3-large); L2 norm 1.000±0.0003 (also after dimensions=16)."),422    obj("CreateModerationResponse", "moderations", "Moderation response.", {"id": "string (modr-...)", "model": "string", "results": "Moderation[] (one per input item; multimodal array = one result)"}, status=LV, source=REF + "moderations"),423    obj("Moderation", "moderations", "Per-input classification.",424        {"flagged": "boolean (any category flagged)",425         "categories": "object<category, boolean> — 13 keys: harassment, harassment/threatening, hate, hate/threatening, illicit, illicit/violent, self-harm, self-harm/instructions, self-harm/intent, sexual, sexual/minors, violence, violence/graphic (illicit* nullable for legacy models)",426         "category_scores": "object<category, number 0-1> model confidence; thresholds may be recalibrated over time",427         "category_applied_input_types": "object<category, ('text'|'image')[]> — image supported only for self-harm*, sexual, violence, violence/graphic"},428        status=LV, source=REF + "moderations",429        example={"flagged": False, "categories": {"violence": False, "...": "..."}, "category_scores": {"violence": 0.00054, "...": "..."}, "category_applied_input_types": {"violence": ["text", "image"], "harassment": ["text"]}},430        notes="Live: with image present, image-capable categories list ['text','image']; text-only input lists ['text'] for all 13."),431    obj("SafetyAlertResource (safety.alert)", "safety", "Misalignment-monitoring alert for one request.",432        {"id": "string", "object": "'safety.alert'", "created_at": "unix s", "request_id": "string", "response_id": "string", "model": "string",433         "request_paused": "boolean — block registration succeeded (does not confirm execution stopped)",434         "error_type": "potentially_unintended_data_transfer|potentially_unintended_data_access|potentially_unintended_destructive_activity|other",435         "reason": "string|null (null for ZDR requests)"},436        status=["DOCUMENTED", "LIVE_VERIFIED"], source=[SPEC, MM], notes="Endpoint reachable (404 safety_alert_not_found for unknown id)."),437    obj("SafetyCaseResource (safety.case)", "safety", "Warning or deactivation issued for a safety identifier.",438        {"id": "string", "object": "'safety.case'", "created_at": "unix s", "entity_identifier": "string (the safety identifier)", "reason": "string|null", "notice": {"type": "warning|deactivation"}},439        status=["LIVE_DISCOVERED", "ACCOUNT_RESTRICTED"], source=SPEC, notes="Requires api.safety.read scope (403 for our key)."),440    obj("Safety webhook events", "safety", "safety.alert.created (project) / safety.org_alert.created (enterprise workspace) carry an alert id for GET /v1/safety/alerts/{id}; safety.warning_issued / safety.deactivation_issued carry a case id for GET /v1/safety/cases/{id}.",441        {"id": "evt_...", "object": "'event'", "created_at": "unix s", "type": "safety.alert.created|safety.org_alert.created|safety.warning_issued|safety.deactivation_issued", "data": {"id": "alert_<32 hex> | C-..."}},442        status=["DOCUMENTED"], source=[SPEC, MM], example={"object": "event", "id": "evt_123", "type": "safety.alert.created", "created_at": 1787659200, "data": {"id": "alert_0123456789abcdef0123456789abcdef"}}),443    obj("ProvenanceResource (content_provenance_check)", "content_provenance", "Synchronous provenance verdicts.",444        {"object": "'content_provenance_check'", "created_at": "unix s", "results": "(C2PAProvenanceResult | SynthIDProvenanceResult)[] — images: c2pa + synthid; audio: synthid only; inapplicable checks are omitted"},445        status=LV, source=GUIDE + "content-provenance",446        example={"object": "content_provenance_check", "created_at": 1789782225, "results": [447            {"type": "c2pa", "outcome": "detected", "validation_state": "trusted", "issuer": "OpenAI OpCo, LLC", "model": "API / gpt-image", "generated_at": "2026-09-19T01:43:43.564995Z"},448            {"type": "synthid", "outcome": "not_detected", "model": None, "generated_at": None}]},449        notes="Live on a fresh gpt-image-1-mini JPEG. Note model string 'API / gpt-image' (docs example: 'gpt-image')."),450    obj("C2PAProvenanceResult", "content_provenance", "C2PA Content Credentials verdict (images).",451        {"type": "'c2pa'", "outcome": "detected|not_detected (detected only if trusted/valid manifest issued by OpenAI with an AI-generation action)",452         "validation_state": "trusted|valid|invalid|not_present", "issuer": "string|null", "model": "string|null", "generated_at": "RFC3339|null"}, status=LV, source=GUIDE + "content-provenance"),453    obj("SynthIDProvenanceResult", "content_provenance", "SynthID watermark verdict (images + audio).",454        {"type": "'synthid'", "outcome": "detected|not_detected", "model": "string|null", "generated_at": "RFC3339|null"}, status=LV, source=GUIDE + "content-provenance"),455]456457# ----------------------------------------------------------------------------------------------------------------458# PRICES459# ----------------------------------------------------------------------------------------------------------------460461462def price(model, dim, val, unit, tier="standard", notes=None, source=PRICING):463    return {"provider": P, "model_or_service": model, "dimension": dim, "price": val, "currency": "USD", "unit": unit, "tier": tier,464            "effective_notes": notes, "source": source, "retrieved_at": RET}465466467PRICES = []468for m, (ti, tc, to, ii, ic, io) in {469    "gpt-image-2.5-sunburst": (5, 1.25, None, 8, 2, 30), "gpt-image-2.5-flare": (5, 1.25, None, 8, 2, 30), "gpt-image-2": (5, 1.25, None, 8, 2, 30),470    "gpt-image-1.5": (5, 1.25, 10, 8, 2, 32), "chatgpt-image-latest": (5, 1.25, 10, 8, 2, 32), "gpt-image-1": (5, 1.25, None, 10, 2.5, 40),471    "gpt-image-1-mini": (2, 0.2, None, 2.5, 0.25, 8)}.items():472    PRICES += [price(m, "text_input", ti, "per 1M tokens"), price(m, "cached_text_input", tc, "per 1M tokens"),473               price(m, "image_input", ii, "per 1M tokens"), price(m, "cached_image_input", ic, "per 1M tokens"), price(m, "image_output", io, "per 1M tokens")]474    if to:475        PRICES.append(price(m, "text_output", to, "per 1M tokens"))476for m, (ti, tc, to, ii, ic, io) in {"gpt-image-2": (2.5, 0.625, None, 4, 1, 15), "gpt-image-1.5": (2.5, 0.63, 5, 4, 1, 16), "gpt-image-1-mini": (1, 0.1, None, 1.25, 0.13, 4),477                                    "gpt-image-1": (2.5, 0.63, None, 5, 1.25, 20), "chatgpt-image-latest": (2.5, 0.63, 5, 4, 1, 16)}.items():478    PRICES += [price(m, "text_input", ti, "per 1M tokens", "batch"), price(m, "cached_text_input", tc, "per 1M tokens", "batch"),479               price(m, "image_input", ii, "per 1M tokens", "batch"), price(m, "cached_image_input", ic, "per 1M tokens", "batch"), price(m, "image_output", io, "per 1M tokens", "batch")]480    if to:481        PRICES.append(price(m, "text_output", to, "per 1M tokens", "batch"))482LEGACY_PER_IMAGE = {"gpt-image-1.5": {"low": (0.009, 0.013, 0.013), "medium": (0.034, 0.05, 0.05), "high": (0.133, 0.2, 0.2)},483                    "chatgpt-image-latest": {"low": (0.009, 0.013, 0.013), "medium": (0.034, 0.05, 0.05), "high": (0.133, 0.2, 0.2)},484                    "gpt-image-1": {"low": (0.011, 0.016, 0.016), "medium": (0.042, 0.063, 0.063), "high": (0.167, 0.25, 0.25)},485                    "gpt-image-1-mini": {"low": (0.005, 0.006, 0.006), "medium": (0.011, 0.015, 0.015), "high": (0.036, 0.052, 0.052)},486                    "gpt-image-2": {"low": (0.006, 0.005, 0.005), "medium": (0.053, 0.041, 0.041), "high": (0.211, 0.165, 0.165)}}487for m, qs in LEGACY_PER_IMAGE.items():488    for q, (sq, po, la) in qs.items():489        for size, v in (("1024x1024", sq), ("1024x1536", po), ("1536x1024", la)):490            PRICES.append(price(m, f"image_output {q} {size}", v, "per image", notes="Output-token equivalent; text/image input tokens billed on top. gpt-image-2 row from the guide's comparison table.",491                                source=MODELS + m if m != "gpt-image-2" else GUIDE + "image-generation"))492for m, res, pv, bv in (("sora-2", "720p (720x1280|1280x720)", 0.10, 0.05), ("sora-2-pro", "720p (720x1280|1280x720)", 0.30, 0.15),493                       ("sora-2-pro", "1024p (1024x1792|1792x1024)", 0.50, 0.25), ("sora-2-pro", "1080p (1080x1920|1920x1080)", 0.70, 0.35)):494    PRICES.append(price(m, f"video_output {res}", pv, "per second"))495    PRICES.append(price(m, f"video_output {res}", bv, "per second", "batch"))496PRICES += [price("text-embedding-3-small", "input", 0.02, "per 1M tokens"), price("text-embedding-3-large", "input", 0.13, "per 1M tokens"),497           price("text-embedding-ada-002", "input", 0.10, "per 1M tokens"), price("omni-moderation-latest", "input", 0.0, "per call", notes="Free"),498           price("content_provenance_checks", "call", 0.0, "per call", notes="No price listed on the pricing page; strict rate limits instead (UNVERIFIED that it is free)")]499500# ----------------------------------------------------------------------------------------------------------------501OUT = {502    "endpoints/openai-images-video-embeddings-moderation.json": ENDPOINTS,503    "parameters/openai-images.json": PARAMS_IMAGES,504    "parameters/openai-videos.json": PARAMS_VIDEOS,505    "parameters/openai-embeddings.json": PARAMS_EMB,506    "parameters/openai-moderations.json": PARAMS_MOD,507    "parameters/openai-safety.json": PARAMS_SAFETY,508    "streaming-events/openai-images.json": STREAM_EVENTS,509    "objects/openai-media-objects.json": OBJECTS,510    "prices/openai-media.json": PRICES,511}512for rel, data in OUT.items():513    p = ROOT / "generated" / "fragments" / rel514    p.parent.mkdir(parents=True, exist_ok=True)515    p.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")516    print(f"{rel}: {len(data)} records")517