# HTTP success needs a status check and a decoder

> For agents: start with the [agent guide](https://ratstack.sh/llms.txt). Every page is Markdown by default; add `Accept: text/html` for HTML.

Free workshop: [how to burn a trillion tokens and get good results](/tokenmaxx#interested).

A fulfilled HTTP request is not proof of application success.
Check the status and decode the body before passing provider data into the application.
Use the `effect/http` modules.

## Check the protocol, not only the connection

The deployment checker decodes tool discovery through `ToolsResponse`.
It also requires status 200 and the expected tool names.

packages/deploy/src/checks.ts at 65e9465f38092e24486392f22c08f45d61230c20; lines 141-150; highlighted none.

```typescript
          const document =
            yield* HttpClientResponse.schemaBodyJson(ToolsResponse)(response);

          return (
            response.status === 200 &&
            ["search", "read", "execute"].every((name) =>
              document.result.tools.some((tool) => tool.name === name)
            )
          );
        })
```

Use the in-memory fixture below when learning.

## Reject an unacceptable response before trusting its data

`HttpClient.filterStatusOk` accepts 2xx status codes.
An adapter can require a narrower status when its protocol needs one.
The fixture applies the filter before decoding a version response.

packages/deploy/test/fixtures/validated-http-response.ts at 4019cffbcbdb282d6f006854ff5b1f26d3b407e0; lines 13-30; highlighted none.

```typescript
    const client = (yield* HttpClient.HttpClient).pipe(
      HttpClient.filterStatusOk
    );

    const response = yield* client
      .get("https://provider.example.test/version")
      .pipe(
        Effect.mapError(
          (cause) => new ResponseFixtureError({ cause, phase: "status" })
        )
      );

    return yield* HttpClientResponse.schemaBodyJson(Reply)(response).pipe(
      Effect.mapError(
        (cause) => new ResponseFixtureError({ cause, phase: "body" })
      )
    );
  }
```

`schemaBodyJson` handles JSON parsing and schema decoding. `ResponseFixtureError` retains the cause and names the failed phase.

The fixture groups request and status failures into its `status` phase.
It groups parsing and schema failures into its `body` phase.
A production adapter should choose failure names that match its job and repair path.

## Supply the transport in tests

The test supplies a client that creates responses in memory.
It does not start an HTTP server or call a provider.

packages/deploy/test/http-response.test.ts at 4019cffbcbdb282d6f006854ff5b1f26d3b407e0; lines 15-30; highlighted none.

```typescript
      Effect.sync(() =>
        HttpClientResponse.fromWeb(
          request,
          new Response(scenario.body, {
            headers: { "content-type": "application/json" },
            status: scenario.status,
          })
        )
      )
    );

    const result = yield* readValidatedResponse().pipe(
      Effect.provideService(HttpClient.HttpClient, client),
      Effect.result
    );

```

| Simulated response             | Required outcome           |
| ------------------------------ | -------------------------- |
| 200, integer version           | Decoded version            |
| 503, otherwise valid body      | Named status-phase failure |
| 200, malformed JSON            | Named body-phase failure   |
| 200, string instead of integer | Named body-phase failure   |

These transport seams establish the fixture's status and decoding policy.
Check live endpoint health separately.

## Decide whether replay is safe before adding retries

`retryTransient` can retry transient responses and errors.
Its classification does not prove that repeating an operation is safe.
A write can complete remotely even when its response is lost.

Before retrying a write:

1. Identify the operation's replay or idempotency guarantee.
2. Decide which failures allow another attempt.
3. Measure the budget and select the schedule.
4. Preserve the final named failure when the budget ends.

Do not copy example delays or retry counts into production as measured limits.
The fixture establishes no production retry budget.

## Common mistakes

- Treat fulfilled fetch as success despite a failed status.
- Trust an object because JSON parsing succeeded.
- Replace the whole adapter in a test and stop exercising its decoder.
- Retry a write only because the transport calls its error transient.

## Effect idiom and house rule

- Supply `HttpClient`, compose request policies, decode responses, and translate failures at the adapter edge.
- In rat-stack, core owns job-shaped ports. Provider clients remain in adapters under [hexagonal architecture](/lore/hexagonal-architecture).
- In rat-stack, [machines own finite retry lifecycles](/lore/lifecycles-are-machines). Effect also has native schedules and retry operators.

Read [schemas](/lore/schemas-define-the-boundary) and [failures](/lore/error-model) before defining the adapter's accepted data.
Then read [tracing](/lore/trace-meaningful-operations) to observe the operation without exposing private values.
Return to the [Effect reading order](/lore/effect-basics#read-next).

## Sources

1. [Effect contributors. 2026. HttpClient. Effect 4.0.0.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/packages/effect/src/http/HttpClient.ts)
   Effect-TS. filterStatusOk rejects non-2xx responses; retryTransient classifies retryable errors and responses but does not establish application replay safety. Accessed 2026-10-06.

2. [Effect contributors. 2026. HttpClientResponse.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/packages/effect/src/http/HttpClientResponse.ts)
   Effect-TS. schemaBodyJson parses a JSON response and decodes it through the supplied Schema. Accessed 2026-10-06.

3. [Effect contributors. 2026. Getting started with HttpClient.](https://github.com/Effect-TS/effect/blob/67ba4e46a11ccda0b6761578bfd22c04ae00167d/ai-docs/src/50_http-client/10_basics.ts)
   Effect-TS. Supplied clients, request middleware, response decoding, named adapter errors, and request construction. Accessed 2026-10-06.

4. [rat-stack contributors. 2026. Deployment behavior checks.](https://github.com/joelhooks/rat-stack/blob/65e9465f38092e24486392f22c08f45d61230c20/packages/deploy/src/checks.ts)
   rat-stack. A protocol check validates response status and schema-decoded tool discovery before reporting behavior-confirmed. Accessed 2026-10-06.

5. [rat-stack contributors. 2026. HTTP response fixture.](https://github.com/joelhooks/rat-stack/blob/4019cffbcbdb282d6f006854ff5b1f26d3b407e0/packages/deploy/test/http-response.test.ts)
   rat-stack. A supplied in-memory client exercises valid data, non-2xx status, malformed JSON, and a wrong decoded field type. Accessed 2026-10-06.

6. [Langton, Kit. 2026. HTTP Clients. Effect Solutions.](https://github.com/kitlangton/effect-solutions/blob/09f82e6c5c928e7232cd32daf04d7c6a830b63f7/packages/website/docs/11-http-clients.md)
   Kit Langton. The draft frames middleware and response decoding. This guide uses exact stable-version source and independent fixtures. Accessed 2026-10-06.

## Lore on this page

- [Schemas define the boundary](/lore/schemas-define-the-boundary)

## Linked from

- Agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms.txt)

- Change log → What changed in the files served here, newest first. → [Read page](https://ratstack.sh/log)

- Concurrent work needs an owner and a bound → Limit active work, retain result order, and stop child work when its owning lifetime ends. → [Read page](https://ratstack.sh/lore/concurrent-work-needs-an-owner)

- Effect basics → An Effect describes work, expected failures, and required services before a runtime executes it. → [Read page](https://ratstack.sh/lore/effect-basics)

- Full agent guide → An Effect stack so pure (aspirational) Kit Langton will blush. → [Read page](https://ratstack.sh/llms-full.txt)

- Rat Stack lore | rat-stack → Short, source-grounded notes on the ideas and decisions behind rat-stack. → [Read page](https://ratstack.sh/lore)

- Source change history (log.md) → The generated change log as Markdown. → [Read page](https://ratstack.sh/log.md)
