skills/aradotso/trending-skills/gpt2api-openai-gateway

gpt2api-openai-gateway

Installation
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
Weekly Installs
137
GitHub Stars
39
First Seen
Today