# Errors and retries (https://www.cal-sdk.com/docs/concepts/errors)

Google and Outlook describe a missing event or a rejected token in different JSON, and your app shouldn't need to know either format. Every error calsdk throws extends `CalsdkError`, and provider API failures are mapped to the same classes on every provider, so a 404 from Google and a 404 from Outlook are both a `NotFoundError`.

## Check errors with `isInstance` [#check-errors-with-isinstance]

```ts
import { AuthenticationError, NotFoundError, getEvent } from '@cal-sdk/core';

try {
  await getEvent({ calendar, eventId });
} catch (error) {
  if (NotFoundError.isInstance(error)) return null;
  if (AuthenticationError.isInstance(error)) return reconnect(user);
  throw error;
}
```

Use `isInstance` rather than `instanceof`. It still works when your app ends up with two copies of calsdk installed, where `instanceof` returns `false` for an error thrown by the other copy.

## What to do with each [#what-to-do-with-each]

| Error                           | Thrown when                                           | Usually                             |
| ------------------------------- | ----------------------------------------------------- | ----------------------------------- |
| `InvalidArgumentError`          | An input value is invalid                             | Fix the call. `argument` names the field |
| `AuthenticationError`           | The token or password was rejected (401)              | Refresh the token or ask the user to reconnect |
| `PermissionDeniedError`         | The user may not do this (403)                        | Ask for a wider scope, or skip this calendar |
| `NotFoundError`                 | The calendar or event does not exist or is hidden (404, 410) | Drop your copy. `resource` is `'calendar'` or `'event'` |
| `ConflictError`                 | `ifMatch` no longer matches (412)                     | Read the event again and retry |
| `RateLimitError`                | The provider is throttling (429)                      | Retried for you. If it still fails, try later; `retryAfterMs` says when |
| `UnsupportedFunctionalityError` | The provider cannot do this                           | Check `capabilities`, or offer something else |
| `SyncCursorExpiredError`        | The sync cursor is no longer accepted                 | Drop the cursor and sync from scratch |
| `FreeBusyUnavailableError`      | `findFreeTime` could not read a calendar              | Leave that calendar out, or ask for access |
| `APICallError`                  | Any other failed request                              | `statusCode` and `isRetryable` say more |
| `InvalidResponseDataError`      | The provider sent data calsdk did not expect          | Report it: it is a provider bug |
| `NoSuchProviderVersionError`    | A provider built for another calsdk version           | Update calsdk and the provider together |

The [errors reference](/docs/reference/errors) lists every field.

## Retries [#retries]

calsdk retries these for you, twice by default:

- Rate limits (`RateLimitError`, or a 429).
- Timeouts (408), server errors (5xx) and network failures, marked `isRetryable` on `APICallError`.

It waits 1 second, then 2, doubling each time, or as long as the provider's `Retry-After` header says. A `Retry-After` over a minute is not waited out: the error is thrown, so a request never hangs, and your app can schedule the work for later.

Creates only retry rate limits. A rate limit means the provider did nothing, but a server error might come after the event was saved, and retrying could create it twice. Deletes that find the event already gone on a retry count as done.

```ts
await listEvents({ calendar, timeMin, timeMax, maxRetries: 5 });
await createEvent({ calendar, event, maxRetries: 0 }); // no retries
```

## Cancel a call [#cancel-a-call]

Pass an `AbortSignal` to stop a call, including any wait between retries:

```ts
await listEvents({ calendar, timeMin, timeMax, abortSignal: AbortSignal.timeout(10_000) });
```

## Private data stays out of messages [#private-data-stays-out-of-messages]

Error messages tend to end up in logs and error trackers, so they leave out what may be personal. Calendar ids are often email addresses, so `NotFoundError`, `SyncCursorExpiredError` and `FreeBusyUnavailableError` keep them in fields, not in the message. `APICallError` never stores request headers, which carry the access token. Response bodies are kept in `responseBody` for debugging, so be careful where you log them.
