Secret resolvers
Resolve secrets lazily at call time with env() and keychain() instead of hard-coding them.
Every auth strategy takes a secret as a reference, not a value, and resolves it
at call time — reach for env() and keychain() so the declaration carries a
capability, not a credential you have to commit.
Example
import { , , , } from 'stitchapi';
// env() reads process.env.API_TOKEN when the stitch is called, not now.
const = ({
: 'https://api.example.com',
: '/me',
: (('API_TOKEN')),
});
// keychain() resolves from ~/.stitch/secrets.json, falling back to the env var.
const = ({
: 'https://api.example.com',
: '/billing',
: (('BILLING_TOKEN')),
});The token is never read at declaration time, so the secret never enters the stitch's source — only the name of where to find it does.
Options
A secret is a string | (() => string). Every strategy — bearer, apiKey,
basic, oauth2 — accepts one and resolves it the moment a request goes out,
so the same declaration works across processes and machines without baking a
value in.
env(name) returns a thunk that reads process.env[name] at call time and
throws missing env var <name> if it is unset — failing loudly at the call
rather than silently sending an empty token.
keychain(name) is a lightweight keychain over a JSON file: it reads
~/.stitch/secrets.json if present and the key is there, otherwise falls back
to the environment variable of the same name, and throws if neither has it.
Why call-time resolution matters: the value is fetched only when a call is
made, so you never commit a secret and the stitch holds a capability rather than
the credential itself — the capability boundary
the product is built on. You can pass a plain string (discouraged — that hard-codes
the value), or any () => string thunk of your own, so plugging in a vault or
secrets manager is just supplying a different resolver.
A plain-string secret is read at declaration time and lives in your source
and history. Prefer env() or keychain() so the value is resolved at call
time instead.