---
title: "useApiFetch"
description: "An API request composable built on Nuxt useFetch, providing automatic authentication, business status code checking and Toast notifications."
canonical_url: "https://nuxt.mhaibaraai.cn/en/docs/api/use-api-fetch"
---
# useApiFetch

> An API request composable built on Nuxt useFetch, providing automatic authentication, business status code checking and Toast notifications.

## Usage

Use the auto-imported `useApiFetch` composable for API requests. Built on top of Nuxt [`useFetch`](https://nuxt.com/docs/api/composables/use-fetch), it provides automatic authentication, data unwrapping, business status code checking and unified error handling.

```vue
<script setup lang="ts">
interface User {
  id: number
  name: string
  email: string
}

// Basic usage (automatically unwraps the data field)
const { data, pending, error, refresh } = await useApiFetch<User[]>('/users')

// POST request
const { data } = await useApiFetch('/users', {
  method: 'POST',
  body: { name: 'test', email: 'test@example.com' }
})

// Use a different endpoint
const { data } = await useApiFetch('/users', { endpoint: 'v2' })
</script>
```

- `useApiFetch` inherits all features of Nuxt [`useFetch`](https://nuxt.com/docs/api/composables/use-fetch) while providing additional API integration.
- Automatically retrieves the token from the session and adds it to request headers (via the `$api` instance).
- Automatically checks business status codes and throws errors.
- Built-in Toast notifications with customizable configuration.

## Data Request Patterns

Movk Nuxt provides three data request composables with identical signatures — choose based on execution timing:

| Composable | Execution | Blocks Navigation | Use Case |
| --- | --- | --- | --- |
| [`useApiFetch`](https://nuxt.mhaibaraai.cn/docs/api/use-api-fetch) | SSR + CSR | Yes | Above-the-fold core data |
| [`useLazyApiFetch`](https://nuxt.mhaibaraai.cn/docs/api/use-lazy-api-fetch) | SSR + CSR | No | Secondary / non-above-the-fold data |
| [`useClientApiFetch`](https://nuxt.mhaibaraai.cn/docs/api/use-client-api-fetch) | CSR only | No | Non-SEO-sensitive data |

`useApiFetch` fires the request during SSR, delivering data on first render:

```vue [ApiQuickstartSsrExample.vue]
<script setup lang="ts">
interface Profile {
  id: string
  name: string
  email: string
  role: string
}

const { data, pending, error, refresh } = await useApiFetch<Profile>('/profile')
</script>

<template>
  <div class="flex flex-col gap-3">
    <div class="flex items-center gap-2 flex-wrap">
      <UBadge v-if="pending" color="warning" variant="subtle">
        pending
      </UBadge>
      <UBadge v-else-if="error" color="error" variant="subtle">
        error
      </UBadge>
      <UBadge v-else color="success" variant="subtle">
        ready
      </UBadge>
      <UButton size="sm" variant="outline" icon="i-lucide-refresh-cw" @click="refresh()">
        刷新
      </UButton>
    </div>
    <pre class="text-xs p-3 rounded bg-elevated overflow-auto">{{ error ? { error: error.message } : data }}</pre>
  </div>
</template>
```

## Data Unwrapping and Transformation

`useApiFetch` already receives the business data unwrapped by [`$api`](https://nuxt.mhaibaraai.cn/docs/api/api-plugin#data-unwrapping-and-business-validation); the generic `T` directly declares that data type. `transform` receives the unwrapped data for a secondary transformation:

```ts
// Generic T directly declares the business data type (the type of the data field)
const { data } = await useApiFetch<User>('/user')
// data.value = { id: 1, name: 'test' }

// Combine with transform for a secondary transformation
const { data } = await useApiFetch<{ content: User[] }, SelectItem[]>('/users', {
  transform: ({ content }) => content.map(u => ({ label: u.name, value: u.id }))
})
```

> [!TIP]
> See: /docs/api/api-plugin#data-unwrapping-and-business-validation
> 
> See the 
> 
> $api
> 
>  page for interactive examples of the unwrapping mechanism and 
> 
> skipUnwrap
> 
>  / 
> 
> skipBusinessCheck

## Business Status Code Checking

The `$api` interceptor automatically checks the business status code and throws an `ApiError` (see [Error Classification](#error-classification) below) when the code is not in `successCodes`. Pass `skipBusinessCheck: true` to skip validation (no `ApiError` is thrown), but the `dataKey` field is still unwrapped:

```ts
const { data } = await useApiFetch('/external', {
  skipBusinessCheck: true
})
```

> [!TIP]
> See: /docs/api/api-plugin#data-unwrapping-and-business-validation
> 
> See interactive examples for business validation and 
> 
> skipBusinessCheck
> 
>  on the 
> 
> $api
> 
>  page

## Error Classification

The module only produces one custom error type — `ApiError` (business errors) — all others are ofetch's native `FetchError`. Distinguish scenarios with `isBusinessError` and `statusCode`. Select an error type in the dropdown to observe the shape of `error.value`:

- `business`: HTTP 200 but `code` not in `successCodes` → `ApiError`, `isBusinessError` is true.
- `http-400` / `http-500`: Native `FetchError` with `statusCode` set to the corresponding code.
- `http-422`: `FetchError` whose `data` field carries additional server information.
- `network`: Connection forcibly cut → `FetchError` with no `statusCode`.

```vue [ApiErrorsExample.vue]
<script setup lang="ts">
import type { ApiError } from '@movk/nuxt'
import type { FetchError } from 'ofetch'

const props = defineProps<{
  mode: 'business' | 'http-400' | 'http-422' | 'http-500' | 'network'
}>()

const urlMap: Record<typeof props.mode, string> = {
  'business': '/demo/errors?type=business',
  'http-400': '/profile?fail=1',
  'http-422': '/demo/errors?type=422',
  'http-500': '/demo/errors?type=500',
  'network': '/demo/errors?type=network'
}

const { error, execute } = useApiFetch(() => urlMap[props.mode], {
  immediate: false,
  toast: false
})

const info = computed(() => {
  const err = error.value
  if (!err) return null
  const apiErr = err as Partial<ApiError>
  const fetchErr = err as FetchError
  return {
    kind: apiErr.isBusinessError ? 'ApiError（业务错误）' : 'FetchError',
    statusCode: apiErr.statusCode ?? fetchErr.statusCode ?? null,
    message: err.message,
    isBusinessError: apiErr.isBusinessError ?? false,
    data: fetchErr.data ?? null
  }
})
</script>

<template>
  <div class="flex flex-col gap-3">
    <UButton size="sm" color="error" variant="outline" icon="i-lucide-circle-alert" @click="execute()">
      触发 {{ mode }}
    </UButton>
    <p class="text-xs text-muted">
      模块只产出 <code>ApiError</code>（业务错误）一种自定义错误，其余均为 ofetch 原生 <code>FetchError</code>；通过 <code>isBusinessError</code> 与 <code>statusCode</code> 区分场景。
    </p>
    <pre class="text-xs p-3 rounded bg-elevated overflow-auto">{{ info }}</pre>
  </div>
</template>
```

## Toast Notifications

The `toast` option supports per-request configuration: `false` to disable all, `{ success: false }` to keep only errors, `successMessage` / `errorMessage` for quick text, or a full set of [`Toast`](https://ui.nuxt.com/components/toast) props (`color`, `icon`, `duration`, etc.):

```ts
// Disable Toast / keep error only / quick text
await useApiFetch('/users', { toast: false })
await useApiFetch('/users', { toast: { success: false } })
await useApiFetch('/users', {
  toast: { successMessage: 'Created!', errorMessage: 'Failed, please retry' }
})

// Full Toast props
await useApiFetch('/users', {
  toast: {
    success: { title: 'Created', color: 'success', icon: 'i-lucide-circle-check' },
    error: { title: 'Failed', color: 'error', duration: 5000 }
  }
})

// Re-enable a globally disabled toast for one request
await useApiFetch('/users', { toast: { success: { show: true } } })

// Custom text counts as the same intent: it shows even when the toast is
// globally disabled or the method misses the whitelist
await useApiFetch('/users', { toast: { successMessage: 'Created!' } })
```

> [!NOTE]
> 
> Precedence: request-level 
> 
> show
> 
>  > request-level 
> 
> successMessage
> 
>  / 
> 
> errorMessage
> 
>  > global 
> 
> success.show
> 
>  / 
> 
> error.show
> 
>  / 
> 
> methods
> 
>  > global 
> 
> enabled
> 
> . With 
> 
> toast.success.show: false
> 
>  set globally, both 
> 
> { success: { show: true } }
> 
>  and 
> 
> { successMessage: '...' }
> 
>  still show the toast; pass 
> 
> { success: false }
> 
>  or 
> 
> { success: { show: false } }
> 
>  to stay silent.

> [!TIP]
> See: /docs/getting-started/configuration#toast-options
> 
> To notify only on POST / PUT / PATCH / DELETE, set 
> 
> toast.success.methods
> 
>  in the global or endpoint config — the field is not accepted per request

> [!TIP]
> See: /docs/api/api-plugin#toast-notifications
> 
> See interactive examples for five Toast modes on the 
> 
> $api
> 
>  page

## API

### useApiFetch()

`useApiFetch<T = unknown, DataT = T>(url: string | (() => string), options?: UseApiFetchOptions<T, DataT>): UseApiFetchReturn<DataT>`

Creates an API request.

#### Parameters

**url** (`string | (() => string)`) *required*: Request URL or a function returning the URL. Supports reactive URLs.

**options** (`UseApiFetchOptions<T, DataT>`): Request configuration options. Supports all Nuxt useFetch options plus additional API integration options.API Integration OptionsUse the specified endpoint configuration. Defaults to the default endpoint of $api.Toast notification configuration. Set to false to disable Toast.Success notification configuration. Can be a full Toast props object or false to disable success notifications.Error notification configuration. Can be a full Toast props object or false to disable error notifications.Quick text for success notifications. Equivalent to success: { title: '...' }.Quick text for error notifications. Equivalent to error: { title: '...' }.Skip business status code checking (no ApiError is thrown), but the dataKey field is still unwrapped. Defaults to false.Skip data unwrapping. When true, returns the full code/message/data envelope. Orthogonal to skipBusinessCheck. Defaults to false.Request Options (inherited from useFetch)HTTP method. Defaults to 'GET'.URL query parameters, automatically serialized into the URL.Alias for query.Request body. Automatically serialized to JSON for POST/PUT/PATCH requests.Request headers.Base URL. Usually configured via the endpoint option; no need to set manually.Response HandlingFunction to transform response data. Receives the unwrapped business data and returns the final data.Select only the specified fields from the response data.Factory function to set a default value.Execution OptionsWhether to execute the request immediately. Set to false to call execute() manually. Defaults to true.Whether to use lazy mode. In lazy mode, client-side navigation is not blocked and data loads in the background. Defaults to false.Whether to execute the request on the server. Defaults to true.Reactive sources to watch; the request re-fires automatically on change. Can be a ref, reactive object or array.Whether to deep-watch the watch objects. Defaults to true.Caching and DeduplicationCustom cache key. Used for sharing data between requests or deduplication.Strategy for handling duplicate requests. Defaults to cancel.'cancel' — Cancel the pending request'defer' — Do not initiate a new requestFunction to retrieve data from cache. The return value is used as the initial data.Request HooksHook called before the request is sent. Merged with built-in hooks.onRequest({ request, options }) {
  console.log('Sending request:', request.url)
}
Hook called on request error. Merged with built-in hooks.onRequestError({ request, error }) {
  console.error('Request error:', error)
}
Hook called when a response is received. Built-in hooks run first, so response._data is already the unwrapped business data (unless skipUnwrap: true); only fires on business success. Return values are ignored by ofetch — use transform for output transformations.onResponse({ response }) {
  console.log('Unwrapped data:', response._data)
}
Hook called on response error (4xx/5xx). Merged with built-in hooks.onResponseError({ response }) {
  console.error('Response error:', response.status)
}

#### Type Parameters

**T** (`type`): Business data type (already automatically unwrapped by $api). This is the data type of the data field in the API response.

**DataT** (`type`): The final type after transform conversion. Defaults to T.

#### Returns

Returns the response object of [`useFetch`](https://nuxt.com/docs/api/composables/use-fetch#return-values), including:

**data** (`Ref<DataT | null>`): Response data (unwrapped and transformed). Initially null; updated to the actual data after a successful request.

**error** (`Ref<FetchError | ApiError | null>`): Error object. Network errors are FetchError; business status code errors are ApiError (with statusCode, response and isBusinessError properties).

**status** (`Ref<'idle' | 'pending' | 'success' | 'error'>`): Request status.'idle' — Not yet started (only when immediate: false)'pending' — Request in progress'success' — Request succeeded'error' — Request failed

**pending** (`Ref<boolean>`): Whether a request is in progress. Equivalent to status.value === 'pending'.

**refresh** (`(opts?: { dedupe?: boolean }) => Promise<void>`): Refresh data by re-executing the request. Alias: execute.// Refresh data
await refresh()

// Skip deduplication check and force refresh
await refresh({ dedupe: false })

**execute** (`(opts?: { dedupe?: boolean }) => Promise<void>`): Alias for refresh. Manually execute the request (commonly used with immediate: false).

**clear** (`() => void`): Clear state: sets data to null, error to null and status to 'idle'.


## Sitemap

See the full [sitemap](https://nuxt.mhaibaraai.cn/sitemap.md) for all pages.
