An authorized API test receives HTTP200 through a residential proxy, then fails with a message such as “body already read.” Logging middleware calls response.text() to inspect the payload, while the application later calls response.json() on the same Response. A second read failure is not evidence that the target sent an empty body. Trace who consumes the response before rotating exits or repeating the network request. This guide describes controlled diagnostics, not measured 98IP behavior.

One incoming response is parsed once into a stored data object shared by two consumers, while a second attempt to read the original body is stopped

1. Separate the network exchange from local body consumption

A Fetch Response body is not a reusable text variable. Methods such as text() and json() consume it. response.bodyUsed is a useful state signal, but it does not certify complete delivery, correct JSON or the target's business result. Inspect it at entry and exit of each middleware stage without reading the body just to create a log.

Record actual HTTP status, final URL, content type, non-secret attempt ID, client/runtime version and the failing stage. Keep network rejection, body decoding, JSON parsing and application schema validation separate. Response.json() can reject with TypeError for a disturbed or locked body, but decoding errors can also produce TypeError. A message string or error class alone does not establish which cause occurred.

Do not confuse a genuinely empty body or intentionally bodyless status with a previously consumed stream. If the first reader completed successfully with the expected fixture, that observation matters more than a later wrapper's “empty response” label. Conversely, bodyUsed alone does not prove that any reader successfully reached the end.

2. Inventory every reader before changing transport

Follow the Response object through fetch wrappers, error formatting, logging, cache code, schema checks and UI handlers. List every call that reads the body, including text(), json(), blob(), arrayBuffer() or a stream reader. An apparently harmless debug statement can be the first consumer. Two different variable names may still refer to the same Response object.

Record the stage sequence and whether the exact same object is passed onward. Use metadata-only tracing first. Do not call text() in a catch block to inspect a body already consumed by json(); that can replace the useful original error with a new read failure. Preserve the original error and classify the first proven boundary.

For streaming code, also inspect whether an earlier reader holds the body lock. A body not yet reported as used is not a promise that a new reader can acquire it. Coordinate ownership of the reader rather than adding competing consumers.

3. Reproduce with one small owned JSON fixture

  1. Use an owned read-only staging endpoint with a tiny fixed valid JSON response. Agree independently on a synthetic fixture_id, two item IDs and their count. Avoid customer data, real writes, ads or analytics conversions.
  2. Positive case: fetch once, record response metadata, call json() once and compare the parsed values with the independent expectation. Confirm the receiver/request correlation, not only a success toast.
  3. Negative case: fetch a fresh Response for the same fixture, await text() in a test-only logging stage, then attempt json() on that same object. Record the second read rejection, first reader result and body state. Do not automatically issue a replacement fetch.
  4. Corrected case: fetch a fresh Response, parse once and pass the resulting data object to the application and a minimal redacted diagnostic consumer. Verify both receive the intended fixture values without reading the original body again.
  5. Compare server-observed request counts and local stage traces. The second local read does not itself issue a new HTTP request; retries added by wrappers must be counted separately.
  6. Repeat through an approved baseline and one selected residential route with the same client code, fixture version and session policy. Record actual egress. If a baseline is unavailable, state that limitation.

These are expected outcomes, not claimed test results. Use a fresh Response for each independent case; resetting a variable name does not rewind a consumed body. Retain the negative case after repair so a catch-all empty object cannot make the test appear to pass.

4. Prefer one owner and one parse

For a bounded JSON integration, assign one component to read and parse the response. Pass the resulting value onward, or return a documented envelope containing status metadata and parsed data. Reading the data object twice is different from consuming the Response body twice. Consumers that mutate shared data need their own application-level ownership rules.

If raw text is required for a controlled small fixture, one owner can read it once and parse that stored text separately under an explicit size limit. This still requires a valid JSON and status/body contract; it does not turn an empty or malformed response into valid data. Do not log full production bodies just because they are already available.

A middleware contract that promises an unread Response must honor that promise. If it instead returns parsed data, callers must be updated explicitly. Silently returning an object in place of a Response can hide the original bug and create new status/header handling errors.

5. Use clone only when its cost and timing are understood

When two independent body consumers are genuinely required, a Response clone must be created before the original body is consumed. Cloning afterward cannot restore the stream. A clone branches the body; it does not perform another fetch or prove the server returned two copies.

Do not clone every response as a universal logging fix. A slower or unread branch can accumulate buffered content, potentially the whole body. For large downloads or unbounded streams, use an explicitly designed bounded observation path rather than assuming clone provides a memory limit. Review both branches' lifecycle with the owner.

  • The first reader succeeds and the second fails: inspect ownership and reuse of the same Response.
  • The first reader itself fails: preserve its original decoding, abort or parse evidence; do not label it automatically as double consumption.
  • Parsed data is correct but a later field is missing: investigate schema or application transformations.
  • Only the instrumented client fails: compare what the instrumentation reads and returns before changing the proxy.
  • Only one route differs: compare final endpoint, response version, client path and correlation evidence before assigning cause.

Stop unchanged network retries caused by a local second-reader bug. If an earlier operation could have real effects and its result is unknown, use the owner's read-back and idempotency process before any resubmission; see the POST timeout and duplicate-submission guide. Never replace every read error with an empty object or empty item list.

6. Control the 98IP route and keep a minimal evidence packet

When an authorized integration needs residential egress, review the 98IP residential proxy product page and API configuration documentation, then use the access mode and parameters supported by your account. Keep the session policy stable while comparing reader behavior and check actual usage.

A proxy supplies a path; it cannot rewind an application-consumed stream or repair middleware ownership. This procedure does not guarantee target compatibility, speed or billing outcomes. Limit synthetic requests and respect endpoint authorization.

  • The actual HTTP exchange is separated from local read, decode, parse and schema stages.
  • Every reader and the Response object's ownership are identified.
  • Positive, second-read negative and corrected parse-once cases have independent fixture expectations.
  • Both data consumers receive the expected values without a new body read.
  • Request counts do not hide extra fetches or unknown writes.
  • Evidence contains only stage/state metadata and synthetic values, never cookies, credentials, authorization headers or full customer payloads.

Does bodyUsed=true mean the server sent no data? No. It concerns local body use, not proof of an empty server response.

Can a late clone recover a consumed body? No. Clone before consumption when appropriate, or redesign around one read.

Will changing the IP fix two readers on one Response? It does not fix the object lifecycle. Compare routes only after the reader contract is controlled.