| name | oneapi-admin |
| description | Operates the one-api admin REST API to manage channels (upstream LLM providers), users, API tokens, groups, and billing ratios. TRIGGER when: the user asks to add/list/test/disable a channel in one-api; create/disable/promote/quota-adjust a user; investigate another user's tokens or quota usage; change group ratios, model ratios, or per-channel pricing; rotate a leaked channel key; inspect admin-only logs. Also include terms: one-api, oneapi, 渠道, 用户, 令牌, 分组, 倍率, 模型价格, quota, upstream, provider, Azure, OpenAI channel, balance refresh. SKIP: tasks that modify application source code, non-admin user-scoped token flows, frontend work, or any request that does not name one-api explicitly and has no admin API verb. |
| license | See repository LICENSE |
| compatibility | Requires: bash, curl, jq. Network access to the target one-api instance. Env vars ONEAPI_BASE_URL and ONEAPI_ADMIN_TOKEN must be set before any call. |
one-api admin
Drive the one-api admin HTTP API from the command line. Every admin operation in this repo is reachable via /api/... — this skill routes you to the right endpoint, gives you copy-paste curl + jq blocks, and flags money-moving ops so they don't ship by accident.
Before you start — one-time setup
- Install tooling —
curl and jq must be on PATH. If missing, install with sudo apt-get install -y curl jq (Debian/Ubuntu) or the platform equivalent.
- Mint an admin access token via the UI:
- Log in as an admin (role ≥ 10) or root (role = 100).
- Go to Profile → "Generate System Access Token" (calls
GET /api/user/token).
- The returned UUID is your
ONEAPI_ADMIN_TOKEN. It lives in users.access_token and stays valid until regenerated.
- Export env vars in the shell you'll work from:
export ONEAPI_BASE_URL="https://your-oneapi.example.com"
export ONEAPI_ADMIN_TOKEN="<the-uuid-from-step-2>"
- Verify you can reach the API and the token belongs to an admin:
curl -fsS -H "Authorization: $ONEAPI_ADMIN_TOKEN" \
"$ONEAPI_BASE_URL/api/channel/?p=0&size=1" | jq '.success, .total'
Expect true and an integer. A 401 means the token is wrong or the user was demoted.
All endpoints return the envelope {success: bool, message: string, data: ..., total?: int64} — always branch on .success, not HTTP status (the server sometimes returns 200 with success: false). See references/errors.md.
Quick start — list enabled channels
curl -fsS -H "Authorization: $ONEAPI_ADMIN_TOKEN" \
"$ONEAPI_BASE_URL/api/channel/?p=0&size=20" \
| jq '.data[] | {uuid, name, type, status, group, priority}'
The bundled scripts/oneapi wraps this: scripts/oneapi channel list.
Identifier contract — UUIDs only
The management API is UUID-only in both directions. Responses expose uuid, user_uuid, channel_uuid, token_uuid, inviter_uuid (strings) and no integer id / user_id / channel / token_id fields (dto/responses.go). Requests must use the same strings:
| Where | What to send |
|---|
Path params /api/channel/:id, /api/user/:id, /api/token/:id, /api/admin/tokens/:id, /api/redemption/:id, /api/trace/log/:log_id | the resource's uuid string |
Update bodies PUT /api/channel/, PUT /api/user/, PUT /api/token/, PUT /api/redemption/ | a uuid field; a missing/empty uuid fails with resource uuid is required (controller/id_refs.go preferUUIDRef) |
POST /api/topup | user_uuid (not user_id) |
GET /api/admin/tokens/?user_id= | the owning user's uuid; omit the param for all users (do not send 0) |
GET /api/log/?channel= | the channel's uuid |
GET /api/user/dashboard?user_id= | a user uuid or the literal all (root only) |
POST /api/user/manage | {username, action} — username-keyed, no id at all |
/api/*/search?keyword= | free text, or a pasted uuid for an exact match (model/search_keyword.go) |
In jq, build bodies with --arg uuid "$UUID" and '{uuid: $uuid, ...}' — never --argjson, the value is a string. Sort keys still accept id, but the value is invisible to clients, so sort by created_at (or created_time where the resource allows it) when you need a stable order.
Which resource am I working with?
Read the matching reference file in full before composing calls — do not skim. Each contains endpoint tables, request/response schemas, pitfalls, and runnable examples.
Common workflows
Step-by-step guides with validation gates. Follow the checkboxes in order — they exist because skipping a step has bitten someone before.
Critical rules (read once, remember always)
- Auth header format is
Authorization: <uuid>, NOT Authorization: Bearer <uuid>. The server strips a Bearer prefix if present (model/user.go:410), so Bearer-form also works — but stay consistent with the raw UUID form to match other examples in this skill.
- Role matters. Admin routes (
role >= 10) cover everything in this skill except /api/option/ (model/group ratios, system toggles) which is Root-only (role == 100). If a ratio PUT fails with 403, your token is admin-not-root — ask a root user to run it.
- Quota is a 64-bit integer in internal "quota units", NOT dollars. 1 USD ≈
QuotaPerUnit units (default 500000). Always fetch QuotaPerUnit from /api/option/ before converting, and present both unit and USD forms when talking to humans. A carelessly typed extra zero grants 10× intended credit.
- Channel
status values: 1=enabled, 2=manually disabled, 3=automatically disabled (by health check / balance check). Setting status=2 by PUT is the admin kill-switch. Do not overwrite 3 → 1 without understanding why it was auto-disabled (check response_time, balance, or run GET /api/channel/test/:id).
- Never delete a channel/user/token as a first step. Disable first (
status=2 for channels; POST /api/user/manage with action=disable; or PUT /api/token/?status_only=1). Delete only after downstream usage has stopped — logs and billing rows reference channel_uuid / user_uuid.
- Ratio changes are system-wide and take effect immediately. The server reloads option JSON on the next request. Validate your JSON with
jq -e before PUT. Never hand-edit ModelRatio JSON in-place in the UI — copy-modify-paste so you have a rollback copy.
- Pagination is zero-indexed (
p=0 is the first page). size is capped at MaxItemsPerPage (from config). Always read from the response envelope and loop . See .
Scripts (executable, don't read first)
Prefer these over hand-rolling curl — they handle pagination, auth, and response unwrapping, and emit structured stderr on failure.
-
scripts/oneapi — unified CLI. Subcommands: channel, user, token, group, option, log. Examples:
scripts/oneapi channel list --enabled-only
scripts/oneapi channel test "$CHANNEL_UUID"
scripts/oneapi user get --keyword alice
scripts/oneapi user grant-quota "$USER_UUID" 5000000
scripts/oneapi option get GroupRatio | jq .
Every positional identifier (channel get/test/disable/..., user get --uuid, user grant-quota/set-group/set-quota, token get/admin-get/disable, token admin-list --user-id, log list --channel) is the resource's uuid string as printed by the corresponding list/search subcommand. Subcommand names and argument order are unchanged from the integer-id era; only the value type changed. Run scripts/oneapi --help for the full command tree. If a subcommand you need is missing, use raw curl with the reference file — don't modify the script unless the user asks.
-
scripts/lib.sh — sourced helpers for custom one-off scripts. Provides oneapi_get, oneapi_post, oneapi_put, oneapi_delete, oneapi_paginate, and oneapi_confirm.
Common pitfalls
key field in create-channel is case-sensitive and may contain commas to encode multi-key round-robin (key1,key2,key3). Encode the whole comma-string as a JSON string, no array.
models on channels and tokens is a comma-separated string, not a JSON array. "gpt-4o,gpt-4o-mini", no spaces after commas.
model_configs on channels is a JSON-encoded string (double-escaped inside the outer JSON). Build it with jq -c and pass as --arg — do not hand-quote.
- Create endpoints do not return the new resource —
POST /api/user/ and POST /api/channel/ answer {success, message} only. To get the new uuid, call GET /api/user/search?keyword=<username> / GET /api/channel/search?keyword=<name> right after.
/api/user/manage takes a username, not a uuid. The adjacent endpoints all take the user's uuid. Read the users reference carefully.
- There is no integer id anywhere.
jq '.data[] | .id' yields null; --argjson id 42 is rejected with resource uuid is required. Use uuid / --arg uuid.
- Admin tokens cannot modify other users' API tokens. Read-only admin token visibility is the supported flow (references/tokens.md). To cut off a user's token, disable the user via
/api/user/manage action=disable.
- Timestamps:
created_at/updated_at are milliseconds; log filter params start_timestamp/end_timestamp are seconds. Don't mix them.
- Channel
test/:id returns 200 with success: false when the upstream responds but returns a billing/auth error. Always print .message and the field (roundtrip ms).
Known limitations
- Admin write access to other users' tokens is intentionally absent. The new
GET /api/admin/tokens/... endpoints added alongside this skill are read-only. To stop a user's token, disable the owning user.
- Groups are derived from the
GroupRatio option JSON. There is no POST /api/group/ — adding a group means adding a key under GroupRatio via PUT /api/option/ (root-only). See references/groups-and-ratios.md.
- Global
ModelRatio / CompletionRatio via /api/option/ is the legacy format. New pricing lives on each channel as model_configs. Treat option-level ratios as fallback; prefer per-channel pricing for new work.
- No bulk endpoints for user/token. To update N users, loop; respect the server's rate-limit middleware (default 60 req/min/ip — check
/api/option/).
If you need to extend the admin API
Only add server-side endpoints when a gap blocks a genuine admin workflow. When you do:
- Guard with
middleware.AdminAuth() (role ≥ 10) or middleware.RootAuth() (role = 100) — pick the tighter one that still lets the job get done.
- Add the route in router/api.go next to related resources.
- Place the handler in the matching
controller/*.go. Wrap errors with github.com/Laisky/errors/v2.
- Keep new endpoints read-only unless the user explicitly asks for a mutation.
- Update references/ to document the new endpoint the same session you add it.
Evaluation quick-prompts
Use these to spot-check the skill is working before real ops:
- "List disabled channels and summarize why each was disabled (manual vs auto)."
- "User
alice says her quota is gone; show her current quota, recent logs, and token list."
- "Add a new Azure OpenAI channel for gpt-4o, test it, and attach it to the
vip group."
- "Lower the
vip group ratio from 0.5 to 0.4; show the before/after JSON." (root-only)
- "Rotate the key on the channel named
openai-prod with zero downtime."