Skip to Content
APIHaoAI OpenAPIBilling Report

Billing Report

Query the authenticated API key’s actual spend for a date range, grouped by billed model, including cost components, token counts, and request counts.

This endpoint is part of HaoAI OpenAPI. A key can read only its own usage; other keys in the same account or team are excluded.

Endpoint

GET https://hao.ai/api/v1/usage/billing-report

Send requests to the website host hao.ai.

Authentication

Pass an API key created in the dashboard in the request header:

Authorization: Bearer sk-haoai-...

Existing sk-pohu-... keys are also accepted. A positive wallet balance is not required. The key must be enabled and unexpired. If it has an IP allowlist, the script’s outbound IP must match that list.

API key authentication grants access only to that key’s JSON report. Usage from other keys is excluded. Pass the secret in the request header, never in the URL.

Query Parameters

Send parameters in the query string. For daily billing, use start_date, end_date, and timezone.

ParameterTypeRequiredDescription
start_datestringWith end_dateFirst date, in YYYY-MM-DD format, inclusive
end_datestringWith start_dateLast date, in YYYY-MM-DD format, inclusive
timezonestringNoIANA timezone, such as Asia/Shanghai; defaults to UTC
rangestringNoPreset date range, such as yesterday, 7d, or 30d; defaults to 30d
from, tointegerFor range=customUnix milliseconds mapped to calendar dates in the selected timezone; includes the entire date containing to
modelstringNoBilled model ID; omit to return all models used by the key

Supported presets: 12h, 24h, 48h, today, yesterday, 2daysago, 3d, 7d, 14d, 30d, 90d, thismonth, lastmonth, and custom. This report uses calendar windows: 12h and 24h mean today, and 48h includes yesterday and today. Prefer explicit dates or yesterday. An explicit date pair takes precedence; avoid mixing range formats.

A query window cannot exceed 90 days and returns only the authenticated API key’s costs. Unknown or duplicate query parameters return 400.

Daily Spend by Model

Set start_date and end_date to the same date to get each model’s actual_cost for that day. For multiple days, query each date separately. A multi-day request aggregates the entire window; granularity and group_by are not supported.

For example, 2026-10-05 with Asia/Shanghai covers midnight on October 5 through midnight on October 6 in that timezone. The response expresses the window in UTC with an exclusive end time.

Request Examples

Terminal
curl --get 'https://hao.ai/api/v1/usage/billing-report' \ -H "Authorization: Bearer $HAOAI_API_KEY" \ --data-urlencode 'start_date=2026-10-05' \ --data-urlencode 'end_date=2026-10-05' \ --data-urlencode 'timezone=Asia/Shanghai'

Response Format

Successful response, 200 OK:

{ "code": 0, "message": "success", "data": { "currency": "USD", "range": { "from": "2026-10-04T16:00:00Z", "to": "2026-10-05T16:00:00Z" }, "rows": [ { "api_key_id": "63a7f4c1-1542-4a65-a03e-4d0e15f71962", "api_key_name": "Production", "api_key_prefix": "sk-haoai-7fA3k9mB", "api_key_suffix": "hJv2", "model": "anthropic/claude-sonnet-4-5", "requests": 18, "input_tokens": 120000, "output_tokens": 45000, "cache_read_tokens": 12000, "cache_creation_tokens": 3000, "input_cost": 0.4, "output_cost": 0.8, "cache_read_cost": 0.1, "cache_creation_cost": 0.5, "search_cost": 0, "image_cost": 0, "video_cost": 0, "other_cost": 0, "actual_cost": 1.8, "official_cost": 3, "effective_discount": 0.6 } ], "totals": { "requests": 18, "input_tokens": 120000, "output_tokens": 45000, "cache_read_tokens": 12000, "cache_creation_tokens": 3000, "input_cost": 0.4, "output_cost": 0.8, "cache_read_cost": 0.1, "cache_creation_cost": 0.5, "search_cost": 0, "image_cost": 0, "video_cost": 0, "other_cost": 0, "actual_cost": 1.8, "official_cost": 3, "effective_discount": 0.6 } } }

Envelope and Report Fields

FieldTypeDescription
codeinteger0 on success
messagestringsuccess on success
data.currencystringAlways USD
data.range.fromstringInclusive start, in UTC RFC 3339 format
data.range.tostringExclusive end, in UTC RFC 3339 format
data.rowsarrayRows grouped by billed model for this key; empty when there is no usage
data.totalsobjectRequest, token, and cost totals across rows; zero when there is no usage

rows[] Fields

FieldTypeDescription
api_key_idstringThe authenticated key’s public UUID
api_key_namestringCurrent key name
api_key_prefix, api_key_suffixstringOptional display prefix and trailing characters; never the full secret
channel_namestringOptional current channel label, not a separate billing group
modelstringBilled model ID
requestsintegerRequest count
input_tokens, output_tokensintegerInput and output token counts
cache_read_tokens, cache_creation_tokensintegerCache read and creation token counts
input_cost, output_costnumberInput and output costs
cache_read_cost, cache_creation_costnumberCache read and creation costs
search_cost, image_cost, video_cost, other_costnumberSearch, image, video, and other costs
actual_costnumberActual platform charge, equal to the sum of cost components
official_costnumberOfficial list-price cost, not the actual charge
effective_discountnumberactual_cost / official_cost; omitted when the official cost is zero

totals contains the same count and cost fields, without key, channel, or model identifiers.

Amounts are in USD, settled at 1 micro-USD precision (0.000001 USD). Use actual_cost for spend tracking; do not recalculate historical bills using today’s model prices. Use decimal arithmetic when summing amounts.

The report uses the same processed usage aggregates as the dashboard. Recently completed requests may appear after a delay; query again later if needed. A failed query returns an error, never a fabricated zero-cost result. Recent calendar-day windows in whole-hour timezones can be represented exactly. Beyond the 100-day hourly retention window, historical ranges must align with whole UTC days. Windows that cannot be represented exactly by retained aggregates return 400.

Rate Limits

A maximum of 3 requests per minute is allowed. Excessive requests return 429.

Send requests sequentially, at least 20 seconds apart. On 429, wait for Retry-After before retrying. Responses include Cache-Control: no-store and must not be stored in a public cache.

Error Responses

Example error response:

{ "code": 429, "message": "too many requests", "reason": "rate_limited" }
StatusreasonDescription
400validation_error / bad_requestInvalid dates, timezone, model filter, or parameters; range too large or not exactly representable
401unauthorizedMissing, invalid, disabled, expired, or deleted key; or inactive owning account
403apikey_ip_not_allowedOutbound IP is not allowed by the key
429rate_limitedToo many requests; wait for Retry-After before retrying
503service_unavailableService temporarily busy; retry after Retry-After
500internal_errorQuery failure or timeout; retry later