# Errors

Part of [Build a custom machine](https://www.ownbox.io/sdk/guide).

A call the Ownbox will not make raises **`sdk.BoxRefused`**. Its `.code` is the reason, which a rewording never
changes; its message is one sentence saying what to change. Decide in code on `.code`, never on the words.

```python
try:
    r = m.fetch("https://api.example.com/v1/quotes", key="example")
except sdk.BoxRefused as e:
    m.save_setting("last_error", e.code)          # "host_not_allowed", "key_missing", ...
```

**Permissions and money**

| Code | Means | Fix |
|---|---|---|
| `not_permitted` | The grant does not cover this call. | Add the line the message names under `permissions:`. |
| `host_not_allowed` | The host is not under `fetch:`. | Add the exact host name. |
| `key_not_allowed` | The key is not under `keys:`. | Add it, with its host and label. |
| `key_wrong_host` | The key belongs to another host. | Fetch its own host, or add a key for this one. |
| `key_missing` | The owner has not entered the key yet. | Link the owner to `m.keys_page`. |
| `no_cap` | The owner has set no monthly limit for this key. | Link the owner to `m.keys_page`. |
| `over_cap` | This billing cycle's limit for the key is used. | Wait for the next cycle. |
| `over_budget` | The AI allowance for the month is used. | Catch it where a page must still answer. |
| `not_on_this_ai` | This Ownbox's AI is signed in with ChatGPT, which `m.think` cannot use yet. | Catch it, as `over_budget`. |
| `ai_unavailable` | The Ownbox's AI did not answer. | Try again on the next run. |
| `never_readable` | `m.secret` was called. | Name the key on the request instead. |

**Sizes and counts**

| Code | Means | Fix |
|---|---|---|
| `rate_limited` | Too many calls or fetches in a minute. | Slow down; try again on the next run. |
| `too_large` | A request, a value, a body or an answer is past its size. | Send less ([Limits](https://www.ownbox.io/sdk/guide/limits)). |
| `settings_full` | Its settings hold 2 MB already. | Keep large data in `m.data_dir()`. |
| `too_many_settings`, `too_many_jobs`, `too_many_tools`, `too_many_kinds`, `too_many_screens`, `too_many_panels` | Past a count. | Declare fewer. |

**Arguments.** Each `bad_*` code names a wrong argument, and the message says which and why: `bad_key`, `bad_idem`,
`bad_every`, `bad_name`, `bad_event`, `bad_args`, `bad_title`, `bad_path`, `bad_slot`, `bad_tool`, `bad_kind`,
`bad_run`, `bad_task`, `bad_prompt`, `bad_body`, `bad_headers`, `bad_url` and `bad_method`.

**The web**

| Code | Means | Fix |
|---|---|---|
| `https_only` | The address is not `https://`. | Use https. |
| `not_public` | The host points at a private address. | Use a public host. |
| `unreachable` | The host did not answer in time. | Try again on the next run. |
| `encoded_answer` | The host answered in a form the Ownbox cannot check for the key. | Ask the host for plain JSON or text. |

**The door.** The SDK handles these itself; a machine sees one only when something is wrong underneath it.

| Code | Means | Fix |
|---|---|---|
| `sdk_too_new` | The machine needs a newer SDK than this Ownbox has. | Wait for the Ownbox's update. |
| `unknown_call` | A call this Ownbox's door does not know. | Check `requires.sdk`. |
| `wrong_machine` | `sdk.machine()` names another machine. | Use the folder's own slug. |
| `unknown_run` | An answer for work nobody is waiting on. | None: the work had ended. |
| `box_error`, `bad_request`, `too_many_connections` | The door could not take the call. | Try again on the next run. |

**Dry runs.** `dry_run`, `not_tapped` and `dry_run_limit` are a test holding a call back ([Testing](https://www.ownbox.io/sdk/guide/testing)). Nothing
to fix, unless the call should have been made: then the owner taps the host.

**Answers with a status.** The people, conversation and send calls answer their outcome in a dict, and raise only
for what the grant does not allow (`not_permitted`, and `over_cap` past the day's count). `"unknown"` always means it
may have gone: never send it again.

| Call | `status` | Common `code` values |
|---|---|---|
| `m.send_dm`, `m.reply_to_comment` | `sent`, `duplicate`, `refused`, `unknown` | `taken_over`, `opted_out`, `box_stopped`, `window_closed`, `hourly_cap`, `not_claimed`, `too_old`, `not_ready`, `unknown_comment` |
| `m.send_email` | `sent`, `queued`, `duplicate`, `refused`, `unknown` | `no_consent`, `opted_out`, `unsubscribed`, `box_stopped`, `no_person`, `no_address`, `bad_draft`, `failed` |
| `m.notify` | `sent`, `skipped`, `refused` | `not_a_member`, `bad_text`, `off`, `outside_slot`, `already_sent`, `nobody` |
| `m.spend` | `recorded`, `duplicate`, `refused` | `no_cap`, `over_cap`, `not_granted`, `bad_units`, `bad_key` |
| `m.propose` | `waiting`, `repeat`, `refused` | `no_such_kind`, `bad_kind`, `bad_title`, `too_large`, `too_many_waiting` |

`sdk.NotYet` is raised by a call a custom machine cannot make yet, and `sdk.Reserved` by a name kept for a later
release; each says, in a sentence, what to use until then.
