keel-sdk
v0.2.4
Published
Drop-in replacements for OpenAI, Anthropic, Google, xAI, and Meta SDKs with built-in AI governance. One-line import change adds permit-first policy enforcement, budget controls, audit trails, and usage reporting to every AI call.
Maintainers
Readme
keel-sdk
TypeScript SDK for the Keel AI governance API.
Keel lets you issue permits before AI calls, enforce policies, track usage, and audit decisions — across any provider.
⚠️ Keel is currently in private beta. You'll need a Keel account and API key to use this SDK. Sign up for early access →
One-Line Provider Migration
Add Keel governance to your existing AI code with a single import change — no other code modifications required.
OpenAI:
// BEFORE:
import OpenAI from "openai";
// AFTER:
import { OpenAI } from "keel-sdk/providers/openai";Anthropic:
// BEFORE:
import Anthropic from "@anthropic-ai/sdk";
// AFTER:
import { Anthropic } from "keel-sdk/providers/anthropic";Google (Gemini):
// BEFORE:
import { GoogleGenerativeAI } from "@google/generative-ai";
// AFTER:
import { GoogleGenerativeAI } from "keel-sdk/providers/google";xAI (Grok):
// BEFORE:
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.x.ai/v1", apiKey: "..." });
// AFTER:
import { Grok } from "keel-sdk/providers/xai";
const client = new Grok();Meta (Llama):
// BEFORE:
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.llama-api.com", apiKey: "..." });
// AFTER:
import { Llama } from "keel-sdk/providers/meta";
const client = new Llama();The rest of your code stays identical. Under the hood, each call automatically requests a governance permit, proxies the request through Keel, and reports token usage. If a permit is denied, a KeelError with status 403 and code permit_denied is thrown.
Set three env vars and you're done:
export KEEL_BASE_URL="https://api.keelapi.com"
export KEEL_API_KEY="keel_sk_..."
export KEEL_PROJECT_ID="prj_..."Optionally pass a subject to attribute usage to a specific user:
const client = new OpenAI({
keelSubject: { type: "user", id: "usr_42" },
});Streaming works transparently — pass stream: true and iterate as usual.
See examples/provider-swap.ts for a full working example.
Install
npm install keel-sdkSetup
import { KeelClient } from "keel-sdk";
const client = new KeelClient({
baseUrl: process.env.KEEL_BASE_URL!,
apiKey: process.env.KEEL_API_KEY!,
});Permits
Request a permit before making an AI call:
const permit = await client.permits.create({
project_id: "proj_123",
idempotency_key: crypto.randomUUID(),
subject: { type: "user", id: "usr_123" },
action: { name: "ai.generate" },
resource: {
type: "request",
id: "req_123",
attributes: {
provider: "openai",
model: "gpt-4o-mini",
estimated_input_tokens: 200,
estimated_output_tokens: 500,
},
},
});
if (permit.decision === "allow") {
// proceed with AI call
}Dry run
const result = await client.permits.dryRun(permitRequest);List and get
const list = await client.permits.list({ project_id: "proj_123", limit: 50 });
const permit = await client.permits.get("permit_id");Report usage
await client.permits.reportUsage("permit_id", {
actual_input_tokens: 180,
actual_output_tokens: 420,
});Attestation, evidence, lineage
await client.permits.attest("permit_id", {
attestor: "[email protected]",
attestation_type: "approve",
evidence_url: "https://example.com/review/123",
});
await client.permits.addEvidence("permit_id", {
evidence_type: "hash",
evidence_value: "abc123",
label: "response_hash",
attached_by: "audit-system",
});
const evidence = await client.permits.listEvidence("permit_id");
const lineage = await client.permits.lineage("permit_id");
const bundle = await client.permits.bundle("permit_id");Executions
Run a model synchronously:
const result = await client.executions.create({
operation: "generate.text",
messages: [{ role: "user", content: "Summarize this document." }],
routing: { provider: "openai", model: "gpt-4o-mini" },
});Stream tokens as they arrive:
for await (const event of client.executions.stream({
operation: "generate.text",
messages: [{ role: "user", content: "Write a poem." }],
routing: { provider: "openai", model: "gpt-4o-mini" },
})) {
if (event.event_type === "content.delta") process.stdout.write(event.data.delta.text);
if (event.event_type === "done") console.log("\nFinished.");
}Execute (unified)
const result = await client.execute.run({
model: "gpt-4o-mini",
input: { text: "Translate to Spanish: Hello world" },
provider: "openai",
});Proxy
Pass requests through to providers with Keel governance applied:
const response = await client.proxy.openai({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hello" }],
});
// Also: client.proxy.anthropic(), .google(), .xai(), .meta()Jobs
Submit async jobs and poll for results:
const job = await client.jobs.create({
permit: { /* PermitRequest */ },
provider_payload: {
messages: [{ role: "user", content: "Analyze this dataset." }],
},
metadata: {},
});
const status = await client.jobs.get(job.job_id);
// status.status: "submitted" | "queued" | "processing" | "completed" | "failed"API Keys
const key = await client.apiKeys.create();
const keys = await client.apiKeys.list();
await client.apiKeys.revoke(key.id);Request Timeline
const timeline = await client.requests.timeline("request_id");Error Handling
import { KeelError } from "keel-sdk";
try {
await client.permits.create(request);
} catch (err) {
if (err instanceof KeelError) {
console.error(err.status); // HTTP status code
console.error(err.code); // e.g. "permit_denied"
console.error(err.message); // human-readable message
console.error(err.field); // field that caused the error, if any
}
}Automatic Retry
Enable automatic retry with exponential backoff for transient errors:
import { KeelClient, DEFAULT_RETRY_CONFIG } from "keel-sdk";
const client = new KeelClient({
baseUrl: process.env.KEEL_BASE_URL!,
apiKey: process.env.KEEL_API_KEY!,
retryConfig: DEFAULT_RETRY_CONFIG,
});The default configuration retries up to 3 times with exponential backoff (500ms initial delay, 2x multiplier, 30s max) on status codes 408, 429, 500, 502, 503, and 504.
Customize the retry behavior:
import { KeelClient } from "keel-sdk";
import type { RetryConfig } from "keel-sdk";
const retryConfig: RetryConfig = {
maxRetries: 5,
initialDelayMs: 1000,
maxDelayMs: 60_000,
backoffMultiplier: 3.0,
retryableStatusCodes: [429, 500, 502, 503, 504],
};
const client = new KeelClient({
baseUrl: process.env.KEEL_BASE_URL!,
apiKey: process.env.KEEL_API_KEY!,
retryConfig,
});Retry applies to all non-streaming requests. Streaming requests (executions.stream()) are never retried. When the server sends a Retry-After header, the SDK respects it as a minimum delay before the next attempt.
Per-Request Timeout
Override the global timeout for individual requests by passing a RequestOptions object:
// Global timeout is 30 seconds (default)
const client = new KeelClient({
baseUrl: process.env.KEEL_BASE_URL!,
apiKey: process.env.KEEL_API_KEY!,
});
// This specific permit creation gets 30 seconds
const permit = await client.permits.create(permitRequest, { timeoutMs: 30_000 });
// Streaming calls can use a longer timeout
const stream = await client.executions.stream(
{ permit_id: permit.permit_id, operation: "generate.text", messages },
{ timeoutMs: 120_000 },
);
// Per-request timeout works on all sub-clients:
// client.permits, client.executions, client.execute, client.proxy, client.jobs, client.requestsFor streaming, the effective timeout is timeoutMs * 6 (same multiplier as the global timeout). When no per-request timeout is provided, the global timeoutMs from client options is used.
Freshness Headers
For replay-protected endpoints, enable freshness headers:
const client = new KeelClient({
baseUrl: process.env.KEEL_BASE_URL!,
apiKey: process.env.KEEL_API_KEY!,
requestFreshness: { enabled: true },
});This adds X-Keel-Timestamp and X-Keel-Nonce to every request.
License
MIT
