C
CHAOTOKEN
Home Chat Playground Pricing
All major AI models · One API · Pay as you go
Reset Password
Enter your email to receive a reset link
Reset link sent. Check your email (or server console in dev mode).
{{ authError }}
or continue with
Scan with WeChat
{{ wechatQrError }}
Open WeChat and scan this QR code to log in
{{ pageTitle }} {{ user?.username }} · {{ user?.email }}
Low Balance: ¥{{ (user.balance / 1e6).toFixed(4) }} — your balance is below the alert threshold of ¥{{ (balanceThreshold / 1e6).toFixed(2) }}.
Top Up →
Balance
¥{{ (user?.balance / 1e6 || 0).toFixed(4) }}
Top Up →
Total Tokens
{{ (usageSummary.total_tokens || 0).toLocaleString() }}
Total Cost
¥{{ (usageSummary.total_cost / 1e6 || 0).toFixed(4) }}
Requests
{{ usageSummary.requests || 0 }}
Loading chart data...
API Calls by Model ({{ chartRange }}d)
Total Tokens ({{ chartRange }}d)
Balance Usage
Total Requests ({{ chartRange }}d)
Access Monitor
Active Now
{{ mActive || 0 }}
Today PV
{{ mTodayPV || 0 }}
Today UV
{{ mTodayUV || 0 }}
Today Peak
{{ mTodayPeak || 0 }}
Hist Peak
{{ mHistPeak || 0 }}
Yesterday PV
{{ mYestPV || 0 }}
Yes UV/Peak
{{ mYestUV }}/{{ mYestPeak }}
Web PV: {{ mWebPV }} dashboard / site API PV: {{ mApiPV }} model API calls Last: {{ monitorLastRefresh }}
Access Trend (24h)
Waiting for data...
Geo Distribution (Top 10)
Waiting for data...
Page Ranking
Waiting for data...
Top IPs ({{ monitorRange }}h)
#{{ i+1 }} {{ ip.ip_address }} {{ ip.count }} req {{ ip.errors }} err Abnormal
No IP data yet.
Recent Visitors
IP Location Path Time Duration UA Type Status
{{ v.ip_address }} {{ v.country || '—' }} {{ v.province || '' }} {{ v.path }} {{ fmtTime(v.created_at) }} {{ v.duration_ms }}ms {{ (v.user_agent||'').slice(0,50) }} {{ v.request_type }} {{ v.status_code }}
No visitor data yet.
Total: {{ monitorVisitorTotal }}
Security & Risk
Total Errors
{{ monitorErrors }}
{{ b.code }}
{{ b.count }}
Suspicious IPs (≥50 req or ≥10 errors)
{{ ip.ip_address }} {{ ip.count }} req {{ ip.errors }} err Blocked
No suspicious activity detected.
Models
Loading models...
No models available yet. Admin needs to enable models in Settings → Chat Models.
{{ m.id.includes('/') ? m.id.split('/')[1] : m.id }}
{{ m.id.includes('/') ? m.id.split('/')[1] : m.id }}
¥{{ (m.price.input/1e6).toFixed(2) }} in / ¥{{ (m.price.output/1e6).toFixed(2) }} out
{{ detailModel.id.includes('/') ? detailModel.id.split('/')[1] : detailModel.id }}
Model: {{ detailModel.id }}
Provider: {{ detailModel.provider || '—' }}
Pricing (per 1M tokens)
Input: ¥{{ (detailModel.price.input/1e6).toFixed(4) }} Output: ¥{{ (detailModel.price.output/1e6).toFixed(4) }}

Create API Key

Key created — copy it now, it won't be shown again.
{{ detailCreatedKey }}

API Usage

Base URL: {{ siteBaseUrl }}
Example curl:
curl {{ siteBaseUrl }}/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "{{ detailModel.id }}",
    "messages": [{"role": "user", "content": "Hello!"}]
  }'
Python:
from openai import OpenAI

client = OpenAI(
    api_key="YOUR_API_KEY",
    base_url="{{ siteBaseUrl }}"
)
response = client.chat.completions.create(
    model="{{ detailModel.id }}",
    messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
Usage Log
Total Requests
{{ (usageOverviewTotals.requests||0).toLocaleString() }}
Total Tokens
{{ (usageOverviewTotals.tokens||0).toLocaleString() }}
Total Cost
¥{{ ((usageOverviewTotals.cost||0)/1e6).toFixed(4) }}
Models Used
{{ usageOverviewByModel.length }}
Daily Usage Trend ({{ usageDays }}d)
By Model
ModelRequestsTokensCost (¥)Avg Latency
{{ m.model }} {{ m.requests.toLocaleString() }} {{ m.tokens.toLocaleString() }} {{ (m.cost/1e6).toFixed(6) }} {{ m.avg_latency_ms ? (m.avg_latency_ms/1000).toFixed(2)+'s' : '—' }}
By Customer
UserRequestsTokensCost (¥)
{{ u.username }} {{ u.email }} {{ u.requests.toLocaleString() }} {{ u.tokens.toLocaleString() }} {{ (u.cost/1e6).toFixed(6) }}
By Country
CountryRequestsTokensCost (¥)
{{ c.country }} {{ c.requests.toLocaleString() }} {{ c.tokens.toLocaleString() }} {{ (c.cost/1e6).toFixed(6) }}
Group by:
{{ usageBreakdownBy==='model'?'Model':usageBreakdownBy==='key'?'API Key':usageBreakdownBy==='user'?'Customer':usageBreakdownBy==='country'?'Country':'Day' }} RequestsTokensCost (¥) Avg Latency
{{ row.label }} {{ row.requests.toLocaleString() }} {{ row.tokens.toLocaleString() }} {{ (row.cost/1e6).toFixed(6) }} {{ row.avg_latency_ms ? (row.avg_latency_ms/1000).toFixed(2)+'s' : '—' }}
No data in this period.
Date/Time User Client Upstream IP Country Model 1st Resp (s) Flow (t/s) Out Tokens In Tokens Price (¥) Status
{{ fmtDateLog(row.created_at) }} {{ row.username || row.api_key_name }} {{ row.api_key_name }} {{ row.upstream_name || '—' }} {{ row.client_ip || '—' }} {{ row.country || '—' }} {{ row.model }} {{ row.latency_ms ? (row.latency_ms/1000).toFixed(2) : '—' }} {{ row.throughput_tps }} {{ row.completion_tokens }} {{ row.prompt_tokens }} {{ (row.cost / 1e6).toFixed(6) }} {{ row.status_code }}
No usage records yet.
Total records: {{ usageLogTotal }}

Export Usage Data

Period:
Generating...
Exports usage data grouped by the selected period. Empty date range exports all data. The CSV includes: period label, request count, total tokens, prompt tokens, completion tokens, and cost in CNY.
Upstream Counter Total
{{ (recSummary.total_counter_tokens||0).toLocaleString() }}
UsageLog Sum Total
{{ (recSummary.total_log_tokens||0).toLocaleString() }}
Delta
{{ (recSummary.total_delta||0)>=0?'+':''}}{{ (recSummary.total_delta||0).toLocaleString() }}
Drift %
{{ (recSummary.total_delta_pct||0).toFixed(4) }}%
Compares UpstreamAccount.total_tokens (perpetual counter) against SUM(UsageLog.total_tokens) (all-time). A non-zero delta means the two data sources have drifted apart. The time selector controls the recent-activity context columns only.
Upstream Provider Status Counter (all-time) Log Sum (all-time) Delta Log Reqs (all) Reqs ({{recDays}}d) First Log Last Log
{{ u.name }} {{ u.provider }} {{ u.status }} {{ u.counter_tokens.toLocaleString() }} {{ u.log_tokens.toLocaleString() }} {{ u.delta>=0?'+':'' }}{{ u.delta.toLocaleString() }} {{ u.log_requests.toLocaleString() }} {{ u.log_requests_recent.toLocaleString() }} {{ u.first_log_at ? fmtDateLog(u.first_log_at) : '—' }} {{ u.last_log_at ? fmtDateLog(u.last_log_at) : '—' }}
No reconciliation data available.
API Management
{{ adminKeysSelected.length }} / {{ allAdminKeys.length }} selected
Name Key Owner Tag Status Scope Tokens Cost (¥) Created Actions
{{ k.name }} {{ k.key_masked }} {{ k.username }}{{ k.email }} {{ k.tag }} {{ k.is_active ? 'Active' : 'Disabled' }} {{ k.scope || 'All' }} {{ k.total_tokens.toLocaleString() }} {{ (k.total_cost/1e6).toFixed(4) }} {{ fmtDate(k.created_at) }}
No API keys found matching filters.
{{ k.name }} {{ k.tag }}
{{ k.key.slice(0, 2) }}••••••••••••••••••••••••••••••••{{ k.key.slice(-4) }}
{{ k.is_active ? 'Active' : 'Disabled' }}
ID: {{ k.id.slice(0,8) }}... Scope: {{ k.scope || 'All models' }} RPM: {{ k.rpm_limit }} Tokens: {{ (k.total_tokens || 0).toLocaleString() }} Cost: ¥{{ (k.total_cost / 1e6 || 0).toFixed(4) }} Created: {{ fmtDate(k.created_at) }}
Monthly budget: ¥{{ (k.monthly_budget / 1e6).toFixed(2) }} Used: ¥{{ ((k.monthly_spent || 0) / 1e6).toFixed(4) }} ({{ budgetPct(k) }}%)
Models: {{ k.allowed_models }} IPs: {{ k.allowed_ips }}
No API keys yet. Click "+ New Key" to create one.
No API keys in the system yet.
{{ keyReport.key_name }}
Total Requests
{{ keyReport.totals.requests.toLocaleString() }}
Total Tokens
{{ keyReport.totals.tokens.toLocaleString() }}
Total Cost
¥{{ (keyReport.totals.cost / 1e6).toFixed(4) }}
Lifetime Cost
¥{{ (keyReport.total_cost / 1e6).toFixed(4) }}
Daily Usage ({{ chartRange }}d)
By Model

Model Breakdown

{{ m.model.split('/').pop() }} {{ m.requests }} req · ¥{{ (m.cost / 1e6).toFixed(4) }}
No model data.

Recent Usage

Time Model Tokens Cost Latency Status
{{ l.day }} {{ l.day }} {{ l.tokens.toLocaleString() }} ¥{{ (l.cost / 1e6).toFixed(6) }} OK
No usage data in this period.
Loading report...
Billing Records
Type Note Amount (¥) Balance After (¥) Time
{{ t.type === 'usage' ? 'Usage' : t.type === 'admin_topup' ? 'Top Up' : t.type }} {{ t.note }} {{ t.amount > 0 ? '+' : '' }}{{ (t.amount / 1e6).toFixed(6) }} {{ (t.balance_after / 1e6).toFixed(4) }} {{ fmtDate(t.created_at) }}
No billing records.
API Reference
Try Playground

Quick Start

  1. Get an API key from API Management
  2. Set the base URL to {{ apiBase }}/v1
  3. Make your first request using any OpenAI-compatible SDK
  4. Monitor usage in the Usage Log

Authentication

All API requests require an API key passed via the Authorization header:
Authorization: Bearer sk-your-api-key
API keys are managed in API Management. Each key supports IP whitelisting, model access control, RPM limits, monthly budgets, and expiry dates.

Base URL

{{ apiBase }}/v1
All endpoints are prefixed with this URL. The API is fully compatible with the OpenAI API format.

Available Models

List all models available to your account. Additional models may appear when upstream providers are added.
{{ m }}
No models available.
Endpoint: GET /v1/models
curl {{ apiBase }}/v1/models \
  -H "Authorization: Bearer sk-your-api-key"

POST /v1/chat/completions

Create a chat completion. Compatible with the OpenAI Chat Completions API format. Supports streaming via SSE (Server-Sent Events).
Request Body
ParameterTypeDefaultDescription
modelstringRequired. Model ID to use
messagesarrayRequired. Array of {role, content} objects
streambooleanfalseEnable SSE streaming
temperaturenumber1.0Sampling temperature (0–2)
max_tokensinteger4096Maximum tokens in response
top_pnumber1.0Nucleus sampling parameter
cURL
curl {{ apiBase }}/v1/chat/completions \
  -H "Authorization: Bearer sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-6",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "Hello!"}
    ],
    "stream": false
  }'
Python (OpenAI SDK)
from openai import OpenAI

client = OpenAI(
    api_key="sk-your-api-key",
    base_url="{{ apiBase }}/v1"
)

response = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello!"}
    ]
)
print(response.choices[0].message.content)
Node.js (OpenAI SDK)
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: "sk-your-api-key",
  baseURL: "{{ apiBase }}/v1",
});

const response = await client.chat.completions.create({
  model: "claude-sonnet-4-6",
  messages: [
    { role: "system", content: "You are a helpful assistant." },
    { role: "user", content: "Hello!" },
  ],
});
console.log(response.choices[0].message.content);
Streaming (Python)
from openai import OpenAI

client = OpenAI(
    api_key="sk-your-api-key",
    base_url="{{ apiBase }}/v1"
)

stream = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[{"role": "user", "content": "Hello!"}],
    stream=True
)
for chunk in stream:
    delta = chunk.choices[0].delta.content or ""
    print(delta, end="", flush=True)

Account & Usage APIs

These endpoints require your JWT session token (from the dashboard login), not your API key.
GET /api/user/balance
curl {{ apiBase }}/api/user/balance \
  -H "Authorization: Bearer your-jwt-token"
GET /api/user/usage-logs?limit=50&offset=0
curl "{{ apiBase }}/api/user/usage-logs?limit=10" \
  -H "Authorization: Bearer your-jwt-token"
GET /api/user/transactions?limit=20
curl "{{ apiBase }}/api/user/transactions?limit=20" \
  -H "Authorization: Bearer your-jwt-token"

Error Codes

CodeMeaningDescription
401UnauthorizedMissing or invalid API key
402Insufficient BalanceAccount balance is too low. Top up in Billing
403ForbiddenIP not whitelisted, key expired, or model not allowed
429Rate LimitedRPM limit exceeded. Wait and retry
502Upstream ErrorUpstream provider returned an error. Try again later

Rate Limits

Each API key has an RPM (Requests Per Minute) limit configurable in API Management. Default is 60 RPM.

When exceeded, the API returns 429 Too Many Requests. To increase your limit, contact support.

Pricing

CHAOTOKEN uses a pre-paid credit system. You top up your account and usage is deducted per-token at model-specific rates.

View full pricing in Billing →

Interactive Playground

Test models interactively in our browser-based API Playground. No coding required.
Open Playground →
Security

Change Password

{{ pwdSuccess }}
{{ pwdError }}

Balance Alert

Alert me when balance is below ¥
Current: ¥{{ (balanceThreshold / 1e6).toFixed(2) }} · {{ user?.balance <= balanceThreshold ? '⚠ Below threshold' : '✓ Above threshold' }} Threshold disabled
{{ ticketDetail ? 'Ticket #' + ticketDetail.ticket_no : 'Help Desk' }}
Loading...
No tickets found.
{{ t.ticket_no }} {{ t.status }} {{ t.priority }}
{{ t.title }}
{{ t.category }} {{ fmtDate(t.created_at) }}
{{ ticketDetail.ticket_no }} {{ ticketDetail.status }} {{ ticketDetail.priority }} {{ ticketDetail.category }}

{{ ticketDetail.title }}

{{ ticketDetail.description }}
Contact: {{ ticketDetail.contact }}
Close reason: {{ ticketDetail.closed_reason }}
No messages yet.
{{ m.sender_name }} · {{ m.sender_role }} [Internal note]
{{ m.content }}
{{ fmtDate(m.created_at) }}
Ticket closed.
Organization
Organization
{{ orgInfo?.tenant?.name || '—' }}
Your Role
{{ orgInfo?.tenant_role || '—' }}
Group
{{ orgInfo.group.name }}
Your Credits
{{ orgInfo.balance.available.toLocaleString() }}
Used
{{ orgInfo.balance.used.toLocaleString() }}
Locked
{{ orgInfo.balance.locked.toLocaleString() }}
Total
{{ orgInfo.balance.total.toLocaleString() }}
No credit balance allocated yet.
User must already have an account.
Email Username Role Group Keys Credits Status Actions
{{ m.email }} {{ m.username }} {{ m.keys_count || 0 }} {{ (m.credits_available || 0).toLocaleString() }} {{ m.is_active ? 'active' : 'disabled' }}
{{ g.name }} {{ g.member_count }} members
Credits: {{ (g.total_credits - g.used_credits).toLocaleString() }}
No groups yet.

Tenant Balance

TypeTotalUsedLockedAvailable
{{ b.credit_type }} {{ b.total.toLocaleString() }} {{ b.used.toLocaleString() }} {{ b.locked.toLocaleString() }} {{ b.available.toLocaleString() }}

Group Balances

GroupTypeTotalUsedLockedAvailable
{{ b.group_name }} {{ b.credit_type }} {{ b.total.toLocaleString() }} {{ b.used.toLocaleString() }} {{ b.locked.toLocaleString() }} {{ b.available.toLocaleString() }}
No group balances
Admin
{{ adminStatLabels[key] || key }}
{{ key.includes('cost') ? '¥'+(val/1e6).toFixed(2) : val?.toLocaleString() }}
Name Status Members Credits Used Note Created Actions
{{ t.name }} {{ t.status }} {{ t.member_count }} {{ (t.total_credits - t.used_credits).toLocaleString() }} {{ t.used_credits.toLocaleString() }} {{ t.note || '—' }} {{ fmtDate(t.created_at) }}
No enterprises yet.

{{ adminTenantDetail.name }}

{{ adminTenantDetail.status }} {{ adminTenantDetail.members?.length || 0 }} members {{ adminTenantDetail.groups?.length || 0 }} groups

Members

User must already be registered.
EmailUsernameRoleGroupKeysCreditsActiveJoined
{{ m.email }}{{ m.username }} {{ m.keys_count || 0 }} {{ (m.credits_available || 0).toLocaleString() }} {{ m.is_active ? 'yes' : 'no' }} {{ fmtDate(m.created_at) }}

Search & Add Users

{{ u.username }} {{ u.email }} (already in another enterprise)
No users found.

Bulk Import Users

Paste one email per line. Accounts will be auto-created with random passwords.
✅ Imported {{ adminBulkImportResults.length }} users ({{ adminBulkImportErrors.length }} skipped)
Login URL: {{ adminBulkImportSiteUrl }}
EmailUsernamePassword
{{ r.email }} {{ r.username }} {{ r.password }}
{{ e.email }}: {{ e.error }}

Groups

NameStatusMembers
{{ g.name }}{{ g.status }}{{ g.member_count }}

Balances

TypeTotalUsedLockedAvailable
{{ b.credit_type }} {{ b.total_credits.toLocaleString() }} {{ b.used_credits.toLocaleString() }} {{ b.locked_credits.toLocaleString() }} {{ b.available.toLocaleString() }}

Allowed Models

Leave empty = allow all models.
Username Email Registered Balance (¥) Role Status Actions
{{ u.username }} {{ u.email }} {{ fmtDate(u.created_at) }} {{ (u.balance / 1e6).toFixed(4) }} {{ u.role }} {{ u.is_active ? 'Active' : 'Disabled' }}
{{ adminUserDetail.username }}
Email: {{ adminUserDetail.email }}
Role: {{ adminUserDetail.role }}
Status: {{ adminUserDetail.is_active ? 'Active' : 'Disabled' }}
Balance: ¥{{ (adminUserDetail.balance / 1e6).toFixed(4) }}
Created: {{ fmtDate(adminUserDetail.created_at) }}
Internal Note
Allowed Models (comma-separated, empty = all)
API Keys ({{ adminUserDetailKeys.length }})
No API keys.
{{ k.name }}
{{ k.key_masked }}
{{ k.is_active ? 'Active' : 'Disabled' }} Expires: {{ fmtDate(k.expires_at) }} {{ (k.total_tokens || 0).toLocaleString() }} tok
Total Tokens
{{ (adminUserDetailUsage.total_tokens || 0).toLocaleString() }}
Total Cost
¥{{ (adminUserDetailUsage.total_cost / 1e6 || 0).toFixed(4) }}
Requests
{{ adminUserDetailUsage.total_requests || 0 }}
● {{ healthSummary.healthy }} / {{ healthSummary.total }}
Model Management
Loading model pools...
No credential models found. Configure upstream accounts first.
{{ pool.canonical_name }} {{ pool.total_upstreams }} key{{ pool.total_upstreams !== 1 ? 's' : '' }}
{{ bp.base_url }} {{ bp.enabled_count }}/{{ bp.upstream_count }} active
{{ u.name }} {{ u.upstream_model_name }} p= w= {{ u.error_count > 0 ? '⚠ err:'+u.error_count : '✓' }}
↓ Primary exhausted → fallback to next base_url pool
Pricing Configuration
Unsaved drafts
{{ modelsMissingPrices.length }} model(s) from Model Management have no price configured.
All prices in USD / 1M tokens
Last published: {{ fmtDate(lastPublishedAt) }}
Model Prov Source Link $ Base In
(edit)
$ Base Out
(edit)
Cost %
(off)
$ Cost (In/Out) Sell %
(off)
$ Selling (In/Out) Status Show
{{ p.model }} L D {{ p.provider || '—' }} {{ p.base_url || '(global)' }} ${{ ((p.cost_input || 0)).toFixed(2) }} / ${{ ((p.cost_output || 0)).toFixed(2) }} ${{ ((p.selling_input || 0)).toFixed(2) }} / ${{ ((p.selling_output || 0)).toFixed(2) }} Live Draft Default
Base = official model price (USD/1M). Discount % is "off" style: 75 = 75% off → cost = base × 25%.
Cost (internal, admin only). Selling (customer-facing). Only selling price affects billing.
Show toggle controls whether model appears on the public subscription page (#models).
• Changes are drafts until "Publish All" is clicked. Published prices become live for billing, home page, and tenant views.
Publish History
Loading...
No logs yet.
TimeActionDetail
{{ fmtDate(l.created_at) }} {{ l.action }} {{ l.detail }}
Chat Model Configuration
Select which models appear in the Chat page model selector.
Loading available models...
No upstream models found
Configure upstream accounts first, then return here to pick which models appear in Chat.
{{ chatSelectedModels.length }} / {{ chatAllModels.length }} selected
{{ group }}
Help Desk — Admin
{{ k }}
{{ v }}
Loading...
No tickets.
{{ t.ticket_no }} {{ t.status }} {{ t.priority }}
{{ t.title }}
{{ t.user_email }} {{ t.category }} {{ fmtDate(t.created_at) }}
{{ toast }}