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.

Comparing execution surfaces? Start with 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> X-Keel-Timestamp: <current Unix seconds> X-Keel-Nonce: <new random value, at least 16 characters> 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
  • replay is bound to a hash of the request; the same key with different request content returns 409 with an execution envelope (error.code: "idempotency_conflict")
  • a retry while the first request is still running returns 503 idempotency_in_progress with Retry-After; retry with the same key

Hosted Keel requires fresh X-Keel-Timestamp and X-Keel-Nonce headers on every request to this route, including each retry. See Request Freshness.

Output length

Keel limits how many output tokens a managed text request may produce:

  • With no limit on the request, Keel caps the response at 256 output tokens. On /v1/executions set parameters.max_output_tokens; on the provider-shaped proxy routes set the provider’s own field (for example max_tokens).
  • A requested limit above the project’s output-token cap (450 by default, a project setting) is clipped to the cap (policy.max_output_tokens_constrained), or refused if the project is set to deny such requests.
  • A capped /v1/executions result still reports status: "completed". Compare usage.output_tokens with the limit to detect truncation. The OpenAI-shaped proxy response reports finish_reason: "length".

Supported public operations

OperationCurrent providers on /v1/executionsNotes
generate.textOpenAI, Anthropic, Google, xAI, MetaPrimary text path.
embed.textOpenAI, Google, MetaReturns output.embeddings.

Only OpenAI text generation has been verified against the live provider; the other providers are implemented and tested against simulated providers. See verification status.

Keel dispatches only operations it can price authoritatively. understand.image, generate.image, generate.audio and transcribe.audio are recognized request shapes, but Keel currently refuses them before any provider call (for example unsupported_operation, pricing_not_configured, or dispatch_model_not_authorized for GPT Image generation). They are not available on this route today.

Not supported on this public route today:

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

Project scoping

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.

Execution responses can include Keel-specific headers for correlation. Read x-keel-permit-id when you need the permit id; the body’s governance block stays decision-focused.

API example

curl

Hosted Keel requires a current timestamp and a new nonce on each request to this route, including Sandbox projects; without them it returns 401 request_not_fresh. These examples use date and openssl to generate them. The official Python and JavaScript SDKs add freshness headers by default. See Request Freshness.

curl -i -sS https://api.keelapi.com/v1/executions \ -H "Authorization: Bearer keel_sk_your_key_here" \ -H "Content-Type: application/json" \ -H "X-Keel-Timestamp: $(date +%s)" \ -H "X-Keel-Nonce: $(openssl rand -hex 16)" \ -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', 'X-Keel-Timestamp': String(Math.floor(Date.now() / 1000)), 'X-Keel-Nonce': crypto.randomUUID(), '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 secrets import time import requests response = requests.post( "https://api.keelapi.com/v1/executions", headers={ "Authorization": "Bearer keel_sk_your_key_here", "Content-Type": "application/json", "X-Keel-Timestamp": str(int(time.time())), "X-Keel-Nonce": secrets.token_hex(16), "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": "policy.rule_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 (execution.started, execution.completed, done) and content.delta events whose data carries delta.type: "text" and the text in delta.text.

const response = await fetch('https://api.keelapi.com/v1/executions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.KEEL_API_KEY!}`, 'X-Keel-Timestamp': String(Math.floor(Date.now() / 1000)), 'X-Keel-Nonce': crypto.randomUUID(), '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 (eventName === 'content.delta' && payload?.delta?.type === 'text') { text += payload.delta.text ?? '' } // Handle errors if (payload?.error) { throw new Error(payload.error.message ?? eventName) } } } console.log(text)

Multimodal request shape

This shows the inputs shape for image and audio operations. Keel currently refuses understand.image on this route before any provider call (see the unsupported list above), so this request does not dispatch today.

curl -i -sS https://api.keelapi.com/v1/executions \ -H "Authorization: Bearer keel_sk_your_key_here" \ -H "Content-Type: application/json" \ -H "X-Keel-Timestamp: $(date +%s)" \ -H "X-Keel-Nonce: $(openssl rand -hex 16)" \ -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Â