floopy-sdk
v0.4.0
Published
Official Floopy AI Gateway SDK for Node/TypeScript. Drop-in replacement for the openai SDK with cache, audit, experiments, and security on top.
Maintainers
Readme
floopy-sdk
Official Floopy AI Gateway SDK for Node and TypeScript. Drop-in replacement for the
openaiSDK with Floopy's cache, audit, experiments, routing, and security on top.
Why
floopy-sdk wraps the official openai-node package and points it at the
Floopy gateway, so:
- Zero migration cost for
chat.completionsandembeddings— same types, same methods. - Security updates to the OpenAI SDK reach you on
pnpm updatewithout forks or paritydrift. - Floopy-only features (audit, experiments, constraints, decision export,
feedback, routing dry-run) get first-class typed methods instead of raw
fetchcalls.
Install
pnpm add floopy-sdk # or: npm i floopy-sdk / bun add floopy-sdkRequires Node >= 20.
Quick start
import { Floopy } from "floopy-sdk";
const floopy = new Floopy({
apiKey: process.env.FLOOPY_API_KEY!,
});
const response = await floopy.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello from Floopy!" }],
});
console.log(response.choices[0]?.message?.content);That's the entire migration path. The next sections show how to opt in to Floopy-specific behavior.
Migrating from openai
- import OpenAI from "openai";
- const client = new OpenAI({
- apiKey: process.env.OPENAI_API_KEY,
- });
+ import { Floopy } from "floopy-sdk";
+ const client = new Floopy({
+ apiKey: process.env.FLOOPY_API_KEY!,
+ });
const r = await client.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "..." }],
});The client.chat, client.embeddings, and client.models getters return
the underlying openai-node resources, so types and runtime behavior are
identical.
Full guide: https://floopy.ai/docs/sdk/migrating-from-openai.
Floopy options (cache, prompt versioning, security firewall)
const floopy = new Floopy({
apiKey: process.env.FLOOPY_API_KEY!,
options: {
cache: { enabled: true, bucketMaxSize: 3 },
promptId: "cd4249d5-44d5-46c8-8961-9eb3861e1f7e",
promptVersion: "1",
llmSecurityEnabled: true,
},
});These map to Floopy-* headers and are forwarded to every request
(both OpenAI-compat calls and Floopy-only ones). Per-call overrides are
available on every Floopy resource.
| Option | Header | Purpose |
| --- | --- | --- |
| cache.enabled | Floopy-Cache-Enabled | Toggle exact + semantic cache |
| cache.bucketMaxSize | Floopy-Cache-Bucket-Max-Size | Max entries per semantic bucket |
| promptId | Floopy-Prompt-Id | Stored prompt to resolve |
| promptVersion | Floopy-Prompt-Version | Pinned version for promptId |
| llmSecurityEnabled | floopy-llm-security-enabled | LLM firewall pre-check |
See https://floopy.ai/docs/concepts/cache and https://floopy.ai/docs/concepts/security for the full feature docs.
Floopy-only resources
Each resource maps to a public /v1/* endpoint of the gateway and is
typed end-to-end. Errors are FloopyError subclasses (see below).
feedback
const r = await floopy.chat.completions.create({ /* ... */ });
await floopy.feedback.submit({ score: 9, useful: true, sessionId: r.id });decisions
const decision = await floopy.decisions.get(requestId);
const page = await floopy.decisions.list({ from, to, limit: 50 });
for await (const d of floopy.decisions.iterate({ from })) { /* one at a time */ }
for await (const p of floopy.decisions.pages({ from })) { /* page at a time */ }experiments
const exp = await floopy.experiments.create({
name: "cost-vs-quality",
variantARoutingRuleId: ruleA,
variantBRoutingRuleId: ruleB,
});
const results = await floopy.experiments.results(exp.id);
await floopy.experiments.rollback(exp.id);create and rollback automatically include the X-Floopy-Confirm:
experiments header the gateway requires (SEC-009).
constraints
const current = await floopy.constraints.get();
await floopy.constraints.put({ costLimitMonthlyUsd: 100 });put is full-replace: omitted fields are reset.
export
for await (const row of floopy.export.decisions({ from, to })) {
// streamed JSONL, parsed and typed
}
// to also read the trailer (truncation reasons, totals):
const { rows, trailer } = floopy.export.decisionsWithTrailer({ from, to });
for await (const r of rows) { /* ... */ }
console.log(trailer.value); // populated after iteration completesevaluations
const run = await floopy.evaluations.create({ datasetId, model: "gpt-4o" });
const status = await floopy.evaluations.get(run.id);
const results = await floopy.evaluations.results(run.id, { limit: 100 });
await floopy.evaluations.cancel(run.id);routing.explain
const explain = await floopy.routing.explain({ model, messages });
console.log(explain.wouldSelect, explain.firewallDecision);Pro plan only. wouldSelect is null if the firewall blocks the request.
files and batches
OpenAI-shaped Batch + Files passthrough. A batch carries no model up
front, so select the upstream with the provider option (sent as the
floopy-provider header) — optional when the key has one provider.
const file = await floopy.files.upload(
{ file: new Blob([jsonl]), filename: "in.jsonl", purpose: "batch" },
{ provider: "openai" },
);
const batch = await floopy.batches.create(
{
input_file_id: file.id,
endpoint: "/v1/chat/completions",
completion_window: "24h",
},
{ provider: "openai" },
);
const done = await floopy.batches.retrieve(batch.id, { provider: "openai" });
if (done.status === "completed" && done.output_file_id) {
const out = await floopy.files.content(done.output_file_id, { provider: "openai" });
console.log(await out.text());
}
await floopy.batches.cancel(batch.id, { provider: "openai" });
await floopy.files.delete(file.id, { provider: "openai" });files.list, files.retrieve, and batches.list are also available.
files.content returns the raw Response so you can stream it.
Per-resource references with full options live under https://floopy.ai/docs/sdk/node.
Streaming
chat.completions streaming is delegated to openai-node and returns an
AsyncIterable<ChatCompletionChunk>. The export.decisions resource
returns a AsyncIterable<DecisionRow> over the gateway's JSONL stream
(0.2.0).
Error handling
Every Floopy-only call rejects with a FloopyError subclass:
import { FloopyRateLimitError, FloopyPlanError } from "floopy-sdk";
try {
await floopy.export.decisions({ from, to });
} catch (err) {
if (err instanceof FloopyRateLimitError) {
await sleep((err.retryAfterSeconds ?? 1) * 1000);
} else if (err instanceof FloopyPlanError) {
console.error(`Upgrade plan: feature ${err.feature} not in current plan`);
} else throw err;
}Errors from chat.completions and embeddings are emitted by the OpenAI
SDK (OpenAI.APIError and friends).
Self-hosting / custom base URL
const floopy = new Floopy({
apiKey: process.env.FLOOPY_API_KEY!,
baseURL: "https://gateway.internal.acme.com/v1",
});Links
- Full SDK reference: https://floopy.ai/docs/sdk/node
- API reference: https://floopy.ai/docs/api
- Migrating from
openai: https://floopy.ai/docs/sdk/migrating-from-openai - Cache concepts: https://floopy.ai/docs/concepts/cache
- Audit concepts: https://floopy.ai/docs/concepts/audit
- Experiments concepts: https://floopy.ai/docs/concepts/experiments
- Changelog:
CHANGELOG.md
License
Apache-2.0 © Floopy
