# api-convert **Repository Path**: skwyl/api-convert ## Basic Information - **Project Name**: api-convert - **Description**: api-convert 是一个基于 Java 25 和 Spring Boot 4 的 AI API 网关,提供 OpenAI Chat Completions、OpenAI Responses API、Anthropic Messages 等兼容入口,支持多供应商路由、协议转换、流式 SSE 转换、API Key 鉴权、额度计费、请求日志和可视化管理端。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2026-05-14 - **Last Updated**: 2026-10-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # api-convert `api-convert` is a Java 25 + Spring Boot 4 AI API gateway. It forwards OpenAI / Anthropic-style client requests to admin-configured upstream channels and models, with unified endpoints, protocol adapters, model routing, gateway API keys, quota controls, request logs, and an admin console. [中文 README](README.md) | [Development Guide](docs/DEVELOPMENT_EN.md) | [开发文档](docs/DEVELOPMENT.md) ## Features - Compatible endpoints: `/v1/chat/completions`, `/v1/responses`, `/v1/videos`, `/v1/images/generations`, `/v1/messages`, `/v1/models` - Upstream types: OpenAI-compatible, Anthropic, OpenAI Responses, GPT_AUTH, CLAUDE_AUTH, DeepSeek Chat, DeepSeek Anthropic, Gemini - Protocol adapters between Chat Completions, Anthropic Messages, Responses API, DeepSeek, and Gemini formats - Routing modes: random, round-robin, weighted, session-sticky, tool-aware routing, failure cooldown, canary release (canary pool + percent split) - Gateway keys: SHA-256 authentication, channel/model allowlists, balance, token-based billing, sliding-window quota limits, sliding-window request-count limits - Reliability: same-channel upstream retry, circuit breaker (sliding-window failure rate, **Redis-shared across instances**), client-abort cancellation, Idempotency-Key replay - Admin console: channels, models, gateway keys, request logs, dashboard, SLO view, circuit-breaker events, audit logs, routing configuration, AUTH channel authorization - Data layer: SQLite by default, MySQL supported, automatic schema installation and migration at startup > `LOCAL` is reserved in the Provider enum for future extension. This version does not include a Local provider client. > Redis is **optional**. When enabled it provides circuit-breaker state sharing, Idempotency-Key replay, and cross-instance rate limiting for multi-instance deployments; when disabled these features automatically fall back to single-instance semantics without affecting the business. See [Redis Deployment and Configuration](#redis-deployment-and-configuration) for details. ## Tech Stack | Layer | Technology | |---|---| | Backend | Java 25, Spring Boot 4.0.6, Maven, MyBatis-Plus, Log4j2 | | Database | SQLite / MySQL | | Cross-instance state | Redis 6+ (Lettuce client, optional, auto-fallback) | | Frontend | Vue 3.5, Vite, TypeScript, Naive UI | | Admin auth | Sa-Token | ## Quick Start Requirements: - Git - Node.js and npm - Maven Wrapper from this repository: `mvnw.cmd` / `mvnw`, called internally by the start script ### Download JDK and Start Extract the JDK next to the repository, not inside the `api-convert` git worktree. Maven-related work, backend startup, and frontend startup are handled inside the start script. Windows PowerShell: ```powershell mkdir api-convert-work cd api-convert-work git clone https://gitee.com/skwyl/api-convert.git Invoke-WebRequest ` -Uri "https://api.adoptium.net/v3/binary/latest/25/ga/windows/x64/jdk/hotspot/normal/eclipse" ` -OutFile "jdk-25.zip" mkdir jdk-25 tar -xf jdk-25.zip -C jdk-25 --strip-components=1 cd api-convert .\scripts\start.ps1 -JavaHome "..\jdk-25" ` -AdminUsername admin ` -AdminPassword "change-me" ``` Linux x64: ```bash mkdir -p api-convert-work cd api-convert-work git clone https://gitee.com/skwyl/api-convert.git curl -L "https://api.adoptium.net/v3/binary/latest/25/ga/linux/x64/jdk/hotspot/normal/eclipse" \ -o jdk-25.tar.gz mkdir -p jdk-25 tar -xzf jdk-25.tar.gz -C jdk-25 --strip-components=1 cd api-convert ./scripts/start.sh --java-home ../jdk-25 \ --admin-username admin \ --admin-password 'change-me' ``` macOS Apple Silicon: ```bash mkdir -p api-convert-work cd api-convert-work git clone https://gitee.com/skwyl/api-convert.git curl -L "https://api.adoptium.net/v3/binary/latest/25/ga/mac/aarch64/jdk/hotspot/normal/eclipse" \ -o jdk-25.tar.gz mkdir -p jdk-25 tar -xzf jdk-25.tar.gz -C jdk-25 --strip-components=3 cd api-convert ./scripts/start.sh --java-home ../jdk-25 \ --admin-username admin \ --admin-password 'change-me' ``` For macOS Intel, replace `aarch64` in the URL above with `x64`. Mainland China mirror directories: | System | Tsinghua TUNA Mirror Directory | |---|---| | Windows x64 | `https://mirrors.tuna.tsinghua.edu.cn/Adoptium/25/jdk/x64/windows/` | | Linux x64 | `https://mirrors.tuna.tsinghua.edu.cn/Adoptium/25/jdk/x64/linux/` | | macOS Intel | `https://mirrors.tuna.tsinghua.edu.cn/Adoptium/25/jdk/x64/mac/` | | macOS Apple Silicon | `https://mirrors.tuna.tsinghua.edu.cn/Adoptium/25/jdk/aarch64/mac/` | After startup: - Backend and built-in admin entry: `http://localhost:8080` - Frontend dev server: `http://localhost:5173` - Default admin account: `admin / admin123` More startup parameters: ```powershell .\scripts\start.ps1 -JavaHome '..\jdk-25' ` -AdminUsername admin ` -AdminPassword 'change-me' ` -BackendPort 8080 ` -DbType mysql ` -DatasourceUrl 'jdbc:mysql://127.0.0.1:3306/api_convert?useSSL=false&serverTimezone=Asia/Shanghai' ` -DatasourceUsername root ` -DatasourcePassword 'mysql-password' ` -RedisHost 127.0.0.1 ` -RedisPort 6379 ` -RedisPassword 'change-me' ``` ```bash ./scripts/start.sh --java-home ../jdk-25 \ --admin-username admin \ --admin-password 'change-me' \ --backend-port 8080 \ --db-type mysql \ --datasource-url 'jdbc:mysql://127.0.0.1:3306/api_convert?useSSL=false&serverTimezone=Asia/Shanghai' \ --datasource-username root \ --datasource-password 'mysql-password' \ --redis-host 127.0.0.1 \ --redis-port 6379 \ --redis-password 'change-me' ``` Do not use the default admin password in production. The default Redis password is `123456`; **production must override it via `SPRING_DATA_REDIS_PASSWORD`**. See [Development Guide](docs/DEVELOPMENT_EN.md#configuration) for more configuration options. ## Common API Calls Health check: ```bash curl http://localhost:8080/health ``` Model list: ```bash curl -H "Authorization: Bearer " \ http://localhost:8080/v1/models ``` OpenAI Chat Completions: ```bash curl -X POST http://localhost:8080/v1/chat/completions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"example-chat","messages":[{"role":"user","content":"hello"}],"stream":false}' ``` Anthropic Messages: ```bash curl -X POST http://localhost:8080/v1/messages \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d '{"model":"example-chat","messages":[{"role":"user","content":"hello"}],"stream":false}' ``` OpenAI Responses API: ```bash curl -X POST http://localhost:8080/v1/responses \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"example-chat","input":"hello","stream":false}' ``` OpenAI Videos API: ```bash curl -X POST http://localhost:8080/v1/videos \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"example-video","prompt":"A cinematic city skyline at sunset","seconds":4,"size":"1280x720"}' ``` OpenAI Images API: ```bash curl -X POST http://localhost:8080/v1/images/generations \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"model":"example-image","prompt":"A clean product render on a white background","size":"1024x1024","n":1}' ``` ## Admin Workflow 1. Log in to the admin console. 2. Create upstream channels and configure their models in Channel Management. 3. Adjust public model capabilities, enabled status, and quota pricing in Model Management. 4. Create gateway API keys and configure balance, limit items, channel allowlists, and model allowlists as needed. 5. Choose routing mode, failure cooldown, and session-stickiness settings in System Configuration. 6. Use Dashboard and Request Logs to inspect usage, errors, and upstream channel selection. ## Documentation | Document | Description | |---|---| | [Development Guide](docs/DEVELOPMENT_EN.md) | Architecture, packages, endpoints, providers, adapters, DB migrations, frontend rules | | [开发文档](docs/DEVELOPMENT.md) | 中文开发文档 | | [Chinese README](README.md) | 中文项目概览 | ## Docker Build a local image: ```bash docker build -t api-convert:local . ``` Run with SQLite (single instance, no Redis required): ```bash docker run --rm -p 8080:8080 \ -v api-convert-data:/app/data \ -e JAVA_OPTS='-XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders' \ -e LOG_PATH=/app/data/logs \ -e API_CONVERT_ADMIN_USERNAME=admin \ -e API_CONVERT_ADMIN_PASSWORD='change-me' \ api-convert:local ``` Multi-instance deployment (with Redis + MySQL): ```bash docker network create api-convert-net # Redis shared state (circuit breaker / Idempotency-Key / sliding-window rate limit) docker run -d --name api-convert-redis --network api-convert-net \ -p 6379:6379 \ redis:7-alpine \ redis-server --requirepass 'change-me' --appendonly yes # MySQL business database docker run -d --name api-convert-mysql --network api-convert-net \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD='change-me' \ -e MYSQL_DATABASE=api_convert \ -v api-convert-mysql:/var/lib/mysql \ mysql:8.0 # Application container docker run --rm -p 8080:8080 \ --network api-convert-net \ -v api-convert-data:/app/data \ -e JAVA_OPTS='-XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders' \ -e LOG_PATH=/app/data/logs \ -e API_CONVERT_ADMIN_USERNAME=admin \ -e API_CONVERT_ADMIN_PASSWORD='change-me' \ -e API_CONVERT_DB_TYPE=mysql \ -e SPRING_DATASOURCE_URL='jdbc:mysql://api-convert-mysql:3306/api_convert?useSSL=false&serverTimezone=Asia/Shanghai' \ -e SPRING_DATASOURCE_USERNAME=root \ -e SPRING_DATASOURCE_PASSWORD='change-me' \ -e SPRING_DATA_REDIS_HOST=api-convert-redis \ -e SPRING_DATA_REDIS_PORT=6379 \ -e SPRING_DATA_REDIS_PASSWORD='change-me' \ api-convert:local ``` Release image example: ```bash docker pull crpi-vqmjtaxg5bb83uba.cn-guangzhou.personal.cr.aliyuncs.com/aping/api-convert:v1.0.5 ``` ## Nginx Reverse Proxy When exposing the service through Nginx, use a `location` block like the following to forward `Authorization`, disable proxy caching, allow larger request bodies, and disable buffering for real-time SSE streaming. Replace `http://your_host:port` with the actual backend address, for example `http://127.0.0.1:8080`; use `https://your_host:port` if the upstream backend runs HTTPS. ```nginx location ^~ / { proxy_pass http://your_host:port; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; proxy_pass_request_headers on; proxy_set_header Authorization $http_authorization; proxy_http_version 1.1; add_header X-Cache $upstream_cache_status; proxy_ssl_server_name off; proxy_ssl_name $proxy_host; # Disable caching to avoid 304 responses or stale API results. proxy_no_cache 1; proxy_cache_bypass 1; add_header Cache-Control "no-cache, no-store, must-revalidate" always; expires off; # base64 payload decoding and upstream processing may take longer. proxy_read_timeout 300s; proxy_send_timeout 300s; proxy_connect_timeout 60s; # Support large base64 request bodies for image and video payloads. client_max_body_size 50m; # SSE streaming: disable buffering so upstream events are returned immediately. proxy_buffering off; proxy_request_buffering off; proxy_cache off; proxy_set_header Connection ''; chunked_transfer_encoding on; } ``` ## Redis Deployment and Configuration Redis is **optional**. When enabled it powers three cross-instance capabilities: | Capability | Without Redis | With Redis | |---|---|---| | Circuit-breaker state (CLOSE/OPEN/HALF_OPEN) | Per-instance only | Shared: any instance opening OPEN is immediately visible to others | | Idempotency-Key replay | Local cache only | Any instance that handled a request can be replayed from any other instance with zero cost | | Sliding-window rate limit (QUOTA / REQUEST) | Each instance counts independently | Global strict enforcement instead of N-times over-quota | When **Redis is unreachable**, all capabilities fall back to in-process state with a 5s backoff; the business keeps running. Always run Redis in production; skip it for local development. ### 1. Install Redis | Platform | Method | |---|---| | Linux | `apt install redis-server` / `yum install redis` / official packages | | macOS | `brew install redis` | | Windows | WSL2 + Ubuntu, or Docker: `docker run -d -p 6379:6379 redis:7-alpine` | | Container | `docker run -d --name redis -p 6379:6379 redis:7-alpine --requirepass yourStrongPass` | ### 2. Enable a Password ```bash # Ad-hoc redis-cli CONFIG SET requirepass 'yourStrongPass' # Permanent: edit /etc/redis/redis.conf requirepass yourStrongPass appendonly yes # AOF persistence (circuit-breaker state must survive restart) appendfsync everysec # 1s fsync, balances safety and performance ``` ### 3. Configuration Knobs All settings use Spring Boot's standard `spring.data.redis.*` namespace and can be set via `application.yaml`, environment variables, or the start script. The table below shows **environment variable names**; the corresponding YAML key is `spring.data.redis.`. | Env var | Default | Notes | |---|---|---| | `SPRING_DATA_REDIS_HOST` | `localhost` | Redis address | | `SPRING_DATA_REDIS_PORT` | `6379` | Redis port | | `SPRING_DATA_REDIS_DATABASE` | `0` | Redis database index (0-15). Use a non-zero value to isolate keyspaces when sharing one Redis cluster across services | | `SPRING_DATA_REDIS_PASSWORD` | `123456` | **Production must override via env**; to skip AUTH for a local Redis without `requirepass`, set `SPRING_DATA_REDIS_PASSWORD=` (empty) | | `SPRING_DATA_REDIS_TIMEOUT` | `2s` | Operation timeout (read/write) | | `SPRING_DATA_REDIS_CONNECT_TIMEOUT` | `1s` | Connect timeout | | `SPRING_DATA_REDIS_POOL_ENABLED` | `true` | Lettuce connection pool toggle. Enabled by default since V1.0.6.3 — Sa-Token + SharedStateClient share Redis and benefit from connection reuse | | `SPRING_DATA_REDIS_POOL_MAX_ACTIVE` | `32` | Max active connections; further callers queue up to `max-wait` | | `SPRING_DATA_REDIS_POOL_MAX_IDLE` | `16` | Max idle connections | | `SPRING_DATA_REDIS_POOL_MIN_IDLE` | `4` | Pre-warmed idle connections so hot path skips the handshake | | `SPRING_DATA_REDIS_POOL_MAX_WAIT` | `1s` | Wait limit when pool is exhausted; throws `RedisConnectionFailureException` past it | ### 4. Start Script ```powershell .\scripts\start.ps1 -JavaHome '..\jdk-25' ` -AdminUsername admin -AdminPassword 'change-me' ` -RedisHost 127.0.0.1 -RedisPort 6379 -RedisPassword 'change-me' ``` ```bash ./scripts/start.sh --java-home ../jdk-25 \ --admin-username admin --admin-password 'change-me' \ --redis-host 127.0.0.1 --redis-port 6379 --redis-password 'change-me' ``` ### 5. Environment Variables ```bash export SPRING_DATA_REDIS_HOST=127.0.0.1 export SPRING_DATA_REDIS_PORT=6379 export SPRING_DATA_REDIS_DATABASE=0 export SPRING_DATA_REDIS_PASSWORD='change-me' ``` ### 6. Verify Connectivity Look for `Tomcat started on port 8080` in the startup log with no `RedisConnectionFailureException`. If you see a WARN like `Redis 共享状态不可用,5000ms 内降级到进程内存`, Redis is unreachable. Check in order: 1. `redis-cli -h 127.0.0.1 -p 6379 -a 'change-me' PING` → expect `PONG` 2. Firewall / security group rules for port 6379 3. `bind 127.0.0.1` too restrictive (for remote deploy, change to `0.0.0.0` or set `protected-mode no` plus a strong password) 4. Password special characters may need URL encoding ### 7. Key TTLs and Cleanup - Circuit-breaker state: `cb:{providerCode}:{providerModel}` Hash, TTL = `max(60, openDuration*4)` seconds - Idempotency-Key L1: `idem:{apiKeyId}:{idempotencyKey}:{requestHash}`, TTL 60s - Sliding-window rate limit: `rl:{type}:{apiKeyId}:{windowValue}{unit}` ZSET, TTL = `windowMs * 1.5` - Sa-Token admin sessions: `satoken:login:token:{tokenValue}` / `satoken:login:session:{loginId}`, TTL follows `gateway_system_config.admin.token_timeout_seconds` Shared-state caches don't need long-term Redis persistence. A Redis crash falls shared state back to single-instance semantics within 5s; admin sessions are lost and admins must log in again. ## Build and Test Local startup and backend build are wrapped by the start script. Pass the JDK path directly: ```powershell .\scripts\start.ps1 -JavaHome "..\jdk-25" ``` ```bash ./scripts/start.sh --java-home ../jdk-25 ``` ## License MIT License. ## Screenshots Screenshots below are based on 1.0.6.1, for demo only. ### Dashboard Real-time stats of requests, token consumption, and success rate; drill down by model / channel / API key. The endpoint list at the bottom exposes Base URL and endpoint metadata for external clients. ![Dashboard](img/Snipaste_2026-06-17_20-38-01.png) ### Channel Management Channels grouped by provider type with endpoint capabilities highlighted as tags. The inline "Refresh" button pulls real upstream quota (when supported). ![Channel Management](img/Snipaste_2026-06-17_20-38-40.png) ### Model Management Aggregated by public model name; manage per-million-token input / output / cache-read quota and capability flags (vision / tools / JSON mode). ![Model Management](img/Snipaste_2026-06-17_20-38-55.png) ### API Key Management Manage API keys for external clients; configure quota, request limits (per window unit), and authorized channel / model scope. Enable failover to auto-retry across channels on failure. ![API Key Management](img/Snipaste_2026-06-17_20-39-07.png) ### System Config Three tabs: Routing Strategy (random / round-robin / weighted / session-sticky), Failure Avoidance (consecutive-failure threshold + cooldown minutes), and Session Sticky (TTL in minutes). Each field has a tooltip and input validation. ![System Config](img/Snipaste_2026-06-17_20-39-37.png) ### Request Log Search request history by request ID / key / protocol / endpoint type / model. The list shows latency, tokens, HTTP status, and error code per request. ![Request Log](img/Snipaste_2026-06-17_20-40-17.png) ## Screenshots Screenshots below are based on 1.0.6.1, for demo only. ### Dashboard Real-time stats of requests, token consumption, and success rate; drill down by model / channel / API key. The endpoint list at the bottom exposes Base URL and endpoint metadata for external clients. ![Dashboard](img/Snipaste_2026-06-17_20-38-01.png) ### Channel Management Channels grouped by provider type with endpoint capabilities highlighted as tags. The inline "Refresh" button pulls real upstream quota (when supported). ![Channel Management](img/Snipaste_2026-06-17_20-38-40.png) ### Model Management Aggregated by public model name; manage per-million-token input / output / cache-read quota and capability flags (vision / tools / JSON mode). ![Model Management](img/Snipaste_2026-06-17_20-38-55.png) ### API Key Management Manage API keys for external clients; configure quota, request limits (per window unit), and authorized channel / model scope. Enable failover to auto-retry across channels on failure. ![API Key Management](img/Snipaste_2026-06-17_20-39-07.png) ### System Config Three tabs: Routing Strategy (random / round-robin / weighted / session-sticky), Failure Avoidance (consecutive-failure threshold + cooldown minutes), and Session Sticky (TTL in minutes). Each field has a tooltip and input validation. ![System Config](img/Snipaste_2026-06-17_20-39-37.png) ### Request Log Search request history by request ID / key / protocol / endpoint type / model. The list shows latency, tokens, HTTP status, and error code per request. ![Request Log](img/Snipaste_2026-06-17_20-40-17.png)