Technical Documentation: Wei-Shaw/sub2api
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
⚠️ Important Notice
Please read the following carefully before using this project:
- 🚨 Terms of Service Risk: Using this project may violate the terms of service of Anthropic and other upstream providers. Please review the relevant providers' user agreements before use; all risks arising from such use are borne solely by the user.
- ⚖️ Compliant Use: Use this project only in compliance with the laws and regulations of your country or region. Any unlawful use is strictly prohibited.
- 📖 Disclaimer: This project is provided for technical learning and research purposes only. The authors assume no liability for account bans, service interruptions, data loss, or any other direct or indirect damages resulting from the use of this project.
- 🚫 No Commercial Authorization: The developers of this project have never authorized any individual or organization to conduct any form of commercial operation based on this project. Any commercial activity conducted in the name of or based on this project is unrelated to this project and its developers, and all resulting disputes, losses, and legal liabilities shall be borne solely by the party conducting such activity.
❤️ Sponsors
| Thanks to CCTK.AI for sponsoring this project! CCTK.AI is an AI API gateway focused on stability and cost-effectiveness, offering fast relay services for Claude, OpenAI, Gemini, and other popular models. It works seamlessly with Claude Code, Codex, and other mainstream coding tools, delivering the same model capabilities at a fraction of the official cost. Register via this link for faster, more stable, and more affordable AI API access. | |
|---|---|
| One API, every top model! OpenModel is a production-grade, high-availability AI API gateway that makes your applications truly fast and stable: automatic failover, smart routing to the best-performing channel, and a production-grade SLA. An SLA that far surpasses any single provider — making stability your core competitive advantage. Works directly with Claude Code, Codex, and Gemini CLI. Register via this link to get started. | |
| Thanks to ETok.ai for sponsoring this project! ETok.ai is dedicated to building a one-stop AI programming tool service platform. We offer professional Claude Code packages and technical community services, with support for Google Gemini and OpenAI Codex. Through carefully designed plans and a professional tech community, we provide developers with reliable service guarantees and continuous technical support, making AI-assisted programming a true productivity tool. Click here to register! | |
| Thanks to APIKEY.FUN for sponsoring this project! APIKEY.FUN is one of the core contributors to the sub2api open-source project, dedicated to providing open, stable, and cost-effective AI API access. The platform supports API relay services for Claude, OpenAI, Gemini, and other popular models, with pricing starting from as low as 7% of the original rate. Register via the exclusive link: APIKEY to enjoy up to 5% off on all recharges. | |
| Thanks to AIGoCode for sponsoring this project! AIGoCode is an all-in-one platform that integrates Claude Code, Codex, and the latest Gemini models, providing you with stable, efficient, and highly cost-effective AI coding services. The platform offers flexible subscription plans, zero risk of account suspension, direct access with no VPN required, and lightning-fast responses. AIGoCode has prepared a special benefit for sub2api users: if you register via this link, you'll receive an extra 10% bonus credit on your first top-up! | |
| Real GPT-5.6 series at 3% of OpenAI pricing — CodexEverywhere is democratizing access to frontier models for developers worldwide. We believe in transparency and honesty, with model quality verified by active community oversight for months. USD and crypto friendly. Start with a free $20 trial at codex-everywhere.com. | |
| Thanks to Bestproxy for sponsoring this project! Bestproxy provides high-purity residential IPs with dedicated one-IP-per-account support. By combining real home networks with fingerprint isolation, it enables link environment isolation and reduces the probability of association-based risk control. | |
| Thanks to PatewayAI for sponsoring this project! PatewayAI is a premium API relay built for heavy AI developers, offering the full Claude and Codex series sourced 100% from official providers, with transparent token-level billing. Enterprise plans include high concurrency, dedicated management, contracts, and invoicing. Register now to get $3 in trial credits, top-ups from 60% off, and referral bonuses up to $150. | |
Thanks to PPToken.cc for sponsoring this project! PPToken.cc specializes in GPT model API relay services, supporting Codex, Claude Code, OpenAI-compatible clients, and Gemini CLI integration. Top-ups are 1:1 (¥1 = $1 credit); GPT models start at 0.16x rate multiplier, with overall cost at roughly 2.2% of official pricing and first-token latency around 1 second — ideal for developers seeking low-cost, high-speed access to GPT model capabilities. Technical support: 24/7 real human responses (no bots), @tech in the group chat and get a reply within 10 minutes. Sponsor benefit: the first 200 users who register via the exclusive registration link and enter promo code SUB2API can claim free Codex / Claude Code trial credits — no minimum spend, no card required. |
|
| Thanks to Veilx for sponsoring this project! Veilx CDN is purpose-built for large-scale AI API traffic, deeply optimized for relay services and call chains across OpenAI, Claude, Gemini, and scenarios like chat, image generation, embeddings, and streaming — delivering lower latency and higher stability under heavy concurrency. It also offers China three-network optimized return lines, making it ideal for global AI relay platforms, overseas AI SaaS, and cross-border high-concurrency deployments. | |
| Thanks to RoxyBrowser for sponsoring this project! RoxyBrowser RoxyBrowser is the perfect partner for Sub2API: it features a built-in native Roxy AI Agent and high-quality native residential IPs, supports batch automation via simple commands, and significantly boosts security and efficiency for multi-account management! Click this link to sign up and receive a free residential IP package plus a 10% lifetime discount. | |
| Nagora is a multi-model AI API gateway built for developers and teams. With a single account and API key, you can access more than 26 leading text and image models through one unified interface. It is compatible with OpenAI, Anthropic, and Gemini protocols and integrates seamlessly with development tools such as Claude Code, Codex, and Gemini CLI. The platform provides intelligent routing, automatic failover, transparent pricing, and consolidated billing, along with budget management, rate limiting, and concurrency controls. This makes AI usage more reliable and manageable across individual development, team collaboration, and production environments. No changes to your existing application are required. Simply replace the Base URL and API key to complete the integration in as little as one minute. | |
| Thanks to Qiniu AI for sponsoring this project! Qiniu AI is the enterprise-grade large-model MaaS platform under Qiniu Cloud (02567.HK), offering one-stop access to 150+ mainstream models worldwide, compatible with the protocols of major global model providers, and covering full-modality capabilities including text, image, audio, video, and file processing, serving over 1.69 million enterprises and developers. Qiniu AI offers an exclusive benefit for Sub2API users: register via this link — enterprise users get 12 million tokens free, and developers get 3 million tokens free. | |
| Thanks to FennoAI for sponsoring this project! FennoAI is a high-stability, high-performance API relay provider for enterprise R&D teams and developers, compatible with the OpenAI and Anthropic protocols and seamlessly integrating with mainstream AI coding tools such as Codex, Claude Code, and OpenCode. The platform delivers enterprise-grade stability, supporting call volumes of 100 billion tokens per day, and supports business-to-business settlement and invoicing for both domestic and overseas entities to meet enterprise R&D and procurement needs. As an exclusive benefit for Sub2API users, purchase a subscription via the exclusive link to get $50 worth of Coding Plan credit for only $1.99. Referral rewards are also available: invite friends to purchase and earn up to 20% commission — the more you invite, the more you earn. | |
| Thank you to LanoX AI for sponsoring this project! LanoX AI provides stable, cost-effective global model access services for developers, teams, and enterprises. 🎁 New User Benefits — Claim millions of free tokens, plus 500+ free models for easier low-cost testing, validation, and deployment 🧠 Global Leading Models — GPT · Claude · Gemini · Qwen · Grok... 🎬 Multimodal Creation — Seedance 2.0 · GPT Image · Gemini Nano Banana 🛡️ Enterprise-Grade Reliability — High availability 💎 native capability output 💎 no intelligence degradation 💎 no model mixing 💎 transparent usage and billing 💎 💰 Lower API Costs — Top-tier models from as low as 10% of official pricing, with clear documentation, simple integration, invoicing support, and enterprise-scale batch usage 🏢 Enterprise Choice — Ideal for AI products, Agents, content platforms, and R&D teams with high-volume model usage | |
| RapidProxy is a data collection proxy solution built for developers, providing stable and reliable residential proxy services. With 90M+ global residential IPs and 200+ country coverage, intelligent rotation, and precise geo-targeting, it helps projects such as web scraping, AI data training, SEO monitoring, and e-commerce data analysis break through access restrictions and improve data collection efficiency. It supports mainstream automation frameworks such as Playwright, Selenium, and Puppeteer, with prices as low as $0.65/GB — start your free test now. | |
| hao.ai is a high-speed, stable unified large-model API gateway for developers and teams. With a single API Key and a unified interface, you can access mainstream models such as GPT, Claude, and xAI Grok, with compatibility for common protocols and SDKs including OpenAI and Anthropic. The platform provides model routing, failover, team management, and complete request logs, with model prices as low as 15% of official reference pricing, helping users build AI applications more simply, more reliably, and at lower cost. | |
| Swiftproxy is a high-performance proxy solution built for developers, providing stable and reliable residential and static residential proxy services. With 90M+ clean residential IPs, global coverage, flexible rotation, and precise geo-targeting, it helps projects such as web scraping, AI automation, browser automation, SEO monitoring, and multi-account management overcome access restrictions and improve workflow efficiency. It supports HTTP(S) and SOCKS5 protocols, integrates with popular automation tools like Playwright, Selenium, and Puppeteer, with dynamic proxy traffic that never expires until used and free testing available — start your free test now! | |
| Thanks to APIMart for sponsoring this project! APIMart is a low-cost API platform for AI image and video generation — GPT-Image-2 from $0.006 per image, with 160+ images per dollar. One async API covers both image and video: submit a task, get an ID, and retrieve results via polling or callback. Batch tens of thousands of images without timeouts, and switch models without changing code. Pay as you go with no monthly fee — sign up here to get started. | |
| Thanks to AxisNow for sponsoring this project! AxisNow protects and accelerates websites and APIs, delivering an optimal access experience across mainland China and globally, while extending acceleration and security capabilities to native/mobile apps through client SDKs — self-hosted private-deployment CDN subscription-based DDoS-protected CDN autonomous, flexibly composable CDN network. | |
| PP.dog is a source-level API gateway with its own massive account pool, purpose-built as an upstream relay for downstream transit stations and high-frequency developers — saving you the hassle of building and maintaining your own account pool. ✅ Direct from source: self-operated account pool with no middleman markup; 🧧 Cost crusher: combined rate multiplier as low as 0.03x, just 0.35% of official pricing; 🚀 Blazing fast: first-token latency < 1s, on par with the official native API. Get started with PP.dog now | |
| ColaProxy provides high-quality residential proxies built for web scraping, automation, and multi-account management. Get a free trial with non-expiring traffic, pricing starting as low as $0.3/GB, unlimited concurrent connections, and intelligent IP rotation for a smoother, more stable proxy experience. Use discount code COLA10 to get 10% off and start scaling your projects with reliable residential proxies today. Get started with ColaProxy | |
| Modelflare: Full strength, stable, nothing watered down. Global SOTA models at a lower cost. Operated by US-based Havenbyte LLC, Modelflare supports GPT, Claude, Gemini, Grok, and other leading models through common compatible protocols, plus 40+ image and video models. Cache compensation is a highlight: selected OpenAI groups (price-first, stable, and premium) guarantee daily cache hit rates of 65%, 75%, or 85%, and if eligible requests fall short, Modelflare refunds the difference, visible on the usage dashboard after settlement. Special offers: a single top-up of US$20 unlocks the GPT discount group at a 0.015x price multiplier, and a single top-up of US$50 unlocks the Claude discount group at a 0.03x multiplier. Register via this exclusive link to get started. |
Overview
Sub2API is an AI API gateway platform designed to distribute and manage API quotas from AI product subscriptions. Users can access upstream AI services through platform-generated API Keys, while the platform handles authentication, billing, load balancing, and request forwarding.
Features
- Multi-Account Management - Support multiple upstream account types (OAuth, API Key)
- API Key Distribution - Generate and manage API Keys for users
- Precise Billing - Token-level usage tracking and cost calculation
- Smart Scheduling - Intelligent account selection with sticky sessions
- Concurrency Control - Per-user and per-account concurrency limits
- Rate Limiting - Configurable request and token rate limits
- Built-in Payment System - Supports EasyPay, Alipay, WeChat Pay, and Stripe for user self-service top-up, no separate payment service needed (Configuration Guide)
- Admin Dashboard - Web interface for monitoring and management
- Composite Groups - Admin routing layer that resolves requested models to concrete providers for multi-provider groups (Operator Guide)
- External System Integration - Embed external systems (e.g. ticketing) via iframe to extend the admin dashboard
Ecosystem
Community projects that extend or integrate with Sub2API:
| Project | Description | Features |
|---|---|---|
| Now Built-in — Payment is now integrated into Sub2API, no separate deployment needed. See Payment Configuration Guide | ||
| sub2api-mobile | Mobile admin console | Cross-platform app (iOS/Android/Web) for user management, account management, monitoring dashboard, and multi-backend switching; built with Expo + React Native |
Tech Stack
| Component | Technology |
|---|---|
| Backend | Go 1.27.0, Gin, Ent |
| Frontend | Vue 3.4+, Vite 5+, TailwindCSS |
| Database | PostgreSQL 15+ |
| Cache/Queue | Redis 7+ |
Nginx Reverse Proxy Note
When using Nginx as a reverse proxy for Sub2API (or CRS) with Codex CLI, add the following to the http block in your Nginx configuration:
underscores_in_headers on;Nginx drops headers containing underscores by default (e.g. session_id), which breaks sticky session routing in multi-account setups.
Deployment
Method 1: Script Installation (Recommended)
One-click installation script that downloads pre-built binaries from GitHub Releases.
Prerequisites
- Linux server (amd64 or arm64)
- PostgreSQL 15+ (installed and running)
- Redis 7+ (installed and running)
- Root privileges
Installation Steps
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bashThe script will:
- Detect your system architecture
- Download the latest release
- Install binary to
/opt/sub2api - Create systemd service
- Configure system user and permissions
Post-Installation
# 1. Start the service
sudo systemctl start sub2api
# 2. Enable auto-start on boot
sudo systemctl enable sub2api
# 3. Open Setup Wizard in browser
# http://YOUR_SERVER_IP:8080The Setup Wizard will guide you through:
- Database configuration
- Redis configuration
- Admin account creation
Upgrade
You can upgrade directly from the Admin Dashboard by clicking the Check for Updates button in the top-left corner.
The web interface will:
- Check for new versions automatically
- Download and apply updates with one click
- Support rollback if needed
Useful Commands
# Check status
sudo systemctl status sub2api
# View logs
sudo journalctl -u sub2api -f
# Restart service
sudo systemctl restart sub2api
# Uninstall
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -yMethod 2: Docker Compose (Recommended)
Deploy with Docker Compose, including PostgreSQL and Redis containers.
Prerequisites
- Docker 20.10+
- Docker Compose v2+
Quick Start (One-Click Deployment)
Use the automated deployment script for easy setup:
# Create deployment directory
mkdir -p sub2api-deploy && cd sub2api-deploy
# Download and run deployment preparation script
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# Start services
docker compose up -d
# View logs
docker compose logs -f sub2apiWhat the script does:
- Downloads
docker-compose.local.yml(saved asdocker-compose.yml) and.env.example - Generates secure credentials (JWT_SECRET, TOTP_ENCRYPTION_KEY, POSTGRES_PASSWORD)
- Creates
.envfile with auto-generated secrets - Creates data directories (uses local directories for easy backup/migration)
- Displays generated credentials for your reference
Manual Deployment
If you prefer manual setup:
# 1. Clone the repository
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
# 2. Copy environment configuration
cp .env.example .env
chmod 600 .env
# 3. Edit configuration (generate secure passwords)
nano .envRequired configuration in .env:
# PostgreSQL password (REQUIRED)
POSTGRES_PASSWORD=your_secure_password_here
# JWT Secret (RECOMMENDED - keeps users logged in after restart)
JWT_SECRET=your_jwt_secret_here
# TOTP Encryption Key (RECOMMENDED - preserves 2FA after restart)
TOTP_ENCRYPTION_KEY=your_totp_key_here
# Optional: Admin account
# Leave empty to auto-generate a random email (login username) and password, shown in logs on first startup.
# Avoid guessable values such as [email protected]: they are brute-force targets.
ADMIN_EMAIL=
ADMIN_PASSWORD=
# Optional: Custom port
SERVER_PORT=8080Generate secure secrets:
# Generate JWT_SECRET
openssl rand -hex 32
# Generate TOTP_ENCRYPTION_KEY
openssl rand -hex 32
# Generate POSTGRES_PASSWORD
openssl rand -hex 32# 4. Create data directories (for local version)
mkdir -p data postgres_data redis_data
# 5. Start all services
# Option A: Local directory version (recommended - easy migration)
docker compose -f docker-compose.local.yml up -d
# Option B: Named volumes version (simple setup)
docker compose up -d
# 6. Check status
docker compose -f docker-compose.local.yml ps
# 7. View logs
docker compose -f docker-compose.local.yml logs -f sub2apiDeployment Versions
| Version | Data Storage | Migration | Best For |
|---|---|---|---|
| docker-compose.local.yml | Local directories | ✅ Easy (tar entire directory) | Production, frequent backups |
| docker-compose.yml | Named volumes | ⚠️ Requires docker commands | Simple setup |
Recommendation: Use docker-compose.local.yml (deployed by script) for easier data management.
Access
Open http://YOUR_SERVER_IP:8080 in your browser.
If the admin email (login username) or password was auto-generated, find them in logs:
docker compose -f docker-compose.local.yml logs sub2api | grep "Generated admin"Upgrade
# Pull latest image and recreate container
docker compose -f docker-compose.local.yml pull
docker compose -f docker-compose.local.yml up -dEasy Migration (Local Directory Version)
When using docker-compose.local.yml, migrate to a new server easily:
# On source server
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz sub2api-deploy/
# Transfer to new server
scp sub2api-complete.tar.gz user@new-server:/path/
# On new server
tar xzf sub2api-complete.tar.gz
cd sub2api-deploy/
docker compose -f docker-compose.local.yml up -dUseful Commands
# Stop all services
docker compose -f docker-compose.local.yml down
# Restart
docker compose -f docker-compose.local.yml restart
# View all logs
docker compose -f docker-compose.local.yml logs -f
# Remove all data (caution!)
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/Method 3: Apple container (macOS)
Apple-silicon Macs running macOS 26 can run the full Sub2API, PostgreSQL, and Redis stack with Apple container 1.1.0 or newer:
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
./apple-container.sh init
./apple-container.sh up
./apple-container.sh statusThis is an operator-managed local workflow; Docker Compose remains the recommended production path. See deploy/APPLE_CONTAINER.md for lifecycle commands, persistence, upgrades, and runtime limitations.
Method 4: Build from Source
Build and run from source code for development or customization.
Prerequisites
- Go 1.21+
- Node.js 18+
- PostgreSQL 15+
- Redis 7+
Build Steps
# 1. Clone the repository
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api
# 2. Install pnpm (if not already installed)
npm install pnpm
# 3. Build frontend
cd frontend
pnpm install
pnpm run build
# Output will be in ../backend/internal/web/dist/
# 4. Build backend with embedded frontend
cd ../backend
VERSION="$(./scripts/resolve-version.sh)"
go build -tags embed -ldflags="-X main.Version=${VERSION}" -o sub2api ./cmd/server
# 5. Create configuration file
cp ../deploy/config.example.yaml ./config.yaml
# 6. Edit configuration
nano config.yamlNote: The
-tags embedflag embeds the frontend into the binary. Without this flag, the binary will not serve the frontend UI.
Key configuration in config.yaml:
server:
host: "0.0.0.0"
port: 8080
mode: "release"
database:
host: "localhost"
port: 5432
user: "postgres"
password: "your_password"
dbname: "sub2api"
redis:
host: "localhost"
port: 6379
username: ""
password: ""
jwt:
secret: "change-this-to-a-secure-random-string"
expire_hour: 24
default:
user_concurrency: 5
user_balance: 0
api_key_prefix: "sk-"
rate_multiplier: 1.0Additional security-related options are available in config.yaml:
cors.allowed_originsfor CORS allowlistsecurity.url_allowlistfor upstream/pricing/CRS host allowlistssecurity.url_allowlist.enabledto disable URL validation (use with caution)security.url_allowlist.allow_insecure_httpto allow HTTP URLs when validation is disabledsecurity.url_allowlist.allow_private_hoststo allow private/local IP addressessecurity.response_headers.enabledto enable configurable response header filtering (disabled uses default allowlist)security.cspto control Content-Security-Policy headersbilling.circuit_breakerto fail closed on billing errorssecurity.trust_forwarded_ip_for_api_key_aclenables legacy raw forwarded-header takeover (enabled by default for upgrade compatibility); disable it to enforceserver.trusted_proxies, which should contain only the exact proxy CIDRs that connect directly to Sub2APIsecurity.forwarded_client_ip_headersconfigures up to 16 third-party CDN client-IP header names; they are checked in order before the built-in headers only while legacy takeover is enabledturnstile.requiredto require Turnstile in release mode
Custom client-IP headers can be set in YAML or as a comma-separated environment variable:
SECURITY_FORWARDED_CLIENT_IP_HEADERS=True-Client-IP,X-CDN-Client-IPHeader names are validated, canonicalized, and de-duplicated. The admin security settings can update the list without a restart; new installations persist YAML/environment defaults and existing installations backfill a missing database value. When legacy takeover is disabled, all custom and built-in raw forwarding headers are ignored and Gin uses only server.trusted_proxies. While takeover is enabled, firewall the origin to CDN/proxy addresses and make the edge overwrite every trusted client-IP header. See deploy/EDGE_SECURITY.md for the complete migration and trust-boundary rules.
⚠️ Security Warning: HTTP URL Configuration
When security.url_allowlist.enabled=false, the system performs minimal URL validation and allows HTTP URLs by default (dev-friendly mode; Docker Compose deployments use the same default). For production, explicitly tighten this to HTTPS-only:
security:
url_allowlist:
enabled: false # Disable allowlist checks
allow_insecure_http: false # HTTPS only (recommended for production)Or via environment variable:
SECURITY_URL_ALLOWLIST_ENABLED=false
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=falseRisks of allowing HTTP:
- API keys and data transmitted in plaintext (vulnerable to interception)
- Susceptible to man-in-the-middle (MITM) attacks
- NOT suitable for production environments
When to use HTTP:
- ✅ Development/testing with local servers (http://localhost)
- ✅ Internal networks with trusted endpoints
- ✅ Testing account connectivity before obtaining HTTPS
- ❌ Production environments (use HTTPS only)
Example error for HTTP URLs when allow_insecure_http: false is set:
Invalid base URL: invalid url scheme: httpIf you disable URL validation or response header filtering, harden your network layer:
- Enforce an egress allowlist for upstream domains/IPs
- Block private/loopback/link-local ranges
- Enforce TLS-only outbound traffic
- Strip sensitive upstream response headers at the proxy
OpenAI Responses WebSocket ingress limits
gateway.openai_ws bounds the lifetime and aggregate count of client-facing
Responses WebSocket sessions. These safeguards apply independently from
per-turn user and account concurrency slots, which are released between turns.
gateway:
openai_ws:
# Total time to receive and decompress the first client message.
client_first_message_timeout_seconds: 30
# Close a client socket idle between completed turns; 0 disables this safeguard.
ingress_inter_turn_idle_timeout_seconds: 300
# Distributed API-key limit for live client ingress sessions; 0 disables it.
max_ingress_connections_per_api_key: 64The first-message timeout is a total read deadline. Deployments that accept
large contexts or image-heavy requests over slower links can raise it to
120-300 seconds. It expires before HTTP bridge routing, so bridge mode does not
override this limit.
The connection cap is coordinated through Redis using a 60-second lease that
is refreshed every 20 seconds. A process that cannot confirm a lease for a
full lease lifetime closes its local WebSocket rather than continuing outside
the global cap.
Enable the v2 mode router before selecting an account-level WS mode such ashttp_bridge:
gateway:
openai_ws:
mode_router_v2_enabled: trueOr set GATEWAY_OPENAI_WS_MODE_ROUTER_V2_ENABLED=true in the environment.
Use http_bridge for client-WebSocket/upstream-HTTP operation when rolling out
or mitigating upstream WebSocket issues.
Force OpenAI upstream HTTP/SSE
When an egress proxy or network repeatedly reconnects OpenAI Responses
WebSockets, set the global fallback in the persisted deployment configuration:
gateway:
openai_ws:
force_http: trueFor Compose and Apple container deployments, the equivalent .env setting is:
GATEWAY_OPENAI_WS_FORCE_HTTP=trueThis selects HTTP/SSE for OpenAI upstream Responses traffic that would
otherwise use WebSocket. It does not change the client-facing protocol or force
HTTP/1.1; configure gateway.openai_http2.enabled (orGATEWAY_OPENAI_HTTP2_ENABLED=false) separately when a proxy is incompatible
with HTTP/2. Unlike the account-level http_bridge mode, this global fallback
takes effect without enabling mode_router_v2_enabled. Keep the setting in the
deployment's persisted .env or config.yaml, rather than inside a running
container, so it is read again after an image update or container recreation.
⚠️ Important: Creating the Admin Account
The initial admin account is only created via the setup wizard (served at http://<host>:8080 on first run). The default.admin_email / default.admin_password fields in config.yaml are not used to create it — they exist in the template for historical reasons.
Because step 5 above pre-creates config.yaml, the setup wizard will be skipped on first run: the server detects an existing config and boots straight into normal mode with an empty users table, so the first login attempt fails with invalid email or password.
Two ways to create the admin account:
Recommended — let the wizard generate
config.yaml: Skip step 5 (do not run thecp). Start./sub2apidirectly; the setup wizard athttp://localhost:8080walks you through database, Redis, and admin account setup, then writesconfig.yamlfor you.If you already created
config.yaml: Temporarily move it aside so the wizard can trigger on first run, then restore it afterwards:bashmv config.yaml config.yaml.bak ./sub2api # wizard runs at http://localhost:8080 and writes a fresh config.yaml # stop the server (Ctrl+C) once the wizard completes, then restore your config: mv config.yaml.bak config.yaml ./sub2api # restart in normal mode and log in with the admin you just created
# 6. Run the application
./sub2apiDevelopment Mode
# Backend (with hot reload)
cd backend
go run ./cmd/server
# Frontend (with hot reload)
cd frontend
pnpm run devCode Generation
When editing backend/ent/schema, regenerate Ent + Wire:
cd backend
go generate ./ent
go generate ./cmd/serverSimple Mode
Simple Mode is designed for individual developers or internal teams who want quick access without full SaaS features.
- Enable: Set environment variable
RUN_MODE=simple - Default groups are seeded on each startup. Set
SIMPLE_MODE_AUTO_CREATE_DEFAULT_GROUPS=false(or YAMLsimple_mode.auto_create_default_groups: false) to manage groups yourself. The default istrue; disabling it does not delete existing groups or change runtime auto-binding or admin concurrency setup. - Difference: Hides SaaS-related features and skips billing process
- Optional key windows: Set
SIMPLE_MODE_KEY_RATE_LIMIT_ENABLED=trueto enforce each API key's configured 5-hour, daily, and 7-day spending windows. The default isfalse; balance and subscription debit remain bypassed when enabled. - Window enforcement uses the database as its source of truth and records only API-key window usage. It is a post-request soft cap, so concurrent in-flight requests can overshoot by their final costs. Historical simple-mode usage is not backfilled.
- Security note: In production, you must also set
SIMPLE_MODE_CONFIRM=trueto allow startup
Asynchronous Image Tasks
Long-running OpenAI/Grok image generation and editing can be submitted through /v1/images/generations/async or /v1/images/edits/async, then polled at /v1/images/tasks/{task_id} without holding a CDN connection open. See Asynchronous Image Tasks for request and response examples.
Grok / xAI Support
Sub2API supports both Grok subscription accounts through xAI OAuth and standard xAI API-key accounts. Both account types forward OpenAI-compatible Responses traffic to xAI.
Supported Scope
- Platform name:
grok - Account types: OAuth subscription accounts and xAI API-key accounts
- Public Responses targets:
/v1/responses,/responses, and/backend-api/codex/responses, forwarded to the Grok subscription proxy for OAuth accounts orhttps://api.x.ai/v1/responsesfor API-key accounts - Public Claude-compatible target:
/v1/messages, converted to xAI Responses and returned as Anthropic Messages output for Claude CLI style clients - Public Chat Completions targets:
/v1/chat/completionsand/chat/completions, forwarded to the account-type-specific xAI upstream - Codex CLI style Responses WebSocket ingress is accepted on the Responses targets and bridged to xAI HTTP/SSE Responses upstream
- Text models:
grok-4.5,grok-4.3,grok-build-0.1,grok-composer-2.5-fast,grok-4.20-0309-reasoning,grok-4.20-0309-non-reasoning, andgrok-4.20-multi-agent-0309 - Media targets for Grok groups:
/v1/images/generations,/images/generations,/v1/images/edits,/images/edits,/v1/videos/generations,/videos/generations,/v1/videos/edits,/videos/edits,/v1/videos/extensions,/videos/extensions,/v1/videos/{request_id}, and/videos/{request_id}. Generation, editing, and extension requests require the group image-generation permission. - Media models:
grok-imagine,grok-imagine-image-quality,grok-imagine-image,grok-imagine-image-2.0,grok-imagine-edit,grok-imagine-video, andgrok-imagine-video-1.5 - JSON image-edit and video-generation requests accept image references in
image,images,reference_images, andmaskobjects. Useurlfor xAI-compatible payloads; the legacyimage_urlfield remains accepted and is normalized tourlbefore forwarding. - Out of scope for this provider: TTS, transcription, browser automation, cookies, and Grok web scraping
OAuth Configuration
The Grok OAuth flow uses PKCE and does not require committing private secrets. The default client details follow the public xAI OAuth flow used by compatible clients, and every value can be overridden by environment variable:
| Variable | Default |
|---|---|
XAI_OAUTH_CLIENT_ID |
Public xAI OAuth client ID |
XAI_OAUTH_SCOPE |
openid profile email offline_access grok-cli:access api:access |
XAI_OAUTH_REDIRECT_URI |
http://127.0.0.1:56121/callback |
XAI_OAUTH_AUTHORIZE_URL |
https://auth.x.ai/oauth2/authorize |
XAI_OAUTH_TOKEN_URL |
https://auth.x.ai/oauth2/token |
XAI_BASE_URL |
https://api.x.ai/v1; runtime-diagnostics override (account base_url controls request forwarding) |
XAI_GROK_CLI_VERSION |
0.2.114; optional override for the client identity sent to cli-chat-proxy.grok.com. The pinned value is also the floor: an override below it is dropped |
Administrators can create Grok OAuth or API-key accounts from the dashboard. OAuth authorization and reauthorization are also available through the admin API:
| Endpoint | Purpose |
|---|---|
POST /api/v1/admin/grok/oauth/auth-url |
Generate an xAI OAuth authorization URL |
POST /api/v1/admin/grok/oauth/exchange-code |
Exchange a callback URL, query string, or code for OAuth credentials |
POST /api/v1/admin/grok/oauth/refresh-token |
Validate or refresh a Grok refresh token |
POST /api/v1/admin/grok/accounts/:id/refresh |
Refresh an existing Grok account |
OAuth credential storage reuses the existing account JSON fields: access_token, refresh_token, token_type, expires_at, base_url, optional email, optional subscription_tier, and entitlement_status. OAuth inference defaults to https://cli-chat-proxy.grok.com/v1; existing OAuth accounts that stored the old https://api.x.ai/v1 default are redirected to the subscription proxy at runtime. Explicit custom upstreams remain unchanged.
For API-key accounts, select Grok → API Key in the create-account dialog. The official base URL defaults to https://api.x.ai/v1; credentials use the existing base_url and api_key account fields. OAuth accounts continue to use the subscription flow above.
Grok Build CLI Configuration
- In the Sub2API admin dashboard, add either a
grokOAuth account and complete xAI authorization, or add a Grok API-key account. - Create a Grok group, attach the account to it, then create a Sub2API API key assigned to that group.
- In the user API-key page, click Use Key and select Grok CLI. The modal generates the correct file and base URL for macOS/Linux or Windows. It also provides an OpenCode configuration on the OpenCode tab.
- If configuring manually, save the following as
~/.grok/config.toml(Windows:%USERPROFILE%\.grok\config.toml):
[models]
default = "grok"
web_search = "grok"
[model."grok"]
model = "grok-4.5"
base_url = "https://your-sub2api.example.com/v1"
name = "Grok 4.5"
api_key = "sk-your-sub2api-key"
api_backend = "responses"
context_window = 1000000
supports_backend_search = trueBack up an existing config.toml before merging the entry. The file contains a Sub2API API key, so keep it private and restrict its permissions where supported. Verify the effective configuration and make a smoke request:
grok inspect
grok -p "Reply with sub2api-ok" -m grokThe base_url above is the public Sub2API URL ending in /v1, not api.x.ai or the internal xAI OAuth proxy URL.
Usage And Quota Display
xAI quota is passive. Sub2API does not invent subscription quota values; it records whitelisted xAI rate-limit headers from successful or rate-limited upstream responses when xAI sends them. Before the first usable upstream response, the dashboard shows quota as unknown and still displays local Sub2API usage stats.
401 responses temporarily remove accounts with invalid credentials from scheduling. 403 responses are treated as access or entitlement failures instead of token-refresh loops. 429 responses use Retry-After or a short cooldown to temporarily remove the account from scheduling.
New Grok image and video generation requests use a media-specific eligibility check. API-key accounts remain eligible. OAuth accounts with explicit Free or forbidden billing evidence are excluded from new media generation. Missing or malformed observations are probed before dispatch; a successful but incomplete billing response is treated as billing_inconclusive and remains eligible for backwards compatibility, because an unknown billing schema is not proof that the account lacks media entitlement. Operators can quarantine a known-bad account with extra.grok_media_eligible=false or force-enable a verified account with true. Imports run the billing-first quota probe proactively. Chat requests and video status lookups are not affected by this media-only quarantine. If no eligible account remains, the media endpoint returns HTTP 503 with error type grok_media_no_eligible_account.
Administrators can override automatic media eligibility through the account create/update API by setting extra.grok_media_eligible to false (exclude) or true (force eligible). On update, set it to null to remove the override and return to automatic probe-based behavior; omitting the field preserves the current override. A weekly allowance period alone is not treated as a paid tier signal. Successful image responses must contain at least one actual image output; empty HTTP 200 responses trigger account failover instead of being counted and returned as successful generations.
TypeSafe / Jev Support
Sub2API supports TypeSafe API-key accounts through Jev's native, non-streaming System One protocol.
- Platform:
typesafe; account type: API Key - Default upstream:
https://api.typesafe.ai - Public endpoint:
POST /v1/systemone - Model:
jev-latest, also returned by/v1/modelsfor TypeSafe groups - Questions:
noul,choice, andscore
Requests and successful responses retain the native System One JSON structure. This endpoint is not compatible with Chat Completions, Responses, Anthropic Messages, or streaming clients.
Question validation follows the TypeSafe OpenAPI wire schema (also used by SDK v0.5.7). instructions may be omitted or null for all question types. Noul criteria may be omitted or null; its true/false descriptions and Choice descriptions accept strings, objects, arrays, or null. Score criteria must be a non-empty array of string, object, or array descriptions; a single level is valid. SDK integer-keyed Score maps are normalized to arrays by the SDK before sending.
curl https://your-sub2api.example.com/v1/systemone \
-H 'Authorization: Bearer sk-your-sub2api-key' \
-H 'Content-Type: application/json' \
--data '{"model":"jev-latest","state":"Text to evaluate","questions":{"safety":{"type":"noul","instructions":"Evaluate whether the text is unsafe"}}}'The built-in jev-latest price is $0.042 per million input tokens and $0 for output tokens. Channel pricing can override both values. Credential, billing, permission, rate-limit, overload, server, and network failures (401, 402, 403, 429, 529, 5xx, transport errors) use the existing account error policy (including custom error codes and temporary-unschedulable rules) and fail over to another account; request errors (400, 413, and 422) are returned without retrying another account and never change account state. TypeSafe groups (and Composite requests routed to TypeSafe) reject Messages, Chat Completions, Responses, and count_tokens requests with 404.
Antigravity Support
Sub2API supports Antigravity accounts. After authorization, dedicated endpoints are available for Claude and Gemini models.
Dedicated Endpoints
| Endpoint | Model |
|---|---|
/antigravity/v1/messages |
Claude models |
/antigravity/v1beta/ |
Gemini models |
Claude Code Configuration
export ANTHROPIC_BASE_URL="http://localhost:8080/antigravity"
export ANTHROPIC_AUTH_TOKEN="sk-xxx"Hybrid Scheduling Mode
Antigravity accounts support optional hybrid scheduling. When enabled, the general endpoints /v1/messages and /v1beta/ will also route requests to Antigravity accounts.
⚠️ Warning: Anthropic Claude and Antigravity Claude cannot be mixed within the same conversation context. Use groups to isolate them properly.
Project Structure
sub2api/
├── backend/ # Go backend service
│ ├── cmd/server/ # Application entry
│ ├── internal/ # Internal modules
│ │ ├── config/ # Configuration
│ │ ├── model/ # Data models
│ │ ├── service/ # Business logic
│ │ ├── handler/ # HTTP handlers
│ │ └── gateway/ # API gateway core
│ └── resources/ # Static resources
│
├── frontend/ # Vue 3 frontend
│ └── src/
│ ├── api/ # API calls
│ ├── stores/ # State management
│ ├── views/ # Page components
│ └── components/ # Reusable components
│
└── deploy/ # Deployment files
├── docker-compose.yml # Docker Compose configuration
├── .env.example # Environment variables for Docker Compose
├── config.example.yaml # Full config file for binary deployment
└── install.sh # One-click installation scriptStar History
License
This project is licensed under the GNU Lesser General Public License v3.0 (or later).
Copyright (c) 2026 Wesley Liddick
Overview (`deploy/README.md`)
Sub2API Deployment Files
This directory contains files for deploying Sub2API on Linux servers and Apple-silicon Macs.
Deployment Methods
| Method | Best For | Setup Wizard |
|---|---|---|
| Docker Compose | Quick setup, all-in-one | Not needed (auto-setup) |
| Apple container | Native local stack on macOS 26 | Not needed (auto-setup) |
| Binary Install | Production servers, systemd | Web-based wizard |
Files
| File | Description |
|---|---|
docker-compose.yml |
Docker Compose configuration (named volumes) |
docker-compose.local.yml |
Docker Compose configuration (local directories, easy migration) |
docker-deploy.sh |
One-click Docker deployment script (recommended) |
apple-container.sh |
Native Apple container lifecycle script |
APPLE_CONTAINER.md |
Apple container deployment and operations guide |
.env.example |
Container environment variables template |
DOCKER.md |
Docker Hub documentation |
install.sh |
One-click binary installation script |
install-datamanagementd.sh |
datamanagementd 一键安装脚本 |
sub2api.service |
Systemd service unit file |
sub2api-datamanagementd.service |
datamanagementd systemd service unit file |
DATAMANAGEMENTD_CN.md |
datamanagementd 部署与联动说明(中文) |
config.example.yaml |
Example configuration file |
EDGE_SECURITY.md |
Reverse proxy, CDN/WAF, trusted proxy, and ingress hardening guide |
Apple container Deployment
Apple-silicon Macs running macOS 26 can run the complete Sub2API, PostgreSQL, and Redis stack with Apple container 1.1.0 or newer:
./apple-container.sh init
./apple-container.sh up
./apple-container.sh status
./apple-container.sh logs app -fThe script uses Apple named volumes, starts dependencies in order, and performs live readiness checks. The application container supervises the Sub2API process so the Web UI's update-and-restart flow can relaunch an updated binary. It does not provide host-level automatic startup; run ./apple-container.sh up after a host reboot. Docker Compose remains the recommended production deployment path.
See APPLE_CONTAINER.md for configuration, upgrades, persistence, networking behavior, and limitations.
Docker Deployment (Recommended)
Method 1: One-Click Deployment (Recommended)
Use the automated preparation script for the easiest setup:
# Download and run the preparation script
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# Or download first, then run
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh -o docker-deploy.sh
chmod +x docker-deploy.sh
./docker-deploy.shWhat the script does:
- Downloads
docker-compose.local.ymland.env.example - Automatically generates secure secrets (JWT_SECRET, TOTP_ENCRYPTION_KEY, POSTGRES_PASSWORD)
- Creates
.envfile with generated secrets - Creates necessary data directories (data/, postgres_data/, redis_data/)
- Displays generated credentials (POSTGRES_PASSWORD, JWT_SECRET, etc.)
After running the script:
# Start services
docker compose -f docker-compose.local.yml up -d
# View logs
docker compose -f docker-compose.local.yml logs -f sub2api
# If admin email/password were auto-generated, find them in logs:
docker compose -f docker-compose.local.yml logs sub2api | grep "Generated admin"
# Access Web UI
# http://localhost:8080Method 2: Manual Deployment
If you prefer manual control:
# Clone repository
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy
# Configure environment
cp .env.example .env
chmod 600 .env
nano .env # Set POSTGRES_PASSWORD and other required variables
# Generate secure secrets (recommended)
JWT_SECRET=$(openssl rand -hex 32)
TOTP_ENCRYPTION_KEY=$(openssl rand -hex 32)
echo "JWT_SECRET=${JWT_SECRET}" >> .env
echo "TOTP_ENCRYPTION_KEY=${TOTP_ENCRYPTION_KEY}" >> .env
# Create data directories
mkdir -p data postgres_data redis_data
# Start all services using local directory version
docker compose -f docker-compose.local.yml up -d
# View logs (check for auto-generated admin email and password)
docker compose -f docker-compose.local.yml logs -f sub2api
# Access Web UI
# http://localhost:8080Deployment Version Comparison
| Version | Data Storage | Migration | Best For |
|---|---|---|---|
| docker-compose.local.yml | Local directories (./data, ./postgres_data, ./redis_data) | ✅ Easy (tar entire directory) | Production, need frequent backups/migration |
| docker-compose.yml | Named volumes (/var/lib/docker/volumes/) | ⚠️ Requires docker commands | Simple setup, don't need migration |
Recommendation: Use docker-compose.local.yml (deployed by docker-deploy.sh) for easier data management and migration.
How Auto-Setup Works
When using Docker Compose with AUTO_SETUP=true:
On first run, the system automatically:
- Connects to PostgreSQL and Redis
- Applies database migrations (SQL files in
backend/migrations/*.sql) and records them inschema_migrations - Generates JWT secret (if not provided)
- Creates admin account (email and password auto-generated if not provided; a provided password must be 8-72 bytes)
- Writes config.yaml
No manual Setup Wizard needed - just configure
.envand startIf
ADMIN_EMAIL/ADMIN_PASSWORDare not set, check logs for the generated admin email (login username) and password:bashdocker compose logs sub2api | grep "Generated admin"
Startup and Database Recovery
Sub2API applies database migrations during application startup. PostgreSQL can
remain in its recovery/startup phase briefly after a host or Docker daemon
restart. The application retries transient PostgreSQL startup and connection
errors with bounded exponential backoff, then starts automatically when the
database becomes ready. Authentication errors, migration checksum mismatches,
SQL errors, and other permanent configuration or data errors fail immediately.
The Compose example also uses a PostgreSQL health check that verifies both
server readiness and a simple SQL query. depends_on: condition: service_healthy
controls dependency ordering for a fresh Compose start, but it is not a
replacement for application-level retries when Docker restores existing
containers after a host restart.
For systemd deployments, keep Restart=always and RestartSec configured insub2api.service; the application retry covers transient database startup,
while systemd remains the supervisor for permanent process exits. For
Kubernetes, use a PostgreSQL readiness probe and retain the Sub2API startup
retry behavior; configure the application liveness probe separately so a
database recovery period is not treated as a permanent process failure.
Database Migration Notes (PostgreSQL)
- Migrations are applied in lexicographic order (e.g.
001_...sql,002_...sql). schema_migrationstracks applied migrations (filename + checksum).- Migrations are forward-only; rollback requires a DB backup restore or a manual compensating SQL script.
Verify users.allowed_groups → user_allowed_groups backfill
During the incremental GORM→Ent migration, users.allowed_groups (legacy BIGINT[]) is being replaced by a normalized join table user_allowed_groups(user_id, group_id).
Run this query to compare the legacy data vs the join table:
WITH old_pairs AS (
SELECT DISTINCT u.id AS user_id, x.group_id
FROM users u
CROSS JOIN LATERAL unnest(u.allowed_groups) AS x(group_id)
WHERE u.allowed_groups IS NOT NULL
)
SELECT
(SELECT COUNT(*) FROM old_pairs) AS old_pair_count,
(SELECT COUNT(*) FROM user_allowed_groups) AS new_pair_count;datamanagementd(数据管理)联动
如需启用管理后台“数据管理”功能,请额外部署宿主机 datamanagementd:
- 主进程固定探测
/tmp/sub2api-datamanagement.sock - Docker 场景下需把宿主机 Socket 挂载到容器内同路径
- 详细步骤见:
deploy/DATAMANAGEMENTD_CN.md
Commands
For local directory version (docker-compose.local.yml):
# Start services
docker compose -f docker-compose.local.yml up -d
# Stop services
docker compose -f docker-compose.local.yml down
# View logs
docker compose -f docker-compose.local.yml logs -f sub2api
# Restart Sub2API only
docker compose -f docker-compose.local.yml restart sub2api
# Update to latest version
docker compose -f docker-compose.local.yml pull
docker compose -f docker-compose.local.yml up -d
# Remove all data (caution!)
docker compose -f docker-compose.local.yml down
rm -rf data/ postgres_data/ redis_data/For named volumes version (docker-compose.yml):
# Start services
docker compose up -d
# Stop services
docker compose down
# View logs
docker compose logs -f sub2api
# Restart Sub2API only
docker compose restart sub2api
# Update to latest version
docker compose pull
docker compose up -d
# Remove all data (caution!)
docker compose down -vEnvironment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
POSTGRES_PASSWORD |
Yes | - | PostgreSQL password |
JWT_SECRET |
Recommended | (auto-generated) | JWT secret (fixed for persistent sessions) |
TOTP_ENCRYPTION_KEY |
Recommended | (auto-generated) | TOTP encryption key (fixed for persistent 2FA) |
SERVER_PORT |
No | 8080 |
Server port |
ADMIN_EMAIL |
No | (auto-generated) | Admin email (login username) |
ADMIN_PASSWORD |
No | (auto-generated) | Admin password (8-72 bytes) |
TZ |
No | Asia/Shanghai |
Timezone |
UPDATE_GITHUB_TOKEN |
No | (empty) | Token for api.github.com release checks only; asset downloads remain anonymous. |
GEMINI_OAUTH_CLIENT_ID |
No | (builtin) | Google OAuth client ID (Gemini OAuth). Leave empty to use the built-in Gemini CLI client. |
GEMINI_OAUTH_CLIENT_SECRET |
No | (builtin) | Google OAuth client secret (Gemini OAuth). Leave empty to use the built-in Gemini CLI client. |
GEMINI_OAUTH_SCOPES |
No | (default) | OAuth scopes (Gemini OAuth) |
GEMINI_QUOTA_POLICY |
No | (empty) | JSON overrides for Gemini local quota simulation (Code Assist only). |
See .env.example for all available options.
Note: The
docker-deploy.shscript automatically generatesJWT_SECRET,TOTP_ENCRYPTION_KEY, andPOSTGRES_PASSWORDfor you.
Easy Migration (Local Directory Version)
When using docker-compose.local.yml, all data is stored in local directories, making migration simple:
# On source server: Stop services and create archive
cd /path/to/deployment
docker compose -f docker-compose.local.yml down
cd ..
tar czf sub2api-complete.tar.gz deployment/
# Transfer to new server
scp sub2api-complete.tar.gz user@new-server:/path/to/destination/
# On new server: Extract and start
tar xzf sub2api-complete.tar.gz
cd deployment/
docker compose -f docker-compose.local.yml up -dYour entire deployment (configuration + data) is migrated!
Gemini OAuth Configuration
Sub2API supports three methods to connect to Gemini:
Method 1: Code Assist OAuth (Recommended for GCP Users)
No configuration needed - always uses the built-in Gemini CLI OAuth client (public).
- Leave
GEMINI_OAUTH_CLIENT_IDandGEMINI_OAUTH_CLIENT_SECRETempty - In the Admin UI, create a Gemini OAuth account and select "Code Assist" type
- Complete the OAuth flow in your browser
Note: Even if you configure
GEMINI_OAUTH_CLIENT_ID/GEMINI_OAUTH_CLIENT_SECRETfor AI Studio OAuth,
Code Assist OAuth will still use the built-in Gemini CLI client.
Requirements:
- Google account with access to Google Cloud Platform
- A GCP project (auto-detected or manually specified)
How to get Project ID (if auto-detection fails):
- Go to Google Cloud Console
- Click the project dropdown at the top of the page
- Copy the Project ID (not the project name) from the list
- Common formats:
my-project-123456orcloud-ai-companion-xxxxx
Method 2: AI Studio OAuth (For Regular Google Accounts)
Requires your own OAuth client credentials.
Step 1: Create OAuth Client in Google Cloud Console
- Go to Google Cloud Console - Credentials
- Create a new project or select an existing one
- Enable the Generative Language API:
- Go to "APIs & Services" → "Library"
- Search for "Generative Language API"
- Click "Enable"
- Configure OAuth Consent Screen (if not done):
- Go to "APIs & Services" → "OAuth consent screen"
- Choose "External" user type
- Fill in app name, user support email, developer contact
- Add scopes:
https://www.googleapis.com/auth/generative-language.retriever(and optionallyhttps://www.googleapis.com/auth/cloud-platform) - Add test users (your Google account email)
- Create OAuth 2.0 credentials:
- Go to "APIs & Services" → "Credentials"
- Click "Create Credentials" → "OAuth client ID"
- Application type: Web application (or Desktop app)
- Name: e.g., "Sub2API Gemini"
- Authorized redirect URIs: Add
http://localhost:1455/auth/callback
- Copy the Client ID and Client Secret
- ⚠️ Publish to Production (IMPORTANT):
- Go to "APIs & Services" → "OAuth consent screen"
- Click "PUBLISH APP" to move from Testing to Production
- Testing mode limitations:
- Only manually added test users can authenticate (max 100 users)
- Refresh tokens expire after 7 days
- Users must be re-added periodically
- Production mode: Any Google user can authenticate, tokens don't expire
- Note: For sensitive scopes, Google may require verification (demo video, privacy policy)
Step 2: Configure Environment Variables
GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com
GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-client-secret
# 可选:如需使用 Gemini CLI 内置 OAuth Client(Code Assist / Google One)
# 安全说明:本仓库不会内置该 client_secret,请在运行环境通过环境变量注入。
# GEMINI_CLI_OAUTH_CLIENT_SECRET=GOCSPX-your-built-in-secretStep 3: Create Account in Admin UI
- Create a Gemini OAuth account and select "AI Studio" type
- Complete the OAuth flow
- After consent, your browser will be redirected to
http://localhost:1455/auth/callback?code=...&state=... - Copy the full callback URL (recommended) or just the
codeand paste it back into the Admin UI
- After consent, your browser will be redirected to
Method 3: API Key (Simplest)
- Go to Google AI Studio
- Click "Create API key"
- In Admin UI, create a Gemini API Key account
- Paste your API key (starts with
AIza...)
Comparison Table
| Feature | Code Assist OAuth | AI Studio OAuth | API Key |
|---|---|---|---|
| Setup Complexity | Easy (no config) | Medium (OAuth client) | Easy |
| GCP Project Required | Yes | No | No |
| Custom OAuth Client | No (built-in) | Yes (required) | N/A |
| Rate Limits | GCP quota | Standard | Standard |
| Best For | GCP developers | Regular users needing OAuth | Quick testing |
Binary Installation
For production servers using systemd.
One-Line Installation
curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bashManual Installation
- Download the latest release from GitHub Releases
- Extract and copy the binary to
/opt/sub2api/ - Copy
sub2api.serviceto/etc/systemd/system/ - Run:bash
sudo systemctl daemon-reload sudo systemctl enable sub2api sudo systemctl start sub2api - Open the Setup Wizard in your browser to complete configuration
Commands
# Install
sudo ./install.sh
# Upgrade
sudo ./install.sh upgrade
# Uninstall
sudo ./install.sh uninstallService Management
# Start the service
sudo systemctl start sub2api
# Stop the service
sudo systemctl stop sub2api
# Restart the service
sudo systemctl restart sub2api
# Check status
sudo systemctl status sub2api
# View logs
sudo journalctl -u sub2api -f
# Enable auto-start on boot
sudo systemctl enable sub2apiConfiguration
Server Address and Port
During installation, you will be prompted to configure the server listen address and port. These settings are stored in the systemd service file as environment variables.
To change after installation:
Edit the systemd service:
bashsudo systemctl edit sub2apiAdd or modify:
ini[Service] Environment=SERVER_HOST=0.0.0.0 Environment=SERVER_PORT=3000Reload and restart:
bashsudo systemctl daemon-reload sudo systemctl restart sub2api
Gemini OAuth Configuration
If you need to use AI Studio OAuth for Gemini accounts, add the OAuth client credentials to the systemd service file:
Edit the service file:
bashsudo nano /etc/systemd/system/sub2api.serviceAdd your OAuth credentials in the
[Service]section (after the existingEnvironment=lines):iniEnvironment=GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com Environment=GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-client-secret如需使用“内置 Gemini CLI OAuth Client”(Code Assist / Google One),还需要注入:
iniEnvironment=GEMINI_CLI_OAUTH_CLIENT_SECRET=GOCSPX-your-built-in-secretReload and restart:
bashsudo systemctl daemon-reload sudo systemctl restart sub2api
Note: Code Assist OAuth does not require any configuration - it uses the built-in Gemini CLI client.
See the Gemini OAuth Configuration section above for detailed setup instructions.
Application Configuration
The main config file is at /etc/sub2api/config.yaml (created by Setup Wizard).
Prerequisites
- Linux server (Ubuntu 20.04+, Debian 11+, CentOS 8+, etc.)
- PostgreSQL 14+
- Redis 6+
- systemd
Directory Structure
/opt/sub2api/
├── sub2api # Main binary
├── sub2api.backup # Backup (after upgrade)
└── data/ # Runtime data
/etc/sub2api/
└── config.yaml # Configuration fileTroubleshooting
Docker
For local directory version:
# Check container status
docker compose -f docker-compose.local.yml ps
# View detailed logs
docker compose -f docker-compose.local.yml logs --tail=100 sub2api
# Check database connection
docker compose -f docker-compose.local.yml exec postgres pg_isready
# Check Redis connection
docker compose -f docker-compose.local.yml exec redis redis-cli ping
# Restart all services
docker compose -f docker-compose.local.yml restart
# Check data directories
ls -la data/ postgres_data/ redis_data/For named volumes version:
# Check container status
docker compose ps
# View detailed logs
docker compose logs --tail=100 sub2api
# Check database connection
docker compose exec postgres pg_isready
# Check Redis connection
docker compose exec redis redis-cli ping
# Restart all services
docker compose restartBinary Install
# Check service status
sudo systemctl status sub2api
# View recent logs
sudo journalctl -u sub2api -n 50
# Check config file
sudo cat /etc/sub2api/config.yaml
# Check PostgreSQL
sudo systemctl status postgresql
# Check Redis
sudo systemctl status redisCommon Issues
- Port already in use: Change
SERVER_PORTin.envor systemd config - Database connection failed: Check PostgreSQL is running and credentials are correct
- Redis connection failed: Check Redis is running and password is correct
- Permission denied: Ensure proper file ownership for binary install
TLS Fingerprint Configuration
Sub2API supports TLS fingerprint simulation to make requests appear as if they come from the official Claude CLI (Node.js client).
💡 Tip: Visit tls.sub2api.org to get TLS fingerprint information for different devices and browsers.
Default Behavior
- Built-in
claude_cli_v2profile simulates Node.js 20.x + OpenSSL 3.x - JA3 Hash:
1a28e69016765d92e3b381168d68922c - JA4:
t13d5911h1_a33745022dd6_1f22a2ca17c4 - Profile selection:
accountID % profileCount
Configuration
gateway:
tls_fingerprint:
enabled: true # Global switch
profiles:
# Simple profile (uses default cipher suites)
profile_1:
name: "Profile 1"
# Profile with custom cipher suites (use compact array format)
profile_2:
name: "Profile 2"
cipher_suites: [4866, 4867, 4865, 49199, 49195, 49200, 49196]
curves: [29, 23, 24]
point_formats: 0
# Another custom profile
profile_3:
name: "Profile 3"
cipher_suites: [4865, 4866, 4867, 49199, 49200]
curves: [29, 23, 24, 25]Profile Fields
| Field | Type | Description |
|---|---|---|
name |
string | Display name (required) |
cipher_suites |
[]uint16 | Cipher suites in decimal. Empty = default |
curves |
[]uint16 | Elliptic curves in decimal. Empty = default |
point_formats |
[]uint8 | EC point formats. Empty = default |
Common Values Reference
Cipher Suites (TLS 1.3): 4865 (AES_128_GCM), 4866 (AES_256_GCM), 4867 (CHACHA20)
Cipher Suites (TLS 1.2): 49195, 49196, 49199, 49200 (ECDHE variants)
Curves: 29 (X25519), 23 (P-256), 24 (P-384), 25 (P-521)
Overview (`.github/release-tools/README.md`)
Release matrix
The release workflow builds the frontend once, then runs each configured Go target on its own Linux runner. CGO_ENABLED=0 permits cross-compilation. The target matrix is read from .goreleaser.yaml, including its exclusions; simple releases select Linux amd64 only.
Each build uses GoReleaser OSS in snapshot mode with the selected release version and one target. Archive naming, bundled files, Go flags and release templates remain in the existing GoReleaser configurations. Every archive is accompanied by its source commit, target, version and SHA256. The publishing job verifies the complete matrix before building images or publishing. It uses GoReleaser's extra_files support to publish existing archives and checksums, with builds disabled. No Pro license is needed.
Go caches are isolated by target and refreshed on each source commit, with fallback to the preceding target cache. Save uses the original restore key, even if a build hook changes go.sum. Matrix jobs upload uniquely named artifacts. The publishing job extracts only the regular Linux binary from each verified archive and restores its executable permission before constructing Docker contexts. QEMU remains limited to runtime-image instructions. DockerHub images are omitted when its credentials are absent; GHCR is always retained. Simple mode still publishes only the amd64 GHCR image and the simple release description.
All build jobs use the commit resolved by prepare, including a manual release's selected tag. Helper scripts come from the workflow revision and are passed as a run-local artifact, so older application tags do not need to contain the new scripts. The workflow serializes release runs to prevent simultaneous updates to moving image tags.
Validate without publication
From a branch containing this workflow:
gh workflow run release.yml --ref <branch> \
-f tag=<branch> -f dry_run=true -f simple_release=falseA dry run builds all selected archives and both runtime images, verifies artifact provenance and produces the final checksum file. It exports images locally as OCI archives instead of pushing them. It skips registry logins, GitHub Release publication, DockerHub description updates, Telegram notifications and VERSION synchronization. Test the simple path separately with simple_release=true.
Dry-run artifacts are available in the Actions run, including release-dry-run-report. Compare job start/end times, GoReleaser's build duration and cache restore results. Do not present an initial cold-cache run as a warmed-cache benchmark; publishing network time is not measured by dry runs.
Helper checks:
python -m pip install -r .github/release-tools/requirements-release.txt
python -m unittest discover -s .github/release-tools -p 'test_release_matrix.py'
bash -n .github/release-tools/release-images.shOverview (`backend/migrations/README.md`)
Database Migrations
Overview
This directory contains SQL migration files for database schema changes. The migration system uses SHA256 checksums to ensure migration immutability and consistency across environments.
Migration File Naming
Format: NNN_description.sql
NNN: Sequential number (e.g., 001, 002, 003)description: Brief description in snake_case
Example: 017_add_gemini_tier_id.sql
`_notx.sql` 命名与执行语义(并发索引专用)
当迁移包含 CREATE INDEX CONCURRENTLY 或 DROP INDEX CONCURRENTLY 时,必须使用 _notx.sql 后缀,例如:
062_add_accounts_priority_indexes_notx.sql063_drop_legacy_indexes_notx.sql
运行规则:
*.sql(不带_notx)按事务执行。*_notx.sql按非事务执行,不会包裹在BEGIN/COMMIT中。*_notx.sql仅允许并发索引语句,不允许混入事务控制语句或其他 DDL/DML。
幂等要求(必须):
- 创建索引:
CREATE INDEX CONCURRENTLY IF NOT EXISTS ... - 删除索引:
DROP INDEX CONCURRENTLY IF EXISTS ...
这样可以保证灾备重放、重复执行时不会因对象已存在/不存在而失败。
Migration File Structure
This project uses a custom migration runner (internal/repository/migrations_runner.go) that executes the full SQL file content as-is.
- Regular migrations (
*.sql): executed in a transaction. - Non-transactional migrations (
*_notx.sql): split by statement and executed without transaction (forCONCURRENTLY).
-- Forward-only migration (recommended)
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS example_column VARCHAR(100);⚠️ Do not place executable "Down" SQL in the same file. The runner does not parse goose Up/Down sections and will execute all SQL statements in the file.
Important Rules
⚠️ Immutability Principle
Once a migration is applied to ANY environment (dev, staging, production), it MUST NOT be modified.
Why?
- Each migration has a SHA256 checksum stored in the
schema_migrationstable - Modifying an applied migration causes checksum mismatch errors
- Different environments would have inconsistent database states
- Breaks audit trail and reproducibility
✅ Correct Workflow
Create new migration
bash# Create new file with next sequential number touch migrations/018_your_change.sqlWrite forward-only migration SQL
- Put only the intended schema change in the file
- If rollback is needed, create a new migration file to revert
Test locally
bash# Apply migration make migrate-up # Test rollback make migrate-downCommit and deploy
bashgit add migrations/018_your_change.sql git commit -m "feat(db): add your change"
❌ What NOT to Do
- ❌ Modify an already-applied migration file
- ❌ Delete migration files
- ❌ Change migration file names
- ❌ Reorder migration numbers
🔧 If You Accidentally Modified an Applied Migration
Error message:
migration 017_add_gemini_tier_id.sql checksum mismatch (db=abc123... file=def456...)Solution:
# 1. Find the original version
git log --oneline -- migrations/017_add_gemini_tier_id.sql
# 2. Revert to the commit when it was first applied
git checkout <commit-hash> -- migrations/017_add_gemini_tier_id.sql
# 3. Create a NEW migration for your changes
touch migrations/018_your_new_change.sqlMigration System Details
- Checksum Algorithm: SHA256 of trimmed file content
- Tracking Table:
schema_migrations(filename, checksum, applied_at) - Runner:
internal/repository/migrations_runner.go - Auto-run: Migrations run automatically on service startup
Best Practices
Keep migrations small and focused
- One logical change per migration
- Easier to review and rollback
Write reversible migrations
- Always provide a working Down migration
- Test rollback before committing
Use transactions
- Wrap DDL statements in transactions when possible
- Ensures atomicity
Add comments
- Explain WHY the change is needed
- Document any special considerations
Test in development first
- Apply migration locally
- Verify data integrity
- Test rollback
Example Migration
-- Add tier_id field to Gemini OAuth accounts for quota tracking
UPDATE accounts
SET credentials = jsonb_set(
credentials,
'{tier_id}',
'"LEGACY"',
true
)
WHERE platform = 'gemini'
AND type = 'oauth'
AND credentials->>'tier_id' IS NULL;Troubleshooting
Checksum Mismatch
See "If You Accidentally Modified an Applied Migration" above.
Migration Failed
# Check migration status
psql -d sub2api -c "SELECT * FROM schema_migrations ORDER BY applied_at DESC;"
# Manually rollback if needed (use with caution)
# Better to fix the migration and create a new oneNeed to Skip a Migration (Emergency Only)
-- DANGEROUS: Only use in development or with extreme caution
INSERT INTO schema_migrations (filename, checksum, applied_at)
VALUES ('NNN_migration.sql', 'calculated_checksum', NOW());References
- Migration runner:
internal/repository/migrations_runner.go - PostgreSQL docs: https://www.postgresql.org/docs/
Overview (`backend/cmd/cleanup-ingress-reject-logs/README.md`)
Ingress rejection log cleanup
This maintenance command removes historical admission rejections fromops_error_logs without matching unrelated authentication or upstream errors.
It is a dry run unless --execute is supplied, and always requires an explicit
RFC3339 cutoff.
go run ./cmd/cleanup-ingress-reject-logs --before 2026-07-17T00:00:00Z
go run ./cmd/cleanup-ingress-reject-logs --before 2026-07-17T00:00:00Z --executeRun the execute form only after every application instance has been upgraded so
older instances cannot add new ingress rejection rows below the chosen cutoff.
The classifier intentionally retains invariant failures such asUSER_NOT_FOUND, database errors, quota/billing errors, and upstream failures.
After the rollout and cleanup are verified, runbackend/scripts/finalize-ingress-reject-cleanup.sql in a maintenance window to
remove the deprecated plaintext-key audit table and attribution columns.
Overview (`backend/pkg/pluginapi/README.md`)
Sub2API 本地插件协议
本目录是插件开发者可以依赖的公开契约。v1/plugin.proto 和 v1/runtime.go 定义进程协议,v1/manifest.schema.json 定义包清单,docs/ 记录开发和发布规范。Provider 私有实现不应放入本目录。
开发文档
实体与运行方式
插件的交付实体是一个 .s2plugin 文件,本质上是带清单、签名、独立可执行文件和静态 UI 的 ZIP 包。管理员在独立的插件管理页手动上传,Sub2API 不从网络自动下载插件,也不要求 Docker。
启用后,Sub2API 以子进程方式拉起当前操作系统和 CPU 架构对应的二进制,通过本机 gRPC 流传递请求与响应。插件进程退出时会随 Sub2API 清理;停用时先停止接收新请求,再等待正在处理的请求结束。
多实例部署不要求共享插件目录。宿主会在数据库保存已验签的原始插件包,各实例缺少本地文件时会重新验签和解包,并周期性对齐启用状态、灰度比例和加密配置。所有实例必须连接同一数据库并使用相同的加密密钥。
独立进程是代码和发布边界,不是操作系统安全沙箱。插件拥有 Sub2API 服务用户所拥有的文件和网络权限,因此只应安装可信发布者的签名包。闭源二进制可提高源码分发门槛,但不能承诺无法反编译。
初期能力边界
当前只接受 openai.oauth.outbound_transport.v1:
- 仅匹配
platform=openai且account_type=oauth的上游 HTTP 请求。 - API Key 账号、其他 provider、OAuth 登录与 Token 刷新流程不进入插件。
- 插件建立真实的上游 HTTP/TLS 连接并返回原始 HTTP 响应。
- 命中插件的 OAuth WebSocket 账号会使用 Sub2API 现有 HTTP Bridge,不直接建立上游 WebSocket,避免绕过 v1 HTTP 插件协议。
- Sub2API 继续负责响应状态处理、SSE 解析、错误映射、用量统计、计费和下游输出。
- 灰度比例以账号 ID 稳定分桶,未命中的 OAuth 账号继续使用原有内置路径。
宿主服务(HostService)
HostService 是宿主经 go-plugin broker 反向暴露给插件的通用能力层,独立于具体插件类型,供有状态插件使用。它通过 TransportPlugin.InitHostServices 在运行时协商:宿主启动后把一个 broker 流 id 交给插件,插件用它拨号回宿主并获得 HostServiceClient。
- 可选且向后兼容:未实现
InitHostServices的旧插件返回Unimplemented,宿主静默跳过,转发能力不受影响。宿主服务有独立的HostServiceAPIVersion,新增能力不会改变传输契约版本,也不会使既有插件失效。 - 随进程回收:宿主服务实例与插件进程生命周期绑定,插件退出时 broker 关闭并自动
GracefulStop,无需插件手动清理。
当前提供的通用设施:
- 命名空间键值存储(KV):
KVGet/KVSet/KVDelete/KVList,为插件持久化跨请求、跨副本、跨重启的状态。存储由 Redis 支撑,因此多实例部署天然共享同一份状态。- 命名空间隔离:宿主根据服务该连接的运行时注入插件身份(
pluginKey),插件无法伪造,也无法读写其它插件的命名空间。 - 护栏:
namespace/key仅允许[A-Za-z0-9._-];单值上限 256 KiB;ttl_seconds为 0 表示不过期、正值有上限;KVList返回条数有上限。
- 命名空间隔离:宿主根据服务该连接的运行时注入插件身份(
新增宿主设施时,在 HostService 上追加 RPC 即可,无需改动传输契约或清单格式。
包结构
manifest.json
signature.json # 生产包必需
runtimes/linux-amd64/plugin
runtimes/linux-arm64/plugin
runtimes/windows-amd64/plugin.exe
ui/index.html
ui/assets/...manifest.json 必须声明所有运行时和 UI 文件的 SHA-256。signature.json 使用受信任发布者的 Ed25519 私钥对 manifest.json 原始字节签名。官方 OpenAI Transport 公钥由宿主内置,第三方发布者公钥由部署者追加到 plugins.trusted_publishers。文件哈希由已签名清单保护。
插件默认保持停用。未签名包默认拒绝安装;plugins.allow_unsigned 只应用于开发者自己构建的本地调试包。
兼容性
清单必须同时声明:
requires.sub2api:允许的 Sub2API 语义化版本范围。requires.recommended_sub2api_version:建议使用的宿主版本。requires.tested_sub2api_versions:发布者实际验证过的宿主版本。plugin_protocol、transport_api、ui_bridge:三个独立协议版本。
宿主版本超出范围时,插件可以安装并查看,但保持“不兼容”状态且不能启用。版本在范围内但未列入已测试版本时,管理员必须再次确认才能启用。
UI 隔离与 Bridge
插件 UI 由包内静态文件实现,宿主使用只有 allow-scripts 权限的 sandbox iframe 加载。iframe 没有管理员 Token,也不能直接访问管理 API。宿主为每次打开配置页生成短时资源 URL 和独立 Bridge Token,并且同时校验消息来源窗口与 Token。
UI 可以发送以下消息。消息按语义分层,鉴权与副作用一致对应(读操作免二次验证,写/主动测试需要二次验证),任何插件都可复用,不针对具体插件定制:
| 消息 | 语义 | 二次验证 | 映射的插件 RPC |
|---|---|---|---|
config.load |
读取已保存配置 | 否 | 宿主数据库 |
config.save |
写入配置 | 是 | ValidateConfig + ApplyConfig |
config.test |
主动测试配置/连通性(可产生副作用) | 是 | TestConfig |
plugin.status |
读取运行时状态(无副作用) | 否 | Health(status_json) |
ui.resize / ui.notify |
仅 UI 交互 | — | — |
每个请求消息带 request_id,宿主以 <type>.result 返回结果。
- 配置整体使用 Sub2API 的密钥加密后存入数据库;运行中插件会先验证并应用新配置,数据库写入失败时恢复旧配置。
config.test的结果由插件 UI 自行展示(内联或经ui.notify),宿主不再对成功结果强制弹出提示,避免插件把它当作轻量状态轮询时刷屏。plugin.status是通用的只读状态通道:宿主经GET /admin/plugins/:id/status调用运行中插件的Health,返回{healthy, message, status_json}。status_json是插件自定义的不透明 JSON 快照(宿主不解析、不参与健康判定),插件必须以无副作用方式生成(不得应用配置、访问上游或触发探测),因此该端点只读、免二次验证。插件未运行时返回healthy=false且不含status_json。这样带状态面板的插件无需滥用config.test即可展示实时状态。
协议源码
v1/plugin.proto:稳定的进程间消息定义。v1/runtime.go:Go 插件进程启动入口和宿主客户端声明。v1/manifest.schema.json:manifest.json的 JSON Schema。
插件通过进程协议协作,不使用 Go 动态链接,也不要求插件与 Sub2API 使用相同编译器或共享内存 ABI。
Overview (`backend/resources/model-pricing/README.md`)
Model Pricing Data
This directory contains a local copy of the mirrored model pricing data as a fallback mechanism.
Source
The original file is maintained by the LiteLLM project and mirrored into the price-mirror branch of this repository via GitHub Actions:
- Mirror branch (configurable via
PRICE_MIRROR_REPO): https://raw.githubusercontent.com//price-mirror/model_prices_and_context_window.json - Upstream source: https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json
Purpose
This local copy serves as a fallback when the remote file cannot be downloaded due to:
- Network restrictions
- Firewall rules
- DNS resolution issues
- GitHub being blocked in certain regions
- Docker container network limitations
Update Process
The pricingService will:
- First attempt to download the latest version from GitHub
- If download fails, use this local copy as fallback
- Log a warning when using the fallback file
Manual Update
To manually update this file with the latest pricing data (if automation is unavailable):
curl -s https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json -o model_prices_and_context_window.jsonFile Format
The file contains JSON data with model pricing information including:
- Model names and identifiers
- Input/output token costs
- Context window sizes
- Model capabilities
Last updated: 2025-08-10
Overview (`frontend/src/router/README.md`)
Vue Router Configuration
Overview
This directory contains the Vue Router configuration for the Sub2API frontend application. The router implements a comprehensive navigation system with authentication guards, role-based access control, and lazy loading.
Files
- index.ts: Main router configuration with route definitions and navigation guards
- meta.d.ts: TypeScript type definitions for route meta fields
Route Structure
Public Routes (No Authentication Required)
| Path | Component | Description |
|---|---|---|
/login |
LoginView | User login page |
/register |
RegisterView | User registration page |
User Routes (Authentication Required)
| Path | Component | Description |
|---|---|---|
/ |
- | Redirects to /dashboard |
/dashboard |
DashboardView | User dashboard with stats |
/keys |
KeysView | API key management |
/usage |
UsageView | Usage records and statistics |
/redeem |
RedeemView | Redeem code interface |
/profile |
ProfileView | User profile settings |
Admin Routes (Admin Role Required)
| Path | Component | Description |
|---|---|---|
/admin |
- | Redirects to /admin/dashboard |
/admin/dashboard |
AdminDashboardView | Admin dashboard |
/admin/users |
AdminUsersView | User management |
/admin/groups |
AdminGroupsView | Group management |
/admin/accounts |
AdminAccountsView | Account management |
/admin/proxies |
AdminProxiesView | Proxy management |
/admin/redeem |
AdminRedeemView | Redeem code management |
Special Routes
| Path | Component | Description |
|---|---|---|
/:pathMatch(.*) |
NotFoundView | 404 error page |
Navigation Guards
Authentication Guard (beforeEach)
The router implements a comprehensive navigation guard that:
- Sets Page Title: Updates document title based on route meta
- Checks Authentication:
- Public routes (
requiresAuth: false) are accessible without login - Protected routes require authentication
- Redirects to
/loginif not authenticated
- Public routes (
- Prevents Double Login:
- Redirects authenticated users away from login/register pages
- Role-Based Access Control:
- Admin routes (
requiresAdmin: true) require admin role - Non-admin users are redirected to
/dashboard
- Admin routes (
- Preserves Intended Destination:
- Saves original URL in query parameter for post-login redirect
Flow Diagram
User navigates to route
↓
Set page title from meta
↓
Is route public? ──Yes──→ Already authenticated? ──Yes──→ Redirect to /dashboard
↓ No ↓ No
↓ Allow access
↓
Is user authenticated? ──No──→ Redirect to /login with redirect query
↓ Yes
↓
Requires admin role? ──Yes──→ Is user admin? ──No──→ Redirect to /dashboard
↓ No ↓ Yes
↓ ↓
Allow access ←────────────────────────────────┘Route Meta Fields
Each route can define the following meta fields:
interface RouteMeta {
requiresAuth?: boolean // Default: true (requires authentication)
requiresAdmin?: boolean // Default: false (admin access only)
title?: string // Page title
breadcrumbs?: Array<{
// Breadcrumb navigation
label: string
to?: string
}>
icon?: string // Icon for navigation menu
hideInMenu?: boolean // Hide from navigation menu
}Lazy Loading
All route components use dynamic imports for code splitting:
component: () => import('@/views/user/DashboardView.vue')Benefits:
- Reduced initial bundle size
- Faster initial page load
- Components loaded on-demand
- Automatic code splitting by Vite
Authentication Store Integration
The router integrates with the Pinia auth store (@/stores/auth):
const authStore = useAuthStore()
// Check authentication status
authStore.isAuthenticated
// Check admin role
authStore.isAdminUsage Examples
Programmatic Navigation
import { useRouter } from 'vue-router'
const router = useRouter()
// Navigate to a route
router.push('/dashboard')
// Navigate with query parameters
router.push({
path: '/usage',
query: { filter: 'today' }
})
// Navigate to admin route (will be blocked if not admin)
router.push('/admin/users')Route Links
<template>
<router-link to="/dashboard">Dashboard</router-link>
<router-link :to="{ name: 'Keys' }">API Keys</router-link>
<router-link :to="{ path: '/usage', query: { page: 1 } }"> Usage </router-link>
</template>Checking Current Route
import { useRoute } from 'vue-router'
const route = useRoute()
// Check if on admin page
const isAdminPage = route.path.startsWith('/admin')
// Get route meta
const requiresAdmin = route.meta.requiresAdminScroll Behavior
The router implements automatic scroll management:
- Browser Navigation: Restores saved scroll position
- New Routes: Scrolls to top of page
- Hash Links: Scrolls to anchor (when implemented)
Error Handling
The router includes error handling for navigation failures:
router.onError((error) => {
console.error('Router error:', error)
})Testing Routes
To test navigation guards and route access:
- Public Route Access: Visit
/loginwithout authentication - Protected Route: Try accessing
/dashboardwithout login (should redirect) - Admin Access: Login as regular user, try
/admin/users(should redirect to dashboard) - Admin Success: Login as admin, access
/admin/users(should succeed) - 404 Handling: Visit non-existent route (should show 404 page)
Development Tips
Adding New Routes
- Add route definition in
routesarray - Create corresponding view component
- Set appropriate meta fields (
requiresAuth,requiresAdmin) - Use lazy loading with
() => import() - Update this README with route documentation
Debugging Navigation
Enable Vue Router debug mode:
// In browser console
window.__VUE_ROUTER__ = router
// Check current route
router.currentRoute.valueCommon Issues
Issue: 404 on page refresh
- Cause: Server not configured for SPA
- Solution: Configure server to serve
index.htmlfor all routes
Issue: Navigation guard runs twice
- Cause: Multiple
next()calls - Solution: Ensure only one
next()call per code path
Issue: User data not loaded
- Cause: Auth store not initialized
- Solution: Call
authStore.checkAuth()in App.vue or main.ts
Security Considerations
- Client-Side Only: Navigation guards are client-side; server must also validate
- Token Validation: API should verify JWT token on every request
- Role Checking: Backend must verify admin role, not just frontend
- XSS Protection: Vue automatically escapes template content
- CSRF Protection: Use CSRF tokens for state-changing operations
Performance Optimization
- Lazy Loading: All routes use dynamic imports
- Code Splitting: Vite automatically splits route chunks
- Prefetching: Consider adding route prefetch for common paths
- Route Caching: Vue Router caches component instances
Future Enhancements
- Add breadcrumb navigation system
- Implement route-based permissions beyond admin/user
- Add route transition animations
- Implement route prefetching for anticipated navigation
- Add navigation analytics tracking
Overview (`frontend/src/stores/README.md`)
Pinia Stores Documentation
This directory contains all Pinia stores for the Sub2API frontend application.
Stores Overview
1. Auth Store (`auth.ts`)
Manages user authentication state, login/logout, and token persistence.
State:
user: User | null- Current authenticated usertoken: string | null- JWT authentication token
Computed:
isAuthenticated: boolean- Whether user is currently authenticated
Actions:
login(credentials)- Authenticate user with username/passwordregister(userData)- Register new user accountlogout()- Clear authentication and logoutcheckAuth()- Restore session from localStoragerefreshUser()- Fetch latest user data from server
2. App Store (`app.ts`)
Manages global UI state including sidebar, loading indicators, and toast notifications.
State:
sidebarCollapsed: boolean- Sidebar collapsed stateloading: boolean- Global loading statetoasts: Toast[]- Active toast notifications
Computed:
hasActiveToasts: boolean- Whether any toasts are active
Actions:
toggleSidebar()- Toggle sidebar statesetSidebarCollapsed(collapsed)- Set sidebar state explicitlysetLoading(isLoading)- Set loading stateshowToast(type, message, duration?)- Show toast notificationshowSuccess(message, duration?)- Show success toastshowError(message, duration?)- Show error toastshowInfo(message, duration?)- Show info toastshowWarning(message, duration?)- Show warning toasthideToast(id)- Hide specific toastclearAllToasts()- Clear all toastswithLoading(operation)- Execute async operation with loading statewithLoadingAndError(operation, errorMessage?)- Execute with loading and error handlingreset()- Reset store to defaults
Usage Examples
Auth Store
import { useAuthStore } from '@/stores'
// In component setup
const authStore = useAuthStore()
// Initialize on app startup
authStore.checkAuth()
// Login
try {
await authStore.login({ username: 'user', password: 'pass' })
console.log('Logged in:', authStore.user)
} catch (error) {
console.error('Login failed:', error)
}
// Check authentication
if (authStore.isAuthenticated) {
console.log('User is logged in:', authStore.user?.username)
}
// Logout
authStore.logout()App Store
import { useAppStore } from '@/stores'
// In component setup
const appStore = useAppStore()
// Sidebar control
appStore.toggleSidebar()
appStore.setSidebarCollapsed(true)
// Loading state
appStore.setLoading(true)
// ... do work
appStore.setLoading(false)
// Or use helper
await appStore.withLoading(async () => {
const data = await fetchData()
return data
})
// Toast notifications
appStore.showSuccess('Operation completed!')
appStore.showError('Something went wrong!', 5000)
appStore.showInfo('FYI: This is informational')
appStore.showWarning('Be careful!')
// Custom toast
const toastId = appStore.showToast('info', 'Custom message', undefined) // No auto-dismiss
// Later...
appStore.hideToast(toastId)Combined Usage in Vue Component
<script setup lang="ts">
import { useAuthStore, useAppStore } from '@/stores'
import { onMounted } from 'vue'
const authStore = useAuthStore()
const appStore = useAppStore()
onMounted(() => {
// Check for existing session
authStore.checkAuth()
})
async function handleLogin(username: string, password: string) {
try {
await appStore.withLoading(async () => {
await authStore.login({ username, password })
})
appStore.showSuccess('Welcome back!')
} catch (error) {
appStore.showError('Login failed. Please check your credentials.')
}
}
async function handleLogout() {
authStore.logout()
appStore.showInfo('You have been logged out.')
}
</script>
<template>
<div>
<button @click="appStore.toggleSidebar">Toggle Sidebar</button>
<div v-if="appStore.loading">Loading...</div>
<div v-if="authStore.isAuthenticated">
Welcome, {{ authStore.user?.username }}!
<button @click="handleLogout">Logout</button>
</div>
<div v-else>
<button @click="handleLogin('user', 'pass')">Login</button>
</div>
</div>
</template>Persistence
- Auth Store: Token and user data are automatically persisted to
localStorage- Keys:
auth_token,auth_user - Restored on
checkAuth()call
- Keys:
- App Store: No persistence (UI state resets on page reload)
TypeScript Support
All stores are fully typed with TypeScript. Import types from @/types:
import type { User, Toast, ToastType } from '@/types'Testing
Stores can be reset to initial state:
// Auth store
authStore.logout() // Clears all auth state
// App store
appStore.reset() // Resets to defaultsOverview (`openspec/changes/add-openai-compatible-prompt-audit/README.md`)
add-openai-compatible-prompt-audit
在不改变现有内容审核行为的前提下,新增独立的 OpenAI 兼容 Qwen3Guard 提示词安全审计模块,完整支持异步审计、同步阻断、持久任务队列、事件工作台与独立管理页面。
阅读顺序:proposal.md → source-baseline.md / source-feature-map.md → design.md → 三个 specs/*/spec.md → implementation-guide.md → tasks.md → verification.md。
Overview (`frontend/src/components/common/README.md`)
Common Components
This directory contains reusable Vue 3 components built with Composition API, TypeScript, and TailwindCSS.
Components
DataTable.vue
A generic data table component with sorting, loading states, and custom cell rendering.
Props:
columns: Column[]- Array of column definitions with key, label, sortable, and formatterdata: any[]- Array of data objects to displayloading?: boolean- Show loading skeletondefaultSortKey?: string- Default sort key (only used if no persisted sort state)defaultSortOrder?: 'asc' | 'desc'- Default sort order (default:asc)sortStorageKey?: string- Persist sort state (key + order) to localStoragerowKey?: string | (row: any) => string | number- Row key field or resolver (defaults torow.id, falls back to index)
Slots:
empty- Custom empty state contentcell-{key}- Custom cell renderer for specific column (receivesrowandvalue)
Usage:
<DataTable
:columns="[
{ key: 'name', label: 'Name', sortable: true },
{ key: 'email', label: 'Email' },
{ key: 'status', label: 'Status', formatter: (val) => val.toUpperCase() }
]"
:data="users"
:loading="isLoading"
>
<template #cell-actions="{ row }">
<button @click="editUser(row)">Edit</button>
</template>
</DataTable>Pagination.vue
Pagination component with page numbers, navigation, and page size selector.
Props:
total: number- Total number of itemspage: number- Current page (1-indexed)pageSize: number- Items per pagepageSizeOptions?: number[]- Available page size options (default: [10, 20, 50, 100])
Events:
update:page- Emitted when page changesupdate:pageSize- Emitted when page size changes
Usage:
<Pagination
:total="totalUsers"
:page="currentPage"
:pageSize="pageSize"
@update:page="currentPage = $event"
@update:pageSize="pageSize = $event"
/>Modal.vue
Modal dialog with customizable size and close behavior.
Props:
show: boolean- Control modal visibilitytitle: string- Modal titlesize?: 'sm' | 'md' | 'lg' | 'xl' | 'full'- Modal size (default: 'md')closeOnEscape?: boolean- Close on Escape key (default: true)closeOnClickOutside?: boolean- Close on backdrop click (default: true)
Events:
close- Emitted when modal should close
Slots:
default- Modal body contentfooter- Modal footer content
Usage:
<Modal :show="showModal" title="Edit User" size="lg" @close="showModal = false">
<form @submit.prevent="saveUser">
</form>
<template #footer>
<button @click="showModal = false">Cancel</button>
<button @click="saveUser">Save</button>
</template>
</Modal>ConfirmDialog.vue
Confirmation dialog built on top of Modal component.
Props:
show: boolean- Control dialog visibilitytitle: string- Dialog titlemessage: string- Confirmation messageconfirmText?: string- Confirm button text (default: 'Confirm')cancelText?: string- Cancel button text (default: 'Cancel')danger?: boolean- Use danger/red styling (default: false)
Events:
confirm- Emitted when user confirmscancel- Emitted when user cancels
Usage:
<ConfirmDialog
:show="showDeleteConfirm"
title="Delete User"
message="Are you sure you want to delete this user? This action cannot be undone."
confirm-text="Delete"
cancel-text="Cancel"
danger
@confirm="deleteUser"
@cancel="showDeleteConfirm = false"
/>StatCard.vue
Statistics card component for displaying metrics with optional change indicators.
Props:
title: string- Card titlevalue: number | string- Main value to displayicon?: Component- Icon componentchange?: number- Percentage change valuechangeType?: 'up' | 'down' | 'neutral'- Change direction (default: 'neutral')formatValue?: (value) => string- Custom value formatter
Usage:
<StatCard title="Total Users" :value="1234" :icon="UserIcon" :change="12.5" change-type="up" />Toast.vue
Toast notification component that automatically displays toasts from the app store.
Usage:
<Toast />// Trigger toasts from anywhere using the app store
import { useAppStore } from '@/stores/app'
const appStore = useAppStore()
appStore.addToast({
type: 'success',
title: 'Success!',
message: 'User created successfully',
duration: 3000
})
appStore.addToast({
type: 'error',
message: 'Failed to delete user'
})LoadingSpinner.vue
Simple animated loading spinner.
Props:
size?: 'sm' | 'md' | 'lg' | 'xl'- Spinner size (default: 'md')color?: 'primary' | 'secondary' | 'white' | 'gray'- Spinner color (default: 'primary')
Usage:
<LoadingSpinner size="lg" color="primary" />EmptyState.vue
Empty state placeholder with icon, message, and optional action button.
Props:
icon?: Component- Icon componenttitle: string- Empty state titledescription: string- Empty state descriptionactionText?: string- Action button textactionTo?: string | object- Router link destinationactionIcon?: boolean- Show plus icon in button (default: true)
Slots:
icon- Custom icon contentaction- Custom action button/link
Usage:
<EmptyState
title="No users found"
description="Get started by creating your first user account."
action-text="Add User"
:action-to="{ name: 'users-create' }"
/>Import
You can import components individually:
import { DataTable, Pagination, Modal } from '@/components/common'Or import specific components:
import DataTable from '@/components/common/DataTable.vue'Features
All components include:
- TypeScript support with proper type definitions
- Accessibility with ARIA attributes and keyboard navigation
- Responsive design with mobile-friendly layouts
- TailwindCSS styling for consistent design
- Vue 3 Composition API with
<script setup> - Slot support for customization
Overview (`frontend/src/components/layout/README.md`)
Layout Components
Vue 3 layout components for the Sub2API frontend, built with Composition API, TypeScript, and TailwindCSS.
Components
1. AppLayout.vue
Main application layout with sidebar and header.
Usage:
<template>
<AppLayout>
<h1>Dashboard</h1>
<p>Welcome to your dashboard!</p>
</AppLayout>
</template>
<script setup lang="ts">
import { AppLayout } from '@/components/layout'
</script>Features:
- Responsive sidebar (collapsible)
- Fixed header at top
- Main content area with slot
- Automatically adjusts margin based on sidebar state
2. AppSidebar.vue
Navigation sidebar with user and admin sections.
Features:
- Logo/brand at top
- User navigation links:
- Dashboard
- API Keys
- Usage
- Redeem
- Profile
- Admin navigation links (shown only if user is admin):
- Admin Dashboard
- Users
- Groups
- Accounts
- Proxies
- Redeem Codes
- Collapsible sidebar with toggle button
- Active route highlighting
- Icons using HTML entities
- Responsive (mobile-friendly)
Used automatically by AppLayout - no need to import separately.
3. AppHeader.vue
Top header with user info and actions.
Features:
- Mobile menu toggle button
- Page title (from route meta or slot)
- User balance display (desktop only)
- User dropdown menu with:
- Profile link
- Logout button
- User avatar with initials
- Click-outside handling for dropdown
- Responsive design
Usage with custom title:
<template>
<AppLayout>
<template #title> Custom Page Title </template>
</AppLayout>
</template>Used automatically by AppLayout - no need to import separately.
4. AuthLayout.vue
Simple centered layout for authentication pages (login/register).
Usage:
<template>
<AuthLayout>
<h2 class="mb-6 text-2xl font-bold">Login</h2>
<form @submit.prevent="handleLogin">
</form>
<template #footer>
<p>
Don't have an account?
<router-link to="/register" class="text-indigo-600 hover:underline"> Sign up </router-link>
</p>
</template>
</AuthLayout>
</template>
<script setup lang="ts">
import { AuthLayout } from '@/components/layout'
function handleLogin() {
// Login logic
}
</script>Features:
- Centered card container
- Gradient background
- Logo/brand at top
- Main content slot
- Optional footer slot for links
- Fully responsive
Route Configuration
To set page titles in the header, add meta to your routes:
// router/index.ts
const routes = [
{
path: '/dashboard',
component: DashboardView,
meta: { title: 'Dashboard' }
},
{
path: '/api-keys',
component: ApiKeysView,
meta: { title: 'API Keys' }
}
// ...
]Store Dependencies
These components use the following Pinia stores:
- useAuthStore: For user authentication state, role checking, and logout
- useAppStore: For sidebar state management and toast notifications
Make sure these stores are properly initialized in your app.
Styling
All components use TailwindCSS utility classes. Make sure your tailwind.config.js includes the component paths:
module.exports = {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}']
// ...
}Icons
Components use HTML entity icons for simplicity:
- 📈 Chart (Dashboard)
- 🔑 Key (API Keys)
- 📊 Bar Chart (Usage)
- 🎁 Gift (Redeem)
- 👤 User (Profile)
- 🔌 Admin
- 👥 Users
- 📁 Folder (Groups)
- 🌐 Globe (Accounts)
- 🔄 Network (Proxies)
- 🏷 Ticket (Redeem Codes)
You can replace these with your preferred icon library (e.g., Heroicons, Font Awesome) if needed.
Mobile Responsiveness
All components are fully responsive:
- AppSidebar: Fixed positioning on desktop, hidden by default on mobile
- AppHeader: Shows mobile menu toggle on small screens, hides balance display
- AuthLayout: Adapts padding and card size for mobile devices
The sidebar uses Tailwind's responsive breakpoints (md:) to adjust behavior.
Overview (`frontend/src/views/auth/README.md`)
Authentication Views
This directory contains Vue 3 authentication views for the Sub2API frontend application.
Components
LoginView.vue
Login page for existing users to authenticate.
Features:
- Username and password inputs with validation
- Remember me checkbox for persistent sessions
- Form validation with real-time error display
- Loading state during authentication
- Error message display for failed login attempts
- Redirect to dashboard on successful login
- Link to registration page for new users
Usage:
<template>
<LoginView />
</template>
<script setup lang="ts">
import { LoginView } from '@/views/auth'
</script>Route:
- Path:
/login - Name:
Login - Meta:
{ requiresAuth: false }
Validation Rules:
- Username: Required, minimum 3 characters
- Password: Required, minimum 6 characters
Behavior:
- Calls
authStore.login()with credentials - Shows success toast on successful login
- Shows error toast and inline error message on failure
- Redirects to
/dashboardor intended route from query parameter - Redirects authenticated users away from login page
RegisterView.vue
Registration page for new users to create accounts.
Features:
- Username, email, password, and confirm password inputs
- Comprehensive form validation
- Password strength requirements (8+ characters, letters + numbers)
- Email format validation with regex
- Password match validation
- Loading state during registration
- Error message display for failed registration
- Redirect to dashboard on success
- Link to login page for existing users
Usage:
<template>
<RegisterView />
</template>
<script setup lang="ts">
import { RegisterView } from '@/views/auth'
</script>Route:
- Path:
/register - Name:
Register - Meta:
{ requiresAuth: false }
Validation Rules:
- Username:
- Required
- 3-50 characters
- Only letters, numbers, underscores, and hyphens
- Email:
- Required
- Valid email format (RFC 5322 regex)
- Password:
- Required
- Minimum 8 characters
- Must contain at least one letter and one number
- Confirm Password:
- Required
- Must match password
Behavior:
- Calls
authStore.register()with user data - Shows success toast on successful registration
- Shows error toast and inline error message on failure
- Redirects to
/dashboardafter successful registration - Redirects authenticated users away from register page
Architecture
Component Structure
Both views follow a consistent structure:
<template>
<AuthLayout>
<div class="space-y-6">
</div>
<template #footer>
</template>
</AuthLayout>
</template>
<script setup lang="ts">
// Imports
// State
// Validation
// Form Handlers
</script>State Management
Both views use:
useAuthStore()- For authentication actions (login, register)useAppStore()- For toast notifications and UI feedbackuseRouter()- For navigation and redirects
Validation Strategy
Client-side Validation:
- Real-time validation on form submission
- Field-level error messages
- Comprehensive validation rules
- TypeScript type safety
Server-side Validation:
- Backend API validates all inputs
- Error responses handled gracefully
- User-friendly error messages displayed
Styling
Design System:
- TailwindCSS utility classes
- Consistent color scheme (indigo primary)
- Responsive design
- Accessible form controls
- Loading states with spinner animations
Visual Feedback:
- Red border on invalid fields
- Error messages below inputs
- Global error banner for API errors
- Success toasts on completion
- Loading spinner on submit button
Dependencies
Components
AuthLayout- Layout wrapper for auth pages from@/components/layout
Stores
authStore- Authentication state management from@/stores/authappStore- Application state and toasts from@/stores/app
Libraries
- Vue 3 Composition API
- Vue Router for navigation
- Pinia for state management
- TypeScript for type safety
Usage Examples
Basic Login Flow
// User enters credentials
formData.username = 'john_doe'
formData.password = 'SecurePass123'
// Submit form
await handleLogin()
// On success:
// - authStore.login() called
// - Token and user stored
// - Success toast shown
// - Redirected to /dashboard
// On error:
// - Error message displayed
// - Error toast shown
// - Form remains editableBasic Registration Flow
// User enters registration data
formData.username = 'jane_smith'
formData.email = '[email protected]'
formData.password = 'SecurePass123'
formData.confirmPassword = 'SecurePass123'
// Submit form
await handleRegister()
// On success:
// - authStore.register() called
// - Token and user stored
// - Success toast shown
// - Redirected to /dashboard
// On error:
// - Error message displayed
// - Error toast shown
// - Form remains editableError Handling
Client-side Errors
// Validation errors
errors.username = 'Username must be at least 3 characters'
errors.email = 'Please enter a valid email address'
errors.password = 'Password must be at least 8 characters with letters and numbers'
errors.confirmPassword = 'Passwords do not match'Server-side Errors
// API error responses
{
response: {
data: {
detail: 'Username already exists'
}
}
}
// Displayed as:
errorMessage.value = 'Username already exists'
appStore.showError('Username already exists')Accessibility
- Semantic HTML elements (
<label>,,) - Proper
forattributes on labels - ARIA attributes for loading states
- Keyboard navigation support
- Focus management
- Error announcements
- Sufficient color contrast
Testing Considerations
Unit Tests
- Form validation logic
- Error handling
- State management
- Router navigation
Integration Tests
- Full login flow
- Full registration flow
- Error scenarios
- Redirect behavior
E2E Tests
- Complete user journeys
- Form interactions
- API integration
- Success/error states
Future Enhancements
Potential improvements:
- OAuth/SSO integration (Google, GitHub)
- Two-factor authentication (2FA)
- Password strength meter
- Email verification flow
- Forgot password functionality
- Social login buttons
- CAPTCHA integration
- Session timeout warnings
- Password visibility toggle
- Autofill support enhancement
Security Considerations
- Passwords are never logged or displayed
- HTTPS required in production
- JWT tokens stored securely in localStorage
- CORS protection on API
- XSS protection with Vue's automatic escaping
- CSRF protection with token-based auth
- Rate limiting on backend API
- Input sanitization
- Secure password requirements
Performance
- Lazy-loaded routes
- Minimal bundle size
- Fast initial render
- Optimized re-renders with reactive refs
- No unnecessary watchers
- Efficient form validation
Browser Support
- Modern browsers (Chrome, Firefox, Safari, Edge)
- ES2015+ required
- Flexbox and CSS Grid
- Tailwind CSS utilities
- Vue 3 runtime
Related Documentation
- Auth Store Documentation
- AuthLayout Component
- Router Configuration
- API Documentation
- Type Definitions
ADMIN PAYMENT INTEGRATION API (`docs/ADMIN_PAYMENT_INTEGRATION_API.md`)
ADMIN_PAYMENT_INTEGRATION_API
单文件中英双语文档 / Single-file bilingual documentation (Chinese + English)
中文
目标
本文档用于对接外部支付系统(如 sub2apipay)与 Sub2API 的 Admin API,覆盖:
- 支付成功后充值
- 用户查询
- 人工余额修正
- 前端购买页参数透传
基础地址
- 生产:
https://<your-domain> - Beta:
http://<your-server-ip>:8084
认证
推荐使用:
x-api-key: admin-<64hex>Content-Type: application/json- 幂等接口额外传:
Idempotency-Key
说明:管理员 JWT 也可访问 admin 路由,但服务间调用建议使用 Admin API Key。
1) 一步完成创建并兑换
POST /api/v1/admin/redeem-codes/create-and-redeem
用途:原子完成“创建兑换码 + 兑换到指定用户”。
请求头:
x-api-keyIdempotency-Key
请求体示例:
{
"code": "s2p_cm1234567890",
"type": "balance",
"value": 100.0,
"user_id": 123,
"notes": "sub2apipay order: cm1234567890"
}幂等语义:
- 同
code且used_by一致:200 - 同
code但used_by不一致:409 - 缺少
Idempotency-Key:400(IDEMPOTENCY_KEY_REQUIRED)
curl 示例:
curl -X POST "${BASE}/api/v1/admin/redeem-codes/create-and-redeem" \
-H "x-api-key: ${KEY}" \
-H "Idempotency-Key: pay-cm1234567890-success" \
-H "Content-Type: application/json" \
-d '{
"code":"s2p_cm1234567890",
"type":"balance",
"value":100.00,
"user_id":123,
"notes":"sub2apipay order: cm1234567890"
}'2) 查询用户(可选前置校验)
GET /api/v1/admin/users/:id
curl -s "${BASE}/api/v1/admin/users/123" \
-H "x-api-key: ${KEY}"3) 余额调整(已有接口)
POST /api/v1/admin/users/:id/balance
用途:人工补偿 / 扣减,支持 set / add / subtract。
请求体示例(扣减):
{
"balance": 100.0,
"operation": "subtract",
"notes": "manual correction"
}curl -X POST "${BASE}/api/v1/admin/users/123/balance" \
-H "x-api-key: ${KEY}" \
-H "Idempotency-Key: balance-subtract-cm1234567890" \
-H "Content-Type: application/json" \
-d '{
"balance":100.00,
"operation":"subtract",
"notes":"manual correction"
}'4) 购买页 / 自定义页面 URL Query 透传(iframe / 新窗口一致)
当 Sub2API 打开 purchase_subscription_url 或用户侧自定义页面 iframe URL 时,会统一追加:
user_idtokentheme(light/dark)lang(例如zh/en,用于向嵌入页传递当前界面语言)ui_mode(固定embedded)
示例:
https://pay.example.com/pay?user_id=123&token=<jwt>&theme=light&lang=zh&ui_mode=embedded5) 失败处理建议
- 支付成功与充值成功分状态落库
- 回调验签成功后立即标记“支付成功”
- 支付成功但充值失败的订单允许后续重试
- 重试保持相同
code,并使用新的Idempotency-Key
6) `doc_url` 配置建议
- 查看链接:
https://github.com/Wei-Shaw/sub2api/blob/main/docs/ADMIN_PAYMENT_INTEGRATION_API.md - 下载链接:
https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/docs/ADMIN_PAYMENT_INTEGRATION_API.md
English
Purpose
This document describes the minimal Sub2API Admin API surface for external payment integrations (for example, sub2apipay), including:
- Recharge after payment success
- User lookup
- Manual balance correction
- Purchase page query parameter forwarding
Base URL
- Production:
https://<your-domain> - Beta:
http://<your-server-ip>:8084
Authentication
Recommended headers:
x-api-key: admin-<64hex>Content-Type: application/jsonIdempotency-Keyfor idempotent endpoints
Note: Admin JWT can also access admin routes, but Admin API Key is recommended for server-to-server integration.
1) Create and Redeem in one step
POST /api/v1/admin/redeem-codes/create-and-redeem
Use case: atomically create a redeem code and redeem it to a target user.
Headers:
x-api-keyIdempotency-Key
Request body:
{
"code": "s2p_cm1234567890",
"type": "balance",
"value": 100.0,
"user_id": 123,
"notes": "sub2apipay order: cm1234567890"
}Idempotency behavior:
- Same
codeand sameused_by:200 - Same
codebut differentused_by:409 - Missing
Idempotency-Key:400(IDEMPOTENCY_KEY_REQUIRED)
curl example:
curl -X POST "${BASE}/api/v1/admin/redeem-codes/create-and-redeem" \
-H "x-api-key: ${KEY}" \
-H "Idempotency-Key: pay-cm1234567890-success" \
-H "Content-Type: application/json" \
-d '{
"code":"s2p_cm1234567890",
"type":"balance",
"value":100.00,
"user_id":123,
"notes":"sub2apipay order: cm1234567890"
}'2) Query User (optional pre-check)
GET /api/v1/admin/users/:id
curl -s "${BASE}/api/v1/admin/users/123" \
-H "x-api-key: ${KEY}"3) Balance Adjustment (existing API)
POST /api/v1/admin/users/:id/balance
Use case: manual correction with set / add / subtract.
Request body example (subtract):
{
"balance": 100.0,
"operation": "subtract",
"notes": "manual correction"
}curl -X POST "${BASE}/api/v1/admin/users/123/balance" \
-H "x-api-key: ${KEY}" \
-H "Idempotency-Key: balance-subtract-cm1234567890" \
-H "Content-Type: application/json" \
-d '{
"balance":100.00,
"operation":"subtract",
"notes":"manual correction"
}'4) Purchase / Custom Page URL query forwarding (iframe and new tab)
When Sub2API opens purchase_subscription_url or a user-facing custom page iframe URL, it appends:
user_idtokentheme(light/dark)lang(for examplezh/en, used to pass the current UI language to the embedded page)ui_mode(fixed:embedded)
Example:
https://pay.example.com/pay?user_id=123&token=<jwt>&theme=light&lang=zh&ui_mode=embedded5) Failure handling recommendations
- Persist payment success and recharge success as separate states
- Mark payment as successful immediately after verified callback
- Allow retry for orders with payment success but recharge failure
- Keep the same
codefor retry, and use a newIdempotency-Key
6) Recommended `doc_url`
- View URL:
https://github.com/Wei-Shaw/sub2api/blob/main/docs/ADMIN_PAYMENT_INTEGRATION_API.md - Download URL:
https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/docs/ADMIN_PAYMENT_INTEGRATION_API.md
ASYNC IMAGE TASKS (`docs/ASYNC_IMAGE_TASKS.md`)
Asynchronous Image Tasks
Asynchronous image tasks let clients submit long-running OpenAI-compatible image requests without keeping one HTTP connection open. This avoids proxy/CDN response timeouts such as Cloudflare 524 while preserving the existing image routing, billing, moderation, concurrency, and failover behavior.
Endpoints
The authenticated gateway exposes both /v1 paths and their existing no-prefix aliases:
POST /v1/images/generations/async
POST /v1/images/edits/async
GET /v1/images/tasks/{task_id}The aliases are /images/generations/async, /images/edits/async, and /images/tasks/{task_id}.
Only OpenAI and Grok groups are supported. Requests use the same JSON or multipart payload as the corresponding synchronous endpoint. Streaming image requests are rejected because a polled task returns one final JSON result.
Enabling the feature (object storage)
Asynchronous image tasks are disabled by default and gated on object storage. When the switch is off — or the S3 credentials are incomplete — the async endpoints return 404 and never create a task or write to Redis. This is deliberate: without offloading, large b64_json results (several MB each, e.g. gpt-image-1) would accumulate in Redis and exhaust its memory.
From the admin UI (recommended)
Admin → Backup → Async image object storage. Saving the form takes effect immediately — the object-storage client is rebuilt on the next request, so there is no container restart.
Because the async image storage and the database backup share one S3 client, the form defaults to reusing the backup S3 configuration: it borrows the endpoint, region and credentials already configured above and keeps only its own bucket and prefix, so backups stay under backups/ while images go to images/. Leave the bucket empty to use the backup bucket as well. Untick the box to point images at a completely separate account.
Saving requires step-up 2FA when that gate is enabled, for the same reason the backup S3 form does: changing the target redirects generated content to another account.
Turning the switch off stops new submissions but keeps already-accepted tasks pollable, so nothing in flight is stranded.
From the config file
The admin setting takes precedence. When nothing has ever been saved there, the image_storage block in config.yaml is used instead, so deployments that enabled the feature before the admin UI existed keep working untouched.
Configure an S3-compatible object store (AWS S3, Cloudflare R2, Aliyun OSS, MinIO, …) in config.yaml (all keys also accept the IMAGE_STORAGE_* environment overrides):
image_storage:
enabled: true
endpoint: "https://<account_id>.r2.cloudflarestorage.com" # AWS 官方可留空
region: "auto"
bucket: "my-images"
access_key_id: "..."
secret_access_key: "..."
prefix: "images/"
force_path_style: false # MinIO/path-style buckets set true
public_base_url: "" # set to return public_base_url/key直链; empty → presigned URL
presign_expiry_hours: 24 # presigned link TTL when public_base_url is empty
max_download_bytes: 33554432 # cap when re-hosting an upstream image URL (32MB)When a task completes, each generated image is uploaded to the bucket and the result is rewritten to a compact form: data[].url points at the stored object (a permanent public_base_url/key link, or a time-limited presigned URL) and b64_json is removed. Only this small JSON is stored in Redis. If an upload fails, the task is marked failed rather than persisting the raw base64.
To support a different vendor beyond the S3-compatible client, implement the service.ImageStorage interface (Save(ctx, key, contentType, data) (url, error)) and provide it in place of the S3 implementation.
Troubleshooting: the endpoints return 404 after enabling
404 async image tasks are not enabled means image_storage did not resolve to a complete configuration, so the feature stayed off. The route exists either way — the 404 comes from the handler, not from an unregistered path, which makes it easy to mistake for a missing build.
Check the startup log for:
WARN image_storage.enabled is true but object storage is not fully configured; async image tasks are disabled missing_keys=[...]missing_keys names exactly which credentials were empty when the config was loaded.
Note that releases before v0.1.161 silently dropped IMAGE_STORAGE_ENDPOINT, _BUCKET, _ACCESS_KEY_ID, _SECRET_ACCESS_KEY and _PUBLIC_BASE_URL when they were supplied only through the environment: those keys had no registered default, and viper cannot see an environment variable for a key it does not already know about. Deployments driven purely by environment: — which is what deploy/docker-compose.yml does by default — therefore reported enabled: true with empty credentials and 404'd on every async call. On an affected release the workaround is to also place the image_storage block in /app/data/config.yaml (copy it from deploy/config.example.yaml); once the keys exist in the file, the environment overrides apply normally.
Two further causes of a 404 that are unrelated to storage: the API key's group must be on the OpenAI or Grok platform (any other platform, or a key with no group at all, yields Images API is not supported for this platform), and a task may only be polled with the same API key that submitted it — polling with a different key of the same user returns image task not found by design.
Submit a task
curl -i https://api.example.com/v1/images/generations/async \
-H 'Authorization: Bearer sk-...' \
-H 'Content-Type: application/json' \
-d '{
"model": "gpt-image-1",
"prompt": "A lighthouse during a winter storm",
"size": "1536x1024"
}'The server stores the initial task in Redis and responds with 202 Accepted:
{
"id": "imgtask_0123456789abcdef",
"task_id": "imgtask_0123456789abcdef",
"object": "image.generation.task",
"status": "processing",
"created_at": 1784092800,
"expires_at": 1784179200,
"poll_url": "/v1/images/tasks/imgtask_0123456789abcdef"
}Location contains the polling path and Retry-After: 3 provides the recommended polling interval.
Poll a task
Use the same API key that submitted the task:
curl https://api.example.com/v1/images/tasks/imgtask_0123456789abcdef \
-H 'Authorization: Bearer sk-...'While work is in progress:
{
"id": "imgtask_0123456789abcdef",
"task_id": "imgtask_0123456789abcdef",
"object": "image.generation.task",
"status": "processing",
"created_at": 1784092800,
"expires_at": 1784179200
}On success, result mirrors the synchronous image API body, except each image has been offloaded to object storage: data[].url points at the stored object and b64_json is stripped (so both URL and base64 upstream formats end up as compact stored links):
{
"id": "imgtask_0123456789abcdef",
"task_id": "imgtask_0123456789abcdef",
"object": "image.generation.task",
"status": "completed",
"http_status": 200,
"image_url": "https://...",
"result": {
"created": 1784092923,
"data": [{"url": "https://..."}]
},
"created_at": 1784092800,
"completed_at": 1784092923,
"expires_at": 1784179323
}For URL responses, image_url mirrors the first data[].url for simple clients. On failure, the task reaches failed and exposes the original OpenAI-compatible error object where available:
{
"id": "imgtask_0123456789abcdef",
"task_id": "imgtask_0123456789abcdef",
"object": "image.generation.task",
"status": "failed",
"http_status": 502,
"error": {
"type": "api_error",
"message": "Upstream request failed"
},
"created_at": 1784092800,
"completed_at": 1784092923,
"expires_at": 1784179323
}All submit and poll responses include Cache-Control: no-store, preventing a CDN from caching the processing state. Tasks and results expire 24 hours after their latest state update. A task executes for at most 30 minutes.
Task ownership is scoped to both user and API key. Unknown task IDs and IDs owned by another key both return 404, avoiding task-existence disclosure. Polling remains available when the completed generation used the key's remaining balance; normal authentication, disabled-key, user, IP, and group checks still apply.
BATCH IMAGE MVP (`docs/BATCH_IMAGE_MVP.md`)
Batch Image MVP
Sub2API Batch Image MVP provides asynchronous Gemini image batch generation through a unified API surface backed by Redis workers, PostgreSQL state, and provider-specific batch backends.
Supported providers:
gemini_apivertex
API users do not see Gemini file names, Vertex job names, GCS paths, signed URLs, API keys, or service account material. Downloads are proxied through Sub2API in this MVP.
API Routes
POST /v1/images/batches
GET /v1/images/batches/{id}
GET /v1/images/batches/{id}/items
GET /v1/images/batches/{id}/items/{custom_id}/content
GET /v1/images/batches/{id}/download
POST /v1/images/batches/{id}/cancel
DELETE /v1/images/batches/{id}/outputsSubmit request:
{
"model": "gemini-2.5-flash-image",
"provider": "gemini_api",
"items": [
{
"custom_id": "cover_001",
"prompt": "A clean product hero image...",
"output_count": 1,
"reference_images": [
{
"id": "product-front",
"type": "subject",
"mime_type": "image/png",
"data": "<base64 image bytes without a data URL prefix>"
},
{
"id": "style",
"type": "style",
"mime_type": "image/jpeg",
"file_uri": "gs://internal-managed-bucket/batch-image/refs/style.jpg"
}
]
}
],
"image_size": "1K",
"response_mime_type": "image/png"
}reference_images is optional per item. Inline data is a base64 string decoded by the backend; file_uri is reserved for internal Google Cloud Storage references and must be a gs:// URI. Each reference image must use one of image/png, image/jpeg, or image/webp. Current model limits are:
gemini-2.5-flash-imageand other Flash Image aliases: up to 3 reference images per item.gemini-3-pro-imageand other Pro Image aliases: up to 14 reference images per item.- Per batch job: up to 1000 reference image attachments total after
output_countexpansion across all items. This is an internal Sub2API guardrail for request size and cost control, not the generated-image cap and not a Pro Image per-item capability. The generated-output cap is 200 images per job. - Per batch job: up to 128 MB decoded inline reference image data total. For large batches or repeated reference images, prefer
gs://file_urireferences or split the request into multiple jobs.
output_count is optional per item and defaults to 1. It means "repeat this prompt and reference image set N times" rather than relying on Gemini to return multiple images from one upstream request. The backend expands each repeat into a separate provider JSONL line with suffixed custom ids such as cover_001_01, cover_001_02. Current limits are:
- Per prompt item: up to 4 output images.
- Per batch job: up to 200 expected output images after expansion. This is the hard generated-output cap for a single job; clients and Codex skills must split larger workloads before submission.
- The output-image limit intentionally matches the default ZIP item limit so newly submitted jobs are always downloadable as one ZIP by item count. ZIP byte size is still capped separately by
max_download_bytes_per_request.
Public batch response:
{
"id": "imgbatch_0123456789abcdef0123456789abcdef",
"object": "image.batch",
"status": "queued",
"model": "gemini-2.5-flash-image",
"provider": "gemini_api",
"item_count": 1,
"success_count": 0,
"fail_count": 0,
"estimated_cost": 0.25,
"actual_cost": null,
"created_at": 1783123200,
"submitted_at": 1783123201,
"settled_at": null
}Public items response:
{
"object": "list",
"data": [
{
"custom_id": "cover_001",
"status": "succeeded",
"mime_type": "image/png",
"file_extension": "png",
"image_count": 1,
"error": null
}
],
"has_more": false
}Lifecycle
Internal lifecycle:
created -> uploading -> submitted -> running -> indexing -> settling -> completedTerminal and cleanup statuses:
failed
cancelled
completed -> output_deletedPublic status mapping:
created/uploading/submitted -> queued
running -> running
indexing -> processing_results
settling -> settling
completed -> completed
failed -> failed
cancelled -> cancelled
output_deleted -> output_deletedcompleted -> output_deleted happens after manual output deletion or TTL cleanup.
Redis
Redis is used for wakeups, retries, worker coordination, per-job locks, and download limiting. PostgreSQL remains the source of truth.
batch_image.queue_enabled defaults to false. When it is set to true, app startup starts BatchImageWorker runtime loops for the Redis ready queue, delayed queue mover, and stale active recovery. The worker reserves jobs from the Redis ready queue and blocks there when no job is available.
Redis structures:
- Ready queue:
batch_image.queue_ready_key - Delayed queue:
batch_image.queue_delayed_key - Active set:
batch_image.queue_active_key - Inflight keys:
batch_image.inflight_key_prefix - Per-job lock keys:
batch_image.lock_key_prefix - Queue idempotency keys:
batch_image.idempotency_key_prefix - Download limiter keys managed by the download limiter
Workers should reserve from Redis. They are not expected to run as a database scan loop.
The worker does not perform DB scan polling. Database reads happen only after a Redis queue reservation yields a specific batch id.
Billing
MVP billing rules:
- Submit may estimate cost.
- Settlement runs after result indexing.
- Only successful images are charged.
- Failed items are not charged.
- Reference images are sent to Gemini as input and can create small upstream input-token and temporary storage cost. They are counted once per expanded output request when
output_count > 1, but the public MVP billing model does not add a separate reference-image surcharge. User-facing estimated, held, and settled amounts are still based on the output image count and configured batch image unit price. - Settlement request id is
batch_image_settlement:{batch_id}. - Settlement is idempotent; re-running settlement must not double charge.
- Settlement billing failures are retried with a bounded retry limit. After the retry limit is reached, the job is failed and the remaining hold is released through the idempotent release path.
Exact production pricing is resolved through model pricing configuration and is not defined here.
Cleanup
Defaults:
- Input retention after terminal status: 24 hours.
- Output retention after terminal status: 72 hours.
- Maximum output retention: 7 days.
- Cleanup interval: 30 minutes.
- Cleanup batch size: 100.
Manual output deletion:
DELETE /v1/images/batches/{id}/outputsAfter output cleanup, downloads return 410 Gone with BATCH_IMAGE_OUTPUT_DELETED.
Cleanup never accepts user-supplied provider paths. Provider cleanup must use server-generated refs and prefix-safe deletion.
For the managed Vertex/GCS batch bucket, disable Cloud Storage soft delete or configure lifecycle carefully to avoid hidden retained storage cost.
Provider Notes
gemini_api:
- Uses Gemini Batch API with JSONL file mode.
- Supports Gemini
apikeyupstream accounts with a configured API key. - Result file refs are internal.
- API keys are never returned.
- The provider can be selected and submitted through Sub2API when an administrator configures a Gemini API-key upstream account. In the 2026-07-07 PR validation, this path was verified as selectable/callable, but successful image generation was not continued because the test API key had no prepayment.
vertex:
- Uses Vertex
BatchPredictionJobwith managed GCS JSONL. - Supports Gemini
service_accountupstream accounts with valid service account JSON. - GCS bucket and prefix are server-managed.
- Vertex job name and GCS paths are internal.
- Batch image output should be treated as
1K/default only in MVP. - Do not promise
2Kor4K.
Other Gemini account/login types are not selected by the current batch image providers unless they expose equivalent API-key or service-account credentials through the same provider flow. They were not covered by the 2026-07-07 PR validation.
Official Google Enablement
Operators must enable Gemini/Vertex capability in Google's official console before turning on Sub2API batch image for any group. Sub2API feature flags and group switches do not create Google-side access by themselves.
Recommended production path:
- Use a Google Cloud project with billing enabled.
- Enable the relevant Gemini API / Vertex AI APIs for the project.
- Use a service account or Application Default Credentials for the Sub2API runtime.
- Create one fixed Cloud Storage bucket for batch image input and output, then grant the runtime and Vertex service agent the minimum required bucket permissions.
- Configure Sub2API with the project id, location, managed bucket, provider account, model whitelist, and pricing.
- Enable
BATCH_IMAGE_ENABLEDglobally, enable image generation on the intended Gemini group, then enableallow_batch_image_generationfor that group. Non-Gemini groups are not eligible for batch image generation, and the admin UI only shows the batch image group switch after image generation is enabled on a Gemini group.
API-key path:
- Google API keys are suitable for Gemini API development and supported Gemini methods.
- The Sub2API
x-goog-api-keycompatibility header still expects a Sub2API key, not a plain Google key. - Plain Google API keys should not be documented as the default production credential for Vertex service-account batch jobs.
- If an administrator configures a Gemini API-key upstream account, validate it with one low-cost batch image after the Google account has the required billing/prepayment state. If it has no prepayment, record only that the provider is selectable/callable and that failed submit releases hold.
Official references:
- Gemini API key guide: https://ai.google.dev/gemini-api/docs/api-key
- Gemini API Batch API: https://ai.google.dev/gemini-api/docs/batch-api
- Gemini API image generation and batch image notes: https://ai.google.dev/gemini-api/docs/image-generation
- Vertex/Gemini batch inference: https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/capabilities/batch-inference
- Vertex batch predictions API: https://docs.cloud.google.com/gemini-enterprise-agent-platform/reference/models/batch-prediction-api
Config
These keys exist in backend/internal/config/config.go:
/* Detailed source-code truncated for AI context efficiency. */Feature flags default to disabled.
Operations Checklist
- Enable
batch_image.enabled. - Configure Redis.
- Enable
batch_image.queue_enabledwhen workers should consume queue jobs. - Configure provider accounts.
- Configure the Vertex managed GCS bucket if using Vertex.
- Ensure bucket permissions are correct.
- Disable or manage GCS soft delete.
- Configure cleanup worker settings.
- Configure max items per job.
- Configure download concurrency.
- Confirm billing pricing.
- Run smoke tests before enabling.
Future Optimization
- Optional object-storage download offload: persist completed image outputs to an operator-configured object store such as GCS, S3, or R2, then issue short-lived signed download links to users. This would avoid routing large image/ZIP downloads through the Sub2API server, which is useful for small-bandwidth deployments. Keep it opt-in because it needs extra storage credentials, lifecycle cleanup, signed-URL expiry policy, access auditing, and compatibility with output deletion.
Security Checklist
- No provider refs in public responses.
- No GCS URI exposure.
- No signed URL exposure.
- No service account exposure.
- No API key exposure.
- No image bytes/base64 in PostgreSQL.
- No base64 in logs.
- Owner-scoped status, item, download, cancel, and delete routes.
- Output deletion is owner-scoped.
- Cleanup paths are server-generated only.
Test Commands
Core smoke and compile commands:
go test -tags=unit ./internal/service -run 'BatchImage' -count=1
go test -tags=unit ./internal/config ./internal/service ./internal/repository -count=1
go test ./internal/config ./internal/service ./internal/repository ./internal/handler ./internal/server/routes -run '^$'
go test ./... -run '^$'These commands should not require Docker, testcontainers, Redis, GCP, Gemini, Vertex, or GCS.
PR Hygiene Checklist
- Do not accidentally commit
rfcs/batch-image-issue-draft.mdunless maintainers explicitly want it. - Keep migrations ordered:
159_batch_image_foundation.sql, then160_batch_image_provider_refs.sql, then later migrations. - Include generated Ent code if generated code is committed in this repository.
- Keep generated server and wire files updated.
- Keep feature flags disabled by default unless maintainers ask otherwise.
- Do not commit real secrets, API keys, service account JSON, or local machine paths.
- Keep fixtures tiny and fake; no real cloud refs or credentials.
- Do not add new public routes, providers, dashboards, queues, or billing behavior in this stabilization PR.
COMPOSITE GROUPS (`docs/COMPOSITE_GROUPS.md`)
Composite Groups
Composite groups are an admin routing layer for API keys that should choose a
concrete provider from the requested model instead of binding the key to a
single provider group. They support both built-in model detection and an
admin-configured model route registry for public model aliases.
Supported Providers
Composite groups can route to these concrete account platforms:
- Anthropic
- Gemini
- OpenAI
- Antigravity
- Grok
- Kimi
- Zhipu GLM
- DeepSeek
- MiniMax
The selected concrete platform is used for account selection, user platform
quota checks, post-usage billing, ops error platform attribution, channel
mapping/pricing lookup, and platform usage reporting.
Route Registry
Admins can configure routes on a composite group from the group list'sRoutes action or through the admin API:
GET /api/v1/admin/groups/:id/composite-routesPOST /api/v1/admin/groups/:id/composite-routesPUT /api/v1/admin/groups/:id/composite-routes/:route_idDELETE /api/v1/admin/groups/:id/composite-routes/:route_idPOST /api/v1/admin/groups/:id/composite-routes/preview
Each route belongs to one composite group and contains:
public_model: model identifier the client sends.match_type:exactorprefix.target_platform: concrete provider platform.upstream_model: model identifier sent upstream. If omitted, the public
model is reused.endpoint:any,messages,count_tokens,responses,chat_completions,embeddings,images, orgemini.priority: lower values win after match specificity.enabled: disabled routes are ignored by runtime resolution but remain
visible to admins.
Resolution order is explicit route first, then built-in detection. When more
than one explicit route matches, exact matches beat prefix matches,
endpoint-specific routes beat any, longer prefixes beat shorter prefixes,
then lower priority, then lower route id.
For JSON-body endpoints, the gateway rewrites the request model field to the
route's upstream_model before dispatch. For Gemini native paths such as/v1beta/models/{model}:generateContent, the gateway resolves {model} and
the handler forwards the resolved upstream model.
Codex Alpha Search and Live requests use the responses route domain. Live
requests resolve the model from session.model, including multipart session
payloads, and apply the configured upstream_model before dispatch.
Codex model manifest requests reuse the existing OpenAI account selection and
failover path within the Composite group.
Responses WebSocket resolves the first response.create model in theresponses route domain (including any routes). Only OpenAI and Grok targets
are supported. The public model remains the admission and response identity;
the route's upstream model is applied before channel/account mapping. Subsequent
turns can repeat or omit the public model. Changing it requires reconnecting so
the gateway can resolve the route and select an account again.
Built-In Detection
Composite routing detects common public model IDs and provider-prefixed IDs:
claude-*andanthropic/claude-*route to Anthropic.gemini-*andgoogle/gemini-*route to Gemini.gpt-*,o*,codex-*,text-embedding-*,dall-e-*, andopenai/*route to OpenAI.grok-*andxai/grok-*route to Grok.kimi-*,moonshot-*, andk3route to Kimi.glm-*routes to Zhipu GLM.deepseek-*routes to DeepSeek.minimax-*,abab*, andminimax/*route to MiniMax.
Unknown or ambiguous model names fail closed with a client error instead of
guessing a provider.
Admin Workflows
- Admins can create a group with platform
composite. - Admins can add, edit, delete, and preview composite model routes.
- Composite groups can copy accounts from concrete provider groups.
- Concrete provider accounts can be assigned directly to composite groups from
account create/edit and bulk account workflows. - Subscription payment plans can bind to a composite group when that group's
subscription_typeissubscription. The plan grants access to the
composite group; each request is still billed and quota-checked against the
resolved concrete provider platform. - Channel configuration exposes composite groups in concrete provider sections.
The channelgroup_idspayload is still flat; provider-specific model
mapping and pricing remain keyed by concrete platform.
Bucket 2 Setup: OpenAI + Claude + Gemini + Grok
Use one composite subscription group when one customer-facing plan should expose
model aliases across OpenAI, Claude, Gemini, and Grok without issuing separate
keys per provider.
Create concrete provider groups for the upstream account pools, for example
OpenAI Paid,Claude Paid,Gemini Paid, andGrok Paid.Create a
compositegroup withsubscription_typeset tosubscription.Assign provider accounts directly to the composite group, or copy accounts
from the concrete provider groups during group creation.Add explicit routes for public aliases that should not rely on built-in
model detection:Public model Endpoint Target platform Upstream model all/gpt-5responsesopenaigpt-5all/claude-sonnetmessagesanthropicclaude-sonnet-4-6all/gemini-progeminigeminigemini-2.5-proall/grokresponsesgrokgrok-4.3Configure channel pricing and model mapping under the concrete platforms
named in each route. Composite routing does not create pricing records.Create a subscription payment plan for the composite group.
The same composite group can also rely on built-in detection for standard model
names such as gpt-*, claude-*, gemini-*, and grok-*. Explicit routes are
recommended for bundled plan aliases because they make endpoint, provider, and
upstream model attribution reviewable in the admin UI.
Limits
Composite routes choose a concrete provider and upstream model; they do not
create synthetic model metadata, pricing, or upstream capability records by
themselves. Keep channel pricing/model mapping configured for the concrete
provider platforms that the routes target.
This PR intentionally does not implement:
- AUTO smart-routing among multiple providers for the same abstract task.
- Direct API-key binding to several existing groups without a composite group.
- Protocol-agnostic provider decoupling or a LiteLLM-style adapter rewrite.
OPENAI GPT55 RETIREMENT (`docs/OPENAI_GPT55_RETIREMENT.md`)
GPT-5.5 retirement on ChatGPT-backed Codex accounts
OpenAI retired GPT-5.5 from Codex with ChatGPT sign-in on October 14,
2026 at 19:00 UTC (October 15 at 03:00 in Asia/Shanghai). Official API access
is unaffected. See the official migration notice.
Sub2API applies this distinction to the upstream account, not the key used by
clients to authenticate to Sub2API:
- OpenAI OAuth and SetupToken accounts, including credential shadows, cannot
serve GPT-5.5. Normal, sticky, and compact scheduling exclude these targets. - API-key accounts retain GPT-5.5 support. In mixed groups, only eligible API-key
upstreams can serve it. Their actual model availability remains authoritative. - Public model lists and Codex catalogs omit models whose targets are retired on
the group's ChatGPT accounts and have no supporting API-key account. This also
filters stale/pinned catalog entries before calculating the client ETag. - Explicit administrator mappings to a supported replacement remain valid.
Aliases mapped to GPT-5.5 are excluded from ChatGPT account routing.
Remote catalogs are projected to public aliases before retirement filtering,
using the same single mapping step as routing. Source metadata and shared
upstream caches are preserved, including when source IDs are also alias keys. - Unsupported requests return the existing
model_not_founderror with migration
guidance when the availability diagnosis identifies a retired subscription
target. Client chat requests are not silently rewritten to a replacement.
Administrator migration
Review group/account mappings, client defaults, custom agents and scheduled tasks
that select GPT-5.5. OpenAI recommends GPT-6.1 Sol for eligible paid plans or
GPT-6 Luna for Free/Go in the desktop app, subject to model availability. Check
availability on the upstream account before selecting a replacement.
The global gateway.openai_compact_model setting remains compatible with API-key
upstreams. If that fallback resolves to retired GPT-5.5 on a ChatGPT account, it
is not applied; compact requests keep their selected model. A compact-only
account mapping targeting retired GPT-5.5 is ineligible for scheduling: update
that explicit mapping. No stored settings are automatically rewritten.
Changing the public registry, deleting GPT-5.5 pricing/history, or globally
mapping GPT-5.5 to a new model is unnecessary and would affect valid API usage.
Regression tests use local fixtures and stub upstream responses. They do not
establish real subscription access to a replacement model.
PAYMENT (`docs/PAYMENT.md`)
Payment System Configuration Guide
Sub2API has a built-in payment system that enables user self-service top-up without deploying a separate payment service.
Table of Contents
- Supported Payment Methods
- Quick Start
- System Settings
- Provider Configuration
- Provider Instance Management
- Webhook Configuration
- Payment Flow
- Migrating from Sub2ApiPay
Supported Payment Methods
| Provider | Payment Methods | Description |
|---|---|---|
| EasyPay | Alipay, WeChat Pay | Third-party aggregation via EasyPay protocol |
| Alipay (Direct) | Desktop QR code, mobile Alipay redirect | Direct integration with Alipay Open Platform, returning desktop QR codes and mobile WAP/app launch links |
| WeChat Pay (Direct) | Native QR, H5, MP/JSAPI Pay | Direct integration with WeChat Pay APIv3 with environment-aware routing |
| Stripe | Card, Alipay, WeChat Pay, Link, etc. | International payments, multi-currency support |
Alipay/WeChat Pay direct and EasyPay can both exist as backend provider instances, but the frontend always exposes only two visible buttons:
AlipayandWeChat Pay. Admins choose exactly one source for each visible method: direct or EasyPay. Direct channels connect to payment APIs directly with lower fees; EasyPay aggregates through third-party platforms with easier setup.
EasyPay Provider Recommendations: Both options below are third-party aggregators compatible with the EasyPay protocol. Pick based on the funding channel and settlement currency you need:
- Domestic channel / CNY settlement — ZPay (
https://z-pay.cn/?uid=23808): direct integration with official Alipay / WeChat Pay APIs, fee 1.6%; funds go straight to the merchant account with T+1 automatic settlement. Supports individual users (no business license required) with up to 10,000 CNY daily transactions; business-licensed accounts have no limit. Link contains the referral code of Sub2ApiPay original author @touwaeriol — feel free to remove it.- International channel / USDT or USD settlement — Kyren Topup (
https://kyrenpay.com/?code=SUB2API): a ready-to-launch global payment stack for AI startups with WeChat Pay and Alipay support, local-currency checkout, and USD settlement. Fees: WeChat 2.5%, Alipay 2.5%; Multiple withdrawal methods are available. Withdrawals to overseas company accounts incur a $20 fee, while USDT withdrawals incur a $30 fee plus a 0.4% transaction fee, settled in USDT or USD. No qualification review required — sign up and use immediately, making it the lowest barrier to entry. Withdrawal threshold is relatively high, recommended for users who do not use domestic Chinese payment channels, cannot tolerate Stripe's 6%+ fees, have high transaction volume, and have USD or USDT channels to receive withdrawn funds. Kyren Topup charges a $200 account opening fee; signing up via this link (which contains Sub2Api author @Wei-Shaw's referral code) waives the opening fee. Feel free to remove it if you prefer.Please evaluate the security, reliability, and compliance of any third-party payment provider on your own — this project does not endorse or guarantee any of them.
Quick Start
- Go to Admin Dashboard → Settings → Payment Settings tab
- Enable Payment
- Configure basic parameters (amount range, timeout, etc.)
- Add at least one provider instance in Provider Management
- Users can now top up from the frontend
System Settings
Configure the following in Admin Dashboard Settings → Payment Settings:
Basic Settings
| Setting | Description | Default |
|---|---|---|
| Enable Payment | Enable or disable the payment system | Off |
| Product Name Prefix | Prefix shown on payment page | - |
| Product Name Suffix | Suffix (e.g., "Credits") | - |
| Minimum Amount | Minimum single top-up amount | 1 |
| Maximum Amount | Maximum single top-up amount (empty = unlimited) | - |
| Daily Limit | Per-user daily cumulative limit (empty = unlimited) | - |
| Order Timeout | Order timeout in minutes (minimum 1) | 30 |
| Max Pending Orders | Maximum concurrent pending orders per user | 3 |
| Load Balance Strategy | Strategy for selecting provider instances | Round Robin |
Frontend Visible Method Routing
The current payment UX keeps the frontend method list unified and does not expose provider brands directly:
- Alipay: when enabled, this button must be routed to either
Alipay (Direct)orEasyPay Alipay - WeChat Pay: when enabled, this button must be routed to either
WeChat Pay (Direct)orEasyPay WeChat - Each visible method can route to only one source at a time
- If a visible method is enabled without a selected source, the frontend will not expose that method
Load Balance Strategies
| Strategy | Description |
|---|---|
| Round Robin | Distribute orders to instances in rotation |
| Least Amount | Prefer instances with the lowest daily cumulative amount |
Cancel Rate Limiting
Prevents users from repeatedly creating and canceling orders:
| Setting | Description |
|---|---|
| Enable Limit | Toggle |
| Window Mode | Sliding / Fixed window |
| Time Window | Window duration |
| Window Unit | Minutes / Hours |
| Max Cancels | Maximum cancellations allowed within the window |
Help Information
| Setting | Description |
|---|---|
| Help Image | Customer service QR code or help image (supports upload) |
| Help Text | Instructions displayed on the payment page |
Provider Configuration
Each provider type requires different credentials. Select the type when adding a new provider instance in Provider Management → Add Provider.
Callback URLs are auto-generated: When adding a provider, the Notify URL and Return URL are automatically constructed from your site domain. You only need to confirm the domain is correct.
EasyPay
Compatible with any payment service that implements the EasyPay protocol.
| Parameter | Description | Required |
|---|---|---|
| Merchant ID (PID) | EasyPay merchant ID | Yes |
| Merchant Key (PKey) | EasyPay merchant secret key | Yes |
| API Base URL | EasyPay API base address | Yes |
| Alipay Channel ID | Specify Alipay channel (optional) | No |
| WeChat Channel ID | Specify WeChat channel (optional) | No |
Alipay (Direct)
Direct integration with Alipay Open Platform. Mobile flows return an Alipay WAP/app redirect URL. Desktop flows prefer Face-to-Face Precreate QR payloads; if the merchant has not enabled that product, the provider falls back to Computer Website Pay and also returns the cashier URL so the frontend can render a QR code or open the hosted checkout page directly.
| Parameter | Description | Required |
|---|---|---|
| AppID | Alipay application AppID | Yes |
| Private Key | RSA2 application private key | Yes |
| Alipay Public Key | Alipay public key | Yes |
WeChat Pay (Direct)
Direct integration with WeChat Pay APIv3. Supports Native QR code payment, H5 payment, and MP/JSAPI payment inside the WeChat environment.
| Parameter | Description | Required |
|---|---|---|
| AppID | WeChat Pay AppID | Yes |
| Merchant ID (MchID) | WeChat Pay merchant ID | Yes |
| Merchant API Private Key | Merchant API private key (PEM format) | Yes |
| APIv3 Key | 32-byte APIv3 key | Yes |
| WeChat Pay Public Key | WeChat Pay public key (PEM format) | Yes |
| WeChat Pay Public Key ID | WeChat Pay public key ID | Yes |
| Certificate Serial Number | Merchant certificate serial number | Yes |
Stripe
International payment platform supporting multiple payment methods and currencies.
| Parameter | Description | Required |
|---|---|---|
| Secret Key | Stripe secret key (sk_live_... or sk_test_...) |
Yes |
| Publishable Key | Stripe publishable key (pk_live_... or pk_test_...) |
Yes |
| Webhook Secret | Stripe Webhook signing secret (whsec_...) |
Yes |
Provider Instance Management
You can create multiple instances of the same provider type for load balancing and risk control:
- Multi-instance load balancing — Distribute orders via round-robin or least-amount strategy
- Independent limits — Each instance can have its own min/max amount and daily limit
- Independent toggle — Enable/disable individual instances without affecting others
- Refund control — Enable or disable refunds per instance
- Payment methods — Each instance can support a subset of payment methods
- Ordering — Drag to reorder instances
Instance Limit Configuration
Each instance supports these limits:
| Limit | Description |
|---|---|
| Minimum Amount | Minimum order amount accepted by this instance |
| Maximum Amount | Maximum order amount accepted by this instance |
| Daily Limit | Daily cumulative transaction limit for this instance |
During load balancing, instances that exceed their limits are automatically skipped.
Webhook Configuration
Payment callbacks are essential for the payment system to work correctly.
Callback URL Format
When adding a provider, the system auto-generates callback URLs from your site domain:
| Provider | Callback Path |
|---|---|
| EasyPay | https://your-domain.com/api/v1/payment/webhook/easypay |
| Alipay (Direct) | https://your-domain.com/api/v1/payment/webhook/alipay |
| WeChat Pay (Direct) | https://your-domain.com/api/v1/payment/webhook/wxpay |
| Stripe | https://your-domain.com/api/v1/payment/webhook/stripe |
Replace
your-domain.comwith your actual domain. For EasyPay / Alipay / WeChat Pay, the callback URL is auto-filled when adding the provider — no manual configuration needed.
Stripe Webhook Setup
- Log in to Stripe Dashboard
- Go to Developers → Webhooks
- Add an endpoint with the callback URL
- Subscribe to events:
payment_intent.succeeded,payment_intent.payment_failed - Copy the generated Webhook Secret (
whsec_...) to your provider configuration
Important Notes
- Callback URLs must use HTTPS (required by Stripe, strongly recommended for others)
- Ensure your firewall allows callback requests from payment platforms
- The system automatically verifies callback signatures to prevent forgery
- Balance top-up is processed automatically upon successful payment — no manual intervention needed
Payment Flow
User selects amount and payment method
│
▼
Create Order (PENDING)
├─ Validate amount range, pending order count, daily limit
├─ Load balance to select provider instance
└─ Call provider to get payment info
│
▼
User completes payment
├─ EasyPay → QR code / H5 redirect
├─ Alipay → Desktop QR payload (Face-to-Face preferred, Website Pay fallback) / mobile Alipay redirect
├─ WeChat Pay → Desktop Native QR / non-WeChat H5 / in-WeChat JSAPI
└─ Stripe → Payment Element (card/Alipay/WeChat/etc.)
│
▼
Webhook callback verified → Order PAID
│
▼
Auto top-up to user balance → Order COMPLETEDOrder Status Reference
| Status | Description |
|---|---|
PENDING |
Waiting for user to complete payment |
PAID |
Payment confirmed, awaiting balance credit |
COMPLETED |
Balance credited successfully |
EXPIRED |
Timed out without payment |
CANCELLED |
Cancelled by user |
FAILED |
Balance credit failed, admin can retry |
REFUND_REQUESTED |
Refund requested |
REFUNDING |
Refund in progress |
REFUNDED |
Refund completed |
Timeout and Fallback
- Before marking an order as expired, the background job queries the upstream payment status first
- If the user has actually paid but the callback was delayed, the system will reconcile automatically
- The background job runs every 60 seconds to check for timed-out orders
Migrating from Sub2ApiPay
If you previously used Sub2ApiPay as an external payment system, you can migrate to the built-in payment system:
Key Differences
| Aspect | Sub2ApiPay | Built-in Payment |
|---|---|---|
| Deployment | Separate service (Next.js + PostgreSQL) | Built into Sub2API, no extra deployment |
| Payment Methods | EasyPay, Alipay, WeChat, Stripe | Same |
| Configuration | Environment variables + separate admin UI | Unified in Sub2API admin dashboard |
| Top-up Integration | Via Admin API callback | Internal processing, more reliable |
| Subscription Plans | Supported | Not yet (planned) |
| Order Management | Separate admin interface | Integrated in Sub2API admin dashboard |
Migration Steps
- Enable payment in Sub2API admin dashboard and configure providers (use the same payment credentials)
- Update webhook callback URLs to Sub2API's callback endpoints
- Verify that new orders are processed correctly via built-in payment
- Decommission the Sub2ApiPay service
Note: Historical order data from Sub2ApiPay will not be automatically migrated. Keep Sub2ApiPay running for a while to access historical records.
PAYMENT CN (`docs/PAYMENT_CN.md`)
支付系统配置指南
Sub2API 内置支付系统,支持用户自助充值,无需部署独立的支付服务。
目录
支持的支付方式
| 服务商 | 支付方式 | 说明 |
|---|---|---|
| EasyPay(易支付) | 支付宝、微信支付 | 兼容易支付协议的第三方聚合支付 |
| 支付宝官方 | 桌面二维码扫码、移动端支付宝跳转或当面付唤起 | 直接对接支付宝开放平台;移动端默认 WAP,也可选择当面付二维码唤起支付宝 |
| 微信官方 | Native 扫码、H5、公众号/JSAPI 支付 | 直接对接微信支付 APIv3,按终端环境自动分流 |
| Stripe | 银行卡、支付宝、微信支付、Link 等 | 国际支付,支持多币种 |
支付宝官方 / 微信官方与易支付可以同时作为后台服务商实例存在,但前台始终只展示
支付宝、微信支付两个可见按钮。管理员需要分别为这两个按钮选择唯一支付来源:官方或易支付。官方渠道直接对接 API,资金直达商户账户,手续费更低;易支付通过第三方平台聚合,接入门槛更低。
易支付服务商推荐:以下两家均为兼容易支付协议的第三方聚合支付,按资金通道与结算方式选择:
- 国内渠道 / 人民币结算 — ZPay(
https://z-pay.cn/?uid=23808):支付宝 / 微信官方 API 直连,手续费 1.6%;资金直达商家账户,T+1 自动到账。支持个人用户(无营业执照)每日 1 万元以内交易;拥有营业执照则无限额。链接含 Sub2ApiPay 原作者 @touwaeriol 的邀请码,介意可去掉。- 国际渠道 / USDT 或美元结算 — 启润支付(
https://merchant.kyrenpay.com/?code=SUB2API):为 AI 项目提供低门槛国际收款通道,支持国际版微信支付与支付宝,本地货币支付、美元结算。微信 2.5%、支付宝 2.5%;多种提现方式,海外公司账户提现手续费 20 美金,USDT 提现手续费 30 美金 + 0.4% 流水,以 USDT 或美元 到账。无资质审核、注册即用,使用门槛最低;提现门槛略高,适合不使用国内支付渠道、无法接受 Stripe 高达 6%+ 手续费、流水较大,且拥有美元或 USDT 渠道可接收提现资金的用户。启润支付开户费 200 美元,通过本链接注册(含 Sub2Api 作者 @Wei-Shaw 邀请码)可免开户费,介意可去掉。支付渠道的安全性、稳定性及合规性请自行鉴别,本项目不对任何第三方支付服务商做担保或背书。
快速开始
- 进入管理后台 → 设置 → 支付设置 标签页
- 开启 启用支付
- 配置基本参数(金额范围、超时时间等)
- 在 服务商管理 中添加至少一个服务商实例
- 用户即可在前端页面进行充值
系统设置
在管理后台 设置 → 支付设置 中配置以下参数:
基本设置
| 设置项 | 说明 | 默认值 |
|---|---|---|
| 启用支付 | 启用或禁用支付系统 | 关闭 |
| 商品名前缀 | 支付页面显示的商品名前缀 | - |
| 商品名后缀 | 商品名后缀(如"元") | - |
| 最低金额 | 单笔最低充值金额 | 1 |
| 最高金额 | 单笔最高充值金额(留空表示不限制) | - |
| 每日限额 | 每用户每日累计充值上限(留空表示不限制) | - |
| 订单超时时间 | 订单超时分钟数,至少 1 分钟 | 30 |
| 最大待支付订单数 | 同一用户最大并行待支付订单数 | 3 |
| 负载均衡策略 | 多服务商实例时的选择策略 | 轮询 |
支付宝移动端当面付唤起
支付宝移动端当面付唤起 默认关闭,仅对前台路由到 支付宝官方 的移动端订单生效。开启后,服务端调用 alipay.trade.precreate 获取动态二维码,前端立即使用支付宝 Scheme 尝试唤起 App;页面未进入后台时会自动展示动态二维码备用页,继续轮询订单状态。桌面端仍保持“当面付二维码优先,电脑网站支付回退”的既有行为。
- 需要为该支付宝应用开通 当面付 / 扫码支付;未开通时保持关闭。
- 管理后台开关保存为
ALIPAY_MOBILE_PRECREATE_DEEP_LINK;部署环境变量ALIPAY_MOBILE_PRECREATE_DEEP_LINK=true可强制开启,未设置时使用后台值。 - 与“支付宝强制二维码支付”同时开启时,当面付唤起优先。关闭本开关即可恢复移动端手机网站支付。
前台可见支付方式路由
当前版本对用户统一展示支付方式,不区分官方渠道还是易支付:
- 支付宝:后台启用后,需要额外指定该按钮路由到
支付宝官方或易支付支付宝 - 微信支付:后台启用后,需要额外指定该按钮路由到
微信官方或易支付微信 - 同一个可见支付方式在同一时刻只能路由到一个来源
- 支付来源未选择时,即使对应按钮被开启,前台也不会暴露该支付方式
负载均衡策略
| 策略 | 说明 |
|---|---|
| 轮询(round-robin) | 按顺序轮流分配到各服务商实例 |
| 最少金额(least-amount) | 优先分配到当日累计金额最少的实例 |
取消频率限制
防止用户频繁创建并取消订单:
| 设置项 | 说明 |
|---|---|
| 启用限制 | 开关 |
| 窗口模式 | 滚动窗口 / 固定窗口 |
| 时间窗口 | 窗口长度 |
| 窗口单位 | 分钟 / 小时 |
| 最大次数 | 窗口内允许的最大取消次数 |
帮助信息
| 设置项 | 说明 |
|---|---|
| 帮助图片 | 充值页面显示的客服二维码等图片(支持上传) |
| 帮助文本 | 充值页面显示的说明文字 |
服务商配置
每种服务商需要不同的凭证和参数。在 服务商管理 → 添加服务商 中选择类型后填写。
回调地址自动生成:添加服务商时,异步回调地址(Notify URL)和同步跳转地址(Return URL)由系统根据你的站点域名自动拼接,无需手动填写。管理员只需确认域名正确即可。
EasyPay(易支付)
兼容任何 EasyPay 协议的支付服务商。
| 参数 | 说明 | 必填 |
|---|---|---|
| 商户 ID(PID) | EasyPay 商户 ID | 是 |
| 商户密钥(PKey) | EasyPay 商户密钥 | 是 |
| API 地址 | EasyPay API 基础地址 | 是 |
| 支付宝通道 ID | 指定支付宝通道(可选) | 否 |
| 微信通道 ID | 指定微信通道(可选) | 否 |
支付宝官方
直接对接支付宝开放平台。移动端默认走支付宝手机网站支付跳转;开启“支付宝移动端当面付唤起”后改为调用当面付,前端尝试打开支付宝 App,失败时显示动态二维码备用页。桌面端优先使用当面付返回扫码串,若商户未开通当面付则回退到电脑网站支付,并将收银台链接同时返回给前端用于渲染二维码或直接打开支付页。
| 参数 | 说明 | 必填 |
|---|---|---|
| AppID | 支付宝应用 AppID | 是 |
| 应用私钥 | RSA2 应用私钥 | 是 |
| 支付宝公钥 | 支付宝公钥 | 是 |
微信官方
直接对接微信支付 APIv3,支持 Native 扫码支付、H5 支付,以及在微信环境内的公众号/JSAPI 支付。
| 参数 | 说明 | 必填 |
|---|---|---|
| AppID | 微信支付 AppID | 是 |
| 商户号(MchID) | 微信支付商户号 | 是 |
| 商户 API 私钥 | 商户 API 私钥(PEM 格式) | 是 |
| APIv3 密钥 | 32 位 APIv3 密钥 | 是 |
| 微信支付公钥 | 微信支付公钥(PEM 格式) | 是 |
| 微信支付公钥 ID | 微信支付公钥 ID | 是 |
| 商户证书序列号 | 商户证书序列号 | 是 |
Stripe
国际支付平台,支持多种支付方式和币种。
| 参数 | 说明 | 必填 |
|---|---|---|
| Secret Key | Stripe 密钥(sk_live_... 或 sk_test_...) |
是 |
| Publishable Key | Stripe 可公开密钥(pk_live_... 或 pk_test_...) |
是 |
| Webhook Secret | Stripe Webhook 签名密钥(whsec_...) |
是 |
服务商实例管理
同一种服务商可以创建多个实例,实现负载均衡和风控:
- 多实例负载均衡 — 按轮询或最少金额策略分流订单
- 独立限额 — 每个实例可独立配置单笔最小/最大金额和每日限额
- 独立启停 — 可单独启用/禁用某个实例,不影响其他实例
- 退款控制 — 每个实例可单独开启或关闭退款功能
- 支付方式 — 每个实例可选择支持的支付方式子集
- 排序 — 拖拽调整实例顺序
实例限额配置
每个实例支持以下限额:
| 限额项 | 说明 |
|---|---|
| 单笔最小金额 | 该实例接受的最小订单金额 |
| 单笔最大金额 | 该实例接受的最大订单金额 |
| 每日限额 | 该实例每日累计交易上限 |
负载均衡时,系统会自动跳过超出限额的实例。
Webhook 配置
支付回调是支付系统的核心环节,必须正确配置:
回调地址格式
添加服务商时,系统会自动根据站点域名拼接回调地址,格式如下:
| 服务商 | 回调路径 |
|---|---|
| EasyPay | https://your-domain.com/api/v1/payment/webhook/easypay |
| 支付宝官方 | https://your-domain.com/api/v1/payment/webhook/alipay |
| 微信官方 | https://your-domain.com/api/v1/payment/webhook/wxpay |
| Stripe | https://your-domain.com/api/v1/payment/webhook/stripe |
将
your-domain.com替换为你的实际域名。EasyPay / 支付宝 / 微信的回调地址在添加服务商时自动填入,无需手动配置。
Stripe Webhook 设置
- 登录 Stripe Dashboard
- 进入 Developers → Webhooks
- 添加端点,填写回调地址
- 订阅事件:
payment_intent.succeeded、payment_intent.payment_failed - 将生成的 Webhook Secret(
whsec_...)填入服务商配置
注意事项
- 回调地址必须是 HTTPS(Stripe 强制要求,其他服务商强烈推荐)
- 确保服务器防火墙允许支付平台的回调请求
- 系统会自动进行签名验证,防止伪造回调
- 支付成功后自动完成余额充值,无需人工干预
支付流程
用户选择充值金额和支付方式
│
▼
创建订单 (PENDING)
├─ 校验金额范围、待支付订单数、每日限额
├─ 负载均衡选择服务商实例
└─ 调用服务商获取支付信息
│
▼
用户完成支付
├─ EasyPay → 扫码 / H5 跳转
├─ 支付宝官方 → 桌面扫码单(当面付优先,电脑网站支付回退)/ 移动端 WAP 或当面付唤起 + 动态二维码备用页
├─ 微信官方 → 桌面 Native 扫码 / 非微信 H5 / 微信内 JSAPI
└─ Stripe → Payment Element(银行卡/支付宝/微信等)
│
▼
支付回调验签 → 订单 PAID
│
▼
自动充值到用户余额 → 订单 COMPLETED订单状态说明
| 状态 | 说明 |
|---|---|
PENDING |
待支付,等待用户完成支付 |
PAID |
已支付,等待充值到账 |
COMPLETED |
已完成,余额已到账 |
EXPIRED |
已过期,超时未支付 |
CANCELLED |
已取消,用户主动取消 |
FAILED |
充值失败,可管理员重试 |
REFUND_REQUESTED |
已申请退款 |
REFUNDING |
退款处理中 |
REFUNDED |
已退款 |
超时与兜底
- 订单超时后,后台任务会先查询上游支付状态再标记过期
- 如果用户实际已支付但回调延迟,系统会通过查询补单
- 后台任务每 60 秒执行一次超时检查
从 Sub2ApiPay 迁移
如果你之前使用 Sub2ApiPay 作为外部支付系统,现在可以迁移到内置支付:
主要差异
| 对比项 | Sub2ApiPay | 内置支付 |
|---|---|---|
| 部署方式 | 独立服务(Next.js + PostgreSQL) | 内置于 Sub2API,无需额外部署 |
| 支付方式 | EasyPay、支付宝、微信、Stripe | 相同 |
| 配置方式 | 环境变量 + 独立管理后台 | Sub2API 管理后台内统一配置 |
| 充值对接 | 通过 Admin API 回调 | 内部直接处理,更可靠 |
| 订阅套餐 | 支持 | 暂不支持(计划中) |
| 订单管理 | 独立管理界面 | 集成在 Sub2API 管理后台 |
迁移步骤
- 在 Sub2API 管理后台启用支付并配置服务商(使用相同的支付凭证)
- 更新 Webhook 回调地址为 Sub2API 的回调地址
- 确认新订单通过内置支付正常处理
- 停用 Sub2ApiPay 服务
注意:Sub2ApiPay 中的历史订单数据不会自动迁移。建议保留 Sub2ApiPay 一段时间以便查询历史记录。
PLUGIN DEVELOPMENT (`docs/PLUGIN_DEVELOPMENT.md`)
Sub2API 插件开发教程
本文面向希望为 Sub2API 开发、打包和发布插件的团队。插件是独立进程和静态 UI 组成的 .s2plugin 包,宿主通过稳定的 gRPC 协议调用它。本文以当前宿主已经定义的 openai.oauth.outbound_transport.v1 能力作为协议示例,说明开发者需要准备什么、哪些职责属于插件、哪些职责仍由 Sub2API 负责。
本文不是一个可直接安装的完整插件,也不代表 Sub2API 已经发布对应的官方插件包。当前文档主要描述公开协议、宿主边界和开发流程。后续是否发布可安装包、支持哪些 Provider,以及如何提供示例仓库,都需要另行公告。
1. 准备开发环境
建议使用以下环境:
- Go 1.21 或更高版本;
- Node.js(仅在插件 UI 使用 JavaScript 时需要);
- Git;
- 与目标部署环境一致的构建工具链。
协议定义和通用说明位于:
backend/pkg/pluginapi/v1/plugin.proto:进程间消息和流式请求定义;backend/pkg/pluginapi/v1/runtime.go:插件进程启动入口;backend/pkg/pluginapi/v1/manifest.schema.json:包清单 JSON Schema;backend/pkg/pluginapi/docs/:开发、UI Bridge、包格式和安全边界说明。
目前暂未提供可直接复制的官方示例源码。开发者可以按照本文的目录和协议说明创建自己的插件工程;示例仓库发布后,会在本文补充正式的获取地址、目录说明和版本要求。公开协议始终以 backend/pkg/pluginapi/ 为准。
2. 创建插件工程
在示例仓库发布前,可以先创建一个独立的 Go 工程,目录建议如下:
my-plugin/
├── cmd/<plugin>/main.go
├── internal/pluginconfig/
├── internal/transport/
├── ui/index.html
├── ui/assets/
├── tools/
├── manifest.source.json
└── build.sh开发时至少准备以下部分:
manifest.source.json:插件 ID、名称、版本、作者、能力和兼容的 Sub2API 版本;cmd//main.go:启动入口和运行时版本注入,并同步打包器中的构建目标和二进制名称;internal/pluginconfig/:配置结构、默认值、严格校验和规范化;internal/transport/:HTTP 客户端、代理、请求头、请求体、网络连接参数、响应流和资源回收;ui/index.html与ui/assets/:插件自己的配置界面;- 单元测试、进程集成测试和目标平台构建配置。
入口文件应保持很小,只负责调用 pluginv1.Serve。实际逻辑放在可独立测试的包中,避免把配置解析、网络请求和协议组装全部写在 main.go。
3. 编写运行时
运行时实现 TransportPlugin 服务,必须满足以下约定:
| 方法 | 要求 |
|---|---|
GetInfo |
返回的插件 ID、版本、协议版本、传输 API 版本和能力必须与清单一致。 |
Health |
快速返回进程是否可以接收新请求,不执行长时间网络探测。 |
ValidateConfig |
严格解析 JSON,拒绝未知字段和非法范围,并返回完整的规范化配置。 |
ApplyConfig |
成功后原子切换配置;失败时保留旧配置和旧连接。 |
TestConfig |
针对已保存配置进行快速诊断,返回简短、可展示的结果。 |
Forward |
按协议接收请求流,发出上游请求,再按顺序返回响应流。 |
请求帧顺序为 start、零到多个 body_chunk、body_end;响应帧顺序为 start、零到多个 body_chunk、end。不能继续处理时发送 error 帧。
ForwardResponseError.request_sent 必须准确:只有在能够确认尚未调用上游 HTTP Transport 时才返回 false;一旦已经调用,或无法确认上游是否收到请求,就返回 true。宿主会据此决定是否允许切换账号重试,避免重复执行同一个请求。
资源管理也属于运行时契约:复用 HTTP Transport 和连接池,配置切换时关闭旧空闲连接,沿用 gRPC stream 的 context 取消 DNS、连接、上传和响应读取,并始终关闭上游响应体。日志和错误消息不能包含 Token、代理凭据、完整请求体或敏感响应头。
4. 设计插件配置
插件配置由插件定义,由 Sub2API 加密保存。推荐流程是:
- 在
internal/pluginconfig.Config中定义字段和默认值; - 使用
json.Decoder.DisallowUnknownFields等严格方式解析; - 将空对象规范化为完整默认配置;
- 在
ValidateConfig和ApplyConfig中复用同一套校验; - 配置应用成功后再让宿主保存,保存失败时允许恢复旧配置。
JSON 字段统一使用 snake_case。敏感配置不要放入 URL、UI 通知、诊断结果或日志。插件不应从 UI 读取、刷新或持久化 OAuth Token;宿主只在运行时调用需要的网络转发接口。
5. 实现插件自己的配置 UI
UI 是插件包内的静态页面,不需要修改 Sub2API 前端源码。宿主会在受限 iframe 中加载 ui/index.html,并通过 UI Bridge 提供配置读写和测试能力。
页面初始化流程:
- 加载包内 HTML、CSS 和 JavaScript;
- 创建 Bridge 并注册
message监听; - 发送
sub2api.plugin.ready; - 调用
config.load渲染表单; - 编辑后调用
config.save; - 测试前先保存,再调用
config.test; - 页面卸载时调用
dispose()。
当前 Bridge 支持:
| 消息 | 用途 |
|---|---|
config.load |
读取当前配置。 |
config.save |
提交配置,由运行时校验、应用并加密保存。 |
config.test |
运行已保存配置的诊断。 |
ui.resize |
调整配置 iframe 高度。 |
ui.notify |
显示成功、错误或提示消息。 |
每条消息都必须带 request_id,并校验 event.source、消息来源标识和 Bridge Token。不要依赖 CDN、远程脚本、Cookie 或本地存储。页面需要兼容窄屏和明暗主题,并正确处理加载、保存、测试、超时和未保存状态。
详细信封格式见 backend/pkg/pluginapi/docs/ui-bridge.md。如果后续示例仓库提供可复用的 Bridge SDK,本文会在示例仓库章节补充对应路径和使用方式。
6. 编写包清单
只维护 manifest.source.json,不要手工编辑构建目录中的 manifest.json。至少需要填写:
{
"schema_version": 1,
"id": "example.openai.transport",
"name": "Example OpenAI Transport",
"version": "0.1.0",
"requires": {
"sub2api": ">=0.1.179 <0.2.0",
"recommended_sub2api_version": "0.1.179",
"tested_sub2api_versions": ["0.1.179"],
"plugin_protocol": 1,
"transport_api": 1,
"ui_bridge": 1
},
"capabilities": [
{
"id": "openai.oauth.outbound_transport.v1",
"platform": "openai",
"account_type": "oauth"
}
],
"runtimes": {},
"ui": { "entrypoint": "ui/index.html" },
"files": {}
}打包器会自动填充目标平台运行时、UI 和运行时文件的 SHA-256。清单中的 requires.sub2api 是硬兼容范围;tested_sub2api_versions 应只填写真实验证过的版本;recommended_sub2api_version 用于管理页面展示。当前宿主仅处理 openai.oauth.outbound_transport.v1,声明其他能力不会自动产生新路由。后续增加 Provider 支持时,会在协议、能力清单和宿主路由完成适配后,再补充对应的清单示例。
7. 生成密钥并签名
生产包应始终签名,宿主默认拒绝未签名包。可以使用插件工程中的密钥生成工具生成一对 Ed25519 密钥;示例仓库发布后会提供标准工具和完整命令:
go run ./tools/keygen -out build/keys/my-publisher生成的 my-publisher.private 只保存在受控的开发机或 CI Secret 中,不能提交到源码仓库、插件包或部署服务器。公钥是 Base64 文本,可以提供给部署者。
插件工程的 build.sh 应调用标准打包器。自定义发布者密钥时必须同时提供 -signing-key 和 -key-id:
./build.sh \
-signing-key /安全目录/my-publisher.private \
-key-id my-publisher-v1 \
-output dist/my-openai-plugin.s2plugin签名覆盖最终 manifest.json 的精确字节;清单中的文件哈希再覆盖运行时和 UI 文件。签名完成后不要重新格式化 manifest.json。
部署者在 Sub2API 配置文件中追加公钥:
plugins:
allow_unsigned: false
trusted_publishers:
my-publisher-v1: "BASE64_ED25519_PUBLIC_KEY"trusted_publishers 是在宿主内置官方公钥之外追加的信任来源,不能覆盖内置公钥。signature.json 中的 key_id 必须与配置键完全一致。密钥轮换时先发布包含新公钥的宿主配置或版本,再发布新签名包,最后再停用旧密钥。
开发阶段如需使用未签名包,只应在隔离的本地环境临时设置 plugins.allow_unsigned: true,测试完成后立即恢复为 false。
8. 构建、测试和安装
在插件目录执行:
go test ./... -count=1
node --check ui/assets/bridge-v1.js
node --check ui/assets/app.js
./build.sh
unzip -t dist/*.s2plugin回到 Sub2API 仓库根目录后,再使用真实构建包运行宿主集成测试:
cd ../..
SUB2API_TEST_PLUGIN_PACKAGE=plugins/my-openai-plugin/dist/my-openai-plugin.s2plugin \
go test ./backend/internal/service -run '^TestPluginRuntimeIntegration$' -count=1最低测试集应覆盖配置默认值和边界值、插件身份、请求和响应分块、流式响应、上下文取消、插件退出、代理开关、包哈希、签名、路径安全、目标平台运行时以及 UI Bridge 的加载、保存、测试、错误和超时。
安装后先保持停用,确认清单兼容性、签名和诊断结果,再按账号灰度启用。API Key 账号和未命中灰度的 OAuth 账号继续走 Sub2API 原有路径。
9. 发布前检查清单
- 插件版本与
GetInfo返回值一致; requires.sub2api覆盖范围经过验证,没有未经测试的破坏性版本;tested_sub2api_versions与实际测试记录一致;- 每个支持的平台和架构都有运行时文件;
- 生产包存在有效
signature.json,公钥已交付部署者; - 包中没有私钥、源映射、测试数据、日志和临时文件;
- UI 不依赖外部资源,也不保存宿主会话信息;
- 配置切换、请求取消、响应关闭和错误重试语义经过测试;
- 发布说明包含升级、停用、回滚和兼容版本信息。
10. 常见问题
| 现象 | 排查方向 |
|---|---|
| 安装提示签名不受信任 | 检查 signature.json.key_id、Base64 公钥和配置键是否完全一致。 |
| 插件显示不兼容 | 检查 requires.sub2api、plugin_protocol、transport_api 和 ui_bridge。 |
| 插件进程无法启动 | 检查目标系统和架构对应的运行时路径、可执行权限和运行用户权限。 |
| 配置页无法加载 | 检查 ui.entrypoint、UI 文件哈希、Bridge Token 校验和 iframe 消息来源。 |
| 保存后配置未生效 | 查看 ValidateConfig、ApplyConfig 返回的规范化配置和诊断信息。 |
| 请求失败后重复执行 | 检查 ForwardResponseError.request_sent 是否准确反映请求是否可能已发出。 |
11. 需要扩展能力时
如果新插件需要支持其他 Provider、其他账号类型或新的消息字段,应先扩展并版本化公开协议,再由宿主增加能力匹配和生命周期处理。不要仅通过清单声明一个宿主尚未实现的能力。这样可以让旧插件继续运行,也能让新宿主明确拒绝不兼容的插件。
Sub2API 后续会持续补充更多 Provider 的插件适配说明,包括能力标识、请求和响应契约、配置字段、UI Bridge 使用方式、版本兼容要求以及测试清单。本文会随着这些能力的落地继续更新,Provider 专属章节会放在本节之后。
12. 示例仓库预留
后续计划提供独立的插件示例仓库,用于存放可复用的运行时骨架、UI 组件、打包工具和各 Provider 的最小实现。目前示例仓库尚未准备完成,因此暂不提供地址;正式发布后会在这里补充仓库地址、适用的 Sub2API 版本、示例插件版本和构建说明。
Channel Monitor V2 Safe Defaults (`docs/channel-monitor-v2-safe-defaults.md`)
Channel Monitor V2 Safe Defaults & Gentle Backfill
Date: 2026-08-08
Status: Approved for implementation
Branch: fix/channel-monitor-v2-ops-ui-blockers (onto channel-monitor-v2)
Problem
- H2 — Migration 195 and code defaults set
channel_monitor_mode=v2, so every upgrade without an existing key auto-opts into passive V2, stops V1 probes, and may leave users on an empty V2 view. - M1 — First-enable bootstrap forces 5s ticks and ~24h
RecomputeRangechunks untilnow-30d, hammering the primary DB. - Error dedup SQL matches
request_idwithout acreated_atbound, scanningops_error_logshistory viacandidate_ids.
Decisions
| Topic | Decision |
|---|---|
| Default mode | v1 (keep active probes). V2 is explicit opt-in. |
Existing channel_monitor_mode=v2 rows |
Unchanged (ON CONFLICT DO NOTHING / no force rewrite). |
| Switch to v2 | V1 runner fire() and RunCheck require ActiveProbesAllowed() → probes stop. |
| Backfill | Unified hard gates + adaptive soft gates (same rules, different observed load). |
| Optional profile knob | Not in this change (YAGNI). |
Mode defaults
- Migration
195_channel_monitor_mode.sql: insert'v1'when key missing. - If 195 already applied with old checksum, add migration checksum compatibility (do not force rewrite applied DBs to v1).
- Code:
defaultChannelMonitorMode = v1; empty/invalid normalize → v1. - Frontend Settings form default and public feature flag fallback → v1 when missing/invalid.
- Nil settings on V1
RunCheckpath remains fail-closed (no probes) for test safety — independent of product default.
Gentle backfill (low-resource phases)
Hard gates (all servers)
- Tick interval =
refresh_interval(60 or 300s). No 5s bootstrap override. - Each tick: (1) recent overlap (~10m), then (2) at most one historical chunk while product bootstrap incomplete / retention walk ongoing.
- Single leader lock; single short transaction budget (~55s).
- Chunk ceiling by depth (product phases 90m → 1d → 7d → 30d → silent 90d).
Soft gates (adaptive)
- Initial historical chunk: 1h (first seed still 2h for 90m UI).
- Min chunk: 15m; max by depth: ≤1d → 2h, ≤7d → 4h, older → 6h.
- Success + fast: grow slowly (×1.5) up to depth max.
- Failure/timeout: halve chunk; exponential backoff on wait (cap 10m).
- Progressive UI: show whatever is covered; bootstrap banner vs 30d product window.
Error dedup
- Bound the
request_id IN candidate_idsbranch withcreated_at >= $1 - INTERVAL '90 minutes' AND created_at < $2.
Out of scope
- Force existing v2 → v1.
- Separate backfill workers / read replicas.
- Admin
backfill_profilesetting (may add later).
Seedance Api (`docs/seedance-api.md`)
Seedance 原生 API
支持火山方舟 Ark 的异步视频任务协议,无需把 content[] 转换成 OpenAI messages 或 Grok prompt。
配置
- 创建 OpenAI 平台的 API Key 账号,填写 Ark API Key,Base URL 使用
https://ark.cn-beijing.volces.com/api/v3。兼容服务可填写自己的/api/v3或/v3Base URL。 - 在账号的端点能力中勾选 Seedance (Ark)。默认不启用,避免请求误调度到其他 OpenAI 账号。支持创建、编辑与批量编辑。
- 将账号加入 OpenAI 分组并启用分组的「允许图片生成」媒体权限;合成分组也可路由到这些账号。
- 配置模型映射,例如将公开模型名
seedance-video映射到实际doubao-seedance-*模型或ep-*推理接入点。配置对应模型的输出 token 价格;本接口不使用 Grok 的按秒视频价格。
调用
curl "$SUB2API_BASE_URL/api/v3/contents/generations/tasks" \
-H "Authorization: Bearer $SUB2API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"model": "seedance-video",
"content": [{"type": "text", "text": "海浪轻轻拍打沙滩"}],
"duration": 5,
"resolution": "720p",
"ratio": "16:9",
"generate_audio": true
}'
# 使用创建响应中的原生 id 查询,直至 succeeded / failed / cancelled 等终态。
curl "$SUB2API_BASE_URL/api/v3/contents/generations/tasks/$TASK_ID" \
-H "Authorization: Bearer $SUB2API_KEY"
curl -X DELETE "$SUB2API_BASE_URL/api/v3/contents/generations/tasks/$TASK_ID" \
-H "Authorization: Bearer $SUB2API_KEY"亦支持 /v3、/v1 和无版本前缀别名。Ark SDK 的 Base URL 可改为 $SUB2API_BASE_URL/api/v3。文本、图片、视频、音频内容、角色及扩展参数原样传递,仅按账号配置改写模型名;响应保持上游原生格式。
任务与计费
- 查询和删除只能访问同一用户、同一 API Key、同一分组创建的任务,并始终使用原提交账号;不会转到其他账号查询。
- 创建时不扣 token 用量。首次查询到
succeeded后,根据上游usage.completion_tokens计费;重复查询由共享缓存声明和持久化用量去重共同保护。失败、排队及运行中的任务不计费。 - Redis 保存任务绑定及创建时的模型快照,默认 24 小时。需保留 Redis 状态并在有效期内查询完成结果。当前不会后台轮询;只使用回调而不查询的任务不会自动结算。
- 不开放上游的任务列表接口,防止共享账号的任务泄露给其他用户。删除遵循上游语义,不自动退款。
- 异步创建的上游错误不自动重试,以免重复创建付费任务。
协议依据:火山官方 Go SDK、创建任务文档、查询任务文档。
Legal Admin Compliance.En (`docs/legal/admin-compliance.en.md`)
Sub2API Deployment and Operation Compliance Commitment
Version: v2026.06.10
This document applies to any individual, organization, or authorized representative that deploys, configures, manages, operates, or effectively controls a Sub2API instance. Before continuing to access or use console functions, the acknowledging party must read, understand, and accept this document in full.
1. Scope
Sub2API is open-source software. Any self-hosted deployment, modification, hosted operation, external service, commercial use, user management, content processing, data processing, payment settlement, customer support, or upstream account/API usage based on Sub2API is the sole responsibility of the party that deploys, operates, or controls the relevant instance.
This document does not replace the open-source license, upstream terms of service, user agreements, privacy policies, data processing agreements, commercial contracts, regulatory filings, administrative permits, security assessments, or any other documents, procedures, or obligations required by applicable law or contract.
2. Responsibility of the Deploying or Operating Party
The acknowledging party must independently assess and continuously comply with the laws, regulations, regulatory requirements, industry rules, contractual obligations, and platform policies that may apply in its location, server location, target-user location, place of actual business operation, and the locations of upstream service providers.
The acknowledging party must ensure that it has all authorizations, qualifications, filings, permits, assessments, contracts, risk-control capabilities, content-safety capabilities, data-protection capabilities, complaint-handling mechanisms, and emergency-response capabilities required for deploying and operating the relevant instance. Such obligations are not transferred, waived, or reduced by the use of open-source software.
3. No Affiliation and Allocation of Responsibility
Any third-party instance, commercial service, paid plan, user solicitation, content processing, data processing, account usage, API call, payment settlement, customer support, or promotional activity is independently carried out by the corresponding deploying, operating, or controlling party. The open-source nature of this project, code contributions, issue discussions, documentation maintenance, version releases, bug fixes, community communications, or general technical explanations do not create participation in, authorization of, approval of, warranty for, joint operation, agency, partnership, employment, authorized operation, joint control, revenue sharing, joint tort, or any other joint-and-several liability relationship between the open-source project, copyright holders, contributors, or maintainers and such activities.
The acknowledging party must not use the project name, marks, documentation, screenshots, community content, or open-source repository information to state or imply that its third-party instance, commercial service, paid plan, or operation is participated in, authorized, approved, warranted, or endorsed by the open-source project, copyright holders, contributors, maintainers, or community.
The acknowledging party is independently responsible for consequences arising from its deployment, configuration, operation, promotion, charging, user-behavior management, content processing, data processing, account usage, API calls, or violations of laws, regulations, regulatory requirements, contractual obligations, or upstream rules.
Any mandatory liability that cannot be excluded or limited by agreement shall be handled according to applicable law. Such statutory exception does not constitute participation in, authorization of, approval of, warranty for, or endorsement of any third-party deployment, operation, or commercial activity.
4. Compliance Commitments
By continuing to use console functions, the acknowledging party makes the following commitments:
- It has independently reviewed and will continuously comply with the terms of service, acceptable use policies, supported countries and regions, account/API key rules, commercial-use requirements, resale restrictions, risk-control requirements, and technical restrictions of OpenAI, Anthropic, Google, and any other upstream service providers.
- It will not use this project to bypass, or assist others in bypassing, upstream regional restrictions, access restrictions, account restrictions, risk controls, billing restrictions, identity verification, usage limits, or terms of service.
- It will not provide API relay, model-call resale, account quota distribution, shared subscriptions, paid calls, top-up/payment agency, or similar services to the public or an indefinite group of users unless all necessary authorizations, qualifications, filings, permits, assessments, or contractual arrangements have been obtained.
- If it provides generative AI services, deep synthesis services, algorithm-related services, API relay, paid calls, or other potentially regulated services within Mainland China or to the Mainland China public, it will independently complete all potentially applicable obligations regarding internet information services, generative AI services, deep synthesis, algorithm filing, security assessment, cybersecurity, data security, personal information protection, content safety, payment settlement, taxes, and upstream authorization.
- It will maintain user management, access control, content review, abuse handling, log retention, privacy protection, data deletion, complaint handling, emergency takedown, and security incident response mechanisms appropriate to the scale and risk of its business.
- It will not make any statement, commitment, marketing representation, or warranty to any user, customer, partner, channel, regulator, or third party that conflicts with Section 3 of this document.
- It will be independently responsible for consequences arising from its deployment, operation, promotion, charging, user-behavior management, content processing, data processing, account usage, API calls, or violations of laws, regulations, regulatory requirements, contractual obligations, or upstream rules.
5. Risk and Responsibility Notice
Using Sub2API for public API services, commercial relay, quota distribution, team sharing, paid calls, or similar purposes may involve risks relating to terms of service, contractual breach, data protection, content safety, consumer protection, payment settlement, taxes, export controls, sanctions compliance, cybersecurity, industry access, and administrative regulation. Requirements vary by jurisdiction and business model and may change over time.
The mandatory notice, document link, exact-phrase acknowledgment, and local acknowledgment record in the console are intended to provide clear, conspicuous, and reproducible notice of deployment and operation risks, confirm that the console user has read the current version of this document, and create a clear responsibility-separation record between the open-source project, copyright holders, contributors, maintainers and any third-party deploying, operating, or controlling party.
6. Electronic Acknowledgment
By continuing to use the console, opening the document link, reading this document, and typing the required confirmation phrase exactly as displayed, the acknowledging party electronically confirms that it has read, understood, and agreed to this document, and agrees that the system may record necessary evidence including the acknowledged version, acknowledgment time, console account identifier, IP address, and User-Agent.
Legal Admin Compliance.Zh (`docs/legal/admin-compliance.zh.md`)
Sub2API 部署与运营合规承诺
版本:v2026.06.10
本文件适用于部署、配置、管理、运营或实际控制 Sub2API 实例的个人、组织及其授权代表。继续访问或使用控制台功能前,确认主体应完整阅读、理解并接受本文件。
一、适用范围
Sub2API 是开源软件。任何基于 Sub2API 进行的自部署、二次开发、托管运行、对外服务、商业化使用、用户管理、内容处理、数据处理、支付结算、客户支持及上游账号或接口使用行为,均由相应实例的部署、运营或控制主体自行负责。
本文件不替代开源许可证、上游服务条款、用户协议、隐私政策、数据处理协议、商业合同、监管备案、行政许可、安全评估或其他依法应当具备的文件、手续或义务。
二、主体责任
确认主体应自行评估并持续遵守其所在地、服务器所在地、目标用户所在地、业务实际开展地以及上游服务提供方所在地可能适用的法律法规、监管要求、行业规范、合同约定和平台规则。
确认主体应确保其已具备部署和运营相关实例所需的授权、资质、备案、许可、评估、合同、风控能力、内容安全能力、数据保护能力、投诉处理机制和应急处置能力。相关义务不得因使用开源软件而转移、免除或降低。
三、非关联关系与责任隔离
任何第三方实例、商业服务、收费套餐、用户招揽、内容处理、数据处理、账号使用、接口调用、支付结算、客户支持或推广活动,均由相应部署、运营或控制主体独立实施,并不因本项目开源、代码贡献、议题讨论、文档维护、版本发布、缺陷修复、社区交流或一般性技术说明而形成开源项目、著作权人、贡献者或维护者对该等活动的参与、授权、认可、担保、共同经营、代理、合伙、雇佣、授权运营、共同控制、收益分配、共同侵权或其他连带责任关系。
确认主体不得以项目名称、标识、文档、截图、社区内容或开源仓库信息明示或暗示其第三方实例、商业服务、收费套餐或运营活动获得开源项目、著作权人、贡献者、维护者或社区的参与、授权、认可、担保或背书。
确认主体应独立承担因其部署、配置、运营、推广、收费、用户行为管理、内容处理、数据处理、账号使用、接口调用及违反法律法规、监管要求、合同约定或上游规则所产生的相关后果。
依法不得由协议排除或限制的强制性责任,依相关法律规定处理;该等法定例外不构成对任何第三方部署、运营或商业活动的参与、授权、认可、担保或背书。
四、合规承诺
确认主体在继续使用控制台功能时,作出以下承诺:
- 已独立审阅并将持续遵守 OpenAI、Anthropic、Google 及其他上游服务提供方的服务条款、可接受使用政策、支持国家和地区、账号/API Key 使用规则、商业使用要求、转售限制、风控要求和技术限制。
- 不利用本项目规避或协助他人规避上游服务的地区限制、访问限制、账号限制、风控限制、计费限制、身份验证、使用限制或服务条款。
- 不在缺乏必要授权、资质、备案、许可、评估或合同安排的情况下,向公众或不特定对象提供 API 中转、模型调用转售、账号额度分发、共享订阅、付费调用、代充代付或其他类似服务。
- 如在中国大陆境内或面向中国大陆公众提供生成式人工智能服务、深度合成服务、算法相关服务、API 中转、付费调用或其他可能受监管服务,将自行完成可能适用的互联网信息服务、生成式人工智能服务、深度合成、算法备案、安全评估、网络安全、数据安全、个人信息保护、内容安全、支付结算、税务及上游授权等义务。
- 建立与业务规模和风险相匹配的用户管理、访问控制、内容审核、滥用处理、日志留存、隐私保护、数据删除、投诉处理、应急下线和安全事件响应机制。
- 不向任何用户、客户、合作方、渠道方、监管机构或第三方作出与本文件第三条相冲突的陈述、承诺、宣传或保证。
- 对其部署、运营、推广、收费、用户行为管理、内容处理、数据处理、账号使用、接口调用及违反法律法规、监管要求、合同约定或上游规则所产生的后果独立承担责任。
五、风险与责任提示
将 Sub2API 用于公开 API 服务、商业中转、额度分发、团队共享、付费调用或类似用途,可能涉及服务条款、合同违约、数据保护、内容安全、消费者权益、支付结算、税务、出口管制、制裁合规、网络安全、行业准入及行政监管等风险。不同司法辖区和业务场景的要求可能不同,并可能随时间变化。
控制台中的强制提示、协议链接、逐字输入确认和本地确认记录,旨在以清晰、显著、可留痕的方式提示部署与运营风险,确认控制台使用者已阅读当前版本文件,并在开源项目、著作权人、贡献者、维护者与第三方部署、运营或控制主体之间形成明确的责任隔离记录。
六、电子确认
确认主体通过继续使用控制台、打开协议链接、阅读本文件并按页面要求逐字输入确认短语,即表示其以电子方式确认已阅读、理解并同意本文件,并同意系统记录确认版本、确认时间、控制台账户标识、IP 地址和 User-Agent 等必要留痕信息。
Backend Pkg Pluginapi Docs Development (`backend/pkg/pluginapi/docs/development.md`)
插件开发指南
稳定边界
当前宿主只支持 openai.oauth.outbound_transport.v1。插件负责建立实际上游 HTTP/TLS 连接,Sub2API 负责账号选择、OAuth Token 生命周期、下游协议、响应解析、SSE、错误映射、用量统计和计费。
插件不应修改 API Key 路径,也不应自行刷新或持久化 OAuth Token。
推荐结构
plugin/
├── cmd/<plugin>/main.go
├── internal/config/
├── internal/transport/
├── ui/index.html
├── ui/assets/
├── tools/packager/
├── manifest.source.json
└── README.md入口只调用 pluginv1.Serve。配置解析和传输实现放入独立包,以便不启动子进程就能单元测试。
运行时方法
| 方法 | 要求 |
|---|---|
GetInfo |
ID、版本、协议和能力必须与清单一致 |
Health |
返回进程是否可以接受新请求,不执行昂贵探测 |
ValidateConfig |
严格解析并返回完整规范化 JSON |
ApplyConfig |
原子应用配置;失败时保留旧配置 |
TestConfig |
验证当前环境和已保存配置,返回简短诊断 |
Forward |
双向流式传输请求与原始 HTTP 响应 |
请求帧顺序:start、零到多个 body_chunk、body_end。响应帧顺序:start、零到多个 body_chunk、end。不能继续处理的错误使用 error 帧。
request_sent 必须如实表示请求是否可能已经到达上游。值为 true 时宿主禁止自动切换账号重放;只有能确认尚未调用上游 Transport 时才能返回 false。
配置
- JSON 字段统一使用
snake_case。 - 拒绝未知字段、非法范围和受保护请求头。
- 默认配置必须完整,空对象应规范化为所有默认字段。
- 保存时由插件先验证和应用,再由宿主加密写入数据库。
- 数据库写入失败时宿主会尝试恢复旧配置,插件必须允许重复应用。
资源管理
- 复用 HTTP Transport 和连接池,不要为每个请求创建新连接池。
- 配置切换后关闭旧空闲连接。
- 使用 stream context 取消 DNS、连接、上传和响应读取。
- 始终关闭上游响应体。
- 不在插件内无限缓存按账号区分的客户端。
最低测试集
- 配置默认值、未知字段、边界值和深复制。
- 插件身份及协议版本。
- 请求体分块、无请求体、固定 Content-Length。
- 响应状态、重复请求头、流式响应和响应读取错误。
- 上下文取消、插件退出和超时。
- 代理开启与禁用。
- 包哈希、签名、路径穿越和目标平台运行时。
- UI Bridge 加载、保存、测试、错误和超时。
发布前还应使用真实构建包运行宿主的插件进程集成测试。
Backend Pkg Pluginapi Docs Package Format (`backend/pkg/pluginapi/docs/package-format.md`)
`.s2plugin` 包格式
.s2plugin 是 ZIP 文件,根目录必须包含 manifest.json,生产包还必须包含 signature.json。
标准布局
manifest.json
signature.json
runtimes/<goos>-<goarch>/<binary>
ui/index.html
ui/assets/...所有运行时和 UI 文件必须出现在 manifest.files,值为小写十六进制 SHA-256。清单和签名文件自身不写入 files。
包不允许绝对路径、父目录跳转、重复路径、符号链接、未声明文件或缺失文件。宿主还限制上传大小、解压后大小和文件数量。
清单
字段规范见 v1/manifest.schema.json。版本字段含义:
version:插件自身语义化版本。requires.sub2api:宿主硬兼容范围。recommended_sub2api_version:建议宿主版本。tested_sub2api_versions:发布者真实验证过的版本。plugin_protocol:进程握手协议。transport_api:请求和响应帧协议。ui_bridge:配置 UI 消息协议。
签名
signature.json:
{
"algorithm": "ed25519",
"key_id": "publisher-key-id",
"signature": "BASE64_SIGNATURE"
}签名对象是 manifest.json 的精确原始字节。发布者私钥不得进入插件包、源码仓库或 Sub2API 运行环境。部署者只配置 Base64 Ed25519 公钥。
默认生产配置拒绝未签名包。官方 OpenAI Transport 使用宿主内置公钥验签,不需要配置;其他发布者仍需配置 trusted_publishers。allow_unsigned 只用于开发者自己构建的本地包。
Backend Pkg Pluginapi Docs Security (`backend/pkg/pluginapi/docs/security.md`)
插件安全边界
能提供的隔离
- 私有实现以独立二进制交付,宿主公开源码不包含其业务逻辑。
- 进程协议避免 Go 动态链接和共享内存 ABI。
- 包签名和文件哈希防止未授权替换。
- UI 使用短时 URL、独立 Bridge Token 和 sandbox iframe。
- 插件故障时 OAuth 插件路径失败关闭,不静默切回另一种网络行为。
不能提供的保证
- 闭源二进制仍可能被逆向分析。
- 子进程不是操作系统沙箱。
- 插件拥有 Sub2API 服务用户可访问的文件、环境变量和网络权限。
- 包签名证明发布者身份,不证明实现无漏洞或符合 Provider 条款。
部署要求
- 官方 OpenAI Transport 使用宿主内置公钥;只向
plugins.trusted_publishers添加经过审核的第三方公钥。 - 使用专用低权限系统用户运行 Sub2API。
- 限制该用户的文件权限、出站网络和环境变量。
- 不向插件环境注入无关密钥。
- 对插件升级保留旧包和回滚流程。
- 记录安装、启用、停用、配置和删除操作,但不记录配置明文。
敏感数据
插件处理真实 OAuth Authorization 请求头,必须避免将请求头、请求体、代理凭据和上游敏感响应写入日志或诊断消息。UI 配置中不应出现 OAuth Token。
Backend Pkg Pluginapi Docs Ui Bridge (`backend/pkg/pluginapi/docs/ui-bridge.md`)
UI Bridge v1
加载方式
宿主为每次打开配置页创建短时 UI 会话:
/api/v1/plugin-ui/<asset-token>/index.html#bridge_token=<bridge-token>资源 Token 用于读取包内 ui/ 文件,Bridge Token 只存在于 URL fragment,不会发送到服务器。iframe 使用 sandbox="allow-scripts",不授予 allow-same-origin。
UI 只能加载包内、已在清单声明的资源。CSP 禁止外部网络连接、表单提交和外部 frame。
消息信封
UI 到宿主:
{
"source": "sub2api-plugin-ui",
"bridge_token": "TOKEN",
"type": "config.load",
"request_id": "UNIQUE_ID"
}宿主到 UI:
{
"source": "sub2api-plugin-host",
"bridge_token": "TOKEN",
"request_id": "UNIQUE_ID",
"ok": true
}方法
type |
UI 参数 | 成功响应 |
|---|---|---|
sub2api.plugin.ready |
无 | 无响应 |
config.load |
无 | config |
config.save |
config 对象 |
规范化后的 config |
config.test |
无 | result |
plugin.status |
无 | result(Health:{healthy, message, status_json}) |
ui.resize |
height |
无响应 |
ui.notify |
level、message |
无响应 |
config.test 在 v1 中测试已保存配置(需二次验证,可产生副作用)。UI 若要测试当前表单,应先调用 config.save。
plugin.status 是只读运行时状态通道:无副作用、免二次验证、不弹宿主提示,供状态面板轮询。它映射到插件 Health,result.status_json 是插件自定义的不透明 JSON 快照。带状态展示的插件应使用它,而不是把 config.test 当作状态轮询。
必须执行的校验
UI 接收消息时必须验证 event.source === parent、消息来源标识、Bridge Token 和等待中的 request_id。每个请求必须有超时和卸载清理。
宿主不会向 iframe 提供管理员 Token。插件 UI 不得尝试访问管理 API、Cookie、父页面 DOM 或浏览器存储中的宿主数据。
--- METRICS ---
- Files Extracted: 29
- Estimated Token Budget: ~50903 tokens
- Canonical Reference: https://codewiki.google/github.com/Wei-Shaw/sub2api