Skip to main content
PromptFlipdocs

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

bash
npm install @promptflip/sdk

Requires Node 18 or newer. Zero runtime dependencies — uses platform fetch.

Quickstart

TypeScript
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:

TypeScript
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:

TypeScript
pm.get("k", "Hello [[name]]");                            // inputs required
pm.get("k", "Hello [[name]]", { inputs: {} });            // 'name' missing
pm.get("k", "Hello [[name]]", { inputs: { name: "A" } }); // ok

Guarantees

  • Never rejects on the serving path. get always resolves to a Prompt; send always resolves. A missing apiKey does not throw either — it selects local-only mode (see below). Construction only throws if you pass the test-only fetch option outside NODE_ENV=test.
  • Hybrid, local-first resolution. get resolves in order: cloud (a live fetch when apiKeyis 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 (if localDir is 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. Every Prompt exposes which tier resolved it via prompt.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. Pass cacheDir to 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. timeoutMs controls 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:

TypeScript
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:

TypeScript
import { checkFallback } from "@promptflip/sdk";

const missing = checkFallback("Hello [[name]] on [[plan]]", { name: "Alice" });
// missing === ["plan"]