# Troubleshooting (/docs/troubleshooting)



## Error messages [#error-messages]

| Message                                                     | Cause and fix                                                                                                                                                                    |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `addToUrl contains a falsy segment at index 0`              | An ID was `null`, `undefined` or `""`. Guard it, or use `addTemplateToUrl`                                                                                                       |
| `Failed to fetch`, `Network request failed` (`0`)           | Never reached the server; the text is the runtime's own error: CORS (check the console for the real reason), offline, a wrong `baseUrl`, or an `http:` API from an `https:` page |
| `Request timed out` (`408`)                                 | The per-attempt `timeout` (default 30 s) ran out. Raise it, or pass `timeout: 0`                                                                                                 |
| `Request aborted` / `Request canceled: …` (`0`)             | A deliberate [cancellation](/docs/cancellation). Check `res.canceled`                                                                                                            |
| `A ReadableStream body cannot be sent through a Web Worker` | Send a `Blob` or `File`, or use a client with `worker: false`                                                                                                                    |
| `Access token expired during a streamed upload…`            | The token was refreshed, but a stream can't be re-sent. Retry with a fresh stream, and use `uploadSkewMs`                                                                        |
| `No base URL for "/users"…`                                 | On the server there's no page origin to fall back to. Set `baseUrl`                                                                                                              |
| `Client destroyed`                                          | The client was used after `destroy()`, or destroyed with requests in flight. Don't destroy a shared client from a component                                                      |
| `Plugin "x" failed …`                                       | A plugin hook threw. Only that call failed                                                                                                                                       |

## Requests go to my own app instead of the API [#requests-go-to-my-own-app-instead-of-the-api]

No `baseUrl` was found, so the browser client used the page's origin. Check what detection sees:

```ts
import { detectBaseUrl } from "@mrzr/api-client";
console.log(JSON.stringify(detectBaseUrl()));   // "" means nothing was found
```

The usual cause is a variable the bundler doesn't expose to the browser (`API_URL` instead of `NEXT_PUBLIC_API_URL` or `VITE_API_URL`), or a dev server not restarted after editing `.env`. Passing `baseUrl` explicitly avoids all of it. See [environment variables](/docs/frameworks#setting-baseurl-with-environment-variables).

## Logged out after every reload [#logged-out-after-every-reload]

* The default `storage: "memory"` is designed not to survive a reload. Use [cookie mode](/docs/authentication#cookie-mode), or accept `storage: "local"`.
* In cookie mode, `isAuthenticated` starts `false` on every load because JavaScript can't see httpOnly cookies. Call `api.restoreSession("/auth/me")` on startup.
* Two clients with different `storageKey`s don't share a session.
* In Nuxt or another SSR framework, create the client in client-only code, once. A new client per render starts with no session.

## Logged out right after logging in [#logged-out-right-after-logging-in]

The client found no tokens in the login response. Look at what the server sends:

```ts
const res = await api.post("/auth/login", creds, { skipAuth: true, fullData: true });
console.log(JSON.stringify(res.data, null, 2));
```

Then describe its shape with [`extractTokens`](/docs/token-refresh#custom-token-shapes).

## `api.isWorker` is `false` in the browser [#apiisworker-is-false-in-the-browser]

1. `worker: false` is set.
2. `extractTokens` or `buildRefreshBody` is a function. Use the object forms.
3. The CSP blocks `blob:` workers. Add `worker-src 'self' blob:`. Browsers often report this late, so `isWorker` can switch to `false` after the first request.

## `cancel()` returns `0` [#cancel-returns-0]

1. Cancellation isn't on: pass `cancel: true`, or use a `cancelScope`.
2. It's a write. Only `GET` is covered unless you set `cancelable: true` or `cancel: { methods: "all" }`.
3. The pattern doesn't match. Patterns match whole segments of the path, without origin or query. Compare with `api.pending().map((r) => r.path)`.

## Tabs don't sync [#tabs-dont-sync]

`multiTab: false` is set, the tabs use different `storageKey`s or origins (`localhost:3000` and `:3001` are different), or storage is `"memory"`, where only logout is shared. See [what syncs](/docs/worker-and-tabs#tabs-stay-in-sync).

## The CSRF header isn't sent [#the-csrf-header-isnt-sent]

It's only sent on `POST`, `PUT`, `PATCH`, `DELETE` and refresh, only to trusted origins, and only if `xsrfCookieName` or `getCsrfToken` finds a value. The CSRF cookie must not be httpOnly, or JavaScript can't read it.

## Cookies aren't sent cross-origin [#cookies-arent-sent-cross-origin]

Cookie mode sends `credentials: "include"`. The server also needs `Access-Control-Allow-Credentials: true`, an explicit `Access-Control-Allow-Origin` (never `*`), and cookies with `SameSite=None; Secure`, which requires HTTPS, locally too.

## `res.data` isn't what I expect [#resdata-isnt-what-i-expect]

* It's the whole wrapper: the body has no top-level `data` key to unwrap. Unwrap it yourself with `afterFunc: (d) => d.payload`.
* It's the payload but you need `meta` or `links`: read `res.body`, or pass `fullData: true`.
* It's `undefined` on success: a `204`, an empty body, or a body that isn't JSON. Check `res.headers?.["content-type"]`.
* Validation errors are missing: `errors` must be at the top level of the body. Otherwise read them from `e.data`.

## A Node process or test runner won't exit [#a-node-process-or-test-runner-wont-exit]

Pass `worker: false, multiTab: false` in tests and scripts, and call `api.destroy()` when done.

Still stuck? Open an [issue](https://github.com/mohammadreza-zr/api-client/issues) with the package version, your `createClient` options (no secrets), the full `IRes` or `ApiError`, and `api.isWorker`.
