Account and catalog API
Query a key's models and remaining balance, and the public price catalog.
| Endpoint | Authentication | What it returns |
|---|---|---|
GET /v1/models | Key (either header) | The models this key's groups serve |
GET /v1/usage | Key (Bearer only) | What this key can still spend, and its quota |
GET /api/v1/models/pricing | None | The public model catalog with list prices |
None of these endpoints is billed, and none needs a positive balance.
List models
GET /v1/models accepts Authorization: Bearer sk-… or x-api-key: sk-…; GET /v1/usage accepts only Authorization: Bearer sk-….
curl https://api.hop-base.com/v1/models \
-H "Authorization: Bearer sk-your-key"The list contains only the models of the groups the key includes (for a key with several groups, the union of all of them), and it comes back complete in one response (no paging). Keys from OpenAI-compatible groups get an OpenAI-shaped list:
{
"object": "list",
"data": [
{
"id": "gpt-6-astra",
"object": "model",
"created": 1790222400,
"owned_by": "hopbase",
"capabilities": ["chat", "reasoning"],
"context_window": 1050000,
"context_length": 1050000,
"max_input_tokens": 1050000,
"max_output_tokens": 128000
}
]
}Keys from Claude groups get the Anthropic shape:
{
"object": "list",
"data": [
{
"id": "claude-sonnet-5-5",
"object": "model",
"type": "model",
"display_name": "Claude Sonnet 5.5",
"created_at": "2026-09-28T00:00:00Z"
}
],
"has_more": false,
"first_id": "claude-sonnet-5-5",
"last_id": "claude-haiku-4-5-20251001"
}| Field | Type | Meaning |
|---|---|---|
data[].id | string | The model ID to send in requests. This is the only field to rely on |
data[].capabilities | string[] | For example chat, reasoning, image_generation |
data[].image_only | boolean | true for image models; absent otherwise |
data[].context_window, context_length, max_input_tokens | integer, tokens | Same value in three spellings for client compatibility; absent when not published |
data[].max_output_tokens | integer, tokens | Absent when not published |
data[].created | integer, Unix seconds | Time of this response, not a release date |
data[].display_name, created_at | string | Claude groups only; created_at is the model's release date (RFC 3339) |
has_more, first_id and last_id are there for SDK compatibility only: the list is never paged, so do not page with them. Fields other than id can be missing for some models; treat unknown fields as optional.
Balance currency: USD
Since 2026-10-01, account balances, key quotas, charges, and usage records are all in US dollars (USD): models are charged at the official USD price multiplied by your group's rate, and both the console and GET /v1/usage show USD amounts.
- Existing balances were converted to USD at a fixed rate, so what you can spend did not change. Group prices and keys did not change either.
- Per-request charges: so that existing integrations keep working,
costandX-HopBase-Costin responses are still the same charge in CNY (up to 6 decimal places), andcurrencyis alwaysCNY. The USD amount actually deducted from your balance is incost_usdandX-HopBase-Cost-USD, matching Usage. See Charges. - Top-ups: the payment currency, fees, and amount credited are the ones the Recharge page shows before you pay; fees are listed separately and are not credited. For CNY company bank transfers and offline top-ups, the USD amount credited is confirmed by support.
Balance and quota
curl https://api.hop-base.com/v1/usage \
-H "Authorization: Bearer sk-your-key"A key with a quota of $10, $2 of it used:
{
"is_active": true,
"balance": 8,
"remaining": 8,
"unit": "USD",
"ledger_currency": "USD",
"balance_cny": 54.4,
"balance_usd": 8,
"quota": {
"remaining": 8,
"api_key_remaining": 8,
"total": 10,
"used": 2,
"unlimited": false,
"remaining_cny": 54.4,
"remaining_usd": 8,
"api_key_remaining_cny": 54.4,
"api_key_remaining_usd": 8,
"total_cny": 68,
"total_usd": 10,
"used_cny": 13.6,
"used_usd": 2
}
}| Field | Type | Meaning |
|---|---|---|
balance | number, USD | What this key can spend right now; see the rules below |
remaining | number, USD | Same value as balance |
is_active | boolean | true when balance is above 0 |
unit | string | Always "USD", matching ledger_currency |
ledger_currency | string | Currency of the unsuffixed amounts: "USD" (it was "CNY" before 2026-10-01) |
quota.remaining | number, USD | Same value as balance |
quota.api_key_remaining | number, USD | Quota left on this key; equals the account balance when the key has no quota |
quota.total | number, USD | The key's quota; 0 means no key quota |
quota.used | number, USD | Total charged to this key so far |
quota.unlimited | boolean | true when the key has no quota of its own |
How balance is set:
- Key without its own quota: the account balance.
- Key with a quota: the quota left on the key.
- Keys of team members and departments are further capped by the member's and department's remaining quota for the period.
The unsuffixed amounts are balance, remaining, quota.remaining, quota.api_key_remaining, quota.total, and quota.used.
Every amount also comes as a CNY and a USD field with a suffix:
| Field | Type | Meaning |
|---|---|---|
balance_cny, balance_usd | number | balance in CNY and in USD |
quota.remaining_cny, quota.remaining_usd | number | quota.remaining in CNY and in USD |
quota.api_key_remaining_cny, quota.api_key_remaining_usd | number or null | quota.api_key_remaining in CNY and in USD; null when the key has no quota |
quota.total_cny, quota.total_usd | number or null | quota.total in CNY and in USD; null when the key has no quota |
quota.used_cny, quota.used_usd | number | quota.used in CNY and in USD |
balance, remaining, and the unsuffixed quota.* amounts are USD values, the same numbers the console shows for your balance and in Usage. For CNY, read the _cny fields: the same amounts in CNY (fixed conversion, not a live exchange rate). Amounts carry up to 8 decimal places. Before 2026-10-01 these fields held CNY values (ledger_currency was "CNY" then), so scripts that reconcile on ledger_currency or the _cny / _usd fields need no change.
A key with its own quota still draws on the account balance: when the account runs out first, requests return 402 even though balance here shows quota left.
This endpoint does not use the standard error body. Failures look like this:
| Case | Status | message |
|---|---|---|
No key, or not an sk- key | 401 | "missing or invalid api key" |
| Unknown or disabled key | 401 | "invalid api key" |
| Expired key | 200 | "api key expired" |
| Team member disabled | 200 | "member disabled" |
The full body of the first 401 case:
{"is_active": false, "balance": 0, "message": "missing or invalid api key"}The two 200 cases also carry ledger_currency and balance_cny / balance_usd (both 0).
Branch on is_active, not only on the HTTP status. For itemised charges, open Usage in the console.
Charges per request, task, and record
Synchronous requests and async tasks report each charge in two currencies: cost_usd is the USD amount actually deducted from your balance, the same as the record under Usage in the console; cost is the same charge in CNY and stays in CNY so existing integrations keep working, so currency is always CNY. cost_usd has up to 8 decimal places; cost is rounded to 6.
Synchronous requests (chat, images, speech)
A billed synchronous request returns its actual charge in three response headers:
| Header | Meaning |
|---|---|
X-HopBase-Cost | The charge for this request in CNY (the same charge as X-HopBase-Cost-USD), such as 0.0816 |
X-HopBase-Cost-USD | The USD amount actually deducted from your balance for this request, the same as its usage record, such as 0.012 |
X-HopBase-Currency | The currency of X-HopBase-Cost, always CNY |
A non-streaming JSON response also carries the same cost, cost_usd, and currency in the top-level usage:
{
"id": "chatcmpl-abc",
"object": "chat.completion",
"model": "gpt-5.6-sol",
"choices": [{ "index": 0, "message": { "role": "assistant", "content": "ok" }, "finish_reason": "stop" }],
"usage": { "prompt_tokens": 9, "completion_tokens": 1, "total_tokens": 10, "cost": 0.001197, "cost_usd": 0.000176, "currency": "CNY" }
}- If the response has no
usage, one is added, as in the JSON response ofPOST /v1/t2a_v2speech synthesis. Existing fields inusage, such as xAI'scost_in_usd_ticks, stay unchanged. - If
usagealready hascost,cost_usd, orcurrency, or is not an object, the body is left as is. Read the headers. - Binary responses, such as synthesized speech audio, carry only the headers.
- Streaming (SSE) responses carry none of these: the headers are sent before generation starts, and the final usage event is passed through unchanged. Check the charge for a streaming request under Usage in the console.
- When image generation takes longer than about 40 seconds, the server sends the 200 headers first and then writes whitespace keep-alives, so the headers carry no charge. Read
usage.cost/usage.cost_usdin the body. - Responses without a charge, such as model lists and unbilled errors, carry none of these. When a failed request is still billed, its error response carries the headers and the error body is unchanged.
- When HopBase retries a request automatically, only the attempt that completes is billed, and that is the amount reported. A key with several groups is charged at the group that served the request.
- For a key with a selling multiplier, the amount is what was deducted from your balance, without the markup.
- If the client disconnects before the response arrives, the request is still billed for what was used; the charge appears in Usage.
Async task queries (images and video)
Once the task finishes, the top-level usage carries cost_usd (the USD amount actually deducted from your balance for this task), cost (the same charge in CNY), and currency (the currency of cost, always CNY). 0 means an uncharged failure; when querying a single task, only the key that created it sees these fields.
{
"id": "vt-1",
"status": "completed",
"usage": { "cost": 3.4, "cost_usd": 0.5, "currency": "CNY" }
}In task lists (such as GET /v1/video/tasks without a task ID), every finished task carries the same cost, cost_usd, and currency in its usage. Lists cover the whole account, so tasks created by other keys of the same account carry the amount too.
Usage records in the console
The Usage page reads GET /api/v1/user/usage and GET /api/v1/user/usage/stats (console sign-in, not an sk- key).
Every record carries ledger_currency (USD since 2026-10-01), cost_cny, and cost_usd — the amount charged for that record in CNY and in USD. The page shows USD.
The statistics carry ledger_currency, total_charged_cny, and total_charged_usd, and every item in by_model, by_key, by_group, by_department, and by_member carries cost_cny / cost_usd.
The older total_cost in the statistics is the official USD list price before your group's rate, not what you were charged; use total_charged_* for the charge.
Public model catalog
curl https://api.hop-base.com/api/v1/models/pricingNo key needed. The response allows cross-origin requests from any site and may be cached for up to 5 minutes (Cache-Control: public, max-age=300). Prices are the list prices in USD before your group's rate; what you actually pay per model is shown in the signed-in model catalog.
{
"code": 0,
"message": "ok",
"data": [
{
"platform": "openai",
"models": [
{
"id": "gpt-6-astra",
"name": "GPT-6 Astra",
"context_window": 1050000,
"capabilities": ["chat", "reasoning"],
"vendor": "openai",
"category": "chat",
"input": 10,
"cached_input": 1,
"output": 50,
"long_context": {
"threshold": 272000,
"input_multiplier": 2,
"cached_multiplier": 2,
"output_multiplier": 1.5
},
"price_unit": "token"
}
]
},
{
"platform": "minimax",
"models": [
{
"id": "speech-2.8-hd",
"name": "MiniMax Speech 2.8 HD",
"capabilities": ["tts"],
"vendor": "minimax",
"series": "minimax-speech",
"category": "audio",
"input": 100,
"output": 0,
"price_unit": "character"
}
]
}
]
}| Field | Type | Meaning |
|---|---|---|
code | integer | 0 on success |
data[].platform | string | Integration family the model is served through, not the maker |
models[].id | string | Model ID to send |
models[].name | string | Display name |
models[].vendor | string | Model maker, for example openai, google; may be absent |
models[].series | string | Family used to group versions in the console; may be absent |
models[].category | string | chat, image, video, audio, or embedding |
models[].capabilities | string[] | For example chat, reasoning, image_generation, image_edit, video_generation, tts |
models[].context_window | integer, tokens | Absent when not published |
models[].price_unit | string | Unit of every price on the entry; see the table below |
models[].input, cached_input, output | number, USD per price_unit | cached_input is absent when caching does not apply. Speech models use input only |
models[].long_context | object | Present when a long-context tier exists; see below |
models[].image | object | Per-image price by resolution tier, for example {"1k": …, "2k": …, "4k": …} |
models[].video_tokens | object | Video price per bucket; unit follows price_unit |
platform is, for example, openai, claude, gemini, or kling; for the model maker, see vendor.
price_unit | Price per |
|---|---|
token | 1M tokens |
second | second of video |
image | image |
character | 1M billed characters |
Above threshold input tokens, the whole request is billed at input_multiplier / cached_multiplier / output_multiplier.
video_tokens buckets are resolution, audio, and reference media. The unit is per 1M video tokens for token and per second for second. The key name is historical.
A model can appear under more than one platform when it is offered through more than one group. New fields may be added; ignore fields you do not recognize.
Response headers
| Header | Sent on | Meaning |
|---|---|---|
x-request-id | Every response | ID of this request. Include it when you report a problem |
Retry-After | 429 from the concurrency cap; 429 and 503 when a wait time is known | Seconds to wait before retrying |
Retry-After-Ms | 429 from the concurrency cap, and other 429s with a known wait | The same wait in milliseconds |
X-HopBase-Cost | Billed synchronous requests (not streaming) | The charge for this request in CNY; see Charges |
X-HopBase-Cost-USD | Same as X-HopBase-Cost | The USD amount actually deducted from your balance |
X-HopBase-Currency | Same as X-HopBase-Cost | The currency of X-HopBase-Cost, always CNY |
HopBase does not send headers with your concurrency cap, requests in flight, or remaining balance. Use GET /v1/usage for the balance, and see Concurrency, timeouts, and billing for the caps. Some model responses carry their own rate-limit headers (x-ratelimit-*, anthropic-ratelimit-*); they do not describe your account's or key's limits, so do not throttle on them.
Next steps
- Concurrency, timeouts, and billing: concurrency caps, timeouts, and billing rules
- Error codes and retries: what each error code means and whether to retry
- Available models: find the model IDs available to a key