SDKs
Add governance to your AI requests in one change.
Keel evaluates requests before execution and records outcomes after. Requests are only executed if a permit is granted.
Install
Python
pip install keel-sdkChange one line
The Keel SDK keeps the provider-shaped calling pattern you already use. Change the import and keep the governance step in front of the provider call.
Python
# Before
from openai import OpenAI
# After
from keel_sdk.providers.openai import OpenAIProvider-shaped request flow. Provider-shaped responses where the wrapper documents them. Governance runs before the provider call.
Working example
Python
from keel_sdk.providers.openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=128,
)
print(response["choices"][0]["message"]["content"])Get your API key
Keel requires a project API key to evaluate and govern requests.
Sign up for Keel to get started. In the Keel dashboard , create a project, generate your API key, add a provider key, and set your first control.
Once approved, set your key:
export KEEL_BASE_URL="https://api.keelapi.com"
export KEEL_API_KEY="keel_sk_your_project_key"
export KEEL_PROJECT_ID="your-project-uuid"You can also pass these directly when constructing the client:
Python
from keel_sdk.providers.openai import OpenAI
client = OpenAI(
keel_base_url="https://api.keelapi.com",
keel_api_key="keel_sk_...",
keel_project_id="your-project-uuid",
)Provider API keys (OpenAI, Anthropic, etc.) are stored in your Keel project. The wrapper does not need them — Keel manages provider credentials on your behalf.
How it works
Every wrapper call runs the same governance flow:
Evaluate → Decide → Execute → Record
- Evaluate — Keel evaluates your project’s policies and budget against the request.
- Decide — If denied, the wrapper raises an error immediately. No provider call is made.
- Execute — If permitted, Keel forwards the request using your project’s stored provider key.
- Record — Usage, decision, and routing evidence are recorded for later readback.
Your code sees a normal provider response. A policy denial surfaces as an explicit error.
Provider wrappers
Each wrapper mirrors the native SDK interface for that provider. No new methods to learn.
OpenAI
Python
from keel_sdk.providers.openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=128,
)
print(response["choices"][0]["message"]["content"])Anthropic
Python
from keel_sdk.providers.anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=128,
)
print(response["content"][0]["text"])Python
from keel_sdk.providers.google import GenerativeModel
model = GenerativeModel("gemini-2.5-flash")
response = model.generate_content("Hello!")xAI
Python
from keel_sdk.providers.xai import Grok
client = Grok()
response = client.chat.completions.create(
model="grok-3-mini",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=128,
)Meta
Python
from keel_sdk.providers.meta import Llama
client = Llama()
response = client.chat.completions.create(
model="llama-4-scout",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=128,
)Provider wrapper scope
All five provider wrappers expose synchronous text generation and streaming. For image, audio, embedding, and other modality operations, use the provider-neutral /v1/executions endpoint — see Executions for the supported operation × provider matrix.
Streaming
Streaming is supported for text generation across all five providers. Pass stream=True (Python) or stream: true (TypeScript) and iterate over chunks. Usage is automatically reported after the stream completes. Advanced operations should be verified for streaming support in your specific deployment.
Python
from keel_sdk.providers.openai import OpenAI
client = OpenAI()
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Write a haiku about governance."}],
max_tokens=64,
stream=True,
)
for chunk in stream:
delta = chunk.get("choices", [{}])[0].get("delta", {})
if "content" in delta:
print(delta["content"], end="", flush=True)Usage is automatically reported after the stream completes.
Error handling
When governance blocks a request, the wrapper stops before any provider call. In TypeScript and Python this surfaces as a KeelError. In Go, the core client also exposes typed *keel.KeelError and *keel.ThrottledError values for API and throttle handling.
Python
from keel_sdk import KeelError
from keel_sdk.providers.openai import OpenAI
client = OpenAI()
try:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=128,
)
except KeelError as e:
print(f"Keel error {e.status}: [{e.code}] {e.message}")Common cases to handle:
permit_deniedon TypeScript and Python wrapper denials before any provider call.budget.rate_limit_throttledon throttle paths, exposed via throttle-specific fields such asreasonCodeorreason_code.- Upstream provider failures after an allow decision, surfaced as normal SDK errors with the provider or proxy message attached.
Advanced: Direct client access
For teams that need fine-grained control over individual governance steps, the KeelClient provides direct access to permits, executions, proxy, and other surfaces.
Supported SDKs
| SDK | Best for | Surface |
|---|---|---|
| TypeScript | Node and TypeScript services | Typed sub-clients for permits, executions (sync + stream), execute, proxy, jobs, API keys, and request timeline |
| Python | Backend services and scripts | Same surface as TypeScript with sync and async variants for every method |
| Go | Backend services | Typed clients for permits, executions (sync + stream), execute, proxy, jobs, API keys, and request timeline |
Permit-only
Use this when your app wants decision-first governance and will call the provider itself.
import { KeelClient } from 'keel-sdk'
const client = new KeelClient({
baseUrl: 'https://api.keelapi.com',
apiKey: process.env.KEEL_API_KEY!
})
const permit = await client.permits.create({
project_id: process.env.KEEL_PROJECT_ID!,
idempotency_key: 'permit-only-001',
subject: { type: 'user', id: 'usr_123' },
action: { name: 'ai.generate.summary' },
resource: {
type: 'request',
id: 'req_123',
attributes: {
provider: 'openai',
model: 'gpt-4o-mini',
operation: 'generate.text',
estimated_input_tokens: 120,
estimated_output_tokens: 80,
max_output_tokens_requested: 120
}
}
})
if (permit.decision !== 'allow') {
throw new Error(`blocked: ${permit.reason}`)
}
console.log(permit.id)
console.log(permit.decision)Permit creation requires
project_idin the body.Execution routes do not use this body field. Use the
Idempotency-Keyheader instead.
Provider-neutral execution: /v1/executions
Use this when you want Keel to execute and your request is provider-neutral.
const response = await fetch('https://api.keelapi.com/v1/executions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KEEL_API_KEY!}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'executions-001'
},
body: JSON.stringify({
operation: 'generate.text',
messages: [
{ role: 'user', content: 'Reply in one short sentence.' }
],
routing: {
provider: 'openai',
model: 'gpt-4o-mini'
},
parameters: {
max_output_tokens: 80
}
})
})
const execution = await response.json()
console.log(execution.id)
console.log(execution.status)
console.log(execution.governance?.permit_id ?? null)
console.log(execution.output?.content?.[0]?.text ?? null)Execution routes infer project context from the project API key and do not require
project_idin the request body.
Provider-shaped execution: /v1/execute
Use this when you want Keel to execute and your request is provider-shaped.
const response = await fetch('https://api.keelapi.com/v1/execute', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KEEL_API_KEY!}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'execute-001'
},
body: JSON.stringify({
provider: 'openai',
model: 'gpt-4o-mini',
input: {
messages: [
{ role: 'user', content: 'Reply with one sentence.' }
],
max_tokens: 80
}
})
})
const execution = await response.json()
console.log(execution.id)
console.log(execution.status)
console.log(execution.resolved?.provider ?? null)
console.log(execution.output?.content?.[0]?.text ?? null)Execution routes infer project context from the project API key and do not require
project_idin the request body.
Provider-native proxy: /v1/proxy/*
Use this when you need provider-native payloads and provider-native responses.
const response = await fetch('https://api.keelapi.com/v1/proxy/openai', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KEEL_API_KEY!}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'proxy-001'
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [
{ role: 'user', content: 'Say hello.' }
],
max_tokens: 32
})
})
const proxyData = await response.json()
console.log(proxyData.id)
console.log(proxyData.model)
console.log(proxyData.choices?.[0]?.message?.content ?? null)Proxy routes return provider-native responses, not the normalized execution envelope.
Common patterns
Parse normalized execution results
Use the same parser shape for /v1/executions and /v1/execute.
function readExecution(data: any) {
return {
requestId: data.id,
status: data.status,
permitId: data.governance?.permit_id ?? null,
text:
data.output?.content?.find((block: any) => block.type === 'output_text')?.text ??
null,
errorCode: data.error?.code ?? null,
errorMessage: data.error?.message ?? null
}
}Permit-only gating
Check policy first, then skip the provider call if Keel says no.
import { KeelClient } from 'keel-sdk'
const client = new KeelClient({
baseUrl: 'https://api.keelapi.com',
apiKey: process.env.KEEL_API_KEY!
})
const permit = await client.permits.create({
project_id: process.env.KEEL_PROJECT_ID!,
idempotency_key: 'gate-001',
subject: { type: 'user', id: 'usr_123' },
action: { name: 'ai.generate.summary' },
resource: {
type: 'request',
id: 'req_gate_001',
attributes: {
provider: 'openai',
model: 'gpt-4o-mini',
operation: 'generate.text',
modality: 'text',
execution_mode: 'sync',
estimated_input_tokens: 120,
estimated_output_tokens: 80,
max_output_tokens_requested: 120
}
}
})
if (permit.decision !== 'allow') {
console.log('blocked:', permit.reason)
} else {
console.log('safe to call provider')
}Handle normalized envelope errors vs top-level API errors
function readKeelFailure(data: any) {
if (data?.object === 'execution') {
return {
kind: 'execution-envelope',
status: data.status,
code: data.error?.code ?? null,
message: data.error?.message ?? null
}
}
return {
kind: 'top-level-api-error',
status: 'error',
code: data?.error?.code ?? null,
message: data?.error?.message ?? null
}
}Provider-native proxy parsing
Do not reuse normalized-envelope parsing on proxy routes.
function readOpenAIProxy(data: any) {
return {
requestId: data.id,
text: data.choices?.[0]?.message?.content ?? null,
finishReason: data.choices?.[0]?.finish_reason ?? null,
providerErrorMessage: data.error?.message ?? null
}
}Auth
Project API key
Create a project API key in the Keel dashboard for the project that will call the SDK.
Send it as Authorization: Bearer <project_api_key>.
Provider keys are handled by Keel
For /v1/execute, /v1/executions, /v1/proxy/*, and /v1/jobs, Keel uses the provider key stored on that project.
Your app does not send raw OpenAI, Anthropic, Google, xAI, or Meta keys on these routes.
Where to get keys
- Create the runtime project API key in the Keel dashboard for your project.
- Add or rotate provider keys in the same project before the first governed execution.
- If you use permit-only mode and call the provider yourself, your app still needs its own provider credential for that second step.
Where to go next
- Permits — decision-first governance
- Executions — provider-neutral execution
- Execute — provider-shaped execution
- Proxy Execution — provider-native passthrough
- Errors — error codes and retry guidance
Managing Keel at scale
Terraform configures Keel resources through infrastructure as code. It is not used to execute or proxy AI requests.