## Errors & limits Every rejection from the SDK is a `DialerError` with a stable `code`, the HTTP `status` of the mint response when there was one, and the original `cause`. Two subclasses carry extra fields. ### Classes | Class | code | Extra fields | | --- | --- | --- | | `DialerError` | any below | `code, status, cause` | | `DialerCapReachedError` | `cap_reached` | `used, included, cap, periodEnd (Date)` | | `DialerRateLimitedError` | `rate_limited` | `retryAfterSeconds` | ### Codes | code | Meaning | | --- | --- | | `cap_reached` | Daily minute cap hit. DialerCapReachedError with used, included, cap, periodEnd. Nothing to retry until periodEnd. | | `rate_limited` | Your server is minting too fast for this key. DialerRateLimitedError with retryAfterSeconds. | | `subscription_required` | No Dialer add-on on the workspace. | | `account_paywalled` | The account is past due. | | `line_unavailable` | The line id is unknown, not yours, inbound-only, or a shared/trial line. | | `not_provisioned` | Business registration not yet approved. | | `token_unavailable` | The tokenProvider threw, returned something that was not a token, or the mint failed for another reason. | | `invalid_state` | call() before start(), after destroy(), or while another call is in progress. | | `microphone_unavailable` | The browser denied microphone access or has no input device. | | `connection_failed` | Could not reach the voice service. | | `call_failed` | Anything else, including a malformed number passed to call(). See error.cause. | The mapping from the mint's server codes to these is in [Mint a token](https://api.tryprojectblue.com/#dialer-token). Branch on `code` or use `instanceof` for the two subclasses; do not match on `message`, which is written for end users and may change. ### Plans, caps and minutes Every call is metered against your Dialer plan in whole minutes. The allowance that arrives with each token tells you where you stand today, for the workspace and for the token's line. Three things follow from it: 1. **Soft threshold.** `allowance.state` becomes `"soft"` as you approach the included minutes. Nothing is refused; show a banner. 2. **Hard stop.** When the workspace or this line reaches its daily cap, the next mint returns `402 dialer_cap_reached` and the SDK emits `capReached`. New calls fail at the mint; calls in progress continue. The session resumes on its own after `periodEnd`, midnight UTC. 3. **Per-line caps.** A capped line is a hard stop for tokens pinned to it even while other lines on the account keep calling. In that case `used` and `cap` in the 402 body are the line's figures, not the workspace's. ### Limits | Limit | Value | What happens | | --- | --- | --- | | Token lifetime | `15 min` | SDK refreshes 60 s early by default (`refreshLeadSeconds`). | | Calls per token | `10` | SDK refreshes before the eleventh call; a well-behaved client never hits this. | | Mints per API key | `30 / min` | `429 rate_limited` with `retryAfterSeconds`. Separate from the messaging API's 60 / min. | | Concurrent calls per session | `1` | `call()` throws `invalid_state` while one is active. Open a second session for a second simultaneous call. | | Daily minutes | `per plan` | Read `allowance`. Resets at midnight UTC. | > **Package** > > [`@tryprojectblue/dialer` on npm](https://www.npmjs.com/package/@tryprojectblue/dialer), MIT licensed, ESM + CJS with bundled types, no framework dependency. Its README repeats the essentials of this section. **Handle errors — browser.ts** ```javascript import { DialerError, DialerCapReachedError, DialerRateLimitedError } from '@tryprojectblue/dialer'; try { await dialer.call(number); } catch (error) { if (error instanceof DialerCapReachedError) { // Nothing to retry until error.periodEnd (midnight UTC). disableDialPad(`Daily minutes used. Calling resumes ${error.periodEnd?.toLocaleTimeString()}.`); } else if (error instanceof DialerRateLimitedError) { setTimeout(retry, error.retryAfterSeconds * 1000); } else if (error instanceof DialerError && error.code === 'microphone_unavailable') { showBanner('Allow microphone access to place calls.'); } else if (error instanceof DialerError) { showBanner(error.message); // error.code, error.status, error.cause } else { throw error; } } ```