Bill Query
curl --request GET \
--url https://client.tennda.ai/prod-api/api/logs/v2/getDailyListimport requests
url = "https://client.tennda.ai/prod-api/api/logs/v2/getDailyList"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://client.tennda.ai/prod-api/api/logs/v2/getDailyList', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://client.tennda.ai/prod-api/api/logs/v2/getDailyList",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://client.tennda.ai/prod-api/api/logs/v2/getDailyList"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://client.tennda.ai/prod-api/api/logs/v2/getDailyList")
.asString();require 'uri'
require 'net/http'
url = URI("https://client.tennda.ai/prod-api/api/logs/v2/getDailyList")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyInterface Modules
Bill Query
Pull day-level consumption details and summaries from TenndaAI for reconciliation, cost analysis, and bill collection
GET
/
prod-api
/
api
/
logs
/
v2
/
getDailyList
Bill Query
curl --request GET \
--url https://client.tennda.ai/prod-api/api/logs/v2/getDailyListimport requests
url = "https://client.tennda.ai/prod-api/api/logs/v2/getDailyList"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://client.tennda.ai/prod-api/api/logs/v2/getDailyList', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://client.tennda.ai/prod-api/api/logs/v2/getDailyList",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://client.tennda.ai/prod-api/api/logs/v2/getDailyList"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://client.tennda.ai/prod-api/api/logs/v2/getDailyList")
.asString();require 'uri'
require 'net/http'
url = URI("https://client.tennda.ai/prod-api/api/logs/v2/getDailyList")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyOverview
Two read-only billing endpoints: day summaries for net spend trends, and line items broken down by model × token type. Amounts are alwaysUSD, billing timezone is utc+8, and data is T+1.
Consumption details
getDailyList: grain (bill day × account × model × token type). Max span 92 days per request; pageSize capped at 100.Day summary
getDailySummary: grain (bill day × account). Max span 366 days per request; pageSize capped at 400.Endpoints
| Endpoint | Method / path | Use case |
|---|---|---|
| Details | GET /api/logs/v2/getDailyList | Line-item reconciliation, cost split |
| Day summary | GET /api/logs/v2/getDailySummary | Daily trends, monthly overviews |
| Item | Value |
|---|---|
| Base URL | https://client.tennda.ai/prod-api |
| Format / encoding | application/json · UTF-8 |
| Protocol | HTTPS |
| Currency / timezone | USD · utc+8 |
Authentication
Send a Bearer credential in the request header — either option works:Authorization: Bearer <access_token>
Authorization: Bearer sk-xxxxxxxxxxxxxxxxxxxx
| Credential | Notes |
|---|---|
| System access token | Console → Profile → Access token |
| API Key | Usually starts with sk- |
401, business code 40100.
Never put a full key in URLs, frontend code, logs, or support tickets.
Common response
{
"code": 0,
"message": "success",
"data": {
"total": 128,
"rows": []
}
}
| Field | Type | Description |
|---|---|---|
code | integer | 0 = success; non-zero = failure |
message | string | Status message |
data | object / null | Payload; null on failure |
data.total | integer | Total matching rows (for pagination) |
data.rows | array | Current page |
Business error codes
| code | HTTP | Description |
|---|---|---|
| 0 | 200 | Success |
| 40001 | 400 | Bad parameter format (invalid date, non-positive page params, etc.) |
| 40002 | 400 | Missing required parameter |
| 40003 | 400 | Invalid date range (start after end, or span over the limit) |
| 40100 | 401 | Token missing, invalid, or expired |
| 50000 | 500 | Internal server error |
Details · getDailyList
GET https://client.tennda.ai/prod-api/api/logs/v2/getDailyList
(bill day × account × model × token type).
Request parameters
Filter by account ID. Multi-sub-account visibility requires Enterprise Management — see Conventions. Omit to return accounts visible to the current credential.
Filter by account name (exact). Same Enterprise Management rules; omit to return accounts visible to the current credential.
Pagination is by detail row;
data.total is the total number of matching detail rows.Response fields (data.rows[])
| Field | Type | Description |
|---|---|---|
billMonth | string | Billing month, yyyyMM |
billDay | string | Billing day, yyyy-MM-dd |
billingDateTimezone | string | Always utc+8 |
account | string | Account name |
modelType | string | Model family, e.g. claude, gpt, gemini |
modelName | string | Model name |
tokenType | string | Token type — see below |
tokenCount | string | Token count for this type (integer as string) |
tokenUnit | string | Always million (priced per million tokens) |
currency | string | Always USD |
subtotalBeforeTax | string | Pre-discount, pre-tax (8 decimals) |
subtotalAfterDiscount | string | Post-discount, pre-tax (8 decimals) |
totalAmountAfterTax | string | Post-discount, post-tax (8 decimals) |
price | string | Pre-discount unit price per million tokens (8 decimals). subtotalBeforeTax = price × tokenCount ÷ 1,000,000; may be omitted when tokenCount is 0 |
entryType | string | normal = normal consumption |
tokenType
| Value | Meaning |
|---|---|
textInputTokens | Text input |
textOutputTokens | Text output |
reasoningTokens | Reasoning / thinking |
cacheCreationTokens5m | Cache creation (5-minute TTL) |
cacheCreationTokens1h | Cache creation (1-hour TTL) |
cacheTokens | Cache hit (read) |
imageInputTokens / imageOutputTokens | Image in / out |
audioInputTokens / audioOutputTokens | Audio in / out |
videoInputTokens / videoOutputTokens | Video in / out |
Sort:
billDay desc, then account, modelName, tokenType, currency asc.Request example
curl "https://client.tennda.ai/prod-api/api/logs/v2/getDailyList?startDate=2026-07-27&endDate=2026-07-27&pageSize=100&pageNum=1" \
-H "Authorization: Bearer <your_token>"
Response example
{
"code": 0,
"message": "success",
"data": {
"total": 23,
"rows": [
{
"billMonth": "202607",
"billDay": "2026-07-27",
"billingDateTimezone": "utc+8",
"account": "your_account",
"modelType": "claude",
"modelName": "claude-opus-4-8",
"tokenType": "textOutputTokens",
"tokenCount": "2931",
"tokenUnit": "million",
"currency": "USD",
"subtotalBeforeTax": "0.07327564",
"subtotalAfterDiscount": "0.07327564",
"totalAmountAfterTax": "0.07327564",
"price": "25.00021836",
"entryType": "normal"
}
]
}
}
Day summary · getDailySummary
GET https://client.tennda.ai/prod-api/api/logs/v2/getDailySummary
Request parameters
Pagination is by
(bill day × account); data.total is the combination count.Response fields (data.rows[])
| Field | Type | Description |
|---|---|---|
billMonth | string | Billing month, yyyyMM |
billDay | string | Billing day, yyyy-MM-dd |
billingDateTimezone | string | Always utc+8 |
account | string | Account name |
currency | string | Always USD |
subtotalBeforeTax | string | Day total, pre-discount pre-tax net (8 decimals) |
subtotalAfterDiscount | string | Day total, post-discount pre-tax net (8 decimals) |
totalAmountAfterTax | string | Day total, post-discount post-tax net (8 decimals) |
Sort:
billDay desc, then account asc.Request example
curl "https://client.tennda.ai/prod-api/api/logs/v2/getDailySummary?startDate=2026-07-01&endDate=2026-07-27&pageSize=31&pageNum=1" \
-H "Authorization: Bearer <your_token>"
Response example
{
"code": 0,
"message": "success",
"data": {
"total": 2,
"rows": [
{
"billMonth": "202607",
"billDay": "2026-07-27",
"billingDateTimezone": "utc+8",
"account": "your_account",
"currency": "USD",
"subtotalBeforeTax": "0.26569400",
"subtotalAfterDiscount": "0.26569400",
"totalAmountAfterTax": "0.26569400"
}
]
}
}
Conventions
- Amounts: Always
stringwith 8 decimal places — use Decimal / BigDecimal; do not convert to binary floats. - Consistency: For the same bill day and account, each day-summary amount equals the sum of the matching detail amounts that day (sum across pages).
- Zero days: Days with no consumption in the range are omitted.
- Freshness: Bills are T+1 — the latest day is queryable the next day.
- Account scope: Default is own account only. A primary account with Enterprise Management can see all sub-accounts — omit
userId/userNamefor all, or pass them to filter; a sub-account credential only sees itself. Contact a TenndaAI admin to enable Enterprise Management. - Out-of-range pages: When
pageNumexceeds total pages,rowsis empty whiletotalremains the true total.
