Перейти к содержанию

Генерация изображений через 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