Skip to Content
Executions

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

RouteStatusPurpose
POST /v1/executionsOfficial publicProvider-neutral execution for the currently supported sync and streaming subset.

Authentication and idempotency

Authorization: Bearer <client_project_api_key> Idempotency-Key: exec-sync-001

Unlike 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-Key are 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

OperationCurrent providers on /v1/executionsNotes
generate.textOpenAI, Anthropic, Google, xAI, MetaPrimary text path.
embed.textOpenAI, Google, MetaReturns output.embeddings.
understand.imageOpenAI, Google, MetaRequires inputs, including at least one image.
generate.imageOpenAIAsset-producing operation.
edit.imageOpenAIRequires inputs, including at least one image.
generate.audioOpenAIText prompt in messages or text-only inputs.
transcribe.audioOpenAI, GoogleRequires inputs, including at least one audio.

Not supported on this public route today:

  • generate.video
  • understand.video
  • run.batch
  • run.async
  • realtime.session
  • provider-native tool-calling payloads

Project scoping

Execution routes infer project context from the project API key and do not require project_id in 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

FieldRequired?Notes
operationYesCanonical capability name such as generate.text or understand.image.
modeNosync by default. stream is the public SSE mode described below.
messagesExactly one of messages or inputsText-only convenience shape.
inputsExactly one of messages or inputsCanonical multimodal shape.
routingNoProvider and model target plus optional routing hints.
parametersNoPortable controls such as max_output_tokens, temperature, and top_p.
provider_optionsNoAdvanced passthrough object keyed by provider name.

messages vs inputs

  • provide exactly one of messages or inputs
  • messages is text-only and is the simplest path for chat-style text requests
  • inputs is the canonical multimodal shape and is required for image and audio operations
  • transcribe.audio requires at least one audio input

routing

routing lets you guide the execution target without switching to a provider-native route:

  • set both routing.provider and routing.model when 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=true and a concrete routing.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, or meta
  • 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, and top_p
  • treat conflicting provider-specific overrides as unsupported on /v1/executions

Response shape overview

Successful sync executions return one normalized envelope:

FieldMeaning
idPublic execution id.
objectAlways execution.
created_atExecution creation timestamp.
statuscompleted, failed, or denied.
status_codeFinal HTTP status code for the execution result.
outputNormalized output.
output_assetsProduced asset metadata for asset-producing operations.
routingRequested and selected provider-model metadata plus reason_code and fallback_occurred.
governancePermit decision summary: decision, reason, actions, constraints, and budgets.
usageFinal normalized usage meters, including cost_usd_micros and estimated_final.
timingstarted_at, completed_at, and duration_ms.
errorPresent 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_key in 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 ?? null

Error 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 ?? null

Failed

{ "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 ?? null

Streaming 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 } }'

For the canonical route matrix, see Route comparison.

Last updated on Edit this page on GitHubÂ