Why I Ditched My Custom API Proxy for New-API
I spent two weeks building a custom API proxy service in Python. It handled authentication, rate limiting, usage tracking, and multi-model routing. It worked, but it was fragile — every edge case required more code, and I was spending more time maintaining the proxy than building my actual product. Then I found New-API, deployed it in 10 minutes, and deleted my custom code. Here's what I learned about deploying New-API correctly, including four pitfalls that will trip you up if you don't know about them.
What is New-API?
New-API is an open-source API management and distribution system designed for LLM APIs. Think of it as a self-hosted version of OpenRouter — it sits between your customers and your LLM provider (OpenAI, DeepSeek, Anthropic, etc.), handling:
- API key management (create, revoke, set quotas per key)
- Usage tracking and billing (per-token, per-request)
- Model routing (send requests to different providers)
- Rate limiting and access control
- A web-based admin dashboard
It's the standard solution for anyone running an API reselling or multi-tenant LLM service. One Docker container, one SQLite database, and you have a production-ready API gateway.
Quick Deployment
docker run -d --name new-api \
-p 3000:3000 \
-v /path/to/data:/data \
--restart always \
calciumion/new-api:latest
Access the admin panel at http://your-server:3000. Default credentials are root / 123456 (change immediately).
That's the easy part. Now for the four pitfalls that cost me real time.
Pitfall 1: The First User Is Not an Admin
When you first access New-API, it asks you to register. The first registered user should become the admin automatically, but in practice, this doesn't always happen — especially if you access the registration page through a reverse proxy or with certain configurations.
Symptom: You register, log in, and see a normal user dashboard instead of the admin panel. No "Channels," "Tokens," or "Users" management options.
Fix: Directly modify the SQLite database:
# Access the SQLite database inside the container
docker exec -it new-api sqlite3 /data/new-api.db
# Check your user's role
SELECT id, username, role FROM users;
# Set role to 100 (super admin)
UPDATE users SET role = 100 WHERE id = 1;
# Verify
SELECT id, username, role FROM users;
# Should show role = 100 for your admin user
.quit
Then restart the container:
docker restart new-api
After restarting, log in again and you should see the full admin panel.
Pitfall 2: Channels and Tokens Must Be Created via Database (Sometimes)
The web UI for creating channels (LLM provider connections) and tokens (customer API keys) usually works. But if you're setting up programmatically or the UI is being stubborn, you can insert directly into SQLite:
docker exec -it new-api sqlite3 /data/new-api.db
-- Create a channel (DeepSeek example)
INSERT INTO channels (type, key, name, base_url, models, status, created_time)
VALUES (
1, -- type: 1 = OpenAI-compatible
'sk-your-deepseek-api-key', -- your provider API key
'DeepSeek', -- channel name
'https://api.deepseek.com', -- provider base URL
'deepseek-v4-flash', -- allowed models (comma-separated)
1, -- status: 1 = enabled
strftime('%s','now')
);
-- Create an ability (maps model to channel)
INSERT INTO abilities (channel_id, model, enabled)
VALUES (1, 'deepseek-v4-flash', 1);
-- Create a user token (customer API key)
INSERT INTO tokens (user_id, key, name, status, used_quota, remain_quota, created_time)
VALUES (
1, -- user_id
'customer-api-key-here', -- the API key customers will use (NO sk- prefix!)
'Customer 1', -- token name
1, -- status: 1 = enabled
0, -- used_quota
100000000, -- remain_quota (in tokens)
strftime('%s','now')
);
.quit
Then restart:
docker restart new-api
Pitfall 3: Token Keys Don't Use the sk- Prefix
This one is counterintuitive. When you create a token for a customer, New-API stores it without the sk- prefix. But when the customer uses the API, they include the sk- prefix in their Authorization header.
# In database: key = 'abc123def456'
# Customer uses: Authorization: Bearer sk-abc123def456
If you store the key with sk- in the database, authentication will fail because New-API will look for a key that starts with sk-sk-. This is a common source of "invalid API key" errors.
Additionally, make sure the token's group field is set to default:
UPDATE tokens SET "group" = 'default' WHERE id = 1;
Pitfall 4: Only Enable the Models You Can Afford
New-API lets you specify which models each channel supports. If you enable a model that uses an expensive provider tier, you can lose money fast. Here's what happened to me:
- I set up DeepSeek as my provider
- DeepSeek has two tiers: Flash (cheap) and Pro (expensive)
- I initially enabled both models in the channel
- A customer used Pro for a long conversation, consuming 50M tokens at Pro prices
- My cost for that one customer was higher than their monthly subscription fee
Fix: Only enable the cheapest model that meets your quality requirements:
-- Disable the expensive model
UPDATE abilities SET enabled = 0 WHERE model = 'deepseek-v4-pro';
-- Or remove it entirely from the channel
UPDATE channels SET models = 'deepseek-v4-flash' WHERE id = 1;
Then restart. Now customers can only use Flash, and your margins are safe.
Complete Working Configuration
Here's my production setup for reference:
# docker-compose.yml
version: '3'
services:
new-api:
image: calciumion/new-api:latest
container_name: new-api
ports:
- "3000:3000"
volumes:
- ./data:/data
restart: always
environment:
- TZ=Asia/Shanghai
Customer Usage
# Customer makes API calls like this:
curl https://your-server:3000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-customer1-key-abc" \
-d '{
"model": "deepseek-v4-flash",
"messages": [{"role": "user", "content": "Hello"}]
}'
Monitoring and Maintenance
Check Usage
docker exec -it new-api sqlite3 /data/new-api.db
-- Usage by token
SELECT t.name, t.used_quota, t.remain_quota,
ROUND(t.used_quota * 100.0 / (t.used_quota + t.remain_quota), 1) as pct_used
FROM tokens t
ORDER BY t.used_quota DESC;
Backup
# The entire state is in one SQLite file
cp /path/to/data/new-api.db /path/to/backup/new-api-$(date +%Y%m%d).db
# That's it. One file = complete backup.
Conclusion
New-API is the fastest way to get a production-ready LLM API gateway. The four pitfalls — admin role, database channel creation, token key format, and model cost control — are the difference between a smooth deployment and hours of debugging. Set it up right the first time, and you can focus on your product instead of maintaining proxy infrastructure.