HTTP serve
Expose a stitch over HTTP for callers that aren't in-process.
Reach for stitch serve when a caller can't import your stitches in-process —
a remote service, another language, a shell script — and you want to expose the
registry of named stitches over a thin local HTTP front door instead.
Example
Start the server, then call a registered stitch by name with a JSON body:
# Serves the registry on http://127.0.0.1:8787 by default.
stitch serve
# List the available stitch names.
curl http://127.0.0.1:8787/
# Run the `me` stitch; the request body is its input.
curl -X POST http://127.0.0.1:8787/stitch/me \
-H 'content-type: application/json' \
-d '{}'The server runs the named stitch through the same engine as an in-process call and returns the final validated result as JSON.
Options
Two routes serve the registry of named stitches:
GET /lists the available stitch names.POST /stitch/:nameruns that stitch; the JSON request body is its input. Any other method returns405, and an unknown name returns404.
:name is one of the names GET / lists: a registry key, which for
stitch serve is the export name. A stitch's configured name labels its
traces and does not route to it; a module's default export has no export name,
so it is keyed by its configured name.
The server binds to 127.0.0.1:8787 by default. Responses are the final
validated result as JSON; send Accept: text/event-stream (or add ?stream=1)
to receive the live event stream as SSE instead.
This front door runs stitches you name; it is not a webhook endpoint, and StitchAPI does not receive webhooks at all — that is your server's job. Receiving a signed webhook draws exactly where the line falls and what the library still owns on the far side of it.
Anti-pattern: don't move the bind off the 127.0.0.1 loopback default
to a public interface so a remote caller can reach it directly — the front
door has no auth of its own, so a public bind turns the registry into an
open proxy that runs every stitch for anyone who can reach the port. Keep it
on loopback and put a fronting layer (a reverse proxy or gateway that
authenticates the caller) in front of it; the stitch still holds its own
credential and resolves it server-side, never handing it to the caller. See
Capability, not credential.
Failures
A failed run answers with a non-2xx status and a JSON body of
{ error, status }. An upstream error status passes through (401, 429,
503); a failure with no status, such as a timeout or a transport error,
answers 502. status in the body is the upstream's own status, when there
was one.
error is the standard reason phrase for the response status, not the
failure's own message:
{ "error": "Bad Gateway" }The raw message can name an internal host (getaddrinfo ENOTFOUND payments.internal.corp) or repeat an upstream's wording to whoever called the
server. Every @stitchapi/* host adapter withholds it by default, and serve
does the same.
SSE mode withholds the same detail. The frames and their event types stay, and these fields are dropped:
- an
event: errorframe'smessageis the reason phrase, and so is the frame sent when the stream throws; - an
event: startframe has nourland notemplate, which name the upstream host and its route (/internal/ledger/{id}); - an
event: progressorevent: infoframe has nodetail. On a retryprogressframe that field is the raw transport error text; - a frame's
name, onstartanderror, is the name you addressed the stitch by in/stitch/:name. A stitch's own name defaults to itspath, so/internal/ledger/{id}would otherwise appear there; - an
event: driftframe'sfindinghas nodetail. For a failed output validation that field is the validator's issue message, which can repeat the value the upstream sent. The finding keeps itslevel,path,changeandsample.
Anything else a stream carries is unchanged: delta and result payloads, the
request input echoed on start, span ids, surface and transport, and a
progress frame's status and errorType.
When the callers are trusted, such as in local development or on an internal
network, turn the detail on with stitch serve --disclose, or
serve(registry, { disclose: true }) in code. It restores the raw message and
the dropped SSE fields together.
A delegate-backoff rate limit (a RateLimitError from
throttle.delegate) also sends a
Retry-After header, in whole seconds, when the upstream gave one. The value
is capped at one day. A caller can then wait as long as the upstream asked.
Programmatic API
The CLI is a thin wrapper. To embed the same server in your own process, import
serve from the stitchapi/serve subpath and hand it a registry of named
stitches:
import { me } from './stitches';
import { serve } from 'stitchapi/serve';
const handle = await serve({ me });
console.log(`listening on ${handle.url}`);
// later
await handle.close();serve(registry, options?) returns a handle with the bound url, port, the
underlying server, and a close() method. It binds 127.0.0.1:8787 by
default; pass { port: 0 } to let the OS pick an ephemeral port, and
{ disclose: true } to send failure detail as described in
Failures.
A served stitch is the same definition as the in-process
function surface and the
stitch CLI — one source of truth, three ways to call it.