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
| Name | Type | Description |
|---|---|---|
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
FatalErrorfor client errors (400-499) to prevent retries. - Handling 429 rate limiting by reading the
Retry-Afterheader and usingRetryableError. - Allowing automatic retries for server errors (5xx) by throwing a plain
Error.