fetch

Make HTTP requests from workflows using a step-wrapped fetch.

Makes HTTP requests from within a workflow. This is a hoisted "use step" wrapper over globalThis.fetch, allowing you to call fetch directly inside a "use workflow" function. Because it runs as a step, thrown request failures follow the normal step retry policy, while ordinary HTTP responses (including 4xx and 5xx) are returned as normal Response objects.

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

fetch is a "use step" wrapper and should be called directly inside workflow functions. It does not add HTTP-status-aware retry behavior on top of the standard fetch API. If you want retries to depend on response.status, headers, or body content, wrap globalThis.fetch in your own "use step" function and throw FatalError or RetryableError yourself.

import { fetch } from "workflow"

async function apiWorkflow() {
    "use workflow"

    // Fetch data from an API
    const response = await fetch("https://api.example.com/data") 
    return await response.json()
}

API Signature

Parameters

Accepts the same arguments as web fetch

NameTypeDescription
args[input: string | URL | Request, init?: RequestInit | undefined]

Returns

Returns the same response as web fetch

Promise<Response>

Examples

Basic Usage

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

import { fetch } from "workflow"

async function apiWorkflow() {
    "use workflow"

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

    // Make a POST request
    const postResponse = await fetch("https://api.example.com/create", { 
        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. Because fetch runs as a step, the workflow runtime handles serialization and replay. If the request throws, normal step retry behavior applies; if it returns a Response, your code decides whether that response should be treated as success, fatal failure, or retryable failure.

This API is provided as a convenience to easily use fetch in a workflow, but you may want to write your own "use step" wrapper when retries should depend on HTTP response details such as status, headers, or body content.

Customizing Fetch Behavior

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

import { FatalError, RetryableError } from "workflow"

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

    const response = await globalThis.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( 
                    `Rate limited by ${url}`, 
                    { retryAfter: retryAfterValue } 
                ) 
            }
        }

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

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

    return response
}

This example demonstrates:

  • Throwing FatalError for client errors (400-499) to prevent retries.
  • Handling 429 rate limiting by reading the Retry-After header and using RetryableError.
  • Allowing automatic retries for server errors (5xx) by throwing a plain Error.

On this page

GitHubEdit this page on GitHub