# Making Requests

There are two ways to send a request. For the common case, the `HttpClient`
service has method accessors — `get`, `post`, `put`, `patch`, `del`, `head`,
`options` — that take a URL and an options object. For anything richer, you
build a request value with `HttpClientRequest` and hand it to `client.execute`.
Both return an `Effect` that yields a `HttpClientResponse`.

```ts
import { Effect, Schema } from "effect"
import { HttpClient, HttpClientRequest } from "effect/unstable/http"

class CreatedTodo extends Schema.Class<CreatedTodo>("CreatedTodo")({
  id: Schema.Number,
  title: Schema.String,
  completed: Schema.Boolean
}) {}

const program = Effect.gen(function*() {
  const client = yield* HttpClient.HttpClient

  // 1. The quick path: a GET with query parameters.
  const listResponse = yield* client.get("https://api.example.com/todos", {
    urlParams: { completed: "false", limit: "20" }
  })

  // 2. The builder path: construct a request value, then execute it.
  //    Useful when you want to compose several modifications.
  const created = yield* HttpClientRequest.post("https://api.example.com/todos").pipe(
    HttpClientRequest.acceptJson,
    HttpClientRequest.setHeader("x-request-source", "docs-example"),
    // bodyJsonUnsafe serializes a value to a JSON body. It is "unsafe" only in
    // that it assumes the value is JSON-encodable; it does not throw at runtime
    // for plain data.
    HttpClientRequest.bodyJsonUnsafe({ title: "Write docs", completed: false }),
    client.execute
  )

  return { listResponse, created }
})
```

A `HttpClientRequest` is an **immutable value**: its method, URL, query
parameters (`urlParams`), hash, headers, and body are stored as separate
structured fields, and every combinator returns a new request. The base URL,
query string, and hash stay separate until the request is converted to a real
URL or Web `Request`, so you can update each part independently.

## Method accessors vs. constructors

There are two entry points that produce the same shapes.

The **`HttpClient` accessors** (`client.get`, `client.post`, …) build the
request *and* execute it in one step — they return an `Effect<HttpClientResponse>`.
There are seven: `get`, `post`, `put`, `patch`, `del` (for `DELETE`), `head`,
and `options`.

```ts
import { Effect } from "effect"
import { HttpClient } from "effect/unstable/http"

const run = Effect.gen(function*() {
  const client = yield* HttpClient.HttpClient
  const res = yield* client.del("https://api.example.com/todos/42")
  return res
})
```

The **`HttpClientRequest` constructors** build a request *value* you can pass
around, transform, and later execute with `client.execute`. Each takes a
`string | URL` plus an optional `Options.NoUrl` object.

### get / post / put / patch / delete / head / options / trace

One constructor per method. `HttpClientRequest.delete` is the `DELETE`
constructor; there is also a `TRACE` constructor.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.get("https://api.example.com/todos")
HttpClientRequest.post("https://api.example.com/todos")
HttpClientRequest.put("https://api.example.com/todos/1")
HttpClientRequest.patch("https://api.example.com/todos/1")
HttpClientRequest.delete("https://api.example.com/todos/1")
HttpClientRequest.head("https://api.example.com/todos/1")
HttpClientRequest.options("https://api.example.com/todos")
HttpClientRequest.trace("https://api.example.com/todos")
// => each yields an immutable HttpClientRequest with the given method
```

### empty

The starting point for all constructors: a `GET` request with no URL, query
parameters, hash, headers, or body.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.empty.method
// => "GET"
HttpClientRequest.empty.url
// => ""
```

### make

Creates a constructor for an arbitrary method, then call it with a URL. This is
what `get`, `post`, etc. are built from.

```ts
import { HttpClientRequest } from "effect/unstable/http"

const report = HttpClientRequest.make("REPORT" as any)
const req = report("https://api.example.com/calendar")
// => HttpClientRequest with method "REPORT"
```

### makeWith

The lowest-level constructor: build a request from fully normalized components
(method, url, `UrlParams`, hash `Option`, `Headers`, `HttpBody`). Rarely needed
directly — prefer the method constructors plus combinators.

```ts
import { Option } from "effect"
import { HttpClientRequest, HttpBody, Headers, UrlParams } from "effect/unstable/http"

const req = HttpClientRequest.makeWith(
  "GET",
  "https://api.example.com",
  UrlParams.empty,
  Option.none(),
  Headers.empty,
  HttpBody.empty
)
// => HttpClientRequest
```

### isHttpClientRequest

Type guard for request values.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.isHttpClientRequest(HttpClientRequest.get("/x"))
// => true
HttpClientRequest.isHttpClientRequest({})
// => false
```

### Web `Request` conversion: fromWeb / toWebResult / toWeb

Bridge between `HttpClientRequest` and the platform `Request`. `fromWeb` reads a
Web `Request` into a request value; `toWebResult` returns a `Result` (failing
with `UrlParamsError` when the URL is invalid); `toWeb` is the effectful variant.

```ts
import { Effect } from "effect"
import { HttpClientRequest } from "effect/unstable/http"

// Web Request -> HttpClientRequest
const fromBrowser = HttpClientRequest.fromWeb(
  new Request("https://api.example.com/todos", { method: "POST" })
)
// => HttpClientRequest with method "POST"

// HttpClientRequest -> Result<Request, UrlParamsError>
const result = HttpClientRequest.toWebResult(fromBrowser)
// => Result.Success(Request) | Result.Failure(UrlParamsError)

// HttpClientRequest -> Effect<Request, UrlParamsError>
const effectful = HttpClientRequest.toWeb(fromBrowser, { signal: AbortSignal.timeout(5000) })
// => Effect<Request, UrlParamsError>
void effectful
void result
void Effect
```

## The options object

The method accessors and the `HttpClientRequest.*` constructors accept the same
`HttpClientRequest.Options` (minus `method`/`url`, which are already implied —
that subset is `Options.NoUrl`). Every field:

```ts
import { HttpClientRequest } from "effect/unstable/http"

const request = HttpClientRequest.get("https://api.example.com/search", {
  // Query string parameters. Accepts a record, an array of pairs, or UrlParams.
  urlParams: { q: "effect", page: "1" },
  // Request headers. Accepts a record or a Headers value.
  headers: { authorization: "Bearer token" },
  // URL fragment (without the leading "#").
  hash: "results",
  // Set the Accept header to a specific media type.
  accept: "application/json",
  // Shortcut that sets Accept to application/json (overrides `accept` if both set).
  acceptJson: true,
  // An HttpBody value (see the body reference below).
  body: undefined
})
```

The full `Options` interface (the `method` and `url` fields are present when
constructing via `make`/`modify`, omitted in the `NoUrl` form used by the
constructors):

| Field | Type | Notes |
| --- | --- | --- |
| `method` | `HttpMethod` | The HTTP method. |
| `url` | `string \| URL` | A `URL` extracts its search params and hash into structured fields. |
| `urlParams` | `UrlParams.Input` | Record, array of pairs, or `UrlParams`. |
| `hash` | `string` | URL fragment without `#`. |
| `headers` | `Headers.Input` | Record or `Headers`. |
| `accept` | `string` | Sets the `Accept` header. |
| `acceptJson` | `boolean` | Sets `Accept: application/json`. |
| `body` | `HttpBody.HttpBody` | The request body. |

## Building requests with combinators

Because a request is immutable, the combinators below each return a new request
and compose cleanly under `.pipe`. A request can be assembled independently of
any client and reused.

```ts
import { Effect } from "effect"
import { HttpClient, HttpClientRequest } from "effect/unstable/http"

const search = HttpClientRequest.get("https://api.example.com/search").pipe(
  HttpClientRequest.bearerToken("secret-token"),
  HttpClientRequest.setUrlParams({ q: "effect", lang: "en" }),
  HttpClientRequest.acceptJson
)

const run = Effect.gen(function*() {
  const client = yield* HttpClient.HttpClient
  return yield* client.execute(search)
})
```

### modify

Applies an `Options` object to a request in one call — the same logic the
constructors use internally.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.empty.pipe(
  HttpClientRequest.modify({ method: "POST", url: "https://api.example.com", acceptJson: true })
)
// => POST request with Accept: application/json
```

### setMethod

Replaces the HTTP method.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.get("https://api.example.com").pipe(
  HttpClientRequest.setMethod("POST")
).method
// => "POST"
```

### setUrl

Replaces the URL. When given a `URL`, its search parameters and fragment are
extracted into the request's `urlParams` and `hash` fields; a string is kept
as-is.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.empty.pipe(
  HttpClientRequest.setUrl(new URL("https://api.example.com/x?q=1#top"))
)
// => url "https://api.example.com/x", urlParams { q: "1" }, hash Some("top")
```

### prependUrl / appendUrl

Add a segment to the front or back of the URL, normalizing slashes (inserting
or trimming exactly one).

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.get("/todos").pipe(
  HttpClientRequest.prependUrl("https://api.example.com")
).url
// => "https://api.example.com/todos"

HttpClientRequest.get("https://api.example.com/todos").pipe(
  HttpClientRequest.appendUrl("42")
).url
// => "https://api.example.com/todos/42"
```

### updateUrl

Transforms the URL string with a function.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.get("https://api.example.com/v1/todos").pipe(
  HttpClientRequest.updateUrl((u) => u.replace("/v1/", "/v2/"))
).url
// => "https://api.example.com/v2/todos"
```

### setHash / removeHash

Set or clear the URL fragment (stored without the leading `#`).

```ts
import { Option } from "effect"
import { HttpClientRequest } from "effect/unstable/http"

const withHash = HttpClientRequest.get("https://api.example.com/docs").pipe(
  HttpClientRequest.setHash("section-2")
)
withHash.hash
// => Option.some("section-2")

HttpClientRequest.removeHash(withHash).hash
// => Option.none()
void Option
```

### setHeader / setHeaders

Set one header, or merge a collection of headers (existing values with matching
names are replaced).

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.get("https://api.example.com").pipe(
  HttpClientRequest.setHeader("x-trace-id", "abc123"),
  HttpClientRequest.setHeaders({ "x-region": "us-east", "x-tenant": "acme" })
)
// => request with all three headers set
```

### accept / acceptJson

Set the `Accept` header to a media type, or directly to `application/json`.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.get("https://api.example.com").pipe(
  HttpClientRequest.accept("text/csv")
)
// => Accept: text/csv

HttpClientRequest.get("https://api.example.com").pipe(
  HttpClientRequest.acceptJson
)
// => Accept: application/json
```

### setUrlParam / setUrlParams / appendUrlParam / appendUrlParams

`set*` replaces existing values for a key; `append*` keeps existing values so
repeated keys are preserved. The single-value variants take `key, value`; the
plural variants take a `UrlParams.Input`.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.get("https://api.example.com/search").pipe(
  HttpClientRequest.setUrlParam("q", "effect"),
  HttpClientRequest.setUrlParams({ page: "1", limit: "20" }),
  HttpClientRequest.appendUrlParam("tag", "ts"),
  HttpClientRequest.appendUrlParams({ tag: "fp" })
)
// => query: q=effect&page=1&limit=20&tag=ts&tag=fp
```

### bearerToken / basicAuth

Set the `Authorization` header. Both accept plain strings or `Redacted` values,
so secrets do not leak into logs.

```ts
import { Redacted } from "effect"
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.get("https://api.example.com").pipe(
  HttpClientRequest.bearerToken(Redacted.make("secret-token"))
)
// => Authorization: Bearer secret-token

HttpClientRequest.get("https://api.example.com").pipe(
  HttpClientRequest.basicAuth("user", Redacted.make("pass"))
)
// => Authorization: Basic dXNlcjpwYXNz
void Redacted
```

## Request bodies

Body combinators set the request body and, where the body carries it,
synchronize the `Content-Type` and `Content-Length` headers. `Empty` and
`FormData` bodies intentionally clear explicit content headers so the runtime
can supply multipart boundaries.

Most are pure functions returning a new request. The exceptions are
`bodyJson`, `schemaBodyJson`, and `bodyFile`, which return an `Effect` because
encoding (or reading a file) can fail.

### setBody

The primitive: attach a prebuilt `HttpBody` value. The other body combinators
are thin wrappers over this plus an `HttpBody.*` constructor.

```ts
import { HttpClientRequest, HttpBody } from "effect/unstable/http"

HttpClientRequest.post("https://api.example.com/upload").pipe(
  HttpClientRequest.setBody(HttpBody.text("hello"))
)
// => Content-Type: text/plain, Content-Length: 5
```

### bodyText

A UTF-8 text body. Content type defaults to `text/plain`.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.post("https://api.example.com/log").pipe(
  HttpClientRequest.bodyText("plain log line")
)
// => Content-Type: text/plain
HttpClientRequest.post("https://api.example.com/doc").pipe(
  HttpClientRequest.bodyText("<x/>", "application/xml")
)
// => Content-Type: application/xml
```

### bodyUint8Array

A raw byte body. Content type defaults to `application/octet-stream`.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.post("https://api.example.com/blob").pipe(
  HttpClientRequest.bodyUint8Array(new Uint8Array([1, 2, 3]), "application/wasm")
)
// => Content-Type: application/wasm, Content-Length: 3
```

### bodyJsonUnsafe

A JSON body via `JSON.stringify`. "Unsafe" because `JSON.stringify` can throw
(e.g. on a `BigInt` or a circular reference); fine for plain data you trust.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.post("https://api.example.com/todos").pipe(
  HttpClientRequest.bodyJsonUnsafe({ title: "Ship it", completed: false })
)
// => Content-Type: application/json
```

### bodyJson

The effectful JSON path: a `JSON.stringify` failure is captured as
`HttpBodyError` in the error channel instead of throwing.

```ts
import { Effect } from "effect"
import { HttpClientRequest } from "effect/unstable/http"

const _ = Effect.gen(function*() {
  return yield* HttpClientRequest.post("https://api.example.com/todos").pipe(
    HttpClientRequest.bodyJson({ title: "Ship it" })
  )
})
// => Effect<HttpClientRequest, HttpBodyError>
```

### schemaBodyJson

Encodes a value through a `Schema` (so the body matches a contract) and sets it
as a JSON body. Returns a function from value to `Effect`; schema and JSON
failures surface as `HttpBodyError`.

```ts
import { Effect, Schema } from "effect"
import { HttpClientRequest } from "effect/unstable/http"

const NewTodo = Schema.Struct({
  title: Schema.String,
  completed: Schema.Boolean
})

// schemaBodyJson(schema) returns a function from value -> Effect of the request.
const buildRequest = (todo: typeof NewTodo.Type) =>
  HttpClientRequest.post("https://api.example.com/todos").pipe(
    HttpClientRequest.schemaBodyJson(NewTodo)(todo)
  )

const _ = Effect.gen(function*() {
  return yield* buildRequest({ title: "Ship it", completed: false })
})
// => Effect<HttpClientRequest, HttpBodyError>
```

### bodyUrlParams

An `application/x-www-form-urlencoded` body from URL-param input (record, pairs,
or `UrlParams`).

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.post("https://api.example.com/login").pipe(
  HttpClientRequest.bodyUrlParams({ username: "ada", password: "secret" })
)
// => Content-Type: application/x-www-form-urlencoded, body "username=ada&password=secret"
```

### bodyFormData

A `multipart/form-data` body from a Web `FormData` value. The content type and
length are left unset so the runtime can generate the multipart boundary.

```ts
import { HttpClientRequest } from "effect/unstable/http"

const fd = new FormData()
fd.append("field", "value")

HttpClientRequest.post("https://api.example.com/upload").pipe(
  HttpClientRequest.bodyFormData(fd)
)
// => multipart body (boundary added by the runtime)
```

### bodyFormDataRecord

Builds the `FormData` for you from a record. Array fields append each item under
the same key; primitives are stringified, `File`/`Blob` values are appended
directly, and `null`/`undefined` are skipped.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.post("https://api.example.com/upload").pipe(
  HttpClientRequest.bodyFormDataRecord({
    title: "Report",
    tags: ["q1", "draft"],
    note: null // skipped
  })
)
// => multipart body with title + two tags fields
```

### bodyStream

A streaming byte body for large payloads you do not want to buffer. Optionally
provide `contentType` and `contentLength`; the type defaults to
`application/octet-stream`.

```ts
import { Stream } from "effect"
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.post("https://api.example.com/upload").pipe(
  HttpClientRequest.bodyStream(
    Stream.make(new Uint8Array([1, 2]), new Uint8Array([3, 4])),
    { contentType: "application/octet-stream", contentLength: 4 }
  )
)
// => streamed request body
void Stream
```

### bodyFile

Streams a file from disk. Returns an `Effect` requiring `FileSystem`; it stats
the file to set `Content-Length` and can fail with `PlatformError`.

```ts
import { Effect } from "effect"
import { HttpClientRequest } from "effect/unstable/http"

const _ = Effect.gen(function*() {
  return yield* HttpClientRequest.post("https://api.example.com/upload").pipe(
    HttpClientRequest.bodyFile("/tmp/report.pdf", { contentType: "application/pdf" })
  )
})
// => Effect<HttpClientRequest, PlatformError, FileSystem>
```
**Note:** The body combinators delegate to `HttpBody.*` constructors. If you are building
a body directly (e.g. to pass to `setBody` or to the `body` option), the
available constructors are `HttpBody.empty`, `HttpBody.raw`, `HttpBody.text`,
`HttpBody.uint8Array`, `HttpBody.json` (effectful), `HttpBody.jsonUnsafe`,
`HttpBody.jsonSchema` (effectful, schema-encoded), `HttpBody.urlParams`,
`HttpBody.formData`, `HttpBody.formDataRecord`, `HttpBody.stream`,
`HttpBody.file`, and `HttpBody.fileFromInfo` (both effectful, require
`FileSystem`). Use `HttpBody.isHttpBody` to guard unknown values.

## toUrl / trace

### toUrl

Assembles the request's URL, query parameters, and hash into a single `URL`,
returning `Option.none()` if the result is not a valid URL.

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.get("https://api.example.com/search").pipe(
  HttpClientRequest.setUrlParam("q", "effect"),
  HttpClientRequest.setHash("top"),
  HttpClientRequest.toUrl
)
// => Option.some(URL "https://api.example.com/search?q=effect#top")
```

### trace

The `TRACE` request constructor (listed above with the other method
constructors).

```ts
import { HttpClientRequest } from "effect/unstable/http"

HttpClientRequest.trace("https://api.example.com/debug").method
// => "TRACE"
```

## HttpMethod reference

The `HttpMethod` module holds the supported method vocabulary used across the
HTTP client, server, and routing APIs. Methods are uppercase string literals:
`"GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS" | "TRACE"`.

### all

A readonly `Set` of every supported method literal — handy for membership tests
or iteration.

```ts
import { HttpMethod } from "effect/unstable/http"

HttpMethod.all.has("PATCH")
// => true
```

### allShort

Tuples mapping each method to its short constructor name (note `DELETE` maps to
`del`).

```ts
import { HttpMethod } from "effect/unstable/http"

HttpMethod.allShort
// => [["GET", "get"], ["POST", "post"], ["PUT", "put"], ["DELETE", "del"], ...]
```

### hasBody

Returns `true` when a method is treated as able to carry a body, narrowing to
`HttpMethod.WithBody`. The bodyless set is `GET`, `HEAD`, `OPTIONS`, `TRACE`;
`DELETE` is treated as able to carry a body.

```ts
import { HttpMethod } from "effect/unstable/http"

HttpMethod.hasBody("POST")
// => true
HttpMethod.hasBody("GET")
// => false
```

### isHttpMethod

Runtime refinement before accepting an unknown value as a method. Lowercase
names are not valid.

```ts
import { HttpMethod } from "effect/unstable/http"

HttpMethod.isHttpMethod("GET")
// => true
HttpMethod.isHttpMethod("get")
// => false
```

## What you get back

Executing a request yields a `HttpClientResponse` in the success channel and an
`HttpClientError` in the failure channel. By default a non-2xx status is **not**
an error — the response is returned as-is so you can inspect the status. To make
a bad status fail, use `HttpClient.filterStatusOk` (covered under
[Resilience](https://effect.plants.sh/http-client/resilience/)).
**Tip:** Rather than repeating the base URL and auth on every call, set them once on the
client with `HttpClient.mapRequest`, as shown on the
[section overview](https://effect.plants.sh/http-client/). Then your call sites only carry the parts
that vary per request.

Decoding the body is covered next, in
[Handling responses](https://effect.plants.sh/http-client/handling-responses/).