gpt2api-openai-gateway

v2026.09.24

OpenAI-compatible SaaS gateway that reverse-engineers chatgpt.com to provide GPT Image 2, multi-account pooling, batch image generation, and billing management.

GitHub
Install command
npx skhub add reason-machines/gpt2api-openai-gateway
Markdown
SKILL.md

gpt2api OpenAI Gateway

Skill by ara.so — Daily 2026 Skills collection.

gpt2api is a self-hosted OpenAI-compatible API gateway that reverse-engineers chatgpt.com to expose GPT Image 2 / DALL·E 3 / IMG2 grayscale capabilities via standard /v1/images/generations, /v1/images/edits, and /v1/chat/completions endpoints. It provides multi-account pooling, proxy pooling, rate limiting, credit billing, and an admin dashboard.


Installation & Deployment

Docker Compose (Recommended)

git clone https://github.com/432539/gpt2api.git
cd gpt2api/deploy
cp .env.example .env

Edit .env — these three are required:

JWT_SECRET=<generate with: openssl rand -base64 48 | tr -d '=/+' | cut -c1-48>
CRYPTO_AES_KEY=<generate with: openssl rand -hex 32>
MYSQL_ROOT_PASSWORD=<strong-password>
MYSQL_PASSWORD=<strong-password>

Generate secrets:

# CRYPTO_AES_KEY (must be exactly 64 hex chars = 32 bytes AES-256)
openssl rand -hex 32

# JWT_SECRET (>=32 chars)
openssl rand -base64 48 | tr -d '=/+' | cut -c1-48

Start services:

docker compose up -d --build
docker compose logs -f server

On startup the server automatically:

  1. Waits for MySQL health check
  2. Runs goose up database migrations
  3. Starts HTTP on :8080

Default admin credentials (change immediately):

  • URL: http://<server-ip>:8080/
  • Email: admin@example.com
  • Password: admin123

Configuration

Main config file: configs/config.yaml (override with GPT2API_* env vars in Docker).

app:
  listen: ":8080"
  base_url: "https://your-domain.com"   # used for signed image proxy URLs

mysql:
  dsn: "user:pass@tcp(mysql:3306)/gpt2api?parseTime=true"
  max_open_conns: 500

redis:
  addr: "redis:6379"
  pool_size: 500                          # needed for distributed locks & rate limiting

jwt:
  secret: "${JWT_SECRET}"
  access_ttl_sec: 7200
  refresh_ttl_sec: 604800

crypto:
  aes_key: "${CRYPTO_AES_KEY}"           # 64-char hex, encrypts account AT/cookies

scheduler:
  min_interval_sec: 10                   # minimum seconds between uses of same account
  daily_usage_ratio: 0.8                 # daily usage cap ratio before circuit break
  cooldown_429_sec: 300                  # backoff when upstream returns 429

Environment variable override pattern (Docker):

GPT2API_APP_BASE_URL=https://api.example.com
GPT2API_SCHEDULER_MIN_INTERVAL_SEC=15
GPT2API_SCHEDULER_COOLDOWN_429_SEC=600

Quick Start: First Image Generation

1. Add a Proxy

Via admin dashboard → Proxy Management → New Proxy, or via API:

curl -X POST http://localhost:8080/api/admin/proxies \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "proxy-us-01",
    "url": "http://user:pass@proxy.example.com:8080",
    "type": "http"
  }'

SOCKS5 example:

-d '{"name":"socks-01","url":"socks5://user:pass@proxy.example.com:1080","type":"socks5"}'

2. Import ChatGPT Accounts

Batch import via admin dashboard → GPT Accounts → Batch Import.

Supported formats — JSON session:

[
  {
    "email": "user@example.com",
    "access_token": "$CHATGPT_ACCESS_TOKEN",
    "refresh_token": "$CHATGPT_REFRESH_TOKEN",
    "proxy_id": 1
  }
]

Or AT/RT/ST plain text (one per line in the UI).

3. Create an API Key

Admin dashboard → User Management → select user → API Keys → Create Key.

Or programmatically:

curl -X POST http://localhost:8080/api/user/apikeys \
  -H "Authorization: Bearer $USER_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-app-key",
    "rpm_limit": 60,
    "daily_quota": 1000,
    "model_whitelist": ["gpt-image-2", "picture_v2"]
  }'

4. Generate an Image

curl -X POST http://localhost:8080/v1/images/generations \
  -H "Authorization: Bearer sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "A serene mountain lake at sunset, photorealistic",
    "n": 2,
    "size": "1024x1024"
  }'

Response:

{
  "created": 1713456789,
  "data": [
    {
      "url": "https://your-domain.com/p/img/task123/0?exp=1713460389&sig=abc123"
    },
    {
      "url": "https://your-domain.com/p/img/task123/1?exp=1713460389&sig=def456"
    }
  ]
}

API Reference

Image Generation

POST /v1/images/generations
{
  "model": "gpt-image-2",      // or "picture_v2" for IMG2 grayscale
  "prompt": "your prompt",
  "n": 1,                       // number of images (1-4 recommended)
  "size": "1024x1024",          // "1024x1024" | "1792x1024" | "1024x1792"
  "response_format": "url"      // "url" | "b64_json"
}

Image Edit (img2img)

POST /v1/images/edits
Content-Type: multipart/form-data
curl -X POST http://localhost:8080/v1/images/edits \
  -H "Authorization: Bearer sk-your-api-key" \
  -F "image=@input.png" \
  -F "prompt=Make the sky more dramatic" \
  -F "model=gpt-image-2" \
  -F "n=1"

Check Task Status

GET /v1/images/tasks/:id
Authorization: Bearer sk-your-api-key

List Models

GET /v1/models
Authorization: Bearer sk-your-api-key

Chat Completions (preserved, UI disabled)

POST /v1/chat/completions
{
  "model": "gpt-4o",
  "messages": [{"role": "user", "content": "Hello"}],
  "stream": true
}

Using with OpenAI SDK

Python

from openai import OpenAI
import os

client = OpenAI(
    api_key=os.environ["GPT2API_KEY"],          # your sk- key from the dashboard
    base_url="http://localhost:8080/v1"          # or your production domain
)

# Single image
response = client.images.generate(
    model="gpt-image-2",
    prompt="A futuristic city skyline at night, cyberpunk style",
    n=1,
    size="1024x1024"
)
print(response.data[0].url)

# Batch images
response = client.images.generate(
    model="gpt-image-2",
    prompt="Abstract watercolor painting, vibrant colors",
    n=4,
    size="1024x1792"   # 9:16 portrait
)
for img in response.data:
    print(img.url)

Node.js / TypeScript

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.GPT2API_KEY,
  baseURL: process.env.GPT2API_BASE_URL ?? "http://localhost:8080/v1",
});

async function generateImages(prompt: string, count: number = 2) {
  const response = await client.images.generate({
    model: "gpt-image-2",
    prompt,
    n: count,
    size: "1024x1024",
  });
  return response.data.map((img) => img.url);
}

// Image edit
async function editImage(imagePath: string, prompt: string) {
  const fs = await import("fs");
  const response = await client.images.edit({
    image: fs.createReadStream(imagePath),
    prompt,
    model: "gpt-image-2",
  });
  return response.data[0].url;
}

Go

package main

import (
    "context"
    "fmt"
    "os"

    openai "github.com/sashabaranov/go-openai"
)

func main() {
    cfg := openai.DefaultConfig(os.Getenv("GPT2API_KEY"))
    cfg.BaseURL = os.Getenv("GPT2API_BASE_URL") // e.g. "http://localhost:8080/v1"
    client := openai.NewClientWithConfig(cfg)

    resp, err := client.CreateImage(context.Background(), openai.ImageRequest{
        Model:  "gpt-image-2",
        Prompt: "A tranquil Japanese garden with cherry blossoms",
        N:      2,
        Size:   openai.CreateImageSize1024x1024,
    })
    if err != nil {
        panic(err)
    }
    for _, img := range resp.Data {
        fmt.Println(img.URL)
    }
}

Key Concepts

IMG2 Grayscale Hit

IMG2 is a grayscale (A/B test) feature on chatgpt.com that returns multiple high-res final images in a single call. The gateway:

  1. Sends the generation request
  2. Detects preview_only in the tool message response
  3. Automatically retries up to 3 times within the same conversation to capture IMG2 output
  4. Returns all signed image URLs aggregated

Use model picture_v2 to specifically target the IMG2 grayscale path:

{ "model": "picture_v2", "prompt": "...", "n": 1 }

Successful IMG2 hit shows in logs:

image runner result summary turns_used=1 is_preview=false signed_count=2

Account Pool Scheduling

The scheduler uses Redis distributed locks to serialize account usage:

  • min_interval_sec: minimum gap between consecutive uses of the same account
  • daily_usage_ratio: if an account hits this fraction of its daily limit, it's circuit-broken
  • cooldown_429_sec: account is cooled down after receiving a 429 from upstream
  • Accounts are bound 1:1 to oai-device-id / oai-session-id fingerprints (stored in DB)

Image Proxy & Anti-Hotlink

All returned image URLs go through the built-in HMAC-signed proxy:

/p/img/:task_id/:index?exp=<unix_timestamp>&sig=<hmac_sha256>

This bypasses chatgpt.com's estuary/content 403 hotlink protection. The proxy fetches and streams the image server-side when accessed.

Credit Billing System

  • Users have a credit wallet; credits are pre-deducted before generation
  • Group multipliers allow VIP / internal / reseller pricing tiers
  • Billing settled after successful generation; failed calls are refunded
  • EPay (易支付) integration for recharge

Admin Operations

Account Pool Management

# Check account statuses via API
curl http://localhost:8080/api/admin/accounts \
  -H "Authorization: Bearer $ADMIN_JWT"

# Manually trigger account token refresh
curl -X POST http://localhost:8080/api/admin/accounts/123/refresh \
  -H "Authorization: Bearer $ADMIN_JWT"

# Bulk import accounts (JSON body)
curl -X POST http://localhost:8080/api/admin/accounts/batch \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d @accounts.json

Database Backup & Restore

Via admin dashboard → Data Backup, or:

# Trigger backup
curl -X POST http://localhost:8080/api/admin/backups \
  -H "Authorization: Bearer $ADMIN_JWT"

# List backups
curl http://localhost:8080/api/admin/backups \
  -H "Authorization: Bearer $ADMIN_JWT"

# Download backup
curl http://localhost:8080/api/admin/backups/backup-2026-04-21.sql.gz \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -o backup.sql.gz

User Credit Management

# Add credits to a user
curl -X POST http://localhost:8080/api/admin/users/42/credits \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{"amount": 10000, "note": "Manual top-up"}'

Directory Structure

gpt2api/
├── cmd/server/          # main entry point
├── configs/
│   ├── config.yaml      # main config
│   └── config.example.yaml
├── deploy/
│   ├── docker-compose.yml
│   ├── .env.example
│   └── README.md        # horizontal scaling guide
├── internal/
│   ├── api/             # Gin route handlers
│   ├── chatgpt/         # upstream client (utls, sentinel, SSE parser)
│   ├── scheduler/       # account pool scheduler + Redis leases
│   ├── billing/         # credit wallet + EPay integration
│   ├── proxy/           # proxy pool health checks
│   ├── imgproxy/        # HMAC-signed image proxy
│   └── model/           # DB models + migrations (goose)
├── web/                 # Vue 3 frontend source
└── docs/screenshots/

Enabling Chat Completions

The /v1/chat/completions route is fully implemented but the UI entry is disabled via a feature flag due to upstream sentinel instability. To re-enable:

// internal/api/routes.go — find the feature flag:
const enableChatCompletions = false   // change to true

Rebuild and redeploy:

docker compose up -d --build server

Troubleshooting

No accounts available / all accounts idle

# Check account pool status
docker compose logs server | grep "scheduler"

# Common causes:
# 1. min_interval_sec too high — reduce in config
# 2. All accounts in cooldown — check for 429s upstream
# 3. Proxy unhealthy — verify proxy health score in dashboard

Images return 403 when accessed directly

Expected behavior — all image URLs must go through the /p/img/ proxy. Ensure base_url in config matches your actual domain so signed URLs are correct.

is_preview=true in logs (IMG2 not hitting)

The account doesn't have IMG2 grayscale enabled. Either:

  • Try different accounts (grayscale is account-specific)
  • Use gpt-image-2 model instead of picture_v2
  • The gateway retries up to 3 times automatically

Redis lock timeout errors

redis:
  pool_size: 500    # increase if seeing lock contention under high concurrency

Database migration fails on startup

docker compose exec server goose -dir /app/migrations mysql "$DSN" status
docker compose exec server goose -dir /app/migrations mysql "$DSN" up

TLS fingerprint / JA3 detection

The gateway uses refraction-networking/utls to mimic Edge 143 browser TLS fingerprint. If accounts are getting flagged:

  • Ensure each account is bound to a dedicated proxy (avoid IP mixing)
  • Check oai-device-id binding is consistent (stored per-account in DB)
  • Rotate accounts that have been flagged

High memory usage

Reduce connection pool sizes for smaller deployments:

mysql:
  max_open_conns: 50
redis:
  pool_size: 50

Security Checklist

  • Change default admin password immediately after first login
  • Set strong JWT_SECRET (≥32 chars random)
  • Set CRYPTO_AES_KEY to exactly 64 hex chars
  • Set strong MySQL passwords
  • Configure base_url to your HTTPS domain
  • Set up nginx reverse proxy with TLS in front of :8080
  • Configure API key IP whitelists for sensitive keys
  • Enable audit log review in admin dashboard
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

Not specified

Source path

skills/gpt2api-openai-gateway

Default branch

main

Latest commit

2384a00

Tree SHA

ac47bb5