Open-source project
songquanpeng/one-api avatar
songquanpeng/one-api

One API: one OpenAI-shaped endpoint for many provider keys

LLM API 管理 & 分发系统,支持 OpenAI、Azure、Anthropic Claude、Google Gemini、DeepSeek、字节豆包、ChatGLM、文心一言、讯飞星火、通义千问、360 智脑、腾讯混元等主流模型,统一 API 适配,可用于 key 管理与二次分发。单可执行文件,提供 Docker 镜像,一键部署,开箱即用。LLM API management & key redistribution system, unifying multiple providers under a single API. Single binary, Docker-ready, with an English UI.

37,067 stars6,874 forksJavaScriptMIT

At a glance

What is it?
One API is a Go gateway that puts OpenAI, Claude, Gemini, Bedrock and other providers behind a single OpenAI-format endpoint and meters the tokens. The decision worth thinking about is what that normalization costs you: a remapped model rebuilds the request body, and the shipped defaults leave a root account on a published database port.
Who is it for?
Adopt one-api when you need to hand many users or many services a metered key across several providers, and run it through docker-compose with MySQL and Redis rather than the single container SQLite default. Skip it if your application depends on request parameters the OpenAI shape does not carry, or if you cannot operate a process that holds every provider key in one place.
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?
Activity is slowing. The repository last received commits 8 months ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

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

Editorial analysis

Three React bundles compiled into one Go binary at /one-api

The shipped runtime is one file. The Dockerfile builds the front end on `node:16`, running an npm install and a build for `/web/default`, `/web/berry` and `/web/air`, then copies the three `build` directories into a `golang:alpine` stage where `CGO_ENABLED=1` compiles `main.go` into a static binary named `one-api`. Cgo is on because the SQLite driver needs it, which is why the builder installs `gcc`, `musl-dev`, `sqlite-dev` and `build-base` first. The final image is alpine with ca-certificates and tzdata, exposes 3000, works from `/data` and starts `/one-api`.

`THEME` picks which of those three bundles the server serves and defaults to `default`. That is a build time choice, not a download: nothing in the repository fetches a theme at runtime, so a custom front end means editing `web/` and rebuilding on node 16. On the Go side `relay/`, `controller/`, `middleware/`, `model/`, `router/` and `monitor/` hold the provider adapters, the admin endpoints, the quota models and the channel routing, with go.mod pinning Go 1.20 and pulling in gin, go-redis, tiktoken-go, the AWS Bedrock runtime client and the Google API client.

The quick start container, and the three failures the README names

bash
docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api

That one command is the whole install for a first run. The host directory has to exist and be writable before the container starts, because the database and the log files both land there. If the container refuses to start, the project's answer is to add `--privileged=true`, with a pointer to issue 482 in the repository. If the image will not pull, swap `justsong/one-api` for `ghcr.io/songquanpeng/one-api`.

Put a real domain in front of it afterwards. The reference nginx block sets `proxy_read_timeout 300s` because GPT-4 needs a longer timeout than the default, and TLS comes from certbot:

bash
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx
sudo service nginx restart

The login page that answers on port 3000 wants the username `root` and the password `123456`.

Compose mounts a different data path than the one-command install

Running against MySQL is the same command with a DSN added:

bash
docker run --name one-api -d --restart always -p 3000:3000 -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api

The README says to set `SQL_DSN` when concurrency is high. That example points at `localhost:3306`, which inside the compose network is the one-api container itself, not the database; the repository's compose file writes the same DSN as `oneapi:123456@tcp(db:3306)/one-api` for exactly that reason.

Switching between the two documented setups also moves your data. The single container mounts `/home/ubuntu/data/one-api` at `/data`, while compose mounts `./data/oneapi` at `/data` and `./logs` at `/app/logs`, passing `command: --log-dir /app/logs` because the one-command deployment has no log directory at all. Take the compose file without moving the SQLite file and the server starts on an empty database with a fresh root / 123456 account and none of your channels, which reads as data loss and is only a path. go.mod carries mysql, postgres and sqlite gorm drivers, yet the deployment docs give a DSN for MySQL only.

root / 123456 and SESSION_SECRET=random_string ship in the compose file

The compose file is also where the credentials are:

yaml
environment:
  - SQL_DSN=oneapi:123456@tcp(db:3306)/one-api
  - REDIS_CONN_STRING=redis://redis
  - SESSION_SECRET=random_string
  - TZ=Asia/Shanghai

`SESSION_SECRET=random_string` is a placeholder, not a secret, and the same file sets `MYSQL_ROOT_PASSWORD: 'OneAPI@justsong'` and `MYSQL_PASSWORD: '123456'` while publishing `3306:3306` on the host. Development values in a repository people clone are normal for a demo compose file. The bill arrives for whoever deploys it unchanged.

The README carries a single warning about the first login: change the default password after signing in as root. Nothing in the documented settings forces that change, and no second factor is described. The alternatives it does describe are Cloudflare Turnstile for user verification, GitHub and Feishu OAuth, email login with a registration whitelist, and a WeChat public account flow that needs a separate `wechat-server` deployment. The healthcheck is a fair summary of the default posture, since it wgets `http://localhost:3000/api/status` and greps for a success flag.

Model mapping rebuilds the request body, and the dropped fields never arrive

Feature 15 is the line to read twice. Model mapping can redirect the model a user asks for, and the same entry warns that once it is set the request body is reconstructed instead of passed through, so fields that are not yet officially supported cannot be delivered.

The failure is quiet. A client sends a parameter the relay has no place for, the request succeeds, and the provider receives the request without that parameter. Nothing in the response says so. A channel can also carry an explicit model list (feature 10), and users and channels sit in groups with different rate multipliers (feature 9), which locks a group to a subset of models without touching the token.

The alternative is to point a client library straight at one provider and hold a single key. That path keeps every parameter the provider accepts, because nothing in the middle rebuilds the request. It also has no per user quota, no multiplier, no channel failover, and no way to hand out a key you can expire, restrict by IP or limit to two models. That accounting layer is the reason one-api exists, and it is what the tiktoken-go dependency and the quota detail page are for. Ollama appears in the channel list, so a model server on your own machine is reached through the same OpenAI shape as a hosted one.

Group multipliers, a quota ledger, and a dollar sign with no stated rate

Metering is where a user group and a channel group meet. A token carries an expiry, a quota, an allowed IP range and an allowed model list (feature 6), load balancing spreads requests across the channels in its group (feature 3), and failed calls retry on their own (feature 16). Redemption codes top accounts up in batches (feature 7), quota details are viewable (feature 11), invite rewards pay out (feature 12), and the display unit can be switched to the US dollar (feature 13).

Here is the gap. Those entries name the multiplier, the ledger and the dollar display, and none of them names what converts a token count into a balance, or where a provider's own price per model is entered. A deployment showing dollar figures is showing numbers the README never derives, and an operator setting customer balances has to reconstruct the arithmetic from the interface. The same feature list is explicit about the one place where requests are visibly reshaped, model mapping, and silent about how a price change upstream reaches a quota that was granted last week.

Multi-machine mode is three commented lines and a 60 second sync window

Multi-machine deployment is listed as supported, and what the repository ships for it is three commented lines in docker-compose.yml:

yaml
- NODE_TYPE=slave
- SYNC_FREQUENCY=60
- FRONTEND_BASE_URL=https://openai.justsong.cn

Uncommented on a second node, the first marks it as a slave, the second sets how often it reloads data from the database, and the third is the base URL it hands to browsers. That default is the maintainer's own demo instance, so a copy that uncomments the line without editing it points its users at openai.justsong.cn.

`SYNC_FREQUENCY=60` is a staleness window rather than a defect. A token created, cut off or given a new model list on the master reaches a slave only at that slave's next load, so a revocation can sit unapplied for a minute while the slave keeps serving it. The README does not say how a slave locates the master, what it does with a request that lands between loads, or what happens to an in-flight request while a reload runs.

Automatic retry fires on failure, and the billing rule is not written down

Two runtime behaviours decide what your logs look like: stream mode, which produces the typewriter effect (feature 4), and automatic retry when a call fails. Which failures count as retryable is not written down. Neither is whether a retried call reuses the same upstream key and is billed a second time by the provider, nor how a stream that broke halfway is resumed, and there is no documented rollback for a version that migrated the database.

Those answers matter more in this gateway than in most, because every request it makes is paid for by a key it holds and metered against a balance somewhere else. A duplicate charge lands on a provider account, a repeated stream lands in a client parser, and neither has a documented switch. The upgrade path has the same shape, since the update command is a watchtower container that swaps the image whenever a new one is pushed:

bash
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR

Point that at a database holding real balances and the migration is no longer yours to schedule.

Editorial conclusion

Adopt one-api when you need to hand many users or many services a metered key across several providers, and run it through docker-compose with MySQL and Redis rather than the single container SQLite default. Skip it if your application depends on request parameters the OpenAI shape does not carry, or if you cannot operate a process that holds every provider key in one place. Before the first request, change the root password from 123456, replace SESSION_SECRET=random_string, and decide whether a channel keeps model mapping off.

Frequently asked questions

What is One API used for?

It puts several model providers behind one endpoint that speaks the OpenAI API format, so a client needs one base URL and one key. Around that it adds token metering, per-group rate multipliers, key redistribution with expiry, IP and model limits, and load balancing across channels.

How do I use one-api?

The Docker path is a single container on port 3000 with a writable host directory mounted at /data for the database, then a first login as root with the password 123456. A docker-compose.yml with MySQL, Redis, a log directory and a status healthcheck sits in the repository root for a fuller deployment.

Does one-api need a separate database server?

No. The single-container command keeps everything in SQLite inside the mounted /data directory. The README says to set SQL_DSN when concurrency is high, and go.mod carries mysql, postgres and sqlite gorm drivers, but the deployment section gives a DSN example for MySQL only.

What happens to new request fields when I use model mapping?

The request body is reconstructed rather than passed through, so parameters the relay does not support yet are dropped before the call reaches the provider. The request still completes, which is what makes the loss hard to notice.

What is the default one-api administrator login?

The username is root and the password is 123456. The README carries a warning to change it after the first login, and does not describe a forced reset or a second factor. GitHub OAuth, Feishu OAuth, Cloudflare Turnstile and email login with a registration whitelist are available instead.

Can one-api serve more than one machine?

The README lists multi-machine deployment as supported. docker-compose.yml leaves NODE_TYPE=slave, SYNC_FREQUENCY=60 and FRONTEND_BASE_URL commented out for the slave node, and SYNC_FREQUENCY sets how often that node reloads data from the database.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
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/songquanpeng-one-api.svg)](https://hysenlabs.com/projects/songquanpeng-one-api)