API Reference
All eleven endpoints an Admin Key can call, with parameters, responses and examples.
The base URL is https://api.calgine.com.cn and every request carries Authorization: Bearer sk-admin-.... Apart from the two export endpoints, responses use the {"success": bool, "message": string, "data": ...} envelope.
Account
Get account profile
GET /api/user/self · scope account:read
Returns the full account profile. balance is the current balance.
curl https://api.calgine.com.cn/api/user/self \
-H "Authorization: Bearer $ADMIN_KEY"{
"success": true,
"data": {
"username": "alice",
"display_name": "Alice",
"balance": "128.5",
"level": "Tier 1",
"language": "en",
"created_at": 1767225600
}
}The response has many more fields; only the common ones are shown. Amounts are decimal strings — parse them as strings to avoid floating-point error. Passwords, session credentials and identity documents never appear in the response. The profile does not include lifetime spend or request counts. To get those for a time range, use "Call statistics" and "List log details" below.
Call logs
How to query cheaply. The log table is partitioned by creation time, and a time range is what lets the database prune partitions instead of scanning the whole table. So the recommended pattern is a narrow rolling window: pull the last few minutes to an hour on a fixed interval. Pulling everything without a time range is the most expensive shape — there is nothing to prune. The endpoints will not reject it, but every query has a time limit (thirty seconds for the log list) and simply fails when it runs out. For one-off reconciliation, fetch day by day or week by week rather than a whole year at once.
Timestamps can be seconds or milliseconds. The endpoints in this group detect the unit of start_timestamp and end_timestamp automatically: values below 100000000000 are Unix seconds, anything larger is milliseconds. Both ends are inclusive and filter on the request's creation time. Timestamps in responses are always milliseconds.
These endpoints only see billed requests: a request that failed before it was charged does not appear here.
List log details
GET /api/log/self · scope logs:read
Returns a page of call log details, newest first. Same data as the export endpoint below, in JSON.
| Parameter | Type | Description |
|---|---|---|
p | int | Page number, starting at 1 |
page_size | int | Items per page, default 10, up to 1000 |
start_timestamp | int | Start time, seconds or milliseconds |
end_timestamp | int | End time, seconds or milliseconds |
model_name | string | Filter by model name, optional |
token_id | int | Filter by key ID, optional, exact match; IDs come from "List keys" |
token_name | string | Filter by key name, optional, substring match: every key whose name contains the text counts |
vendor_id | int | Filter by provider, optional |
status | string | Filter by status, optional: SUCCESS, FAILURE, or UNFINISHED (async tasks still running) |
skip_count | bool | Pass 1 to skip counting the total (total comes back as -1); faster when paging through data |
curl -G https://api.calgine.com.cn/api/log/self \
-H "Authorization: Bearer $ADMIN_KEY" \
-d p=1 -d page_size=20 \
-d start_timestamp=1767225600 \
-d end_timestamp=1767311999{
"success": true,
"data": {
"items": [
{
"id": 1,
"created_at": 1767225612345,
"completed_at": 1767225614012,
"model_name": "gpt-4o",
"token_id": 42,
"token_name": "production",
"is_stream": true,
"status": "SUCCESS",
"request_id": "a3241aced92f97ac",
"quota": "0.0123",
"usage": {
"prompt_tokens": 1200,
"completion_tokens": 340,
"total_tokens": 1540,
"text_input_tokens": 1200,
"text_output_tokens": 340
}
}
],
"total": 1,
"page": 1,
"page_size": 20,
"has_more": false
}
}Each item has more fields than shown; the common ones are:
| Field | Description |
|---|---|
id | Position in this result (1, 2, 3, …), not a stable log ID — it changes with the page and the filters. Use request_id to deduplicate |
request_id | Unique ID of the request |
created_at, completed_at | When the request started and finished, in milliseconds; completed_at is 0 for async tasks still running |
quota | Amount charged for the request, as a decimal string |
usage | Usage breakdown. prompt_tokens and completion_tokens follow OpenAI's convention (input may include cache hits); text_input_tokens, text_output_tokens, text_cache_read_tokens and so on are the billing breakdown. Requests billed per call may not have it |
has_more | Whether more data follows; use it to decide whether to fetch another page |
total | Number of matching items, may be -1 — see below |
total may come back as -1. The count is a separate query limited to three seconds; over a wide time range it may not finish, in which case total is -1 for "unknown" while items is still complete. Page with has_more, not with total. When you only need the data, pass skip_count=1 to skip that count on every page; when you need the total, use "Count log entries" below.
Count log entries
GET /api/log/self/count · scope logs:read
Counts the log entries that match your filters — in other words, the number of requests over a period. It takes the same filters as the log list and may count for up to ten seconds, so it finishes in cases where the three-second count inside the list gives up.
| Parameter | Type | Description |
|---|---|---|
start_timestamp | int | Start time, seconds or milliseconds |
end_timestamp | int | End time, seconds or milliseconds |
model_name | string | Filter by model name, optional |
token_id | int | Filter by key ID, optional, exact match |
token_name | string | Filter by key name, optional, substring match |
vendor_id | int | Filter by provider, optional |
status | string | Filter by status, optional, same values as the log list |
curl -G https://api.calgine.com.cn/api/log/self/count \
-H "Authorization: Bearer $ADMIN_KEY" \
-d token_id=42 \
-d start_timestamp=1767225600 \
-d end_timestamp=1767311999{ "success": true, "data": { "total": 1530 } }A total of -1 means the count did not finish within ten seconds. Split the range into pieces that do not overlap (both ends are inclusive, so adjacent pieces must not share an instant), count each and add them up. For a table of counts per key or per hour, the requests column of "Export usage analytics" below is easier.
Call statistics
GET /api/log/self/stat · scope logs:read
Returns the amount spent over a time range, plus throughput over the last 60 seconds.
| Parameter | Type | Description |
|---|---|---|
start_timestamp | int | Start time, seconds or milliseconds; with neither bound given, defaults to today |
end_timestamp | int | End time, seconds or milliseconds |
model_name | string | Filter by model name, optional |
token_id | int | Filter by key ID, optional, exact match |
token_name | string | Filter by key name, optional, substring match |
vendor_id | int | Filter by provider, optional |
skip_rate | bool | Pass 1 to skip rpm, tpm and mpm (returned as 0); faster when you only need the amount |
curl -G https://api.calgine.com.cn/api/log/self/stat \
-H "Authorization: Bearer $ADMIN_KEY" \
-d start_timestamp=1767225600 \
-d end_timestamp=1769904000 \
-d model_name=gpt-4o{
"success": true,
"data": {
"quota": 12.3456,
"rpm": 8,
"tpm": 15420,
"mpm": 0.42,
"unfinished_count": 0,
"unfinished_by_vendor": {}
}
}| Field | Description |
|---|---|
quota | Total amount spent over the range |
rpm | Requests completed in the last 60 seconds, regardless of the time range parameters |
tpm | Tokens in the same window: input + cache writes + output, excluding cache reads |
mpm | Amount spent in the same window |
unfinished_count | Async tasks submitted in the range that have not finished yet; unfinished_by_vendor splits it by provider ID |
unavailable | Present only when part of the statistics could not be computed — see below |
Check unavailable before using the numbers. The three parts are computed under separate time limits. When one of them does not finish in time, the response is still "success": true but carries an unavailable array naming the missing parts: quota (the amount), rate (rpm, tpm, mpm) and unfinished (unfinished tasks). The 0 in the matching fields is then not a real value. Reconciliation scripts must check this field and retry with a narrower range when they see it.
{
"success": true,
"data": {
"quota": 0,
"rpm": 8,
"tpm": 15420,
"mpm": 0.42,
"unfinished_count": 0,
"unfinished_by_vendor": {},
"unavailable": ["quota"]
}
}Export log details
GET /api/log/self/stream-export · scope logs:read
Streams log details.
This endpoint returns the CSV body directly; it does not return a download link. The response is Content-Type: text/csv; charset=utf-8, written row by row as a stream, with a UTF-8 BOM at the start so Excel reads it correctly. Redirect the response body to a file — no second request is needed.
| Parameter | Type | Description |
|---|---|---|
start_timestamp | int | Start time, seconds or milliseconds |
end_timestamp | int | End time, seconds or milliseconds |
model_name | string | Filter by model name, optional |
token_id | int | Filter by key ID, optional, exact match |
token_name | string | Filter by key name, optional, substring match |
vendor_id | int | Filter by provider, optional |
status | string | Filter by status, optional, same values as the log list |
columns | string | Comma-separated columns; empty exports all |
currency | string | CNY or USD, affects amount columns |
curl -G https://api.calgine.com.cn/api/log/self/stream-export \
-H "Authorization: Bearer $ADMIN_KEY" \
-d start_timestamp=1767225600 \
-d end_timestamp=1769904000 \
-d currency=USD \
-o logs.csvimport requests
r = requests.get(
"https://api.calgine.com.cn/api/log/self/stream-export",
headers={"Authorization": f"Bearer {ADMIN_KEY}"},
params={"start_timestamp": 1767225600, "end_timestamp": 1769904000},
stream=True,
)
r.raise_for_status()
with open("logs.csv", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)Available columns include created_at, model_name, token_name, prompt_tokens, completion_tokens, cached_tokens, reasoning_tokens and quota. Column order is fixed and does not follow the order in columns, so read columns by header. If you use caching, reasoning or multimodal models, export the detailed columns explicitly — the aggregate counts alone will not reconcile against what you were charged.
Usage analytics
Export usage analytics
GET /api/data/self/pivot/stream-export · scope usage:read
Aggregates usage by dimension and streams it as CSV, same as above. Each row is one group, with columns including requests (request count), errors (failed requests), spend (amount), token counts by type and avg_latency_ms, up to 5000 rows.
| Parameter | Type | Description |
|---|---|---|
start_timestamp | int | Start time, milliseconds |
end_timestamp | int | End time, milliseconds |
group_by | string | Dimension: model (default), token, vendor, token_group, time |
metric | string | What rows are sorted by, largest first; default spend, also requests, tokens and others |
currency | string | CNY or USD (default), affects amount columns |
model_name | string | Filter by model name, optional |
token_id | int | Filter by key ID, optional |
With group_by=token the key column is the key ID; with group_by=time rows are hourly and key is the hour's start in Unix seconds.
This endpoint takes milliseconds only. The log endpoints accept either unit, but this one does not: seconds are read as a date in 1970 and the file comes back with only a header. Data is aggregated by the hour, so both bounds work on whole hours: the hour containing the start is included, and if the end falls exactly on the hour, that hour is excluded. When exporting in pieces, start each piece where the previous one ended — they will meet without overlapping. A single query spans at most about six months (186 days). Aggregation runs on a schedule, so the most recent requests may not be included yet; use the log endpoints for up-to-the-minute numbers.
curl -G https://api.calgine.com.cn/api/data/self/pivot/stream-export \
-H "Authorization: Bearer $ADMIN_KEY" \
-d start_timestamp=1767225600000 \
-d end_timestamp=1769904000000 \
-d group_by=token \
-d metric=requests \
-o usage.csvAPI key management
These endpoints manage API keys used to call models, not Admin Keys. An Admin Key can never create, edit or revoke any Admin Key, including itself.
List keys
GET /api/token/ · scope keys:read
Returns a page of API keys. Key values are masked; plaintext is not retrievable through any endpoint.
| Parameter | Type | Description |
|---|---|---|
p | int | Page number, starting at 1 |
page_size | int | Items per page |
curl -G https://api.calgine.com.cn/api/token/ \
-H "Authorization: Bearer $ADMIN_KEY" \
-d p=1 -d page_size=20{
"success": true,
"data": {
"items": [
{
"id": 42,
"name": "production",
"key_format": "sk-",
"key_mask": "e3Xq**********9f2c",
"status": 1,
"remain_quota": "50",
"unlimited_quota": false,
"used_quota": "12.5",
"expired_time": -1,
"group": "default",
"created_time": 1767225600
}
],
"page": 1,
"page_size": 20,
"total": 1
}
}An expired_time of -1 means the key never expires. used_quota is the total amount the key has spent.
Get one key
GET /api/token/{id} · scope keys:read
Returns the full configuration of one key, with the value masked. Fetch it before updating a key — see "Update a key" below.
curl https://api.calgine.com.cn/api/token/42 \
-H "Authorization: Bearer $ADMIN_KEY"Create a key
POST /api/token/ · scope keys:write
Creates a new API key. The plaintext appears only in this response.
| Field | Type | Description |
|---|---|---|
name | string | Name, up to 50 characters |
remain_quota | number | Quota; ignored when unlimited_quota is true |
unlimited_quota | bool | Whether the key has unlimited quota |
expired_time | int | Expiry as Unix seconds, -1 for never |
group | string | Group; empty uses the default |
curl -X POST https://api.calgine.com.cn/api/token/ \
-H "Authorization: Bearer $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"batch","remain_quota":10,"expired_time":-1,"group":"default"}'{ "success": true, "data": { "id": 43, "key": "sk-the-new-key-plaintext" } }Save key right away. Only its hash is stored, so no endpoint can return the plaintext afterwards — that applies to Admin Keys and signed-in users alike.
Update a key
PUT /api/token/ · scope keys:write
Updates an existing key. The body must include id.
This replaces the whole configuration; it does not patch the fields you send. Anything missing from the body is reset to its default: send only the name and quota and expired_time becomes 0 and unlimited_count becomes false, so the key stops working at once — and the group, model limits, IP allowlist and rate-limit settings are cleared along with it. The right way is to fetch the full configuration with "Get one key", change the fields you need, and send the whole object back.
# 1. Fetch the full configuration and change what you need
curl -s https://api.calgine.com.cn/api/token/43 \
-H "Authorization: Bearer $ADMIN_KEY" \
| jq '.data | .name = "batch-adjusted" | .remain_quota = 20' > token.json
# 2. Send the whole object back
curl -X PUT https://api.calgine.com.cn/api/token/ \
-H "Authorization: Bearer $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d @token.jsonimport requests
headers = {"Authorization": f"Bearer {ADMIN_KEY}"}
token = requests.get("https://api.calgine.com.cn/api/token/43", headers=headers).json()["data"]
token["name"] = "batch-adjusted"
token["remain_quota"] = 20
r = requests.put("https://api.calgine.com.cn/api/token/", headers=headers, json=token)
print(r.json()["success"])The key and key_mask fields in the fetched configuration are masks; send them back as they are and the key itself is not changed.
Delete a key
DELETE /api/token/{id} · scope keys:write
Deletes the given key, effective immediately.
curl -X DELETE https://api.calgine.com.cn/api/token/43 \
-H "Authorization: Bearer $ADMIN_KEY"The bulk-delete endpoint is not available to Admin Keys. To delete several keys, call this once per key or use the console.