Skip to content
// EFFECT DAYS 2026

HttpClient unstable

Provides the service used to run outgoing HTTP requests.

HttpClient executes immutable HttpClientRequest values and returns HttpClientResponse values. Keeping HTTP behind this service lets programs, tests, and generated API clients use the same request model without depending on one concrete platform transport. This module includes request accessors, constructors and layers, request and response transformations, status filtering, retries, rate limiting, cookies, redirect handling, scoped request abortion, and tracing support.

50 exports Since v4.0.0 Source

Accessors

del

unstable
Since v4.0.0

Executes a DELETE request using the HttpClient service from the environment.

Signature

declare const del: (url: string | URL, options?: HttpClientRequest.Options.NoUrl) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error.HttpClientError, HttpClient>

execute

unstable
Since v4.0.0

Executes a prebuilt HttpClientRequest using the HttpClient service from the environment.

Signature

declare const execute: (request: HttpClientRequest.HttpClientRequest) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error.HttpClientError, HttpClient>

get

unstable
Since v4.0.0

Executes a GET request using the HttpClient service from the environment.

Signature

declare const get: (url: string | URL, options?: HttpClientRequest.Options.NoUrl) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error.HttpClientError, HttpClient>

options

unstable
Since v4.0.0

Executes an OPTIONS request using the HttpClient service from the environment.

Signature

declare const options: (url: string | URL, options?: HttpClientRequest.Options.NoUrl) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error.HttpClientError, HttpClient>

patch

unstable
Since v4.0.0

Executes a PATCH request using the HttpClient service from the environment.

Signature

declare const patch: (url: string | URL, options?: HttpClientRequest.Options.NoUrl) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error.HttpClientError, HttpClient>

post

unstable
Since v4.0.0

Executes a POST request using the HttpClient service from the environment.

Signature

declare const post: (url: string | URL, options?: HttpClientRequest.Options.NoUrl) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error.HttpClientError, HttpClient>

put

unstable
Since v4.0.0

Executes a PUT request using the HttpClient service from the environment.

Signature

declare const put: (url: string | URL, options?: HttpClientRequest.Options.NoUrl) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error.HttpClientError, HttpClient>

query

unstable
Since v4.0.0

Executes a QUERY request using the HttpClient service from the environment.

Signature

declare const query: (url: string | URL, options?: HttpClientRequest.Options.NoUrl) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error.HttpClientError, HttpClient>

Circuit Breaking

The default withCircuitBreaker classifier.

Details

  • Failure: status 408, 500, 502, 503 or 504, returned or wrapped in a StatusCodeError, and any TransportError or Cause.TimeoutError.
  • Success: any other response, including other 4xx, since the dependency answered.
  • Ignored: status 429 (left to withRateLimiter), EncodeError, InvalidUrlError, DecodeError, EmptyBodyError, unknown typed errors and defects. HttpClient errors are typed, so a defect here is a bug in the caller.

When a cause holds several errors, a failure beats a success and a success beats an ignored outcome. A failure carries the longest Retry-After it saw. The result is an Effect only because parsing an HTTP-date Retry-After reads Clock.

Gotchas

TransportError also covers client-side refusals, which count as failures.

Signature

declare function circuitBreakerClassifier<E>(exit: Exit<HttpClientResponse, E>): Effect<Outcome>

Guards requests with a CircuitBreaker.

Details

Requests are keyed by origin unless key is set, and classified with circuitBreakerClassifier unless classify is set. A rejected request fails with CircuitBreakerError without reaching the wrapped client.

Apply it after withRateLimiter and retryTransient, so the breaker sees one outcome per call and an open circuit skips the retry loop. retryTransient never retries a CircuitBreakerError. A custom classify can delegate the cases it doesn't handle to circuitBreakerClassifier.

Gotchas

  • The breaker only sees the response headers. To count an error in the body, lift it into the error channel below the breaker with transformResponse and count it in classify.
  • withRateLimiter retries 429 without limit by default, which can hold the probe slot for minutes. Set its times.
  • A rejected request has no client span. The rejection fails the caller's span instead.

Example (Counting an error in the body as a failure)

This API answers 200 with { "status": "degraded" } when it is failing.

Signature

declare const withCircuitBreaker: {
<E, R2 = never>(options: Options<E, R2>): <R>(self: With<E, R>) => With<E | CircuitBreakerError, R2 | R>;
<E, R, R2 = never>(self: With<E, R>, options: Options<E, R2>): With<E | CircuitBreakerError, R | R2>;
}

Example Guarding an API in the recommended order

import { Effect, Layer, Ref, Schedule } from "effect"
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"
import { CircuitBreaker, RateLimiter } from "effect/persistence"
const program = Effect.gen(function*() {
const requests = yield* Ref.make(0)
// A dependency that always answers 503.
const unavailable = HttpClient.make((request) =>
Effect.as(Ref.update(requests, (n) => n + 1), HttpClientResponse.fromWeb(request, new Response(null, { status: 503 })))
)
const breaker = yield* CircuitBreaker.make({
tripPolicy: CircuitBreaker.consecutive(1),
openDuration: "30 seconds"
})
const client = unavailable.pipe(
HttpClient.withRateLimiter({
limiter: yield* RateLimiter.RateLimiter,
key: "inventory",
limit: 100,
window: "1 second",
times: 3
}),
HttpClient.retryTransient({ times: 2, schedule: Schedule.exponential("1 millis") }),
HttpClient.withCircuitBreaker({ breaker })
)
// Three attempts, then the breaker records one failure and opens.
yield* client.execute(HttpClientRequest.get("http://inventory.test/items"))
// Rejected before the retry loop starts, so no further request is sent.
const rejected = yield* Effect.flip(client.execute(HttpClientRequest.get("http://inventory.test/items")))
return [yield* Ref.get(requests), rejected._tag]
}).pipe(
Effect.scoped,
Effect.provide(Layer.merge(
CircuitBreaker.layerStoreMemory(),
RateLimiter.layer.pipe(Layer.provide(RateLimiter.layerStoreMemory))
))
)
await Effect.runPromise(program) // => [3, "CircuitBreakerError"]

Example Counting an error in the body as a failure

import { Data, Effect, Exit, Option } from "effect"
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http"
import { CircuitBreaker } from "effect/persistence"
class Degraded extends Data.TaggedError("Degraded") {}
const degraded = HttpClient.make((request) =>
Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ status: "degraded" })))
)
const program = Effect.gen(function*() {
const breaker = yield* CircuitBreaker.make({
tripPolicy: CircuitBreaker.consecutive(1),
openDuration: "30 seconds"
})
const client = degraded.pipe(
HttpClient.transformResponse(Effect.flatMap((response) =>
Effect.flatMap(response.json, (body) =>
typeof body === "object" && body !== null && "status" in body && body.status === "degraded"
? Effect.fail(new Degraded())
: Effect.succeed(response))
)),
HttpClient.withCircuitBreaker({
breaker,
classify: (exit) =>
Option.exists(Exit.findErrorOption(exit), (error) => error._tag === "Degraded")
? CircuitBreaker.failure()
: HttpClient.circuitBreakerClassifier(exit)
})
)
yield* Effect.flip(client.get("http://status.test/"))
const state = yield* breaker.state("http://status.test")
return state._tag
}).pipe(Effect.scoped, Effect.provide(CircuitBreaker.layerStoreMemory()))
await Effect.runPromise(program) // => "Open"

Constructors

make

unstable
Since v4.0.0

Constructs an HttpClient from a low-level request runner.

Details

The runner receives the request, resolved URL, abort signal, and current fiber. The client wrapper handles URL construction failures, tracing and propagation, header redaction, and aborting non-scoped requests on interruption.

Signature

declare function make(f: (request: HttpClientRequest, url: URL, signal: AbortSignal, fiber: Fiber<HttpClientResponse, HttpClientError>) => Effect<HttpClientResponse, HttpClientError>): HttpClient

makeWith

unstable
Since v4.0.0

Constructs an HttpClient.With from a preprocessing function and a postprocessing function.

Details

execute applies preprocessing to the request and then passes the resulting request effect to postprocessing.

Signature

declare function makeWith<E2, R2, E, R>(postprocess: (request: Effect<HttpClientRequest, E2, R2>) => Effect<HttpClientResponse, E, R>, preprocess: Preprocess<E2, R2>): With<E, R>

Cookies

Since v4.0.0

Adds a Ref of stored cookies to the client for handling cookies across requests.

When to use

Use to add shared cookie storage to a client so response cookies are retained and sent by later requests to a matching host, path, and scheme.

Details

Response cookies are merged into the ref with Cookies.mergeIntoArray for the response URL. When a request is sent, Cookies.toRequestCookieHeader selects the cookies for its final URL, after later transformations such as HttpClientRequest.prependUrl, and sets them as the cookie header. With followRedirects applied after withCookiesRef, this runs for every hop. Expiry uses the current Clock.

Signature

declare const withCookiesRef: {
(ref: Ref<readonly Array<Cookie>>): <E, R>(self: With<E, R>) => With<E, R>;
<E, R>(self: With<E, R>, ref: Ref<readonly Array<Cookie>>): With<E, R>;
}

Error Handling

catchTag

unstable
Since v4.0.0

Handles client failures with one or more matching _tag values and returns a transformed client.

Signature

declare const catchTag: {
<K extends string | readonly [Tags<E>, Tags<E>], E, E1, R1>(tag: K, f: (e: ExtractTag<NoInfer<E>, K extends readonly [string, string] ? K[number] : K>) => Effect<HttpClientResponse, E1, R1>): <R>(self: With<E, R>) => With<E1 | ExcludeTag<E, K extends readonly [string, string] ? K[number] : K>, R1 | R>;
<R, E, K extends string | readonly [Tags<E>, Tags<E>], R1, E1>(self: With<E, R>, tag: K, f: (e: ExtractTag<E, K extends readonly [string, string] ? K[number] : K>) => Effect<HttpClientResponse, E1, R1>): With<E1 | ExcludeTag<E, K extends readonly [string, string] ? K[number] : K>, R | R1>;
}

catchTags

unstable
Since v4.0.0

Handles client failures by matching their _tag values against a case map.

Signature

declare const catchTags: {
<E, Cases extends { [K in string]: (error: Extract<E, {
_tag: K;
}>) => Effect<HttpClientResponse, any, any> } | { [K in string]: (error: Extract<E, {
_tag: K;
}>) => Effect<HttpClientResponse, any, any> } & { [K in number | symbol]: never }>(cases: Cases): <R>(self: With<E, R>) => With<Exclude<E, {
_tag: keyof Cases;
}> | { [K in string | number | symbol]: Cases[K] extends (...args: Array<any>) => Effect<any, E, any> ? E : never }[keyof Cases], R | { [K in string | number | symbol]: Cases[K] extends (...args: Array<any>) => Effect<any, any, R> ? R : never }[keyof Cases]>;
<E extends {
_tag: string;
}, R, Cases extends { [K in string]: (error: Extract<E, {
_tag: K;
}>) => Effect<HttpClientResponse, any, any> } | { [K in string]: (error: Extract<E, {
_tag: K;
}>) => Effect<HttpClientResponse, any, any> } & { [K in number | symbol]: never }>(self: With<E, R>, cases: Cases): With<Exclude<E, {
_tag: keyof Cases;
}> | { [K in string | number | symbol]: Cases[K] extends (...args: Array<any>) => Effect<any, E, any> ? E : never }[keyof Cases], R | { [K in string | number | symbol]: Cases[K] extends (...args: Array<any>) => Effect<any, any, R> ? R : never }[keyof Cases]>;
}

retry

unstable
Since v4.0.0

Retries the request based on a provided schedule or policy.

Signature

declare const retry: {
<E, O extends NoExcessProperties<Options<E>, O>>(options: O): <R>(self: With<E, R>) => With<O extends {
schedule: Schedule.Schedule<infer _O, infer _I, infer _E, infer _R>;
} ? _E | E : O extends {
times: number;
} ? E : O extends {
until: Predicate.Refinement<E, infer E2>;
} ? E2 : E | O extends {
while: (...args: Array<any>) => Effect.Effect<infer _A, infer E, infer _R>;
} ? E : never | O extends {
until: (...args: Array<any>) => Effect.Effect<infer _A, infer E, infer _R>;
} ? E : never, R | O extends {
schedule: Schedule.Schedule<infer _O, infer _I, infer _E, infer R>;
} ? R : never | O extends {
while: (...args: Array<any>) => Effect.Effect<infer _A, infer _E, infer R>;
} ? R : never | O extends {
until: (...args: Array<any>) => Effect.Effect<infer _A, infer _E, infer R>;
} ? R : never>;
<B, E, ES, R1>(policy: Schedule<B, NoInfer<E>, ES, R1>): <R>(self: With<E, R>) => With<E | ES, R1 | R>;
<E, R, O extends NoExcessProperties<Options<E>, O>>(self: With<E, R>, options: O): With<O extends {
schedule: Schedule.Schedule<infer _O, infer _I, infer _E, infer _R>;
} ? _E | E : O extends {
times: number;
} ? E : O extends {
until: Predicate.Refinement<E, infer E2>;
} ? E2 : E | O extends {
while: (...args: Array<any>) => Effect.Effect<infer _A, infer E, infer _R>;
} ? E : never | O extends {
until: (...args: Array<any>) => Effect.Effect<infer _A, infer E, infer _R>;
} ? E : never, R | O extends {
schedule: Schedule.Schedule<infer _O, infer _I, infer _E, infer R>;
} ? R : never | O extends {
while: (...args: Array<any>) => Effect.Effect<infer _A, infer _E, infer R>;
} ? R : never | O extends {
until: (...args: Array<any>) => Effect.Effect<infer _A, infer _E, infer R>;
} ? R : never>;
<E, R, B, ES, R1>(self: With<E, R>, policy: Schedule<B, E, ES, R1>): With<E | ES, R | R1>;
}
Since v4.0.0

Retries common transient errors, such as rate limiting, timeouts or network issues.

When to use

Use to focus on retrying errors, transient responses, or both.

Details

Specifying a while predicate allows you to consider other errors as transient, and is ignored in "response-only" mode.

A Retry-After header on a transient response, or on the response of a status code error, sets the minimum delay before the next attempt.

Signature

declare const retryTransient: {
<E, B = never, ES = never, R1 = never, RetryOn extends "response-only" | "errors-only" | "errors-and-responses" = "response-only" | "errors-only" | "errors-and-responses", Input = RetryOn extends "errors-only" ? E : RetryOn extends "response-only" ? HttpClientResponse : HttpClientResponse | E>(options: {
readonly retryOn?: RetryOn;
readonly schedule?: Schedule.Schedule<B, NoInfer<Input>, ES, R1>;
readonly times?: number;
readonly while?: Predicate.Predicate<NoInfer<E | ES>>;
}): <R>(self: With<E, R>) => With<E | ES, R1 | R>;
<E, R, B = never, ES = never, R1 = never, RetryOn extends "response-only" | "errors-only" | "errors-and-responses" = "response-only" | "errors-only" | "errors-and-responses", Input = RetryOn extends "errors-only" ? E : RetryOn extends "response-only" ? HttpClientResponse : HttpClientResponse | E>(self: With<E, R>, options: {
readonly retryOn?: RetryOn;
readonly schedule?: Schedule.Schedule<B, NoInfer<Input>, ES, R1>;
readonly times?: number;
readonly while?: Predicate.Predicate<NoInfer<E | ES>>;
}): With<E | ES, R | R1>;
<B, E, ES = never, R1 = never>(options: Schedule<B, NoInfer<HttpClientResponse | E>, ES, R1>): <R>(self: With<E, R>) => With<E | ES, R1 | R>;
<E, R, B, ES = never, R1 = never>(self: With<E, R>, options: Schedule<B, NoInfer<HttpClientResponse | E>, ES, R1>): With<E | ES, R | R1>;
}

timeout

unstable
Since v4.1.0

Fails requests that do not receive a response within the given duration.

When to use

Use to configure a timeout once on the client, for example when providing the HttpClient service, while keeping the standard HttpClientError error model.

Details

A timed out request is interrupted and fails with an HttpClientError whose reason is a TransportError with a Cause.TimeoutError cause. retryTransient treats it as transient. Interrupting a request aborts the underlying transport, unless the request is attached to a scope with withScope, in which case it stays open until the scope closes.

The timer starts after request preprocessing and covers everything the wrapped client does to produce a response. For a plain client this ends when the response headers arrive, but response middleware applied before timeout, such as tap or retryTransient, runs inside the budget, including any body reads it performs. Composition order therefore decides how it interacts with retries: applying timeout before retryTransient gives each attempt its own budget, while applying it after bounds the whole retry loop.

Gotchas

Body reads outside the timed client are not covered, including those made by response middleware applied after timeout and by consumers of the HttpClient service. Apply a separate timeout to body reads such as response.json or response.stream when needed.

Signature

declare const timeout: {
(duration: Input): <E, R>(self: With<E, R>) => With<HttpClientError | E, R>;
<E, R>(self: With<E, R>, duration: Input): With<HttpClientError | E, R>;
}

Example Timing out each attempt

import { Effect, Layer } from "effect"
import { FetchHttpClient, HttpClient } from "effect/http"
const TimedHttpClient = Layer.effect(
HttpClient.HttpClient,
Effect.map(
HttpClient.HttpClient,
HttpClient.timeout("5 seconds")
)
).pipe(Layer.provide(FetchHttpClient.layer))
const program = Effect.gen(function*() {
const client = (yield* HttpClient.HttpClient).pipe(
HttpClient.retryTransient({ times: 3 })
)
return yield* client.get("https://example.com")
}).pipe(Effect.provide(TimedHttpClient))

Filtering

Since v4.0.0

Filters the result of a response, or runs an alternative effect if the predicate fails.

Signature

declare const filterOrElse: {
<B extends HttpClientResponse, E2, R2>(refinement: Refinement<HttpClientResponse, B>, orElse: (response: EqualsWith<HttpClientResponse, B, HttpClientResponse, Exclude<HttpClientResponse, B>>) => Effect<HttpClientResponse, E2, R2>): <E, R>(self: With<E, R>) => With<E2 | E, R2 | R>;
<E2, R2>(predicate: Predicate<HttpClientResponse>, orElse: (response: HttpClientResponse) => Effect<HttpClientResponse, E2, R2>): <E, R>(self: With<E, R>) => With<E2 | E, R2 | R>;
<E, R, B extends HttpClientResponse, E2, R2>(self: With<E, R>, refinement: Refinement<HttpClientResponse, B>, orElse: (response: EqualsWith<HttpClientResponse, B, HttpClientResponse, Exclude<HttpClientResponse, B>>) => Effect<HttpClientResponse, E2, R2>): With<E | E2, R | R2>;
<E, R, E2, R2>(self: With<E, R>, predicate: Predicate<HttpClientResponse>, orElse: (response: HttpClientResponse) => Effect<HttpClientResponse, E2, R2>): With<E | E2, R | R2>;
}
Since v4.0.0

Filters successful responses, or fails with the error produced by orFailWith when the predicate does not match.

Signature

declare const filterOrFail: {
<B extends HttpClientResponse, E2>(refinement: Refinement<HttpClientResponse, B>, orFailWith: (response: HttpClientResponse) => E2): <E, R>(self: With<E, R>) => With<E2 | E, R>;
<E2>(predicate: Predicate<HttpClientResponse>, orFailWith: (response: HttpClientResponse) => E2): <E, R>(self: With<E, R>) => With<E2 | E, R>;
<E, R, B extends HttpClientResponse, E2>(self: With<E, R>, refinement: Refinement<HttpClientResponse, B>, orFailWith: (response: HttpClientResponse) => E2): With<E | E2, R>;
<E, R, E2>(self: With<E, R>, predicate: Predicate<HttpClientResponse>, orFailWith: (response: HttpClientResponse) => E2): With<E | E2, R>;
}
Since v4.0.0

Filters responses by HTTP status code.

Signature

declare const filterStatus: {
(f: (status: number) => boolean): <E, R>(self: With<E, R>) => With<HttpClientError | E, R>;
<E, R>(self: With<E, R>, f: (status: number) => boolean): With<HttpClientError | E, R>;
}
Since v4.0.0

Filters responses that return a 2xx status code.

Signature

declare const filterStatusOk: <E, R>(self: HttpClient.With<E, R>) => HttpClient.With<E | Error.HttpClientError, R>

Guards

Since v4.0.0

Returns true if the provided value is an HttpClient.

Signature

declare function isHttpClient(u: unknown): u is HttpClient

Layers

Since v4.0.0

Creates an HttpClient layer and merges the layer construction context into client response effects.

Signature

declare function layerMergedContext<E, R>(effect: Effect<HttpClient, E, R>): Layer<HttpClient, E, R>

Mapping

mapRequest

unstable
Since v4.0.0

Appends a transformation of the request object before sending it.

Signature

declare const mapRequest: {
(f: (a: HttpClientRequest) => HttpClientRequest): <E, R>(self: With<E, R>) => With<E, R>;
<E, R>(self: With<E, R>, f: (a: HttpClientRequest) => HttpClientRequest): With<E, R>;
}
Since v4.0.0

Appends an effectful transformation of the request object before sending it.

Signature

declare const mapRequestEffect: {
<E2, R2>(f: (a: HttpClientRequest) => Effect<HttpClientRequest, E2, R2>): <E, R>(self: With<E, R>) => With<E2 | E, R2 | R>;
<E, R, E2, R2>(self: With<E, R>, f: (a: HttpClientRequest) => Effect<HttpClientRequest, E2, R2>): With<E | E2, R | R2>;
}
Since v4.0.0

Prepends a transformation of the request object before sending it.

Signature

declare const mapRequestInput: {
(f: (a: HttpClientRequest) => HttpClientRequest): <E, R>(self: With<E, R>) => With<E, R>;
<E, R>(self: With<E, R>, f: (a: HttpClientRequest) => HttpClientRequest): With<E, R>;
}
Since v4.0.0

Prepends an effectful transformation of the request object before sending it.

Signature

declare const mapRequestInputEffect: {
<E2, R2>(f: (a: HttpClientRequest) => Effect<HttpClientRequest, E2, R2>): <E, R>(self: With<E, R>) => With<E2 | E, R2 | R>;
<E, R, E2, R2>(self: With<E, R>, f: (a: HttpClientRequest) => Effect<HttpClientRequest, E2, R2>): With<E | E2, R | R2>;
}

transform

unstable
Since v4.0.0

Transforms a client by wrapping the response effect for each request.

Details

The transformation receives both the response effect and the original request, allowing it to change success, error, and environment behavior.

Signature

declare const transform: {
<E, R, E1, R1>(f: (effect: Effect<HttpClientResponse, E, R>, request: HttpClientRequest) => Effect<HttpClientResponse, E1, R1>): (self: With<E, R>) => With<E | E1, R | R1>;
<E, R, E1, R1>(self: With<E, R>, f: (effect: Effect<HttpClientResponse, E, R>, request: HttpClientRequest) => Effect<HttpClientResponse, E1, R1>): With<E | E1, R | R1>;
}
Since v4.0.0

Transforms a client by applying an effectful transformation to each response effect.

Signature

declare const transformResponse: {
<E, R, E1, R1>(f: (effect: Effect<HttpClientResponse, E, R>) => Effect<HttpClientResponse, E1, R1>): (self: With<E, R>) => With<E1, R1>;
<E, R, E1, R1>(self: With<E, R>, f: (effect: Effect<HttpClientResponse, E, R>) => Effect<HttpClientResponse, E1, R1>): With<E1, R1>;
}

Models

HttpClient

interface unstable
Since v4.0.0

HTTP client whose requests produce HttpClientResponse values and can fail with HttpClientError.

Signature

interface HttpClient extends With<Error.HttpClientError> {}

Other

Signature

declare const catch: {
<E, E2, R2>(f: (e: E) => Effect<HttpClientResponse, E2, R2>): <R>(self: With<E, R>) => With<E2, R2 | R>;
<E, R, E2, R2>(self: With<E, R>, f: (e: E) => Effect<HttpClientResponse, E2, R2>): With<E2, R | R2>;
}

HttpClient

unstable
Since v4.0.0

Namespace containing type-level members associated with HttpClient.

Retry

unstable
Since v4.0.0

Namespace containing type-level helpers for retrying HTTP clients.

Namespace containing configuration types for withCircuitBreaker.

Since v4.0.0

Namespace containing configuration types for withRateLimiter.

Predicates

Since v4.1.0

Returns true for an error that retryTransient retries by default: a timeout, a transport error, or a status code error with a transient response.

When to use

Use to classify failures the same way retryTransient does, for example in a custom retry policy or a circuit breaker.

See

Signature

declare function isTransientError(error: unknown): boolean

Example Classifying client errors

import { Cause } from "effect"
import { HttpClient, HttpClientError, HttpClientRequest } from "effect/http"
const request = HttpClientRequest.get("https://example.com")
const transportError = new HttpClientError.HttpClientError({
reason: new HttpClientError.TransportError({ request })
})
HttpClient.isTransientError(transportError) // => true
HttpClient.isTransientError(new Cause.TimeoutError()) // => true
HttpClient.isTransientError(new Error("boom")) // => false
Since v4.1.0

Returns true for a response whose status retryTransient retries by default: 408, 429, 500, 502, 503 or 504.

When to use

Use to retry the same responses as retryTransient in a custom retry policy.

See

Signature

declare function isTransientResponse(response: HttpClientResponse): boolean

Rate Limiting

Since v4.0.0

Applies request rate limiting using the RateLimiter service.

Details

It can update limits by inspecting common rate limit response headers and automatically retries HTTP 429 responses (or HttpClientError values wrapping a 429 response) by forcing the retry back through the limiter. When a response reports the remaining budget, requests wait for the reported reset once that budget is exhausted.

Gotchas

Automatic HTTP 429 retries are unlimited unless times is specified. Disabling response inspection does not disable retries; set times to 0 to return or fail with the first 429.

Signature

declare const withRateLimiter: {
(options: Options): <E, R>(self: With<E, R>) => With<E | RateLimiterError, R>;
<E, R>(self: With<E, R>, options: Options): With<E | RateLimiterError, R>;
}

Redirects

Since v4.0.0

Enables following HTTP redirects up to a specified number of times.

Details

Follows 3xx responses that include a Location header, up to maxRedirects (default 10) times. When the limit is reached or the Location header is not a valid URL, the last redirect response is returned.

Gotchas

This only sees redirects that the underlying client returns. fetch follows redirects itself by default, so with FetchHttpClient provide FetchHttpClient.RequestInit with { redirect: "manual" }. Browsers return an opaque response without a Location header for manual redirects, so this cannot follow them there.

Signature

declare const followRedirects: {
(maxRedirects?: number): <E, R>(self: With<E, R>) => With<E, R>;
<E, R>(self: With<E, R>, maxRedirects?: number): With<E, R>;
}

Example Following redirects with FetchHttpClient

import { Effect } from "effect"
import { FetchHttpClient, HttpClient } from "effect/http"
const program = Effect.gen(function*() {
const client = (yield* HttpClient.HttpClient).pipe(
HttpClient.followRedirects(5)
)
return yield* client.get("https://example.com/redirect")
}).pipe(
Effect.provide(FetchHttpClient.layer),
Effect.provideService(FetchHttpClient.RequestInit, { redirect: "manual" })
)

Resource Management

withScope

unstable
Since v4.0.0

Attaches the lifetime of the HttpClientRequest to a Scope.

Signature

declare function withScope<E, R>(self: With<E, R>): With<E, Scope | R>

Sequencing

tap

unstable
Since v4.0.0

Performs an additional effect after a successful request.

Signature

declare const tap: {
<_, E2, R2>(f: (response: HttpClientResponse) => Effect<_, E2, R2>): <E, R>(self: With<E, R>) => With<E2 | E, R2 | R>;
<E, R, _, E2, R2>(self: With<E, R>, f: (response: HttpClientResponse) => Effect<_, E2, R2>): With<E | E2, R | R2>;
}

tapError

unstable
Since v4.0.0

Performs an additional effect after an unsuccessful request.

Signature

declare const tapError: {
<_, E, E2, R2>(f: (e: NoInfer<E>) => Effect<_, E2, R2>): <R>(self: With<E, R>) => With<E | E2, R2 | R>;
<E, R, _, E2, R2>(self: With<E, R>, f: (e: NoInfer<E>) => Effect<_, E2, R2>): With<E | E2, R | R2>;
}

tapRequest

unstable
Since v4.0.0

Performs an additional effect on the request before sending it.

Signature

declare const tapRequest: {
<_, E2, R2>(f: (a: HttpClientRequest) => Effect<_, E2, R2>): <E, R>(self: With<E, R>) => With<E2 | E, R2 | R>;
<E, R, _, E2, R2>(self: With<E, R>, f: (a: HttpClientRequest) => Effect<_, E2, R2>): With<E | E2, R | R2>;
}

Services

HttpClient

unstable
Since v4.0.0

Service tag for the default outgoing HTTP client service.

When to use

Use to provide the default outgoing HTTP client service used by request accessors such as execute, get, and post.

Signature

declare const HttpClient: Service<HttpClient, HttpClient>
Since v4.0.0

Context reference for generating the span name used for outgoing client request spans.

Details

Defaults to the request method unchanged.

Signature

declare const SpanNameGenerator: Reference<(request: HttpClientRequest) => string>
Since v4.0.0

Context reference for a predicate that disables client-side tracing for matching outgoing requests.

Signature

declare const TracerDisabledWhen: Reference<Predicate<HttpClientRequest>>
Since v4.0.0

Context reference for filtering request and response headers added to client spans.

Details

Header capture is opt-in: the default filter records no headers. Captured headers listed in Headers.CurrentRedactedNames are recorded as <redacted>.

Signature

declare const TracerHeaderFilter: Reference<(headerName: string, phase: "request" | "response") => boolean>

Context reference that controls whether outgoing client spans are propagated to request headers.

Details

When this is true, the client writes the compact B3 b3 header and the W3C traceparent header. The default is on. The client span is still created when this is false.

Gotchas

A browser preflight fails when the target does not list b3 and traceparent in Access-Control-Allow-Headers. Set this to false for those requests.

Signature

declare const TracerPropagationEnabled: Reference<boolean>