# The API, call by call

> What each Hub call does, what it takes and what comes back.

This page is the map. The full reference is generated from the code:

- **For reading:** the [API reference](https://insurgently.com/docs/model-hub/api), with every declaration, its signature and its comment.
- **For tools and coding agents:** [`model-hub-api.d.ts`](https://insurgently.com/docs/model-hub/api/model-hub-api.d.ts), every public declaration in one TypeScript file, and [`api.json`](https://insurgently.com/docs/model-hub/api/api.json), the same as data.

Both are also in the repo under [`docs/api/`](../../api/api.json).

## Finding the Hub

| Call | What it does |
|---|---|
| `getHub(app, wanted)` | Returns `{ hub }`, or `{ hub: null, reason, message }` when the Hub is missing or its API is too old. |
| `onHubReady(app, plugin, wanted, cb)` | Runs `cb(hub)` now if the Hub is loaded, and again each time it loads. |
| `isCompatible(actual, wanted)` | True when an API at `actual` serves a plugin built for `wanted`. |
| `hub.version` | The API's version. |

## Roles and tiers

| Call | What it does |
|---|---|
| `registerRoles(pluginId, roles)` | Declares your roles. Harmless to repeat. |
| `roles(pluginId)` | Your roles as they resolve now: the tier, the models to try, and every limit that applies. `tierMissing` is set when a role names a tier that doesn't exist. |
| `setRole(pluginId, role, override)` | Changes one of your roles; `null` puts it back to your default. Only the owning plugin may. |
| `openRoleEditor(pluginId)` | Opens the Hub's editor for your roles. |
| `tiers()` | Every tier as the user has set it: the seven built-in ones, then the user's own. Each has a stable `id`, a display `name` and `custom`. |
| `createTier(pluginId, name, policy)` | Asks the user to add a tier. The Hub shows a dialog naming your plugin. Resolves to the tier, or `null` if the user declines. |
| `deleteTier(pluginId, id)` | Asks the user to delete one of their own tiers. Resolves `true` or `false`. |

A role definition:

```ts
{
  name: 'mine.summarize',       // namespace.name, lowercase
  description: 'Summaries of a note',
  tier: 'quick',                // a tier's id or name; 'balanced' if left out
  ceilingUsd: 0.02,             // optional: this role's own limit per call
  monthlyBudgetUsd: 2,          // optional: this role's own limit per month
  allowAnyModel: false,         // true for interactive roles where the user picks a model per run
}
```

A role may also carry its own ordered `candidates` instead of a tier. Store the `id` that `createTier` returns; names keep working across renames.

## Calls

Every call takes a role name first. All of them reject with an error whose `code` is one of the [refusal codes](errors.md).

| Call | What it does |
|---|---|
| `complete(role, messages, opts?)` | One call. Returns the text, plus tool calls, sources and a structured `object` when asked for. |
| `stream(role, messages, opts?)` | The same, with `textStream` to read as it arrives. |
| `embed(role, values, opts?)` | Embeddings for a batch of texts. |
| `decide(role, request, opts?)` | Typed decisions: yes/no, pick one, or a score. `calibrated` says whether the probabilities come from a decision model. |
| `batch(items, fn, opts?)` | Runs many calls with limited concurrency, reporting progress and cost. Stops early on an overcharged call, a spend limit or an abort. |

Options on `complete` and `stream`:

| Option | What it does |
|---|---|
| `system` | The system prompt. |
| `maxOutputTokens` | The longest reply allowed. The per-call limit is checked against it. |
| `tools` | Tools the model may call. They are returned, not run: calls are single-turn. |
| `output` | A schema (zod or JSON Schema) the reply must match. `complete` only. |
| `webSearch` | Ask the provider to search the web. |
| `skills` | Skills to apply, by name. |
| `needs` | Capabilities you need beyond what the options imply. |
| `for` | What the call is about, for the ledger. |
| `signal` | An `AbortSignal` to stop the call. |
| `tier`, `model`, `candidates` | Another tier, one model or a list for this run. Only for roles with `allowAnyModel`. |
| `routing` | Routing merged into each model's own for this call, such as an OpenRouter price cap. |

What every finished call reports:

| Field | What it holds |
|---|---|
| `servedModel`, `servedProvider` | Who answered. |
| `usage` | Tokens in and out, cached and reasoning tokens. |
| `listedUsd` | The cost at listed prices; `null` when unknown. |
| `billedUsd` | What the provider says it charged, when it says. |
| `overcharged` | Billed well above listed. |
| `attempts`, `fellBack` | Every model considered, in order, and whether the answer came from a fallback. |
| `budgets` | The monthly budgets that apply, with this month's spend; `warn` at 80%. |
| `ledgerId` | The call's row in the ledger. |

## Models, skills and usage

| Call | What it does |
|---|---|
| `models(filter?)` | The models the Hub knows, filtered by provider or by capability, for pickers. |
| `skills()` | The skills the Hub can apply: its own, those plugins registered, and the vault's. Each has `files()` for its own text files. |
| `registerSkills(pluginId, skills)` | Registers your built-in skills, each as a folder of text files with a `SKILL.md`. Replaces what you registered before. |
| `skillsReady()` | Resolves once the vault's skills have loaded. |
| `onSkillsChanged(cb)` | Calls `cb` when skills change. Returns the function that stops it. |
| `usage(filter?)` | Ledger rows: every call, with tokens and cost. |

A skill your plugin registers is used only when a call names it; it can't be always on. A vault skill with the same name overrides yours.

## Related

- [Add Model Hub to your plugin](index.md)
- [Refusal codes](errors.md)
