Генерация изображений через OpenAI Codex OAuth¶
Что это такое¶
Обычно для генерации картинок через OpenAI нужно:
- Зарегистрироваться на platform.openai.com
- Пополнить баланс
- Получить OPENAI_API_KEY
Codex OAuth — альтернативный путь. Вы аутентифицируетесь через свой ChatGPT-аккаунт (логин/пароль от chatgpt.com), и используете модель gpt-image-2 без отдельного API-ключа. Картинки генерируются через тот же бэкенд, что и в интерфейсе ChatGPT.
Как работает OAuth¶
Используется Device Code Flow (RFC 8628):
1. Клиент → POST https://auth.openai.com/api/oauth/device/code
← device_code, user_code, verification_uri_complete
2. Пользователь открывает verification_uri_complete в браузере
(это ссылка вида https://chatgpt.com/auth/... с уже подставленным кодом)
3. Пользователь логинится в ChatGPT и подтверждает доступ
4. Клиент → POST https://auth.openai.com/api/oauth/device/token
← access_token, refresh_token, id_token
5. access_token используется для API-запросов
6. refresh_token — для продления сессии без повторного логина
Токены хранятся в JSON-файле. Структура:
{
"providers": {
"openai-codex": {
"tokens": {
"id_token": "eyJh...",
"access_token": "eyJh...",
"refresh_token": "rt_...",
"account_id": "2e6b5149-..."
},
"last_refresh": "2026-04-28T22:58:16Z"
}
}
}
Из JWT access_token извлекается chatgpt_account_id — он нужен для заголовков.
Генерация изображения: вызов API¶
Эндпоинт: POST https://chatgpt.com/backend-api/codex
В отличие от стандартного client.images.generate(), здесь используется Responses API с тулзой image_generation. Фактически мы просим модель gpt-5.4 вызвать тулзу image_generation, которая рендерит через gpt-image-2.
Python-пример¶
import openai
import base64, json, time, uuid
from datetime import datetime
from pathlib import Path
# === 1. Загружаем токены из файла ===
def load_codex_tokens(auth_path: str = "auth.json") -> dict:
with open(auth_path) as f:
data = json.load(f)
return data["providers"]["openai-codex"]["tokens"]
# === 2. Извлекаем account_id из JWT (нужно для Cloudflare) ===
def get_account_id(access_token: str) -> str | None:
try:
parts = access_token.split(".")
if len(parts) < 2:
return None
payload_b64 = parts[1] + "=" * (-len(parts[1]) % 4)
claims = json.loads(base64.urlsafe_b64decode(payload_b64))
return claims.get("https://api.openai.com/auth", {}).get("chatgpt_account_id")
except Exception:
return None
tokens = load_codex_tokens()
account_id = get_account_id(tokens["access_token"])
# === 3. Создаём клиент с Cloudflare-заголовками ===
client = openai.OpenAI(
api_key=tokens["access_token"],
base_url="https://chatgpt.com/backend-api/codex",
default_headers={
"User-Agent": "codex_cli_rs/0.0.0 (My Bot)",
"originator": "codex_cli_rs",
"ChatGPT-Account-ID": account_id or "",
},
)
# === 4. Генерируем ===
SIZES = {
"landscape": "1536x1024",
"square": "1024x1024",
"portrait": "1024x1536",
}
prompt = "A cyberpunk cat in a neon-lit alley"
size = SIZES["square"] # или landscape / portrait
quality = "medium" # low / medium / high
image_b64 = None
with client.responses.stream(
model="gpt-5.4",
store=False,
instructions="You are an assistant that must fulfill image generation requests by using the image_generation tool when provided.",
input=[{
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": prompt}],
}],
tools=[{
"type": "image_generation",
"model": "gpt-image-2",
"size": size,
"quality": quality,
"output_format": "png",
"background": "opaque",
"partial_images": 1,
}],
tool_choice={
"type": "allowed_tools",
"mode": "required",
"tools": [{"type": "image_generation"}],
},
) as stream:
for event in stream:
event_type = getattr(event, "type", "")
if event_type == "response.output_item.done":
item = getattr(event, "item", None)
if getattr(item, "type", None) == "image_generation_call":
result = getattr(item, "result", None)
if isinstance(result, str) and result:
image_b64 = result
elif event_type == "response.image_generation_call.partial_image":
partial = getattr(event, "partial_image_b64", None)
if isinstance(partial, str) and partial:
image_b64 = partial
# На случай если стрим завершился до output_item.done
final = stream.get_final_response()
for item in getattr(final, "output", None) or []:
if getattr(item, "type", None) == "image_generation_call":
result = getattr(item, "result", None)
if isinstance(result, str) and result:
image_b64 = result
# === 5. Сохраняем ===
if image_b64:
raw = base64.b64decode(image_b64)
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
path = Path(f"output_{ts}_{uuid.uuid4().hex[:8]}.png")
path.write_bytes(raw)
print(f"Сохранено: {path}")
else:
print("Ошибка: изображение не получено")
Доступные размеры и качество¶
| Параметр | Значения |
|---|---|
landscape |
1536×1024 |
square |
1024×1024 |
portrait |
1024×1536 |
low |
~15 секунд, дёшево |
medium |
~40 секунд, баланс (по умолчанию) |
high |
~2 минуты, максимальное качество |
Обязательные Cloudflare-заголовки¶
Cloudflare перед chatgpt.com вайтлистит только конкретные originator-ы. Без этих заголовков — 403 даже с валидным токеном:
headers = {
"User-Agent": "codex_cli_rs/0.0.0 (My Bot)", # любой codex_cli_rs/...
"originator": "codex_cli_rs", # строго это значение
"ChatGPT-Account-ID": account_id, # из JWT: https://api.openai.com/auth.chatgpt_account_id
}
Редактирование изображений (image-to-image)¶
Через Codex OAuth — НЕТ¶
Текущая реализация Responses API с тулзой image_generation не поддерживает передачу входных изображений. Только text-to-image.
Через стандартный API-ключ — ДА¶
Если у вас есть OPENAI_API_KEY (от platform.openai.com), можно редактировать через client.images.edit():
from openai import OpenAI
client = OpenAI() # читает OPENAI_API_KEY из окружения
# Режимы редактирования:
# А) Inpainting — частичная замена с маской
result = client.images.edit(
model="gpt-image-2",
image=open("photo.png", "rb"), # оригинал (PNG, до 50MB)
mask=open("mask.png", "rb"), # PNG с альфа-каналом (прозрачность = редактировать)
prompt="A golden retriever sitting on the sofa",
n=1,
size="1024x1024",
output_format="png",
)
# Б) Редактирование без маски — переработка всего изображения
result = client.images.edit(
model="gpt-image-1.5", # для старых моделей можно без маски
image=open("photo.png", "rb"),
prompt="Make this photo look like a watercolor painting",
n=1,
size="1536x1024",
)
# Сохранение результата
import base64
for i, item in enumerate(result.data):
with open(f"edited_{i+1}.png", "wb") as f:
f.write(base64.b64decode(item.b64_json))
Важно для gpt-image-2:
- Всегда требует маску (inpainting mode)
- Не поддерживает input_fidelity (всегда high)
- Не поддерживает прозрачный фон (background: transparent)
- size должен быть кратен 16px, макс. 3840 по любой стороне, соотношение сторон ≤ 3:1
Для gpt-image-1.5 (legacy):
- Можно редактировать без маски
- Поддерживает background: transparent
- Размеры фиксированы: 1024x1024, 1536x1024, 1024x1536
Рефреш токенов¶
Access-токен живёт ~10 дней. Для продления — один POST с refresh_token:
import httpx
def refresh_codex_token(refresh_token: str) -> dict:
"""
Обменивает refresh_token на новую пару токенов.
Возвращает {'access_token': ..., 'refresh_token': ..., 'id_token': ...}
"""
resp = httpx.post(
"https://auth.openai.com/oauth/token",
data={
"grant_type": "refresh_token",
"client_id": "app_EMoamEEZ73f0CkXaXp7hrann",
"refresh_token": refresh_token,
},
timeout=20,
)
resp.raise_for_status()
return resp.json()
# Использование:
tokens = load_codex_tokens()
new_tokens = refresh_codex_token(tokens["refresh_token"])
# Сохраняем new_tokens в auth.json
Как получить токены в первый раз¶
Способ 1: Готовый CLI (если есть доступ к Hermes)¶
hermes auth codex
# Откроет ссылку в браузере → логинитесь → токены сохраняются в ~/.hermes/auth.json
Способ 2: Вручную через Device Code Flow¶
import httpx, time, webbrowser
CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann" # OpenAI ChatGPT OAuth client_id
AUTH_URL = "https://auth.openai.com"
client = httpx.Client(timeout=30)
# Шаг 1: запросить device_code
resp = client.post(
f"{AUTH_URL}/api/oauth/device/code",
data={"client_id": CLIENT_ID},
)
data = resp.json()
print("Откройте ссылку и подтвердите доступ:")
print(data["verification_uri_complete"])
webbrowser.open(data["verification_uri_complete"])
# Шаг 2: поллить токен
interval = data.get("interval", 5)
deadline = time.time() + data["expires_in"]
while time.time() < deadline:
time.sleep(interval)
try:
token_resp = client.post(
f"{AUTH_URL}/api/oauth/device/token",
data={
"client_id": CLIENT_ID,
"device_code": data["device_code"],
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
},
)
if token_resp.status_code == 200:
tokens = token_resp.json()
print("Токены получены!")
# tokens["access_token"], tokens["refresh_token"], tokens["id_token"]
break
elif token_resp.status_code == 428:
continue # пользователь ещё не подтвердил
else:
print(f"Ошибка: {token_resp.status_code} {token_resp.text}")
break
except Exception as e:
print(f"Ошибка поллинга: {e}")
Способ 3: Импорт из Codex CLI¶
Если у вас установлен codex CLI и вы уже залогинены (codex login), токены лежат в ~/.codex/auth.json. Их можно скопировать оттуда.
Сравнение: Codex OAuth vs API Key¶
| Codex OAuth | OPENAI_API_KEY | |
|---|---|---|
| Регистрация | Аккаунт ChatGPT | platform.openai.com + пополнение |
| Генерация (text2img) | ✅ Да | ✅ Да |
| Редактирование | ❌ Нет | ✅ Да (images.edit) |
| Прозрачный фон | ❌ Нет (только opaque) | ✅ Да (для legacy моделей) |
| Лимиты | Зависят от ChatGPT-подписки | Зависят от баланса и tier |
| Модель | gpt-image-2 (low/med/high) |
gpt-image-2 и старые |
Резюме¶
- Codex OAuth — удобный способ генерировать картинки без API-ключа, используя ChatGPT-аккаунт
- Генерация — через Responses API с тулзой
image_generation, модельgpt-image-2 - Редактирование — через Codex OAuth невозможно, нужен
OPENAI_API_KEYиclient.images.edit() - Cloudflare — без заголовков
originator: codex_cli_rsиChatGPT-Account-IDбудет 403 - Рефреш — access-токен живёт ~10 дней, продляется через
refresh_token