Skip to content

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.

bash
curl https://api.calgine.com.cn/api/user/self \
  -H "Authorization: Bearer $ADMIN_KEY"
json
{
  "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.

ParameterTypeDescription
pintPage number, starting at 1
page_sizeintItems per page, default 10, up to 1000
start_timestampintStart time, seconds or milliseconds
end_timestampintEnd time, seconds or milliseconds
model_namestringFilter by model name, optional
token_idintFilter by key ID, optional, exact match; IDs come from "List keys"
token_namestringFilter by key name, optional, substring match: every key whose name contains the text counts
vendor_idintFilter by provider, optional
statusstringFilter by status, optional: SUCCESS, FAILURE, or UNFINISHED (async tasks still running)
skip_countboolPass 1 to skip counting the total (total comes back as -1); faster when paging through data
bash
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
json
{
  "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:

FieldDescription
idPosition 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_idUnique ID of the request
created_at, completed_atWhen the request started and finished, in milliseconds; completed_at is 0 for async tasks still running
quotaAmount charged for the request, as a decimal string
usageUsage 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_moreWhether more data follows; use it to decide whether to fetch another page
totalNumber 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.

ParameterTypeDescription
start_timestampintStart time, seconds or milliseconds
end_timestampintEnd time, seconds or milliseconds
model_namestringFilter by model name, optional
token_idintFilter by key ID, optional, exact match
token_namestringFilter by key name, optional, substring match
vendor_idintFilter by provider, optional
statusstringFilter by status, optional, same values as the log list
bash
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
json
{ "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.

ParameterTypeDescription
start_timestampintStart time, seconds or milliseconds; with neither bound given, defaults to today
end_timestampintEnd time, seconds or milliseconds
model_namestringFilter by model name, optional
token_idintFilter by key ID, optional, exact match
token_namestringFilter by key name, optional, substring match
vendor_idintFilter by provider, optional
skip_rateboolPass 1 to skip rpm, tpm and mpm (returned as 0); faster when you only need the amount
bash
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
json
{
  "success": true,
  "data": {
    "quota": 12.3456,
    "rpm": 8,
    "tpm": 15420,
    "mpm": 0.42,
    "unfinished_count": 0,
    "unfinished_by_vendor": {}
  }
}
FieldDescription
quotaTotal amount spent over the range
rpmRequests completed in the last 60 seconds, regardless of the time range parameters
tpmTokens in the same window: input + cache writes + output, excluding cache reads
mpmAmount spent in the same window
unfinished_countAsync tasks submitted in the range that have not finished yet; unfinished_by_vendor splits it by provider ID
unavailablePresent 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.

json
{
  "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.

ParameterTypeDescription
start_timestampintStart time, seconds or milliseconds
end_timestampintEnd time, seconds or milliseconds
model_namestringFilter by model name, optional
token_idintFilter by key ID, optional, exact match
token_namestringFilter by key name, optional, substring match
vendor_idintFilter by provider, optional
statusstringFilter by status, optional, same values as the log list
columnsstringComma-separated columns; empty exports all
currencystringCNY or USD, affects amount columns
bash
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.csv
python
import 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.

ParameterTypeDescription
start_timestampintStart time, milliseconds
end_timestampintEnd time, milliseconds
group_bystringDimension: model (default), token, vendor, token_group, time
metricstringWhat rows are sorted by, largest first; default spend, also requests, tokens and others
currencystringCNY or USD (default), affects amount columns
model_namestringFilter by model name, optional
token_idintFilter 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.

bash
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.csv

API 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.

ParameterTypeDescription
pintPage number, starting at 1
page_sizeintItems per page
bash
curl -G https://api.calgine.com.cn/api/token/ \
  -H "Authorization: Bearer $ADMIN_KEY" \
  -d p=1 -d page_size=20
json
{
  "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.

bash
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.

FieldTypeDescription
namestringName, up to 50 characters
remain_quotanumberQuota; ignored when unlimited_quota is true
unlimited_quotaboolWhether the key has unlimited quota
expired_timeintExpiry as Unix seconds, -1 for never
groupstringGroup; empty uses the default
bash
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"}'
json
{ "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.

bash
# 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.json
python
import 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.

bash
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.