TypeScript SDK
Fetch managed prompts and report run telemetry — without your code ever crashing if the service is unreachable. The same surface as the Python SDK, for Node and the browser.
Install
npm install @promptflip/sdkRequires Node 18 or newer. Zero runtime dependencies — uses platform fetch.
Quickstart
import { PromptFlip } from "@promptflip/sdk";
const pm = new PromptFlip({ apiKey: "sk_...", environment: "prod" });
// Fetch a prompt. Always resolves to a usable Prompt — never rejects.
const prompt = await pm.get(
"support-agent-system",
"You are a helpful support agent for [[plan]]-plan users.",
{ inputs: { plan: user.plan }, assignmentKey: user.id },
);
// Use it with your model, report telemetry in one callback:
await prompt.run(async (r) => {
const response = await callYourModel(prompt.text);
r.tokens = response.totalTokens;
r.converted = true; // any property name -> custom metric
});
// Or report metrics directly (inline, or from a worker later):
await pm.send(prompt, { csatScore: 5 });The three primitives
That is the entire surface a caller writes against:
pm.get(key, fallback, options?) -> Promise<Prompt>
pm.send(promptOrId, options)
prompt.run(async (r) => { ... })Compile-time validation
When fallback is a string literal, the types extract its [[inputs]] and require matching keys in the inputs object — your editor flags mismatches as you type:
pm.get("k", "Hello [[name]]"); // inputs required
pm.get("k", "Hello [[name]]", { inputs: {} }); // 'name' missing
pm.get("k", "Hello [[name]]", { inputs: { name: "A" } }); // okGuarantees
- Never rejects on the serving path.
getalways resolves to aPrompt;sendalways resolves. A missingapiKeydoes not throw either — it selects local-only mode (see below). Construction only throws if you pass the test-onlyfetchoption outsideNODE_ENV=test. - Hybrid, local-first resolution.
getresolves in order: cloud (a live fetch whenapiKeyis set — served from the disk cache first if it's fresh, or its last-known cached value — memory, then disk, even if stale — the instant the live call fails or errors) → local repo (iflocalDiris set — the same files the CLI manages) → your fallback string → empty, with a warning. In other words: a cached cloud value always wins over local files and the fallback, even when the network is down. EveryPromptexposes which tier resolved it viaprompt.source("cloud","local","fallback", or"none"). - Caching. An in-memory LRU cache holds the last-seen value per key with no expiry (evicted only by size, capped by
cacheMaxSize) — this is what backs the last-known fallback above. PasscacheDirto also persist a cross-process disk cache with a TTL (cacheTtlSeconds, default 300) and stale-while-revalidate — a stale entry is served immediately while a fresh one is fetched in the background.timeoutMscontrols the request timeout. - Zero runtime dependencies.
Error handling
The SDK never rejects on the serving path. Failures fire an optional logger callback you pass at construction time:
const pm = new PromptFlip({
apiKey: "sk_...",
environment: "prod",
logger: (level, msg, extra) => {
if (extra.kind === "out_of_credits") {
alert("PromptFlip out of credits");
}
console.warn(`[PM ${level}]`, msg, extra);
},
});extra.kind is one of: transport, out_of_credits, quota_exceeded, missing_inputs, missing_vars, missing_variables, missing_inputs_in_fallback, invalid_metric, not_found, unresolved, unknown.
Fallback templates
A fallback uses the same [[placeholders]] syntax as the stored prompt; the SDK fills them client-side from inputs so online and offline render identically. Check a fallback in your tests:
import { checkFallback } from "@promptflip/sdk";
const missing = checkFallback("Hello [[name]] on [[plan]]", { name: "Alice" });
// missing === ["plan"]