List Models
Read the model catalogue an account may call — context window, capabilities and list pricing — without opening the console.
This is not the inference API's GET /v1/models.
That one is called with an sk-... key; this one with a console token, and it
answers per account and per member.
https://console-api.siraya.ai/extapi/v1/models
Query Parameters
Whose catalogue to read. Use any id from List Accounts.
Omit it and default_account_id — the first account in that list — is used.
If your token is pinned, omit it or pass the pinned account; any other id is
refused with 403.
import requests
url = "https://console-api.siraya.ai/extapi/v1/models"
headers = {"Authorization": "Bearer <CONSOLE_TOKEN>"}
params = {"page": 1, "page_size": 20}
response = requests.get(url, headers=headers, params=params)
body = response.json()["data"]
for model in body["data"]:
for line in model["sale_prices_by_billing_unit"]:
print(model["code"], line["billing_unit"]["code"], line["sale_price"])
Example Response
{
"isSuccess": true,
"data": {
"data": [
{
"id": "7c1e2b4a-5d6f-4e8a-9b0c-1d2e3f4a5b6c",
"code": "gpt-4o",
"name": "GPT-4o",
"owner": "OpenAI",
"output_type": "text",
"input_types": ["text", "image"],
"context_window": "128000",
"max_output_tokens": "16384",
"capabilities": ["function_calling"],
"currency": "USD",
"sale_prices_by_billing_unit": [
{
"billing_unit": {
"id": "b1f0c2d4-0000-4000-8000-000000000001",
"code": "input_tokens",
"name": "Input Tokens",
"description": "",
"unit": ["tokens"]
},
"unit_scale": "1000000",
"unit": "tokens",
"sale_price": "2.5000000000"
},
{
"billing_unit": {
"id": "b1f0c2d4-0000-4000-8000-000000000002",
"code": "output_tokens",
"name": "Output Tokens",
"description": "",
"unit": ["tokens"]
},
"unit_scale": "1000000",
"unit": "tokens",
"sale_price": "10.0000000000"
}
],
"rules": [
{
"id": "3a9d7e10-0000-4000-8000-000000000010",
"name": "input<=128k",
"conditions": [
{
"attribute": "Input Tokens",
"operator": "lte",
"value": "128000",
"type": "number"
}
],
"sale_prices_by_billing_unit": [
{
"billing_unit": {
"id": "b1f0c2d4-0000-4000-8000-000000000001",
"code": "input_tokens",
"name": "Input Tokens",
"description": "",
"unit": ["tokens"]
},
"unit_scale": "1000000",
"unit": "tokens",
"sale_price": "1.2500000000"
},
{
"billing_unit": {
"id": "b1f0c2d4-0000-4000-8000-000000000002",
"code": "output_tokens",
"name": "Output Tokens",
"description": "",
"unit": ["tokens"]
},
"unit_scale": "1000000",
"unit": "tokens",
"sale_price": "5.0000000000"
}
]
},
{
"id": "3a9d7e10-0000-4000-8000-000000000011",
"name": "default",
"conditions": [],
"sale_prices_by_billing_unit": [
{
"billing_unit": {
"id": "b1f0c2d4-0000-4000-8000-000000000001",
"code": "input_tokens",
"name": "Input Tokens",
"description": "",
"unit": ["tokens"]
},
"unit_scale": "1000000",
"unit": "tokens",
"sale_price": "2.5000000000"
},
{
"billing_unit": {
"id": "b1f0c2d4-0000-4000-8000-000000000002",
"code": "output_tokens",
"name": "Output Tokens",
"description": "",
"unit": ["tokens"]
},
"unit_scale": "1000000",
"unit": "tokens",
"sale_price": "10.0000000000"
}
]
}
]
}
],
"page": 1,
"page_size": 20,
"has_more": true
}
}
Response fields
The models sit at data.data — the outer data is the response envelope, the
inner one is the page. Each model carries:
| Field | Type | Meaning |
|---|---|---|
id |
string | The model's identifier in the catalogue |
code |
string | What to pass as model when you call it for inference |
name |
string | Display name |
owner |
string | The model's publisher |
output_type |
string | What the model produces, e.g. text |
input_types |
string[] | What the model accepts, e.g. text, image. Always present; [] when none are recorded |
context_window |
string | Context window size. A string, not a number — see below |
max_output_tokens |
string | Largest completion the model returns. A string, not a number — see below |
capabilities |
string[] | Features the model supports, e.g. function_calling. Always present; [] when none are recorded |
currency |
string | The currency every price of this model is in. "" when the model has no price recorded |
sale_prices_by_billing_unit |
object[] | The default rule's price lines, repeated here so you need not walk rules — see How to read pricing |
rules |
object[] | Every pricing rule, default and conditional alike — see Conditional pricing |
The page itself carries:
| Field | Type | Meaning |
|---|---|---|
page |
integer | The page returned, after clamping |
page_size |
integer | The page size used, after clamping |
has_more |
boolean | true while a further page exists |
Treat the payload as open: ignore fields you do not recognise.
context_window and max_output_tokens are strings
The catalogue stores both as free text. Parse them yourself, and handle a value that is not a number rather than failing on it.
How to read pricing
A price is a list of price lines, one per thing the model meters — input
tokens, output tokens, reasoning tokens, images, seconds. Each line in
sale_prices_by_billing_unit carries:
| Field | Meaning |
|---|---|
billing_unit.code |
What the line meters, e.g. input_tokens, output_tokens. Key on this |
billing_unit.name |
Display name of the same, e.g. Input Tokens |
billing_unit.description |
Free-text description. May be "" |
billing_unit.unit |
The measures this billing unit can be counted in. Always present; [] when none |
billing_unit.id |
The billing unit's identifier |
unit |
The measure this line is counted in, e.g. tokens, image |
unit_scale |
The divisor the price is quoted per, as a string. "1000000" with unit tokens is a price per 1M tokens |
sale_price |
The price per unit_scale × unit, as a decimal string |
To price a request: for each line, divide the metered quantity by unit_scale
and multiply by sale_price. With the example above, 200,000 input tokens on
the default rule cost 200000 / 1000000 × 2.5 = 0.5 USD.
- List price, not your price. Prices are not adjusted for any discount package the account holds. An account on one is billed less than shown here; the billed amount is on each usage record.
- Prices are unrounded.
sale_priceis published exactly as it is billed — parse it with a decimal type, not a float. - Match lines by
billing_unit.code, not by position. A model may price billing units beyond input and output, and may not price both. - No price at all is
currency""withsale_prices_by_billing_unitandrulesboth[]. Every list is[]when empty, nevernull.
Conditional pricing
A model can charge different rates depending on the request — a cheaper rate up
to 128k input tokens, for example. Each entry in rules is one rate card:
| Field | Meaning |
|---|---|
id |
The rule's identifier |
name |
Display name, e.g. input<=128k |
conditions |
What must hold for the rule to apply. All must hold |
sale_prices_by_billing_unit |
The price lines that apply when it does, shaped as above |
Each condition is one predicate:
| Field | Meaning |
|---|---|
attribute |
What the condition tests, e.g. Input Tokens |
operator |
One of eq, neq, gt, gte, lt, lte, in, nin |
value |
The value compared against, as a string |
type |
How to read value: number, text or boolean |
The default rule is the one with an empty conditions array, and its lines
are the ones repeated in the model-level sale_prices_by_billing_unit. A model
priced only by conditional rules has no default rule, so its
sale_prices_by_billing_unit is [] while rules still carries its prices —
read rules whenever the top-level list is empty.
Conditions are published for display. Which rule a request was actually billed under is decided by the billing engine; the billed amount is on each usage record.
Paginate with has_more
There is no total count. Request pages until has_more is false.
page is clamped rather than rejected, so a loop that ignores has_more is
served the last page again and again instead of an error:
import requests
url = "https://console-api.siraya.ai/extapi/v1/models"
headers = {"Authorization": "Bearer <CONSOLE_TOKEN>"}
page, models = 1, []
while True:
body = requests.get(
url, headers=headers, params={"page": page, "page_size": 100}
).json()["data"]
models.extend(body["data"])
if not body["has_more"]:
break
page += 1
The order of the list is not part of the contract. Key on code, not on
position.
Which models are listed
The list is the models the account can call and this member may see:
- models from the account's resource pool, limited to the ones granted to the account — all of them, or only those of chosen providers;
- narrowed again to the member's own grants when the member holds a narrower set than the account;
- withdrawn models are never listed.
An account that is entitled to the catalogue but holds no model grants answers
200 with an empty data.data, not 403. An empty list and a refusal are
different answers.
Who may read
Two checks, and both must pass:
- the account must be equipped with the
res.modelsresource, and - the member must hold read on
res.models— or onres.models.provider, which is what the console grants when models are granted per provider.
The member's membership in the account must also be active.
Errors
| Status | error.code |
Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR |
account_id is not a UUID |
| 400 | BAD_REQUEST |
page or page_size is not an integer |
| 401 | UNAUTHORIZED / INVALID_CREDENTIALS |
Token missing, malformed, expired or revoked |
| 403 | FORBIDDEN |
The account is not equipped with res.models; the member lacks read on both res.models and res.models.provider; membership inactive; or the token is pinned to another account |
| 404 | NOT_FOUND |
account_id is not reachable with this token |
| 429 | RATE_LIMIT_EXCEEDED |
Per-user rate limit |
| 500 | INTERNAL_ERROR |
Account, membership or catalogue lookup failed |
Out-of-range page and page_size values are clamped, not rejected:
page_size=500 answers 200 with 100 models.
The console's search, owner and output-type filters are not available here; passing them has no effect.
Why 404 is deliberately vague
An account_id that does not exist and one outside your reach return the
same 404, so the parameter cannot be used to discover which account ids
are real. A pinned token is the exception: it has one valid account, so naming
any other gets a plain 403.