#!/usr/bin/env python3 """Generator for the OpenAI media/safety fragments (images, video, embeddings, moderation, safety, provenance). Run from repo root: .venv/bin/python tmp-live/gen_media.py -> writes generated/fragments/**/openai-*.json All facts come from sources/openai/** (retrieved 2026-09-18) plus live probes logged in reports/live-requests.jsonl. """ import json from pathlib import Path ROOT = Path(__file__).resolve().parent.parent RET = "2026-09-18" TODAY = "2026-09-18" P = "openai" REF = "https://developers.openai.com/api/reference/resources/" GUIDE = "https://developers.openai.com/api/docs/guides/" SPEC = "https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml" PRICING = "https://developers.openai.com/api/docs/pricing" DEPREC = "https://developers.openai.com/api/docs/deprecations" MODELS = "https://developers.openai.com/api/docs/models/" def src(*urls): return [{"url": u, "retrieved_at": RET} for u in urls] def ver(method="live_api", result="success", status=200, note="", at=TODAY): return {"method": method, "verified_at": at, "result": result, "http_status": status, "request_note": note} DOCS_ONLY = ver("docs_only", "not_run", None, "documented only; not called in this run") GPT_IMAGE_ALL = ["gpt-image-2.5-sunburst", "gpt-image-2.5-sunburst-2026-09-08", "gpt-image-2.5-flare", "gpt-image-2.5-flare-2026-09-08", "gpt-image-2", "gpt-image-2-2026-04-21", "gpt-image-1.5", "gpt-image-1", "gpt-image-1-mini", "chatgpt-image-latest"] GPT_IMAGE_25 = GPT_IMAGE_ALL[:4] GPT_IMAGE_2X = GPT_IMAGE_ALL[:6] GPT_IMAGE_LEGACY = ["gpt-image-1.5", "gpt-image-1", "gpt-image-1-mini", "chatgpt-image-latest"] DALLE = ["dall-e-2", "dall-e-3"] # RETIRED 2026-05-12 (live: "The model 'dall-e-3' does not exist.") IMAGE_MODELS_GEN = GPT_IMAGE_ALL + DALLE IMAGE_MODELS_EDIT = GPT_IMAGE_ALL + ["dall-e-2"] VIDEO_MODELS = ["sora-2", "sora-2-pro", "sora-2-2025-10-06", "sora-2-pro-2025-10-06", "sora-2-2025-12-08"] EMB_MODELS = ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"] MOD_MODELS = ["omni-moderation-latest", "omni-moderation-2024-09-26"] MOD_LEGACY = ["text-moderation-latest", "text-moderation-stable", "text-moderation-007"] # ---------------------------------------------------------------------------------------------------------------- # ENDPOINTS # ---------------------------------------------------------------------------------------------------------------- IMG_SRC = src(REF + "images", GUIDE + "image-generation", SPEC) VID_SRC = src(REF + "videos", GUIDE + "video-generation", SPEC, DEPREC) VIDEO_DEPRECATION = ("Videos API + all Sora 2 models: deprecation notice 2026-03-24, shutdown 2026-09-24 " "(deprecations page). Still reachable on 2026-09-18.") def ep(method, path, name, description, status, *, family, sdk_py, sdk_node, content_type="application/json", body_ref=None, response=None, streaming=None, pagination=None, verification=None, sources=None, notes=None, auth="Bearer API key", idempotency="none documented", beta_header=None): return { "provider": P, "api_family": family, "method": method, "path": path, "name": name, "description": description, "status": status, "auth": auth, "beta_header": beta_header, "request": {"content_type": content_type, "body_ref": body_ref}, "response": response or {}, "streaming": streaming or {"supported": False, "events_ref": None}, "pagination": pagination, "idempotency": idempotency, "sdk": {"python": sdk_py, "node": sdk_node}, "verification": verification or DOCS_ONLY, "sources": sources or [], "notes": notes, } ENDPOINTS = [ ep("POST", "/v1/images/generations", "Create image", "Creates one or more images from a text prompt. GPT image models return base64 (b64_json) plus token usage; " "supports streaming of partial images (stream=true, partial_images 0-3).", ["DOCUMENTED", "LIVE_VERIFIED"], family="images", sdk_py="client.images.generate(**params)", sdk_node="client.images.generate({...})", body_ref="CreateImageRequest", response={"content_type": "application/json", "object": "ImagesResponse", "live_note": "data[0] contained b64_json AND an undocumented generation_id (LIVE_DISCOVERED); usage present"}, streaming={"supported": True, "events_ref": "openai-images streaming events (image_generation.partial_image, image_generation.completed)", "transport": "SSE, `event:` + `data:` lines"}, 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.\""), 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."), ep("POST", "/v1/images/edits", "Create image edit", "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.", ["DOCUMENTED", "UNVERIFIED"], family="images", sdk_py="client.images.edit(**params)", sdk_node="client.images.edit({...})", content_type="multipart/form-data | application/json", body_ref="CreateImageEditRequest", response={"content_type": "application/json", "object": "ImagesResponse"}, streaming={"supported": True, "events_ref": "openai-images streaming events (image_edit.partial_image, image_edit.completed)"}, 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"), 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)."), ep("POST", "/v1/images/variations", "Create image variation (legacy)", "Creates variations of a square PNG (< 4 MB). Only dall-e-2 was ever supported.", ["DOCUMENTED", "LEGACY", "RETIRED", "FAILED_VERIFICATION"], family="images", sdk_py="client.images.create_variation(**params)", sdk_node="client.images.createVariation({...})", content_type="multipart/form-data", body_ref="CreateImageVariationRequest", response={"content_type": "application/json", "object": "ImagesResponse"}, 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."), sources=IMG_SRC + src(DEPREC), notes="Still present in reference + OpenAPI, but non-functional for our key: treat as RETIRED."), # ---- videos ep("POST", "/v1/videos", "Create video", "Starts an asynchronous Sora video render job (status queued -> in_progress -> completed|failed). Accepts JSON or multipart (multipart allows uploading input_reference bytes).", ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.create(**params) / client.videos.create_and_poll(...)", sdk_node="client.videos.create({...})", content_type="application/json | multipart/form-data", body_ref="CreateVideoJsonBody | CreateVideoMultipartBody", response={"content_type": "application/json", "object": "Video"}, verification=ver("docs_only", "not_run", None, "not called (cost $0.10-$0.70 per second). List/retrieve verified instead."), 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."), ep("GET", "/v1/videos", "List videos", "Lists video jobs of the current project (cursor pagination).", ["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], family="videos", sdk_py="client.videos.list(**params)", sdk_node="client.videos.list({...})", content_type=None, 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)"}, pagination={"style": "cursor", "params": ["after", "limit", "order"], "fields": ["first_id", "last_id", "has_more"]}, verification=ver(note="GET /v1/videos?limit=2 -> 200, 1 completed sora-2 job from 2026-07 (expired assets)"), sources=VID_SRC, notes=VIDEO_DEPRECATION), ep("GET", "/v1/videos/{video_id}", "Retrieve video", "Fetches the latest metadata/status of a video job.", ["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], family="videos", sdk_py="client.videos.retrieve(video_id)", sdk_node="client.videos.retrieve(videoID)", content_type=None, response={"content_type": "application/json", "object": "Video"}, verification=ver(note="existing id -> 200 (status completed, progress 100); unknown id -> 404 invalid_request_error \"Video with id '...' not found.\" (code null)"), sources=VID_SRC, notes=VIDEO_DEPRECATION), ep("DELETE", "/v1/videos/{video_id}", "Delete video", "Permanently deletes a completed or failed video and its stored assets.", ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.delete(video_id)", sdk_node="client.videos.delete(videoID)", content_type=None, response={"content_type": "application/json", "object": "VideoDeleteResponse {id, deleted, object:'video.deleted'}"}, verification=ver("docs_only", "not_run", None, "destructive; not called"), sources=VID_SRC, notes=VIDEO_DEPRECATION), ep("GET", "/v1/videos/{video_id}/content", "Download video content", "Streams the rendered MP4 (default) or a derived asset: ?variant=thumbnail (image/webp) | spritesheet (jpg).", ["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], family="videos", sdk_py="client.videos.download_content(video_id, variant=...)", sdk_node="client.videos.downloadContent(videoID, {variant})", content_type=None, response={"content_type": "video/mp4 | image/webp | image/jpeg | application/json (error)", "object": "binary"}, 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)"), sources=VID_SRC, notes="Assets expire (expires_at; live message says 48 h; batch guide says 24 h after batch completion). " + VIDEO_DEPRECATION), ep("POST", "/v1/videos/{video_id}/remix", "Create video remix (being replaced by edits)", "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.", ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.remix(video_id, prompt=...)", sdk_node="client.videos.remix(videoID, {prompt})", body_ref="{prompt}", response={"content_type": "application/json", "object": "Video (remixed_from_video_id set)"}, sources=VID_SRC + src("https://developers.openai.com/api/docs/changelog"), notes=VIDEO_DEPRECATION), ep("POST", "/v1/videos/edits", "Create video edit", "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.", ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.edit(**params)", sdk_node="client.videos.edit({...})", content_type="application/json | multipart/form-data", body_ref="CreateVideoEditJsonBody | CreateVideoEditMultipartBody", response={"content_type": "application/json", "object": "Video (live list shows edited_from_video_id)"}, sources=VID_SRC, notes=VIDEO_DEPRECATION), ep("POST", "/v1/videos/extensions", "Create video extension", "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.", ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.extend(**params)", sdk_node="client.videos.extend({...})", content_type="application/json | multipart/form-data", body_ref="CreateVideoExtendJsonBody | CreateVideoExtendMultipartBody", response={"content_type": "application/json", "object": "Video (seconds = stitched total; live list shows extended_from_video_id)"}, sources=VID_SRC, notes=VIDEO_DEPRECATION), ep("POST", "/v1/videos/characters", "Create character", "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).", ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.create_character(video=..., name=...)", sdk_node="client.videos.createCharacter({...})", content_type="multipart/form-data", body_ref="CreateVideoCharacterBody", response={"content_type": "application/json", "object": "VideoCharacterResource {id, name, created_at}"}, 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"), sources=VID_SRC, notes=VIDEO_DEPRECATION), ep("GET", "/v1/videos/characters/{character_id}", "Retrieve character", "Fetches a character by id.", ["DOCUMENTED", "DEPRECATED", "UNVERIFIED"], family="videos", sdk_py="client.videos.get_character(character_id)", sdk_node="client.videos.getCharacter(characterID)", content_type=None, response={"content_type": "application/json", "object": "VideoCharacterResource"}, sources=VID_SRC, notes=VIDEO_DEPRECATION), # ---- embeddings ep("POST", "/v1/embeddings", "Create embeddings", "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.", ["DOCUMENTED", "LIVE_VERIFIED"], family="embeddings", sdk_py="client.embeddings.create(**params)", sdk_node="client.embeddings.create({...})", body_ref="CreateEmbeddingRequest", response={"content_type": "application/json", "object": "CreateEmbeddingResponse {object:'list', data[Embedding], model, usage{prompt_tokens,total_tokens}}"}, 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."), sources=src(REF + "embeddings", GUIDE + "embeddings", SPEC, PRICING), notes="Also available via Batch API (all three models) and regional endpoints. Observed header x-ratelimit-limit-requests: 10000 (account-specific)."), # ---- moderation ep("POST", "/v1/moderations", "Create moderation", "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.", ["DOCUMENTED", "LIVE_VERIFIED"], family="moderations", sdk_py="client.moderations.create(**params)", sdk_node="client.moderations.create({...})", body_ref="CreateModerationRequest", response={"content_type": "application/json", "object": "CreateModerationResponse {id, model, results[Moderation]}"}, 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)."), sources=src(REF + "moderations", GUIDE + "moderation", SPEC, PRICING, DEPREC), notes="Inline alternative: pass top-level `moderation: {model}` in /v1/responses or /v1/chat/completions to get scores for input and output (changelog)."), # ---- safety ep("GET", "/v1/safety/alerts/{id}", "Get project safety alert", "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}$).", ["DOCUMENTED", "LIVE_VERIFIED"], family="safety", sdk_py="client.safety.alerts.retrieve(id)", sdk_node="client.safety.alerts.retrieve(id)", content_type=None, response={"content_type": "application/json", "object": "SafetyAlertResource"}, 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"), sources=src(GUIDE + "safety-checks/misalignment-monitoring", SPEC), 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)."), ep("GET", "/v1/safety/cases/{id}", "Get safety case", "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').", ["LIVE_DISCOVERED", "ACCOUNT_RESTRICTED"], family="safety", sdk_py="not in python-sdk-api.md (spec-only)", sdk_node="not in node-sdk-api.md (spec-only)", content_type=None, response={"content_type": "application/json", "object": "SafetyCaseResource"}, 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."), sources=src(SPEC), notes="Spec-only (operationId Getsafetycase, group safety-cases); no guide/reference page in the downloaded docs."), ep("POST", "/v1/content_provenance_checks", "Create content provenance check", "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.", ["DOCUMENTED", "LIVE_VERIFIED"], family="content_provenance", sdk_py="client.content_provenance_checks.create(file=...)", sdk_node="client.contentProvenanceChecks.create({file})", content_type="multipart/form-data", body_ref="CreateContentProvenanceBody {file}", response={"content_type": "application/json", "object": "ProvenanceResource {object:'content_provenance_check', created_at, results[C2PA|SynthID]}"}, 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."), sources=src(GUIDE + "content-provenance", REF + "content_provenance_checks/methods/create", SPEC), 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."), ] # ---------------------------------------------------------------------------------------------------------------- # PARAMETERS # ---------------------------------------------------------------------------------------------------------------- def prm(endpoint, name, typ, desc, *, location="body", required=False, default=None, minimum=None, maximum=None, enum=None, models=None, status=None, source=None, notes=None): return { "provider": P, "endpoint": endpoint, "parameter": name, "location": location, "type": typ, "required": required, "default": default, "minimum": minimum, "maximum": maximum, "enum": enum, "description": desc, "compatible_models": models, "beta_header": None, "status": status or ["DOCUMENTED"], "source": source or REF, "notes": notes, } IMG_REF = REF + "images" GEN = "POST /v1/images/generations" EDT = "POST /v1/images/edits" VAR = "POST /v1/images/variations" LV = ["DOCUMENTED", "LIVE_VERIFIED"] SIZE_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. " "Other GPT image models: 1024x1024, 1536x1024, 1024x1536, auto. dall-e-2: 256x256, 512x512, 1024x1024. dall-e-3: 1024x1024, 1792x1024, 1024x1792.") PARAMS_IMAGES = [ 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), 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."), 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), 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."), 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.'"), 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)."), 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), 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), 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), 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), 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), 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."), 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."), prm(GEN, "user", "string", "Stable end-user identifier for abuse monitoring (see safety identifiers).", models=IMAGE_MODELS_GEN, source=IMG_REF), # edits 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)."), 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), prm(EDT, "prompt", "string", "Desired edit. Max 32000 chars (GPT image), 1000 (dall-e-2).", required=True, models=IMAGE_MODELS_EDIT, source=IMG_REF), 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), 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), 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."), 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), 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), 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), prm(EDT, "n", "integer|null", "1-10 edited images.", default=1, minimum=1, maximum=10, models=IMAGE_MODELS_EDIT, source=IMG_REF), prm(EDT, "moderation", "string|null", "low | auto. GPT image models.", default="auto", enum=["low", "auto"], models=GPT_IMAGE_ALL, source=IMG_REF), 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), prm(EDT, "output_compression", "integer|null", "0-100 for jpeg/webp.", default=100, minimum=0, maximum=100, models=GPT_IMAGE_ALL, source=IMG_REF), 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), prm(EDT, "stream", "boolean|null", "Stream image_edit.partial_image / image_edit.completed events.", default=False, models=GPT_IMAGE_ALL, source=IMG_REF), 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), prm(EDT, "user", "string", "End-user identifier.", models=IMAGE_MODELS_EDIT, source=IMG_REF), # variations prm(VAR, "image", "file (multipart)", "Square PNG < 4 MB.", required=True, models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF), 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), prm(VAR, "n", "integer|null", "1-10.", default=1, minimum=1, maximum=10, models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF), 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), 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."), prm(VAR, "user", "string", "End-user identifier.", models=["dall-e-2"], status=["DOCUMENTED", "RETIRED"], source=IMG_REF), ] VREF = REF + "videos" VG = GUIDE + "video-generation" DEP = ["DOCUMENTED", "DEPRECATED"] PARAMS_VIDEOS = [ 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), 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), 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."), 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), 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), 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)."), prm("GET /v1/videos", "after", "string", "Cursor: id of the last item of the previous page.", location="query", status=DEP, source=VREF), prm("GET /v1/videos", "limit", "integer", "Page size.", location="query", status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], source=VREF, notes="limit=2 accepted live."), prm("GET /v1/videos", "order", "string", "asc | desc by timestamp.", location="query", enum=["asc", "desc"], status=DEP, source=VREF), 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."), prm("DELETE /v1/videos/{video_id}", "video_id", "string", "Video job id.", location="path", required=True, status=DEP, source=VREF), prm("GET /v1/videos/{video_id}/content", "video_id", "string", "Video job id.", location="path", required=True, status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], source=VREF), 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)."), prm("POST /v1/videos/{video_id}/remix", "video_id", "string", "Completed source video id.", location="path", required=True, status=DEP, source=VREF), prm("POST /v1/videos/{video_id}/remix", "prompt", "string", "Updated prompt for the remix.", required=True, status=DEP, source=VREF), 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), 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), prm("POST /v1/videos/edits", "model", "string", "Required only when uploading a new video (multipart).", enum=VIDEO_MODELS, status=DEP, source=VG), prm("POST /v1/videos/extensions", "video", "{id: string} (JSON) | file (multipart)", "Completed video to extend.", required=True, status=DEP, source=VREF), prm("POST /v1/videos/extensions", "prompt", "string", "How the scene continues (1-32000 chars).", required=True, minimum=1, maximum=32000, status=DEP, source=VREF), 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), 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), 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), prm("GET /v1/videos/characters/{character_id}", "character_id", "string", "Character id.", location="path", required=True, status=DEP, source=VREF), ] EREF = REF + "embeddings" PARAMS_EMB = [ 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."), 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'."), 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.'"), 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."), prm("POST /v1/embeddings", "user", "string", "End-user identifier for abuse monitoring.", models=EMB_MODELS, source=EREF), ] MREF = REF + "moderations" PARAMS_MOD = [ 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), prm("POST /v1/moderations", "input[].type", "string", "text | image_url (multimodal array items).", enum=["text", "image_url"], models=MOD_MODELS, status=LV, source=MREF), prm("POST /v1/moderations", "input[].text", "string", "Text to classify (type=text).", models=MOD_MODELS, status=LV, source=MREF), 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."), 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'."), ] MM = GUIDE + "safety-checks/misalignment-monitoring" SBP = GUIDE + "safety-best-practices" PARAMS_SAFETY = [ 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."), 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."), 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"), 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."), prm("POST /v1/chat/completions", "safety_identifier", "string|null", "Same semantics as on /v1/responses (max 64 chars).", maximum=64, status=["DOCUMENTED"], source=SBP), 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"), 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."), ] # ---------------------------------------------------------------------------------------------------------------- # STREAMING EVENTS # ---------------------------------------------------------------------------------------------------------------- COMMON_IMG_FIELDS = {"b64_json": "string (base64 image bytes)", "background": "transparent|opaque|auto", "created_at": "integer (unix s)", "output_format": "png|webp|jpeg", "quality": "low|medium|high|xhigh|max|auto", "size": "WIDTHxHEIGHT string | 1024x1024 | 1024x1536 | 1536x1024 | auto"} USAGE_SCHEMA = {"input_tokens": "integer", "input_tokens_details": {"image_tokens": "integer", "text_tokens": "integer"}, "output_tokens": "integer (image output tokens)", "total_tokens": "integer"} def sev(event, api, desc, schema, example, source): return {"provider": P, "api": api, "direction": "server→client", "event": event, "description": desc, "schema": schema, "example": example, "source": source, "status": ["DOCUMENTED", "UNVERIFIED"], "transport": "SSE: `event: ` line followed by `data: `; only with stream=true on GPT image models"} STREAM_EVENTS = [ sev("image_generation.partial_image", "POST /v1/images/generations", "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.", {**COMMON_IMG_FIELDS, "partial_image_index": "integer (0-based)", "type": "image_generation.partial_image"}, {"type": "image_generation.partial_image", "b64_json": "...", "partial_image_index": 0}, REF + "images/generation-streaming-events"), sev("image_generation.completed", "POST /v1/images/generations", "Final image is available; carries the full usage block.", {**COMMON_IMG_FIELDS, "type": "image_generation.completed", "usage": USAGE_SCHEMA}, {"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"), sev("image_edit.partial_image", "POST /v1/images/edits", "Partial edited image during streaming edits.", {**COMMON_IMG_FIELDS, "partial_image_index": "integer (0-based)", "type": "image_edit.partial_image"}, {"type": "image_edit.partial_image", "b64_json": "...", "partial_image_index": 0}, REF + "images/edit-streaming-events"), sev("image_edit.completed", "POST /v1/images/edits", "Final edited image with usage.", {**COMMON_IMG_FIELDS, "type": "image_edit.completed", "usage": USAGE_SCHEMA}, {"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"), ] # ---------------------------------------------------------------------------------------------------------------- # OBJECTS # ---------------------------------------------------------------------------------------------------------------- def obj(name, api, desc, fields, *, status, source, example=None, notes=None): return {"provider": P, "object": name, "api_family": api, "description": desc, "fields": fields, "status": status, "example": example, "notes": notes, "sources": src(*([source] if isinstance(source, str) else source))} OBJECTS = [ obj("ImagesResponse", "images", "Response of generations/edits/variations.", {"created": "integer unix s", "data": "Image[]", "background": "transparent|opaque (GPT image)", "output_format": "png|webp|jpeg", "quality": "low|medium|high|xhigh|max", "size": "string", "usage": "ImageUsage (GPT image models)"}, status=LV, source=REF + "images", example={"created": 1789782224, "background": "opaque", "output_format": "jpeg", "quality": "low", "size": "1024x1024", "data": [{"b64_json": "<39383 bytes of JPEG, base64>", "generation_id": ""}], "usage": {"input_tokens": 10, "input_tokens_details": {"image_tokens": 0, "text_tokens": 10}, "output_tokens": 272, "output_tokens_details": {"image_tokens": 272, "text_tokens": 0}, "total_tokens": 282}}, notes="Live example (gpt-image-1-mini, low, 1024x1024). `created` present; no `revised_prompt`/`url` for GPT image models."), obj("Image", "images", "One generated image.", {"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)", "revised_prompt": "string (dall-e-3 only)", "generation_id": "string — LIVE_DISCOVERED, undocumented (observed on gpt-image-1-mini generations)"}, status=["DOCUMENTED", "LIVE_VERIFIED", "LIVE_DISCOVERED"], source=REF + "images"), obj("ImageUsage", "images", "Token usage for GPT image models (docs text still says 'gpt-image-1 only' but present for all GPT image models).", {"input_tokens": "int", "input_tokens_details": {"image_tokens": "int", "text_tokens": "int"}, "output_tokens": "int", "output_tokens_details": {"image_tokens": "int", "text_tokens": "int"}, "total_tokens": "int"}, status=LV, source=REF + "images"), obj("Video", "videos", "Video generation job.", {"id": "string (video_...)", "object": "'video'", "model": "VideoModel", "status": "queued|in_progress|completed|failed", "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)", "prompt": "string|null", "size": "VideoSize", "seconds": "string (stitched total for extensions)", "remixed_from_video_id": "string|null", "error": "VideoCreateError|null", "edited_from_video_id": "string|null — LIVE_DISCOVERED (not in docs/spec)", "extended_from_video_id": "string|null — LIVE_DISCOVERED (not in docs/spec)", "quality": "'standard' — appears in a doc example only"}, status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED", "LIVE_DISCOVERED"], source=REF + "videos", example={"id": "video_6a5e...a57a", "object": "video", "created_at": 1784590069, "status": "completed", "completed_at": 1784590117, "edited_from_video_id": None, "error": None, "expires_at": 1784762917, "extended_from_video_id": None, "model": "sora-2", "progress": 100, "prompt": "A simple blue geometric cube rotating on a white background.", "remixed_from_video_id": None, "seconds": "4", "size": "720x1280"}, notes="Live example from GET /v1/videos (2026-09-18). expires_at - completed_at = 172,800 s = 48 h."), obj("VideoCreateError", "videos", "Why a job failed.", {"code": "string", "message": "string", "misalignment": {"error_type": "potentially_unintended_data_transfer|potentially_unintended_data_access|potentially_unintended_destructive_activity|other|", "detailed_explanation": "string", "steer": {"message": "string"}}}, status=["DOCUMENTED", "DEPRECATED"], source=REF + "videos"), obj("VideoList", "videos", "Paginated list.", {"object": "'list'", "data": "Video[]", "first_id": "string|null", "last_id": "string|null", "has_more": "boolean"}, status=["DOCUMENTED", "DEPRECATED", "LIVE_VERIFIED"], source=REF + "videos"), obj("VideoDeleteResponse", "videos", "Deletion confirmation.", {"id": "string", "deleted": "boolean", "object": "'video.deleted'"}, status=["DOCUMENTED", "DEPRECATED"], source=REF + "videos"), 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"), 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}).", {"id": "evt_...", "object": "'event'", "created_at": "unix s", "type": "video.completed|video.failed", "data": {"id": "video_..."}}, status=["DOCUMENTED", "DEPRECATED"], source=[GUIDE + "video-generation", REF + "webhooks"], example={"id": "evt_abc123", "object": "event", "created_at": 1758941485, "type": "video.completed", "data": {"id": "video_abc123"}}), 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"}}, 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}}), obj("Embedding", "embeddings", "One vector.", {"object": "'embedding'", "index": "int", "embedding": "float[] (encoding_format=float) | base64 string of little-endian float32 (encoding_format=base64)"}, 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)."), 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"), obj("Moderation", "moderations", "Per-input classification.", {"flagged": "boolean (any category flagged)", "categories": "object — 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)", "category_scores": "object model confidence; thresholds may be recalibrated over time", "category_applied_input_types": "object — image supported only for self-harm*, sexual, violence, violence/graphic"}, status=LV, source=REF + "moderations", example={"flagged": False, "categories": {"violence": False, "...": "..."}, "category_scores": {"violence": 0.00054, "...": "..."}, "category_applied_input_types": {"violence": ["text", "image"], "harassment": ["text"]}}, notes="Live: with image present, image-capable categories list ['text','image']; text-only input lists ['text'] for all 13."), obj("SafetyAlertResource (safety.alert)", "safety", "Misalignment-monitoring alert for one request.", {"id": "string", "object": "'safety.alert'", "created_at": "unix s", "request_id": "string", "response_id": "string", "model": "string", "request_paused": "boolean — block registration succeeded (does not confirm execution stopped)", "error_type": "potentially_unintended_data_transfer|potentially_unintended_data_access|potentially_unintended_destructive_activity|other", "reason": "string|null (null for ZDR requests)"}, status=["DOCUMENTED", "LIVE_VERIFIED"], source=[SPEC, MM], notes="Endpoint reachable (404 safety_alert_not_found for unknown id)."), obj("SafetyCaseResource (safety.case)", "safety", "Warning or deactivation issued for a safety identifier.", {"id": "string", "object": "'safety.case'", "created_at": "unix s", "entity_identifier": "string (the safety identifier)", "reason": "string|null", "notice": {"type": "warning|deactivation"}}, status=["LIVE_DISCOVERED", "ACCOUNT_RESTRICTED"], source=SPEC, notes="Requires api.safety.read scope (403 for our key)."), 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}.", {"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-..."}}, status=["DOCUMENTED"], source=[SPEC, MM], example={"object": "event", "id": "evt_123", "type": "safety.alert.created", "created_at": 1787659200, "data": {"id": "alert_0123456789abcdef0123456789abcdef"}}), obj("ProvenanceResource (content_provenance_check)", "content_provenance", "Synchronous provenance verdicts.", {"object": "'content_provenance_check'", "created_at": "unix s", "results": "(C2PAProvenanceResult | SynthIDProvenanceResult)[] — images: c2pa + synthid; audio: synthid only; inapplicable checks are omitted"}, status=LV, source=GUIDE + "content-provenance", example={"object": "content_provenance_check", "created_at": 1789782225, "results": [ {"type": "c2pa", "outcome": "detected", "validation_state": "trusted", "issuer": "OpenAI OpCo, LLC", "model": "API / gpt-image", "generated_at": "2026-09-19T01:43:43.564995Z"}, {"type": "synthid", "outcome": "not_detected", "model": None, "generated_at": None}]}, notes="Live on a fresh gpt-image-1-mini JPEG. Note model string 'API / gpt-image' (docs example: 'gpt-image')."), obj("C2PAProvenanceResult", "content_provenance", "C2PA Content Credentials verdict (images).", {"type": "'c2pa'", "outcome": "detected|not_detected (detected only if trusted/valid manifest issued by OpenAI with an AI-generation action)", "validation_state": "trusted|valid|invalid|not_present", "issuer": "string|null", "model": "string|null", "generated_at": "RFC3339|null"}, status=LV, source=GUIDE + "content-provenance"), obj("SynthIDProvenanceResult", "content_provenance", "SynthID watermark verdict (images + audio).", {"type": "'synthid'", "outcome": "detected|not_detected", "model": "string|null", "generated_at": "RFC3339|null"}, status=LV, source=GUIDE + "content-provenance"), ] # ---------------------------------------------------------------------------------------------------------------- # PRICES # ---------------------------------------------------------------------------------------------------------------- def price(model, dim, val, unit, tier="standard", notes=None, source=PRICING): return {"provider": P, "model_or_service": model, "dimension": dim, "price": val, "currency": "USD", "unit": unit, "tier": tier, "effective_notes": notes, "source": source, "retrieved_at": RET} PRICES = [] for m, (ti, tc, to, ii, ic, io) in { "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), "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), "gpt-image-1-mini": (2, 0.2, None, 2.5, 0.25, 8)}.items(): PRICES += [price(m, "text_input", ti, "per 1M tokens"), price(m, "cached_text_input", tc, "per 1M tokens"), 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")] if to: PRICES.append(price(m, "text_output", to, "per 1M tokens")) for 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), "gpt-image-1": (2.5, 0.63, None, 5, 1.25, 20), "chatgpt-image-latest": (2.5, 0.63, 5, 4, 1, 16)}.items(): PRICES += [price(m, "text_input", ti, "per 1M tokens", "batch"), price(m, "cached_text_input", tc, "per 1M tokens", "batch"), 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")] if to: PRICES.append(price(m, "text_output", to, "per 1M tokens", "batch")) LEGACY_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)}, "chatgpt-image-latest": {"low": (0.009, 0.013, 0.013), "medium": (0.034, 0.05, 0.05), "high": (0.133, 0.2, 0.2)}, "gpt-image-1": {"low": (0.011, 0.016, 0.016), "medium": (0.042, 0.063, 0.063), "high": (0.167, 0.25, 0.25)}, "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)}, "gpt-image-2": {"low": (0.006, 0.005, 0.005), "medium": (0.053, 0.041, 0.041), "high": (0.211, 0.165, 0.165)}} for m, qs in LEGACY_PER_IMAGE.items(): for q, (sq, po, la) in qs.items(): for size, v in (("1024x1024", sq), ("1024x1536", po), ("1536x1024", la)): 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.", source=MODELS + m if m != "gpt-image-2" else GUIDE + "image-generation")) for m, res, pv, bv in (("sora-2", "720p (720x1280|1280x720)", 0.10, 0.05), ("sora-2-pro", "720p (720x1280|1280x720)", 0.30, 0.15), ("sora-2-pro", "1024p (1024x1792|1792x1024)", 0.50, 0.25), ("sora-2-pro", "1080p (1080x1920|1920x1080)", 0.70, 0.35)): PRICES.append(price(m, f"video_output {res}", pv, "per second")) PRICES.append(price(m, f"video_output {res}", bv, "per second", "batch")) PRICES += [price("text-embedding-3-small", "input", 0.02, "per 1M tokens"), price("text-embedding-3-large", "input", 0.13, "per 1M tokens"), price("text-embedding-ada-002", "input", 0.10, "per 1M tokens"), price("omni-moderation-latest", "input", 0.0, "per call", notes="Free"), 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)")] # ---------------------------------------------------------------------------------------------------------------- OUT = { "endpoints/openai-images-video-embeddings-moderation.json": ENDPOINTS, "parameters/openai-images.json": PARAMS_IMAGES, "parameters/openai-videos.json": PARAMS_VIDEOS, "parameters/openai-embeddings.json": PARAMS_EMB, "parameters/openai-moderations.json": PARAMS_MOD, "parameters/openai-safety.json": PARAMS_SAFETY, "streaming-events/openai-images.json": STREAM_EVENTS, "objects/openai-media-objects.json": OBJECTS, "prices/openai-media.json": PRICES, } for rel, data in OUT.items(): p = ROOT / "generated" / "fragments" / rel p.parent.mkdir(parents=True, exist_ok=True) p.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n") print(f"{rel}: {len(data)} records")