Release candidate — 1.0.0-rc.7
StitchAPI
GuidesAuthoring & composition

Hooks

Observe a call as it runs — onRequest, onResponse, onError, onRetry — and where each fires relative to auth, retry, throttle, and timeout.

Hooks are observation points on a stitch's lifecycle: small callbacks that run as a call progresses, for logging, metrics, or tracing. They never change what a stitch returns — a call's only result is its response — so reach for a hook when you want to watch a call, for transform when you want to reshape its output, and for the input schema when you want to reshape what it sends.

Example

import {  } from 'stitchapi';

const  = ({
    : 'https://api.example.com/users/{id}',
    : {
        : ({ , ,  }) => {
            .(`→ ${} #${}: ${?.} ${?.}`);
        },
        : ({  }) => {
            .(`← ${?.}`);
        },
        : ({ , ,  }) => {
            .(`retrying after #${}`,  ?? ?.);
        },
    },
});

The four hooks

Each hook receives a HookContext{ name, attempt, req?, res?, error? } — with the fields relevant to its moment populated:

HookFires whenPopulated
onRequestjust before the request goes outreq
onResponsea response came back (any status)res
onErrorthe transport threw (network/abort/timeout)error
onRetrythe engine decided to retry, before backofferror or res

onResponse fires on any status, which makes it the seam for everything a server tells you on a call that succeeded. A vendor announcing a shutdown does exactly that — Deprecation and Sunset ride a 200, so nothing fails and nothing retries; the vendor told you for six months, in a header finds the three places a response header is actually reachable.

Firing order

Hooks interleave with the engine's per-attempt pipeline. Within a single attempt, in order:

  1. throttle — wait for a concurrency / rate slot
  2. auth.apply — credentials are stamped onto the request
  3. onRequest — the request is fully shaped (auth headers included)
  4. the network call (bounded by timeout.each)
  5. on a thrown transport error → onError, then if another attempt remains → onRetry → backoff → next attempt
  6. on a response → onResponse
  7. auth.shouldRefresh / auth.refresh — a token wall (e.g. a 401) refreshes auth and replays the attempt
  8. on a retryable status → onRetry → backoff → next attempt

So onRequest always sees the post-auth request, and onRetry fires with error populated on a network failure or res populated on a retryable status.

Hooks run on every attempt, not once per call: a stitch that retries twice fires onRequest three times. Use attempt to tell them apart.

Observe the request; don't reshape it. ctx.req is the live object the transport is about to send, so mutating it in onRequest does change the call — but step 3 puts you after auth.apply, and a strategy that signs the payload (AWS SigV4 with signBody, or any HMAC scheme) has already hashed the body you are replacing. The signature then covers bytes that never went out, and the API rejects the call with a 403 that points at nothing. Reshape the request in its input schema, which resolves before auth signs anything — and before the cache key is derived from the request, which hook mutation also misses.

Order across composed layers

When stitches compose via extends, hooks chain rather than overwrite — every layer's hook runs. They unwind like middleware:

  • onRequest runs base → child (outermost layer first)
  • onResponse, onError, onRetry run child → base (innermost layer first)
import {  } from 'stitchapi';

const  = ({
    : 'https://api.example.com',
    : { : () => .('base onRequest') },
});

const  = ({
    : [],
    : '/users/{id}',
    : { : () => .('child onRequest') },
});
// Calling getUser logs "base onRequest" then "child onRequest".

See also

On this page