Documentation

Errors

Handle failed responses, missing auth, response parsing, and global error observation.

Errors

Failed HTTP responses throw MicroApiError.

Missing required auth throws MicroAuthRequiredError before fetch is called.

Network errors, refresh errors, and JSON parsing errors are thrown as their original errors.

MicroApiError.status
response status number.
MicroApiError.statusText
response status text.
MicroApiError.data
parsed error body, text, or null.
MicroApiError.response
original Response object.
TypeScript
1import { MicroApiError, MicroAuthRequiredError } from "micro-rq";2 3try {4  await users.detail.fn("user-1")();5} catch (error) {6  if (error instanceof MicroApiError) {7    console.log(error.status, error.data);8  }9 10  if (error instanceof MicroAuthRequiredError) {11    console.log("The user must sign in first.");12  }13}

Global error observer

onError lets the API client observe request failures without replacing the original error.

Use it for logging, telemetry, or auth cleanup. TanStack Query still receives the original thrown error.

TypeScript
1const api = createMicroApi({2  name: "main",3  baseUrl: "/api",4  onError: (error, context) => {5    console.log(context.method, context.url, error);6  },7});

Handling 401 Unauthorized

For endpoints with authMode: "optional" or authMode: "required", a 401 automatically triggers tokenProvider.refreshAccessToken() when refresh is configured.

micro-rq retries the original request once after refresh succeeds. If the retry still returns 401, the final MicroApiError is thrown to TanStack Query and observed by onError.

Use global onError for auth cleanup after the final failure. Do not clear tokens on the first 401; micro-rq may still be able to refresh and retry.

Auto refresh does not run when the endpoint uses `authMode
"none"`.

Key points

  • Auto refresh happens only for 401.
  • The request is retried once, not forever.
  • After the final failure, TanStack Query receives the original MicroApiError.
TypeScript
1const api = createMicroApi({2  name: "main",3  baseUrl: "/api",4  tokenProvider,5  authHeader: (token) => ({6    Authorization: `Bearer ${token}`,7  }),8  onError: (error, context) => {9    if (error instanceof MicroApiError && error.status === 401) {10      // This is the final 401 after refresh/retry failed.11      localStorage.removeItem("accessToken");12      localStorage.removeItem("refreshToken");13 14      if (context.authMode === "required") {15        router.navigate("/login");16      }17    }18  },19});
TypeScript
1const meQuery = useQuery({2  ...auth.me.toQuery(),3  retry: false,4  onError: (error) => {5    if (error instanceof MicroApiError && error.status === 401) {6      toast.error("Your session expired. Please sign in again.");7    }8  },9});

Handling 403 Forbidden

403 means the request was authenticated, but the user is not allowed to perform that action.

micro-rq does not refresh the token for 403. Refreshing usually will not help because the access token is valid but lacks permission.

Handle 403 as an authorization or permissions problem: show an access-denied state, redirect to a safe page, or hide unavailable actions.

401
authentication problem; refresh may help.
403
authorization problem; refresh normally should not run.

Key points

  • Use TanStack Query onError for page-specific UI behavior.
  • Use global onError for telemetry or central permission logging.
TypeScript
1const deleteUser = useMutation({2  ...users.remove.toMutation(),3  onError: (error) => {4    if (error instanceof MicroApiError && error.status === 403) {5      toast.error("You do not have permission to delete this user.");6    }7  },8});

Forcing a token refresh

Automatic refresh is tied to 401 responses. If you need to refresh before a request, call tokenProvider.refreshAccessToken() manually.

This is useful before a sensitive action, before opening a long-lived screen, or after the app becomes active again.

After manual refresh succeeds, invalidate or refetch the queries that should use the new access token.

Key points

  • Manual refresh uses the same shared refresh promise, so parallel calls still share one refresh request.
  • Manual refresh throws if refresh is not configured or refresh fails.
  • Use queryClient.invalidateQueries() after refresh when existing cached data should be reloaded.
TypeScript
1async function refreshSession() {2  await tokenProvider.refreshAccessToken();3 4  await queryClient.invalidateQueries({5    queryKey: auth.me.baseKey(),6  });7}
TypeScript
1const saveSettings = useMutation({2  mutationFn: async (values: SettingsDto) => {3    await tokenProvider.refreshAccessToken();4 5    return settings.update.fn(values);6  },7});

Response parsing

Successful responses are parsed before they are returned from queryFn, mutationFn, or fn().

Key points

  • 204 responses return undefined.
  • Empty successful bodies return undefined.
  • JSON content types are parsed with JSON.parse.
  • Non-JSON bodies return text.
  • Empty error bodies become null on MicroApiError.data.