---
title: fetch
description: Make HTTP requests from workflows with automatic serialization and retry semantics.
type: reference
summary: Use the workflow-aware fetch to make HTTP requests with automatic serialization and retry semantics.
prerequisites:
  - /docs/foundations/workflows-and-steps
related:
  - /docs/errors/fetch-in-workflow
  - /docs/foundations/idempotency
---

Makes HTTP requests from within a workflow. This is a special step function that wraps the standard `fetch` API, automatically handling serialization and providing retry semantics.

This is useful when you need to call external APIs or services from within your workflow.

<Callout type="warn">
Because workflow `fetch()` has retry semantics, use idempotency keys when the request mutates an external system, such as creating a charge, sending an email, or enqueueing work. See [Idempotency](/docs/foundations/idempotency).
</Callout>

<Callout>
`fetch` is a *special* type of step function provided and should be called directly inside workflow functions.
</Callout>

```typescript lineNumbers
import { fetch } from "workflow"

async function apiWorkflow() {
    "use workflow"

    // Fetch data from an API
    const response = await fetch("https://api.example.com/data") // [!code highlight]
    return await response.json()
}
```

## API Signature

### Parameters

Accepts the same arguments as web [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch)

<TSDoc
definition={`
import { fetch } from "workflow";
export default fetch;`}
showSections={['parameters']}
/>

### Returns

Returns the same response as web [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch)

<TSDoc
definition={`
import { fetch } from "workflow";
export default fetch;`}
showSections={['returns']}
/>

## Examples

### Basic Usage

Here's a simple example of how you can use `fetch` inside your workflow.

```typescript lineNumbers
import { fetch } from "workflow"

async function apiWorkflow() {
    "use workflow"

    // Fetch data from an API
    const response = await fetch("https://api.example.com/data") // [!code highlight]
    const data = await response.json()

    // Make a POST request
    const postResponse = await fetch("https://api.example.com/create", { // [!code highlight]
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({ name: "test" })
    })

    return data
}
```

We call `fetch()` with a URL and optional request options, just like the standard fetch API. The workflow runtime automatically handles the response serialization.

This API is provided as a convenience to easily use `fetch` in workflow, but often, you might want to extend and implement your own fetch for more powerful error handing and retry logic.

### Customizing Fetch Behavior

Here's an example of a custom fetch wrapper that provides more sophisticated error handling with custom retry logic:

```typescript lineNumbers
import { FatalError, RetryableError } from "workflow"

export async function customFetch(
    url: string,
    init?: RequestInit
) {
    "use step"

    const response = await fetch(url, init)

    // Handle client errors (4xx) - don't retry
    if (response.status >= 400 && response.status < 500) {
        if (response.status === 429) {
            // Rate limited - retry with backoff from Retry-After header
            const retryAfter = response.headers.get("Retry-After")

            if (retryAfter) {
                // The Retry-After header is either a number (seconds) or an RFC 7231 date string
                const retryAfterValue = /^\d+$/.test(retryAfter)
                    ? parseInt(retryAfter) * 1000  // Convert seconds to milliseconds
                    : new Date(retryAfter);        // Parse RFC 7231 date format

                // Use `RetryableError` to customize the retry
                throw new RetryableError( // [!code highlight]
                    `Rate limited by ${url}`, // [!code highlight]
                    { retryAfter: retryAfterValue } // [!code highlight]
                ) // [!code highlight]
            }
        }

        // Other client errors are fatal (400, 401, 403, 404, etc.)
        throw new FatalError( // [!code highlight]
            `Client error ${response.status}: ${response.statusText}` // [!code highlight]
        ) // [!code highlight]
    }

    // Handle server errors (5xx) - will retry automatically
    if (!response.ok) {
        throw new Error(
            `Server error ${response.status}: ${response.statusText}`
        )
    }

    return response
}
```

This example demonstrates:

- Setting custom `maxRetries` to 5 retries (6 total attempts including the initial attempt).
- Throwing [`FatalError`](/docs/api-reference/workflow/fatal-error) for client errors (400-499) to prevent retries.
- Handling 429 rate limiting by reading the `Retry-After` header and using [`RetryableError`](/docs/api-reference/workflow/retryable-error).
- Allowing automatic retries for server errors (5xx).
