# Refusal codes

> Every code a Hub error can carry, and what to do about each.

The Hub never falls back silently. Every refusal or failure is an error with a `code`, a `message` written for the user, and `details`. Show the message; branch on the code.

```ts
try {
  await hub.complete('mine.summarize', messages);
} catch (e) {
  const err = e as Error & { code?: string };
  if (err.code === 'aborted') return;
  new Notice(err.message);
}
```

Refused and failed calls are written to the ledger at a cost of zero.

## Limits

| Code | When | What to do |
|---|---|---|
| `over-ceiling` | The call could cost more than a per-call limit. Checked before sending. | Show the message. Send less, or lower `maxOutputTokens`. |
| `over-budget` | A monthly budget on the role or its tier is used up. | Show the message. The user raises the limit or waits. |
| `unpriced-model` | The model has no listed price, so a limit can't be checked. | Show the message. |

## Models and providers

| Code | When | What to do |
|---|---|---|
| `no-models` | The role's tier has no models set. | Show the message; it points the user to the Hub's settings. |
| `unknown-provider` | A model's provider isn't set up in the Hub. | Show the message. |
| `unknown-model` | The model isn't one the Hub knows. | Show the message. |
| `provider-unavailable` | The provider has no key, or its server isn't answering. | Show the message. |
| `provider-error` | The provider returned an error. `details.retryable` says whether trying again may help. | Retry if retryable; otherwise show the message. |
| `capability-missing` | The model can't do what the call needs: tools, structured output, web search, images, embeddings. `details.missing` lists them. | Show the message. Declare `needs` so this is caught on every model. |
| `model-not-allowed` | The call named a tier, model or list for one run, and the role doesn't allow it. | Set `allowAnyModel` on roles where the user picks a model. |

## Your own calls

| Code | When | What to do |
|---|---|---|
| `invalid-role-name` | A role isn't named `namespace.name` in lowercase. | Fix the name. |
| `role-taken` | The namespace belongs to another plugin, or you tried to change a role you don't own. | Use your own namespace. |
| `unknown-role` | No plugin has registered the role. | Register before calling. |
| `unknown-tier` | `deleteTier` named a tier that doesn't exist. | Check `tiers()` first. |
| `invalid-tier-name` | A tier's name is taken, empty, over 40 characters or a built-in tier's. | Choose another name. |
| `invalid-skill` | A registered skill has no or a bad `SKILL.md`, a path outside its folder, a repeated name, or `mode: always`. | Fix the skill. |
| `skill-taken` | The skill's name belongs to another plugin or to the Hub. | Choose another name. |
| `invalid-request` | The request is malformed. | Fix the call. |
| `aborted` | Your `signal` stopped the call. | Nothing; the user stopped it. |

## Related

- [The API, call by call](api.md)
