Executions
Provider-neutral execution for governed Keel requests.
Use POST /v1/executions when you want Keel to execute a governed request through one public, provider-neutral request shape. You send a canonical operation plus messages or inputs, and Keel returns one normalized execution envelope for the supported subset of operations on this route.
Need to choose between execution surfaces first? See the route comparison in Quickstart.
Route summary
| Route | Status | Purpose |
|---|---|---|
POST /v1/executions | Official public | Provider-neutral execution for the currently supported sync and streaming subset. |
Authentication and idempotency
Authorization: Bearer <client_project_api_key>
Idempotency-Key: exec-sync-001Unlike POST /v1/permits, the idempotency key for /v1/executions is an HTTP header, not a JSON body field.
- if the header is omitted, Keel still executes the request
- retries without
Idempotency-Keyare treated as new executions - when retrying the same execution request, reuse the same
Idempotency-Key - proxy routes still have the strongest public same-key replay contract; do not assume proxy-strength request-hash replay binding across every execution surface
On hosted runtimes, execution routes may require additional request freshness headers.
Supported public operations
| Operation | Current providers on /v1/executions | Notes |
|---|---|---|
generate.text | OpenAI, Anthropic, Google, xAI, Meta | Primary text path. |
embed.text | OpenAI, Google, Meta | Returns output.embeddings. |
understand.image | OpenAI, Google, Meta | Requires inputs, including at least one image. |
generate.image | OpenAI | Asset-producing operation. |
edit.image | OpenAI | Requires inputs, including at least one image. |
generate.audio | OpenAI | Text prompt in messages or text-only inputs. |
transcribe.audio | OpenAI, Google | Requires inputs, including at least one audio. |
Not supported on this public route today:
generate.videounderstand.videorun.batchrun.asyncrealtime.session- provider-native tool-calling payloads
Project scoping
Execution routes infer project context from the project API key and do not require
project_idin the request body.
Project scope is inferred from your API key. Unlike POST /v1/permits, execution routes do not require project_id in the request body.
Request shape overview
| Field | Required? | Notes |
|---|---|---|
operation | Yes | Canonical capability name such as generate.text or understand.image. |
mode | No | sync by default. stream is the public SSE mode described below. |
messages | Exactly one of messages or inputs | Text-only convenience shape. |
inputs | Exactly one of messages or inputs | Canonical multimodal shape. |
routing | No | Provider and model target plus optional routing hints. |
parameters | No | Portable controls such as max_output_tokens, temperature, and top_p. |
provider_options | No | Advanced passthrough object keyed by provider name. |
messages vs inputs
- provide exactly one of
messagesorinputs messagesis text-only and is the simplest path for chat-style text requestsinputsis the canonical multimodal shape and is required for image and audio operationstranscribe.audiorequires at least oneaudioinput
routing
routing lets you guide the execution target without switching to a provider-native route:
- set both
routing.providerandrouting.modelwhen you want an explicit target - set one of them when you want Keel to fill in the missing public default
- omit both when you want the current public default target for the operation
Important boundaries:
- provider-neutral routing does not mean autonomous whole-fleet routing
- cross-provider fallback is explicit and request-scoped
- if you want fallback, send
routing.allow_cross_provider_fallback=trueand a concreterouting.fallback_chain
routing does not mean the same thing on every surface:
- On permits, routing is governance intent and recorded decision context.
- On
/v1/executions, routing is applied to actual provider and model selection and dispatch.
context
POST /v1/executions does not expose a public top-level context field. Use the documented execution fields in the request body.
provider_options
provider_options is for advanced passthrough only.
- keys must be one of
openai,anthropic,google,xai, ormeta - each provider value must be an object
- top-level route fields remain authoritative for model, streaming mode, and portable controls such as
max_output_tokens,temperature, andtop_p - treat conflicting provider-specific overrides as unsupported on
/v1/executions
Response shape overview
Successful sync executions return one normalized envelope:
| Field | Meaning |
|---|---|
id | Public execution id. |
object | Always execution. |
created_at | Execution creation timestamp. |
status | completed, failed, or denied. |
status_code | Final HTTP status code for the execution result. |
output | Normalized output. |
output_assets | Produced asset metadata for asset-producing operations. |
routing | Requested and selected provider-model metadata plus reason_code and fallback_occurred. |
governance | Permit decision summary: decision, reason, actions, constraints, and budgets. |
usage | Final normalized usage meters, including cost_usd_micros and estimated_final. |
timing | started_at, completed_at, and duration_ms. |
error | Present when the execution failed or was denied. |
Denied and failed execution results stay inside this normalized execution envelope. A standalone top-level { "error": ... } object is for request-level API failures before Keel can return an execution object.
The HTTP response may also include Keel-specific headers with additional metadata about the execution.
If you need the associated permit id for correlation, read the x-keel-permit-id response header. The body’s governance block is decision-focused.
API example
curl
curl -i -sS https://api.keelapi.com/v1/executions \
-H "Authorization: Bearer keel_sk_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: exec-sync-001" \
-d '{
"operation": "generate.text",
"messages": [
{"role": "system", "content": "Reply in one sentence."},
{"role": "user", "content": "What does Keel do before calling a model?"}
],
"routing": {
"provider": "openai",
"model": "gpt-4o-mini"
},
"parameters": {
"max_output_tokens": 80,
"temperature": 0.2
}
}'Permit creation uses
idempotency_keyin the JSON body, not this header.
Test this request in the Playground: Try in PlaygroundÂ
JavaScript
const response = await fetch('https://api.keelapi.com/v1/executions', {
method: 'POST',
headers: {
Authorization: 'Bearer keel_sk_your_key_here',
'Content-Type': 'application/json',
'Idempotency-Key': 'exec-sync-001'
},
body: JSON.stringify({
operation: 'generate.text',
messages: [
{ role: 'system', content: 'Reply in one sentence.' },
{ role: 'user', content: 'What does Keel do before calling a model?' }
],
routing: {
provider: 'openai',
model: 'gpt-4o-mini'
},
parameters: {
max_output_tokens: 80,
temperature: 0.2
}
})
})
const data = await response.json()
console.log(data)Python
import requests
response = requests.post(
"https://api.keelapi.com/v1/executions",
headers={
"Authorization": "Bearer keel_sk_your_key_here",
"Content-Type": "application/json",
"Idempotency-Key": "exec-sync-001",
},
json={
"operation": "generate.text",
"messages": [
{"role": "system", "content": "Reply in one sentence."},
{
"role": "user",
"content": "What does Keel do before calling a model?",
},
],
"routing": {
"provider": "openai",
"model": "gpt-4o-mini",
},
"parameters": {
"max_output_tokens": 80,
"temperature": 0.2,
},
},
)
print(response.json())Response example
output.content is an array of normalized content blocks. For simple text responses, look for blocks with type: "text" and read text.
Future responses may include multiple blocks or different types.
{
"id": "exec_req_01hy0m9m1d3z5r8j52x6m7n8pq",
"object": "execution",
"created_at": "2026-03-09T00:00:00Z",
"status": "completed",
"status_code": 200,
"output": {
"content": [
{
"type": "text",
"role": "assistant",
"text": "Keel evaluates the permit, applies execution controls, dispatches the request, and records governed usage."
}
]
},
"output_assets": [],
"routing": {
"requested_provider": "openai",
"requested_model": "gpt-4o-mini",
"selected_provider": "openai",
"selected_model": "gpt-4o-mini",
"reason_code": "explicit_request",
"fallback_occurred": false
},
"governance": {
"decision": "allow",
"reason": "ok",
"actions": [],
"constraints": null,
"budgets": null
},
"usage": {
"input_tokens": 29,
"output_tokens": 18,
"total_tokens": 47,
"cost_usd_micros": 15,
"estimated_final": false,
"metrics": [
{"meter": "input_tokens", "quantity": 29, "unit": "tokens"},
{"meter": "output_tokens", "quantity": 18, "unit": "tokens"}
]
},
"timing": {
"started_at": "2026-03-09T00:00:00Z",
"completed_at": "2026-03-09T00:00:01Z",
"duration_ms": 842
},
"error": null
}const requestId = data.id
const status = data.status
const text =
data.output?.content?.find(block => block.type === 'text')?.text ?? null
const routingReason = data.routing?.reason_code ?? null
const costMicros = data.usage?.cost_usd_micros ?? null
const permitId = response.headers.get('x-keel-permit-id')
const errorCode = data.error?.code ?? null
const errorMessage = data.error?.message ?? nullError response examples
Denied
{
"id": "exec_req_01hy0m9m1d3z5r8j52x6m7n8pr",
"object": "execution",
"created_at": "2026-03-09T00:00:00Z",
"status": "denied",
"status_code": 403,
"output": null,
"output_assets": [],
"routing": {
"requested_provider": "openai",
"requested_model": "gpt-4o-mini",
"selected_provider": "openai",
"selected_model": "gpt-4o-mini",
"reason_code": "explicit_request",
"fallback_occurred": false
},
"governance": {
"decision": "deny",
"reason": "...",
"actions": [
{
"type": "deny",
"message": "The request did not satisfy the configured project policy."
}
],
"constraints": null,
"budgets": null
},
"usage": {
"input_tokens": 0,
"output_tokens": 0,
"total_tokens": 0,
"cost_usd_micros": 0,
"estimated_final": false,
"metrics": []
},
"timing": {
"started_at": "2026-03-09T00:00:00Z",
"completed_at": "2026-03-09T00:00:00Z",
"duration_ms": 19
},
"error": {
"code": "denied",
"message": "The request did not satisfy the configured project policy."
}
}const requestId = denied.id
const status = denied.status
const text = denied.output?.content?.find(block => block.type === 'text')?.text ?? null
const permitId = response.headers.get('x-keel-permit-id')
const errorCode = denied.error?.code ?? null
const errorMessage = denied.error?.message ?? nullFailed
{
"id": "exec_req_01hy0m9m1d3z5r8j52x6m7n8ps",
"object": "execution",
"created_at": "2026-03-09T00:00:00Z",
"status": "failed",
"status_code": 502,
"output": null,
"output_assets": [],
"routing": {
"requested_provider": "openai",
"requested_model": "gpt-4o-mini",
"selected_provider": "openai",
"selected_model": "gpt-4o-mini",
"reason_code": "explicit_request",
"fallback_occurred": false
},
"governance": {
"decision": "allow",
"reason": "ok",
"actions": [],
"constraints": null,
"budgets": null
},
"usage": {
"input_tokens": 16,
"output_tokens": 0,
"total_tokens": 16,
"cost_usd_micros": 10,
"estimated_final": false,
"metrics": [
{"meter": "input_tokens", "quantity": 16, "unit": "tokens"}
]
},
"timing": {
"started_at": "2026-03-09T00:00:00Z",
"completed_at": "2026-03-09T00:00:01Z",
"duration_ms": 611
},
"error": {
"code": "upstream_error",
"message": "The upstream provider request failed."
}
}const requestId = failed.id
const status = failed.status
const text = failed.output?.content?.find(block => block.type === 'text')?.text ?? null
const permitId = response.headers.get('x-keel-permit-id')
const errorCode = failed.error?.code ?? null
const errorMessage = failed.error?.message ?? nullStreaming example
mode="stream" returns Server-Sent Events from the same route.
On /v1/executions, the documented streaming switch is mode: "stream" in the request body. The provider-native stream: true flag belongs to provider-shaped or proxy payloads, not this provider-neutral route.
Use mode: "stream" on the documented OpenAI text-generation path first. Treat other providers and multimodal operations as deployment-specific unless you have verified the exact combination in your environment.
The public stream uses SSE: blank-line-delimited records with event: and data: lines. Events include lifecycle signals (start, completion, error) and content deltas.
const response = await fetch('https://api.keelapi.com/v1/executions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.KEEL_API_KEY!}`,
'Content-Type': 'application/json',
Accept: 'text/event-stream',
'Idempotency-Key': 'exec-stream-001'
},
body: JSON.stringify({
operation: 'generate.text',
mode: 'stream',
messages: [
{ role: 'user', content: 'Explain Keel in one short sentence.' }
],
routing: {
provider: 'openai',
model: 'gpt-4o-mini'
},
parameters: {
max_output_tokens: 80
}
})
})
if (!response.ok || !response.body) {
throw new Error(`stream setup failed: ${response.status}`)
}
const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
let text = ''
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const events = buffer.split('\n\n')
buffer = events.pop() ?? ''
for (const rawEvent of events) {
if (!rawEvent.trim()) continue
const lines = rawEvent.split('\n')
let eventName = 'message'
const dataLines: string[] = []
for (const line of lines) {
if (line.startsWith('event:')) eventName = line.slice(6).trim()
if (line.startsWith('data:')) dataLines.push(line.slice(5).trim())
}
const payload = dataLines.length ? JSON.parse(dataLines.join('\n')) : null
// Accumulate text from content deltas
if (payload?.delta?.type === 'output_text_delta') {
text += payload.delta.text ?? ''
}
// Handle errors
if (payload?.error) {
throw new Error(payload.error.message ?? eventName)
}
}
}
console.log(text)Multimodal example
Use inputs for image and audio operations.
curl -i -sS https://api.keelapi.com/v1/executions \
-H "Authorization: Bearer keel_sk_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: exec-image-001" \
-d '{
"operation": "understand.image",
"inputs": [
{
"type": "text",
"role": "user",
"text": "Describe the image in one sentence."
},
{
"type": "image",
"role": "user",
"asset": {
"source": "url",
"url": "https://example.com/invoice.png",
"mime_type": "image/png"
}
}
],
"routing": {
"provider": "openai",
"model": "gpt-4o-mini"
},
"parameters": {
"max_output_tokens": 96
}
}'Related route guidance
For the canonical route matrix, see Route comparison.