# Model catalog libraries

Read the live model list from TypeScript or Python: context windows, output limits, exact prices, modalities and reasoning levels, with an offline cache.

[`GET /v1/models`](/docs/reference/list-models) describes every model as an OpenRouter provider Model Document, with its limits and prices nested under each input and output. `@vultr/model-catalog` (TypeScript) and `vultr-model-catalog` (Python) flatten each one into a single model object. Every provider on [Coding agents](/docs/integrations/coding-agents) is built on them, and the two give the same answer for the same input.

```bash
npm install @vultr/model-catalog
pip install vultr-model-catalog
```

Neither has dependencies: Node 20 or later (or Bun), Python 3.11 or later.

```python
from vultr_model_catalog import load_catalog

# No key needed. The last good copy is kept in cache_path for when the network fails.
catalog = load_catalog(
    base_url="https://api.vultrinference.com/v1",
    cache_path=".vultr-catalog.json",
    max_age=300,
)

# Ready, with tool calling and a known context window
for model in catalog.agent_models:
    price = model.price_per_million()
    reasoning = model.reasoning
    efforts = " ".join(reasoning.supported_efforts or []) if reasoning else "-"
    print(model.id, model.context_window, price["prompt"], price["completion"], efforts)
```

```typescript
import { isAgentModel, loadCatalog, pricePerMillion } from "@vultr/model-catalog";

// No key needed. The last good copy is kept in cachePath for when the network fails.
const catalog = await loadCatalog({
  baseUrl: "https://api.vultrinference.com/v1",
  cachePath: ".vultr-catalog.json",
  maxAgeMs: 300_000,
});

// Ready, with tool calling and a known context window
for (const model of catalog.models.filter(isAgentModel)) {
  const price = pricePerMillion(model);
  const efforts = model.reasoning?.supportedEfforts?.join(" ") ?? "-";
  console.log(model.id, model.contextWindow, price.prompt, price.completion, efforts);
}
```

## What a model carries

Names are the Python ones; TypeScript uses camelCase (`context_window` is `contextWindow`).

| Field | What it holds |
| --- | --- |
| `id`, `name`, `description` | As `GET /v1/models` lists them |
| `context_window`, `max_output_tokens` | Tokens |
| `input_modalities`, `output_modalities` | What the model reads (`text`, `image`, `file`, `audio`, `video`) and makes (`text`, `embeddings`, `image` and the rest) |
| `pricing` | Exact USD per token, as strings. `price_per_million()` (`pricePerMillion(model)`) converts without rounding |
| `tools`, `structured_outputs`, `streaming` | Whether the model supports each |
| `supported_parameters`, `parameters` | The request parameters it takes, with their ranges |
| `reasoning` | `supported_efforts` (strongest first), whether a budget is enforced, and the defaults; `null` for a model that cannot reason |
| `is_ready`, `deprecation_date` | Whether it serves now, and when it will stop |

## Filters

- `is_chat` (`isChatModel`): the model outputs text. The catalog also holds embedding, rerank, image, transcription and decision models.
- `is_agent` (`isAgentModel`): a chat model a coding agent can drive, ready, with tool calling and a known context window. A safety classifier outputs text but calls no tools, so it is a chat model and not an agent model.

## Cache and failures

With a cache path, the last good answer is written there and served when a fetch fails; `catalog.source` says `network`, `cache` or `stale-cache`. An entry the library cannot read is skipped and reported in `issues`, never failing the rest. `CatalogError` is raised only when there is nothing to serve.

The list needs no key, so `apiKey` (`api_key`) is optional.
