Under heavy development
StitchAPI
Reference

Request surfaces

http, graphql, sse, stream, and download — the request styles a stitch can speak, each a subpath import riding one engine.

A surface is the request style a stitch speaks. http is the default — a plain JSON-over-HTTP call, what most of these docs use. GraphQL, Server-Sent Events, a raw byte stream, and a file download are peer surfaces: each shapes and interprets its own request, but they all ride the same engine, so auth, retry, throttle, timeout, validation, and the event stream compose with every one.

A request surface is how a stitch shapes its request. Don't confuse it with the four invocation surfaces — function, CLI, HTTP, MCP — which are how you call a stitch. The two are orthogonal: any request surface is reachable through any invocation surface.

Every non-http surface ships as its own subpath import, so import { stitch } from the root pulls in only the http engine — a surface's code (and its parser/decoder) loads only when you import it.

SurfaceImportShapesawait resolves to
httpstitch (default)a JSON-over-HTTP callthe validated body
graphqlstitchapi/graphqlPOST { query, variables }, unwrap datathe data payload
ssestitchapi/ssea text/event-stream reader (over fetch)every parsed event, collected
streamstitchapi/streama raw ReadableStream readerevery decoded chunk, collected
downloadstitchapi/downloada buffered binary GET{ blob, filename }

http — the default

stitch(...) with no kind is the http surface: send the request, parse JSON when the response is JSON-ish, validate, return. Nothing to import.

import { stitch } from 'stitchapi';

const getUser = stitch('https://api.example.com/users/{id}');

graphql

graphql() POSTs { query, variables } and unwraps data; a 200 carrying errors[] is treated as a failure, never silently passed.

import { graphql } from 'stitchapi/graphql';

const getThing = graphql({
    baseUrl: 'https://api.example.com',
    query: 'query ($id: ID) { thing(id: $id) { name } }',
});

const thing = await getThing({ variables: { id: 1 } });

sse

sse parses the text/event-stream wire format over fetch + Web Streams — never EventSource, so it keeps your auth, headers, and retries. Each event becomes a delta chunk; await collects them, .stream() yields them live.

import { sse } from 'stitchapi/sse';

const ticks = sse({ url: 'https://api.example.com/ticks' });

for await (const ev of ticks.stream()) {
    if (ev.type === 'delta') handle(ev.chunk); // a parsed SseEvent
}

data is JSON-parsed when it parses, else kept as the raw string:

Prop

Type

stream

stream is the raw streaming sibling — it hands back the response body decoded per stream.decode: 'bytes' (the default, lossless Uint8Array chunks), 'lines' (UTF-8 lines), or 'ndjson' (a parsed value per line).

import { stream } from 'stitchapi/stream';

const logs = stream({
    url: 'https://api.example.com/logs',
    stream: { decode: 'ndjson' },
});

for await (const ev of logs.stream()) {
    if (ev.type === 'delta') console.log(ev.chunk); // one parsed JSON value per line
}

Prop

Type

Streaming members open on the same auth/retry/throttle spine, with one twist: opening a stream charges the rate limiter once but never holds a concurrency slot, so a long-lived connection can't pin a seam's concurrency budget.

Validating streamed values

A streaming surface has no single response body, so an output contract validates each delta before it is emitted — never the collected array. A value that fails a critical/schema check fails the stream and is never delivered; a drift watch path warns and the value still flows, so a .stream() consumer only ever sees values that passed the contract.

import { stream } from 'stitchapi/stream';
import { z } from 'zod';

const logs = stream({
    url: 'https://api.example.com/logs',
    stream: { decode: 'ndjson' },
    output: z.object({ level: z.string(), msg: z.string() }), // checked per record
});

For sse the contract describes the event's data payload, not the { event, data, id, retry } envelope. transform and unwrap reshape a whole buffered body and aren't applied to the delta path, and drift snapshots are a whole-body concept — not taken per-delta.

download

download fetches a file: a GET buffered into a Blob, with byte progress, a Content-Disposition filename (with a URL-last-segment fallback), and a per-call AbortSignal. It never writes to disk — saving the Blob is your call, which keeps it browser-first.

import { download } from 'stitchapi/download';

const getReport = download({ url: 'https://api.example.com/report.pdf' });

const { blob, filename } = await getReport({
    signal: controller.signal,
    onProgress: (p) => console.log(p.loaded, '/', p.total),
});

Prop

Type

Both spellings, one engine

Every surface has two co-existing spellings. The monomorphic helper above (sse(...), download(...), …) is pre-bound to its surface and gives surface-specific types. The generic form passes the surface as kind on the core stitch, so a seam can mint members of any surface without forking:

import { seam, stitch } from 'stitchapi';
import { downloadSurface } from 'stitchapi/download';

const file = stitch({ kind: downloadSurface, url: 'https://x/report.pdf' });

const api = seam({ baseUrl: 'https://api.example.com' });
const member = api.stitch({ kind: downloadSurface, path: '/report.pdf' });

Either way the surface is named in __config by a stable string id ('sse', 'download', …), so a stitch's declaration still round-trips to JSON for MCP, the playground, and llms.txt — the behaviour hooks stay a runtime detail. An id a consumer doesn't recognise degrades to "an http-shaped call of unknown style", never a crash.

On this page