Model or dataset
fruitbars/simple-one-api avatar
fruitbars/simple-one-api

simple-one-api: a single Go binary that fronts several model providers with capacity-aware key routing

OpenAI-compatible API adapter supporting Qianfan, iFlytek Spark, Tencent Hunyuan, MiniMax, DeepSeek and other OpenAI-compatible LLM platforms - a single executable with super simple configuration for one-click deployment, ready to use out of the box.

2,344 stars209 forksGoMIT

At a glance

What is it?
A Go gateway that speaks OpenAI Chat Completions, OpenAI Responses and Anthropic Messages at once, pools API keys per model, and tracks a rolling token window so one exhausted key does not stall a provider.
Who is it for?
simple-one-api earns its place when you hold several keys against one or more providers and keep hitting 429s from naive round robin, because the rolling reservation window and the per key plus model cooldown are a real scheduler rather than a reshuffle.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Three client protocols behind one gateway

The headline capability is protocol coverage rather than provider count. The gateway exposes OpenAI Chat Completions at `/v1/chat/completions`, the OpenAI Responses protocol at `/v1/responses`, and Anthropic Messages at `/v1/messages`, plus `GET /v1/models`, a single model lookup at `/v1/models/:model`, and an embeddings endpoint.

That combination is the practical reason to run something like this. Existing clients keep working without modification: an OpenAI-compatible SDK only needs `base_url` pointed at the gateway, Codex uses the Responses protocol, and Claude Code uses the Anthropic Messages protocol with its `x-api-key` header and `anthropic-version` field. A request arrives in whichever dialect the client speaks, and the gateway translates upstream.

The repository description names several Chinese and international providers as supported, Qianfan, Spark, Hunyuan, MiniMax and DeepSeek among them, and the `samples/` directory backs that up with ready-made files: `config_qianfan.json`, `config_xinghuo.json`, `config_hunyuan.json`, `config_huoshan.json`, `config_zhipu.json`, `config_deepseek.json`, `config_siliconflow.cn.json`, plus international samples for Azure OpenAI, Gemini, Groq, Cloudflare, Ollama and the Llama family.

The `go.mod` file shows how many of these are first class. It requires the Azure OpenAI SDK, Google's Vertex AI client, the Baidu Qianfan SDK, Tencent Cloud's Hunyuan client and Volcengine's Go SDK, alongside `sashabaranov/go-openai`, gin, `modernc.org/sqlite` and zap. Native SDKs for most providers, with a generic OpenAI-compatible path for the rest.

Rolling token windows instead of round robin

The most interesting design decision is in how keys are chosen, and the README is direct about why plain round robin fails. Round robin only answers who comes next. It does not know whether that key can carry the request you are about to send. A key configured for a million tokens per minute may still handle a single very large request per minute if the prompt is enormous, so random or fixed rotation keeps landing on a key that just filled up, and you get a run of consecutive 429s.

The gateway instead estimates token cost before the request leaves, then maintains a 60 second rolling reservation window per provider, key and model combination. The documented sequence filters out disabled, tripped-circuit and clearly unavailable keys first, computes the cost of this specific request, then selects among keys that can absorb that cost, preferring the one with the most remaining headroom. Keys that cannot fit the request sort to the back by expected recovery time.

Cost estimation is protocol-aware. Chat requests account for messages, tool definitions and maximum output. Responses requests are estimated from the raw request body. Embeddings are estimated from input size.

Two details make this honest engineering rather than a scheduling demo. Reservations are not returned when upstream finishes early, because the provider has usually already counted the request against the limit. And a 429 cools only that key plus model pair, for 30 seconds by default, with an automatic retry against the next healthy key if no response bytes have been written yet. Measured against the same setup, that produces large requests spreading across keys, independent accounting per model on one key, and a single throttled key not dragging down its whole provider.

Four limit layers that all have to pass

Rate limiting is organised in four nested layers: provider, provider plus model, key, and key plus model. Within any layer you can configure QPS, QPM, RPM, TPM and concurrency, and they take effect together rather than as alternatives.

The rule is that a request only goes out when every configured limit is satisfied. The README also states the boundary of what key rotation can buy you, which is the part most gateways get wrong: switching keys escapes a key's total limit and that key's per-model limit, so you recover the pool's combined capacity. It does not escape a provider-level limit or a shared provider-plus-model limit, because those apply no matter which credential you present.

On top of that sit circuit breaking with half-open recovery at provider, key and model granularity, pass-through of vendor-specific extra parameters, model aliases, request translation, multimodal routing, proxies at both global and provider level, and streaming output of model reasoning where the upstream supports it. Load balancing strategies at the top level include random, preferred key, round robin and hash-based routing.

The `GET /api/admin/capacity` endpoint exposes the runtime state, and the console shows per key reservations, remaining capacity, cooldown status and estimated recovery time. Capacity lives in the Go process's memory only, so a restart clears local reservations and cooldowns.

Configuration console, embedded chat, SQLite store

The smallest useful configuration is four lines, which is the whole pitch of the project:

json
{
  "server_port": ":9090",
  "enable_web": true,
  "log_level": "info",
  "services": {}
}

With that, `http://localhost:9090/` opens the configuration console and `http://localhost:9090/chat` opens the chat interface, with `/admin` kept as a compatible path. The console edits base settings, providers, upstream protocol, models, key pools, proxies and access keys, and can switch to raw config source view.

The web interface is a React application embedded into the server binary with `go:embed`, so the deployable artifact stays a single file. It supports Markdown rendering, streaming output statistics and up to 50 locally stored conversations. Alongside it there is a toggleable live log view with level filtering, follow mode, pinned capacity and redaction of sensitive values.

Configuration persistence goes through SQLite rather than the JSON file alone. On first run it imports JSON or YAML, then validates, saves and applies changes atomically at runtime. The database sits next to the config file by default and can be relocated with `SIMPLE_ONE_API_DB`. The README is candid about one gap: SQLite has no static encryption, so the file should be kept at `0600` and the data directory access restricted. Secrets are redacted at the API boundary when drafts are returned, and unchanged placeholders are restored on publish.

Authentication has two modes worth knowing. With a master `api_key` set, admin endpoints need `Authorization: Bearer` with it. Without one, loopback requests can enter first-time configuration, while remote access uses a temporary bootstrap token printed in the startup log, which stops working as soon as you publish a real key.

Docker, multi-arch releases and the build scripts

Running it in a container is four lines:

sh
docker pull ghcr.io/fruitbars/simple-one-api:latest

docker run -d --name simple-one-api -p 9090:9090 \
  -v /absolute/path/config.json:/app/config.json:ro \
  -v /absolute/path/data:/app/data \
  -e SIMPLE_ONE_API_DB=/app/data/config.db \
  ghcr.io/fruitbars/simple-one-api:latest

The README recommends pinning a fixed version such as `v0.12.1` rather than tracking `latest` for production. Images cover `linux/amd64` and `linux/arm64`. The `Dockerfile` shows the details: Alpine 3.22 base, a non-root `app` user, port 9090 exposed, and a health check that hits `/healthz` every 30 seconds. `docker-compose.yml` in the repository mounts the current directory's `config.json` read-only plus a `data/` directory, with the database path pointed at the writable volume. That last point is a real constraint, since a read-only config mount means SQLite has to live somewhere writable.

Three releases are visible: v0.11.0 on 2026-08-23, then v0.12.0 and v0.12.1 both on 2026-09-07, with the last push dated 2026-09-08. Release artifacts include server archives for multiple platforms, desktop packages and `SHA256SUMS`, and the release workflow only creates the GitHub Release after every platform and container image succeeds.

Builds come in four flavours. `./quick_build.sh` produces a binary for the current platform, `./quick_build.sh linux amd64` targets one, `./build.sh --release` produces multi-platform archives under `build/`, and `./build_docker.sh vX.Y.Z` builds an image without pushing. Requirements are Go 1.25 or newer, Node.js and pnpm. Every script builds `web/` first, specifically to avoid embedding a stale frontend into the server. The frontend build itself is:

sh
cd web
pnpm install --frozen-lockfile
pnpm typecheck
pnpm test
pnpm build

Output lands in `internal/webui/dist/`. The `Makefile` cross-compiles with `CGO_ENABLED=0` for Darwin, Windows, Linux and FreeBSD on both amd64 and arm64, and the choice of a pure-Go SQLite driver is what makes that static build possible.

A Wails desktop build for local clients

There is a second distribution shape worth knowing about, because it changes how you would actually use this. The project ships a Wails v2 desktop application in `cmd/desktop/`, built with `wails dev` for development and `wails build -clean` for a release, with output in `cmd/desktop/build/bin/`.

When the desktop app launches, it starts the gateway on a loopback address, by default `127.0.0.1:9090`, and shuts the gateway and its listening port down when you quit the app. Local clients can point their base URL at `http://127.0.0.1:9090/v1`. The README names Codex and zcode as the kind of local client this suits.

That gives you a private gateway on your own machine with a graphical console, without standing up a service or a container. The tradeoff is that in-process capacity state is still in memory, so closing the app discards it, and any key material sits in a local SQLite file with no static encryption.

Usage statistics are kept deliberately light: input and output tokens, how complete usage reporting is, P50 and P95 latency, streaming time to first token, provider and model distribution, combined filtering, period comparison and CSV export. Prompt text and response bodies are not stored, which is the right default for a gateway that sees every request in an organisation.

Editorial conclusion

simple-one-api earns its place when you hold several keys against one or more providers and keep hitting 429s from naive round robin, because the rolling reservation window and the per key plus model cooldown are a real scheduler rather than a reshuffle. What it explicitly does not do is manage billing or balances: the README says the capacity shown in the console comes from a local rolling window and is not a provider invoice, and that state lives in process memory and clears on restart. For a first run, start the binary with a minimal `config.json`, watch `/healthz` if you containerise it, and read `docs/configuration-reference.md` before adding a key pool, since the four-level limit interaction is where the surprising behaviour lives.

Frequently asked questions

What is simple-one-api and which providers can it talk to?

It is a Go gateway that exposes several model providers through one set of client-compatible endpoints, speaking OpenAI Chat Completions, OpenAI Responses and Anthropic Messages. The repository description names Qianfan, Spark, Hunyuan, MiniMax and DeepSeek, and the samples directory adds Azure OpenAI, Gemini, Groq, Cloudflare, Ollama and the Llama family. go.mod carries native SDKs for the major Chinese and cloud providers.

How do I run simple-one-api with Docker?

Pull `ghcr.io/fruitbars/simple-one-api:latest` and run the container with port 9090 published, your config.json mounted read-only and a writable data directory. Point `SIMPLE_ONE_API_DB` at a path inside that writable volume so SQLite has somewhere to live. The README recommends pinning a fixed tag such as v0.12.1 instead of latest in production.

How does the key pool avoid repeated 429 errors?

Before each request it estimates token cost and maintains a 60 second rolling reservation window for each provider, key and model combination. It picks among keys that can absorb the cost, preferring the one with the most headroom, and on a 429 it cools only that key and model for 30 seconds before trying the next healthy key. Reservations are not refunded when a call ends early, because upstream has already counted the tokens.

How do I get access to the configuration console?

Set `enable_web` to true and open `http://localhost:9090/`, with `/admin` as an equivalent path. With a master `api_key` configured, admin API calls need `Authorization: Bearer` carrying it. Without one, loopback requests can do first-time setup while remote access needs the temporary bootstrap token printed in the startup log, which is invalidated once you publish a real key.

Official sources

  1. fruitbars/simple-one-api on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/fruitbars-simple-one-api.svg)](https://hysenlabs.com/projects/fruitbars-simple-one-api)