Skip to content
External Console API

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.

GET https://console-api.siraya.ai/extapi/v1/models

Query Parameters

account_id string Default: your default account

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.

page integer Default: 1
1-indexed page number. Clamped to 1–1000.
page_size integer Default: 20
Models per page. Clamped to 1–100.
curl -G https://console-api.siraya.ai/extapi/v1/models \
  -H "Authorization: Bearer <CONSOLE_TOKEN>" \
  --data-urlencode "page=1" \
  --data-urlencode "page_size=20"
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_price is 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 "" with sale_prices_by_billing_unit and rules both []. Every list is [] when empty, never null.

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.models resource, and
  • the member must hold read on res.models — or on res.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.