Inspect what the server actually sent
Probe a fresh call with .inspect() to read the unredacted raw body next to the validated value and the drift findings diffed between them — without throwing — when you need to see what changed after the fact.
Task
The schema coerced a field, filled a default, or stripped a key — and now you
want to see what the server actually sent. An await getUser(...) hands back
only the validated value; the raw body and the soft drift findings rode the
event stream and are gone. You want both, on the
resolved result, without consuming a stream and without throwing when the
response breaks the contract.
Example
.inspect() runs one fresh call and resolves to an Inspection —
{ value, raw, findings, status, error }. It never throws: a hard contract
violation comes back as value: null with error set, and raw / findings /
status still populated.
import { , } from 'stitchapi';
import { } from 'zod';
const = ({
: 'https://demo.stitchapi.dev',
: '/users/{id}',
: 'data',
: (
.({ : .(), : .(), : .() }),
),
});
const = await .({ : { : 1 } });
.; // the validated User (coerced/defaulted/stripped) — or null on a hard fail
.; // every drift finding: undeclared, coerced, defaulted, invalid
.; // the HTTP status, so `raw` is interpretable (a 422 body reads unlike a 200)
.; // a StitchError on a contract violation, else null — value/error are inverse
// `raw` is the pre-validation body the findings are diffed against. Reach for it
// deliberately (see the caveat below); here, only when something actually drifted.
if (.. > 0) {
.('server sent:', .);
}value is the same validated value await getUser(...) returns; raw is the
body before validation, the coordinate space each finding.path is anchored
to. The pair is self-explaining: a coerced finding at role plus
result.raw.role tells you exactly which wire value the schema rewrote.
How it works
.inspect() consumes one run with the raw body retained and surfaces it on a
non-enumerable field. The other four fields are ordinary properties; raw
alone is hidden, so it cannot leak into a log by accident.
Anti-pattern: don't log the whole wrapper — raw is unredacted, so
a stray token or PII in an undeclared field would land in your logs. Because
raw is non-enumerable, JSON.stringify(result), {...result}, and trace
sinks all skip it; that protection only holds if you don't defeat it. Read
result.raw deliberately, at the one place you mean to.
.inspect() always hits the network. It is a fresh probe — it bypasses
the cache by default, reading and writing neither — so it is not an observer
of what your cached await call did. Pass { cache: true } to honour the
cache policy instead; on a cache hit the entry stores only { value, status },
so raw is then null.
import { } from 'stitchapi';
const = ({
: 'https://demo.stitchapi.dev',
: '/users/{id}',
: { : '60s' },
});
// Honour the cache; on a hit, `raw` is null (the cache holds no raw body).
const = await .({ : { : 1 } }, { : true });raw is null on a streaming surface too — the
engine refuses to buffer an unbounded delta spine — while findings and status
still populate; use .stream() for incremental inspection there.
See also
Catch a silent selector rename in scraped HTML
Scrape an HTML page into a structured object with transform, then drift the structured shape against its schema so a markup or selector rename becomes a hard contract error instead of a silently missing field.
Watch retries and throttling as they happen
Iterate a stitch's event stream instead of awaiting it, and observe every retry, pause, and drift in real time.