> ## Documentation Index
> Fetch the complete documentation index at: https://bundleup.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Handling Errors

> Tell BundleUp errors from provider errors, and decide what to do about each.

An error from a BundleUp request comes from one of two places:

* **BundleUp**, before the request reached the provider: a bad key, an unknown connection, the rate limit, a failed token refresh.
* **The provider**, passed through unchanged: a missing scope, a resource that doesn’t exist, the provider’s own rate limit.

They need different handling, so the first step is always telling them apart.

```mermaid theme={null}
flowchart TD
    E[Non-2xx response] --> Q{"JSON body with a BundleUp<br/>code, or the Unified API<br/>error envelope?"}
    Q -->|Yes| BU[BundleUp error]
    Q -->|No| PR["Provider error<br/>(provider's status and body)"]

    BU --> B401["401: API key missing or wrong<br/>→ fix configuration, don't retry"]
    BU --> B400C["400 connection_invalid / 404 (Unified)<br/>→ connection gone: stop using it, ask user to reconnect"]
    BU --> B400R["400 connection_refresh_failed<br/>→ retry later; if it persists, ask user to reconnect"]
    BU --> B400M["400 mcp_not_supported / 404 (Unified)<br/>→ use the Proxy for this provider"]
    BU --> B429["429 rate_limit<br/>→ wait Retry-After, slow down"]

    PR --> P401["401 / 403<br/>→ revoked access or missing scope"]
    PR --> P404["404<br/>→ resource doesn't exist"]
    PR --> P429["429<br/>→ provider's limit; back off"]
    PR --> P5xx["5xx / 408 / 502<br/>→ already retried for GETs; retry later"]
```

Look at the body first. BundleUp’s own errors have a recognizable JSON body; everything else is the provider’s. Then act on the status.

## BundleUp errors

| Status | Code | What happened | What to do |
| - | - | - | - |
| `401` | — | API key missing or not recognized. | Check your configuration. Retrying won’t help. |
| `400` | `connection_invalid` | Connection ID missing, unknown, deleted, or from another workspace. | Stop using the connection. If it should exist, ask the user to reconnect. |
| `400` | `connection_refresh_failed` | The token expired and couldn’t be refreshed. | Retry later. If it keeps failing, the user probably revoked access. Ask them to reconnect. |
| `400` | `integration_not_found` | The connection’s integration was deleted. | The connection is unusable. Recreate the integration and have the user reconnect. |
| `400` | `mcp_not_supported` | MCP only: the provider has no MCP server. | Use the [Proxy](/docs/guides/proxy/overview) for this provider. |
| `429` | `rate_limit` | Over 100 requests in 60 s on this connection. | Wait for `Retry-After` (60 s), then slow down. See [Rate limits](/docs/guides/proxy/rate-limits). |
| `5xx` | — | An error inside BundleUp, or a failed token refresh the provider rejected. | Retry with backoff. |

The Unified API reports the same situations with its own status codes and envelope (`404` for an unknown connection or unsupported endpoint). See the [errors reference](/docs/api-reference/errors).

## Provider errors

Provider errors are passed through exactly as the provider sent them, so the provider’s documentation is the authority. Common cases:

| Status | Usually means | What to do |
| - | - | - |
| `401` | The user revoked access, or the token is no longer valid. | Ask the user to [reconnect](/docs/guides/auth/connection-lifecycle#when-users-revoke-access-at-the-provider). |
| `403` | The token lacks a scope, or the user lacks permission on the resource. | Check the integration’s [scopes](/docs/guides/integrations/scopes). The body often names what’s missing. |
| `404` | The resource doesn’t exist, or the user can’t see it. | Handle as not found. |
| `422` / `400` | The request body or parameters were invalid. | Fix the request. |
| `429` | The provider’s own rate limit. | Back off using the provider’s headers. Idempotent requests were already retried. |
| `408`, `502`, `503`, `504` | The provider timed out or was unavailable. | `GET`s were already [retried](/docs/guides/proxy/retries). Retry later, or retry writes if they’re idempotent. |

## A pattern for your code

```typescript theme={null}
async function callProvider(connectionId: string, path: string) {
  const response = await client.proxy(connectionId).get(path);
  if (response.ok) return response.json();

  const contentType = response.headers.get('content-type') ?? '';
  const body = contentType.includes('json') ? await response.json() : await response.text();

  // BundleUp's own errors carry a "code"
  if (typeof body === 'object' && body?.code) {
    switch (body.code) {
      case 'connection_invalid':
      case 'integration_not_found':
        await markNeedsReconnect(connectionId);
        throw new NeedsReconnectError();
      case 'connection_refresh_failed':
        await recordRefreshFailure(connectionId); // reconnect after repeated failures
        throw new RetryLaterError();
      case 'rate_limit':
        throw new RetryLaterError(Number(response.headers.get('Retry-After') ?? 60));
    }
  }

  // Otherwise it's the provider's error
  if (response.status === 401) {
    await markNeedsReconnect(connectionId);
    throw new NeedsReconnectError();
  }
  throw new ProviderError(response.status, body);
}
```

## Designing for reconnects

Every integration eventually loses access for some users: they leave the company, revoke your app, or change their password. Plan for it:

* Store a status next to each connection in your database (`active`, `needs_reconnect`).
* Set `needs_reconnect` on `connection_invalid`, on repeated `connection_refresh_failed`, or on a provider `401`.
* Show a clear “Reconnect” button that runs the [authorization flow](/docs/guides/auth/authorization-flow) again.
* After reconnecting, store the new connection ID and delete the old connection.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.