Skip to Content
Workflow IntentAPI Reference

Workflow Intent API Reference

Workflow intent endpoints create, amend, complete, and read caller-declared workflow declarations.

Authorization: Bearer <project_api_key> Content-Type: application/json

Workflow intent is available on every plan, Starter included. See Plan Availability.

Route summary

RoutePurpose
POST /v1/workflowsDeclare a workflow.
POST /v1/workflows/{workflow_id}/amendAppend an amendment to the workflow declaration.
POST /v1/workflows/{workflow_id}/completeExplicitly complete a workflow.
GET /v1/workflowsList workflow declarations in the authenticated project.
GET /v1/workflows/{workflow_id}Fetch one workflow declaration with state and amendment history.

POST /v1/workflows

Declares a workflow before it runs. The caller supplies workflow_id and an intent object. workflow_id is idempotent per project.

If the same workflow_id is declared again with the same canonical intent, Keel returns the existing declaration. If the same workflow_id is declared with a different canonical intent, Keel returns 409 with workflow_intent.idempotency_conflict.

Request

{ "workflow_id": "invoice-batch-2026-05-13", "intent": { "expected_calls": 10000, "max_calls": 12000, "expected_model": "gpt-5-mini", "expected_input_tokens_per_call": 4000, "expected_output_tokens_per_call": 500, "max_duration_seconds": 86400 }, "budget_envelope_id": null }
FieldRequired?Meaning
workflow_idYesCaller-supplied workflow handle, unique per project. Use [A-Za-z0-9_-], 1 to 255 characters.
intentYesCaller-declared workflow intent.
intent.expected_callsConditionalExpected number of governed calls. Informational baseline for drift detection. Required if intent.max_calls is omitted.
intent.max_callsConditionalMaximum governed calls allowed for this workflow. Enforcement ceiling. Required if intent.expected_calls is omitted.
intent.expected_modelNoModel the caller expects the workflow to use. Used in projection methodology when supplied.
intent.expected_input_tokens_per_callNoCaller-declared input-token estimate per call. Used for cost projection.
intent.expected_output_tokens_per_callNoCaller-declared output-token estimate per call. Used for cost projection.
intent.max_duration_secondsNoDeclared duration used to calculate expires_at. The existing 15-minute maintenance sweep expires at most 100 due workflows per run. Lifecycle expiry may lag the declared time; duration is advisory, not an exact dispatch deadline. Explicitly complete workflows you close.
budget_envelope_idNoOptional budget envelope in the same project, recorded on the signed declaration. It does not enforce dollars by itself; governed calls reserve against an envelope only when they send their own budget_envelope_id. Use null or omit when no envelope is recorded.

At least one of intent.expected_calls or intent.max_calls must be set. When both are set, expected_calls must be less than or equal to max_calls.

Accepted response

Accepted and rejected declarations both return HTTP 200 with the same object shape. Fields that do not apply are null.

{ "id": "0e9d4c3a-5b6f-4a1e-9c2d-7f8e9a0b1c2d", "workflow_id": "invoice-batch-2026-05-13", "decision": "accepted", "status": "active", "version": 1, "expected_calls": 10000, "max_calls": 12000, "cached_actual_calls": 0, "projected_cost": { "amount_micros": 22500000, "currency": "USD", "methodology": { "basis": "caller_declared_workflow_x_point_pricing", "provenance": "caller_declared_workflow", "expected_calls": 10000, "input_tokens_per_call_estimated": 4000, "output_tokens_per_call_estimated": 500, "pricing_table_id": "openai_v3.2_2026-04-15", "tokenizer": "cl100k_base", "quality": "authoritative" } }, "reason_code": null, "decision_details": null, "declared_by": { "type": "api_key", "id": "5a7c1e2f-3b4d-4c6e-8f9a-0b1c2d3e4f5a" }, "declared_via": { "sdk": "keel-python", "sdk_version": "0.4.1" }, "declaration_canonical_hash": "9f2c1e7a...hex-sha256", "declaration_signature_b64": "base64-signature", "declared_at": "2026-05-13T14:21:00Z", "expires_at": "2026-05-14T14:21:00Z", "completed_at": null, "amendments": null }
FieldMeaning
idKeel identifier for the declaration record.
workflow_idCaller-supplied workflow handle.
decisionDeclaration result: accepted or rejected. accepted means the workflow can run subject to policy and call limits.
statusLifecycle status: active, completed, expired, or rejected. New accepted declarations start as active.
versionDeclaration version. Starts at 1 and increments on each amendment.
expected_callsEffective expected call count.
max_callsEffective maximum call count.
cached_actual_callsCount of calls currently attributed to the workflow.
projected_costCost projection calculated from caller-declared intent, when Keel has enough pricing inputs.
projected_cost.amount_microsProjected cost in USD microdollars.
projected_cost.currencyCurrency for the projection.
projected_cost.methodologyRecorded methodology for how the projection was calculated.
projected_cost.methodology.basisProjection basis. For workflow intent this records caller-declared workflow pricing.
projected_cost.methodology.provenanceProvenance of the input values. Workflow intent uses caller_declared_workflow.
projected_cost.methodology.expected_callsDeclared expected call count used in the projection.
projected_cost.methodology.input_tokens_per_call_estimatedDeclared input-token estimate per call used in the projection.
projected_cost.methodology.output_tokens_per_call_estimatedDeclared output-token estimate per call used in the projection.
projected_cost.methodology.pricing_table_idPricing table used for the calculation.
projected_cost.methodology.tokenizerTokenizer assumed for the estimate.
projected_cost.methodology.qualityMethodology quality label for the projection inputs.
reason_codeRejection reason code; null when accepted.
decision_detailsNumeric inputs for a rejection; null when accepted.
declared_byPrincipal that declared the workflow.
declared_by.typePrincipal type: api_key or dashboard_session.
declared_by.idIdentifier for the declaring principal.
declared_viaClient-provided SDK metadata when present. These values are recorded as claims from the caller.
declared_via.sdkClaimed SDK name.
declared_via.sdk_versionClaimed SDK version.
declaration_canonical_hashSHA-256 hex hash of the canonical declaration artifact.
declaration_signature_b64Signature over the canonical declaration artifact.
declared_atDeclaration timestamp.
expires_atExpiration timestamp when max_duration_seconds was declared; otherwise null.
completed_atCompletion timestamp; null while active.
amendmentsAmendment history. null on declare and list responses; an array on amend and fetch responses.

Rejected response

{ "id": "0e9d4c3a-5b6f-4a1e-9c2d-7f8e9a0b1c2d", "workflow_id": "invoice-batch-2026-05-13", "decision": "rejected", "status": "rejected", "version": 1, "reason_code": "workflow_intent.declaration_exceeds_budget_cap", "projected_cost": { "amount_micros": 22500000, "currency": "USD", "methodology": { "basis": "caller_declared_workflow_x_point_pricing", "provenance": "caller_declared_workflow" } }, "decision_details": { "current_monthly_spend_usd_micros": 810000000, "monthly_cap_usd_micros": 825000000, "projected_workflow_cost_usd_micros": 22500000, "projected_monthly_spend_usd_micros": 832500000 } }

The rejected response has the same shape as the accepted response; the example shows only the fields that differ.

FieldMeaning
decisionrejected means the workflow was not admitted.
statusrejected.
reason_codeStable reason code for the rejection.
projected_costProjection that caused the rejection, when available.
decision_detailsNumeric inputs used for the decision.
decision_details.current_monthly_spend_usd_microsCurrent month-to-date spend before this declaration.
decision_details.monthly_cap_usd_microsProject monthly cap.
decision_details.projected_workflow_cost_usd_microsProjected workflow cost being evaluated.
decision_details.projected_monthly_spend_usd_microsMonth-to-date spend plus the projected workflow cost.

POST /v1/workflows/{workflow_id}/amend

Appends an amendment to a workflow declaration. The caller must send if_match_version so Keel can reject lost updates.

Request

{ "if_match_version": 1, "new_expected_calls": 15000, "new_max_calls": 20000, "reason_provided": "ticket volume higher than expected today" }
FieldRequired?Meaning
if_match_versionYesVersion the caller read before submitting the amendment. Keel rejects the amendment if the current version differs.
new_expected_callsNoReplacement expected call count.
new_max_callsNoReplacement maximum call count.
reason_providedNoCaller-provided reason for the amendment.

At least one of new_expected_calls or new_max_calls must be supplied.

Response

Returns the updated declaration in the same shape as POST /v1/workflows, with amendments set to the full amendment history.

{ "id": "0e9d4c3a-5b6f-4a1e-9c2d-7f8e9a0b1c2d", "workflow_id": "invoice-batch-2026-05-13", "decision": "accepted", "status": "active", "version": 2, "expected_calls": 15000, "max_calls": 20000, "cached_actual_calls": 9412, "projected_cost": { "amount_micros": 45000000, "currency": "USD", "methodology": { "basis": "caller_declared_workflow_x_point_pricing", "provenance": "caller_declared_workflow" } }, "declaration_canonical_hash": "9f2c1e7a...hex-sha256", "declaration_signature_b64": "base64-signature", "declared_at": "2026-05-13T14:21:00Z", "expires_at": "2026-05-14T14:21:00Z", "completed_at": null, "amendments": [ { "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f", "workflow_declaration_id": "0e9d4c3a-5b6f-4a1e-9c2d-7f8e9a0b1c2d", "applied_against_version": 1, "previous_expected_calls": 10000, "new_expected_calls": 15000, "previous_max_calls": 12000, "new_max_calls": 20000, "reason_provided": "ticket volume higher than expected today", "amendment_canonical_hash": "4b8e2d1f...hex-sha256", "amendment_signature_b64": "base64-signature", "created_at": "2026-05-13T16:05:00Z" } ] }
FieldMeaning
versionNew declaration version after applying the amendment.
expected_callsEffective expected call count after the amendment.
max_callsEffective maximum call count after the amendment.
projected_costRecomputed projection after applying the amendment, when available.
amendmentsOrdered amendment history.
amendments[].idAmendment identifier.
amendments[].workflow_declaration_idDeclaration the amendment belongs to.
amendments[].applied_against_versionVersion the amendment was applied against.
amendments[].previous_expected_callsPrevious expected call count.
amendments[].new_expected_callsNew expected call count.
amendments[].previous_max_callsPrevious maximum call count.
amendments[].new_max_callsNew maximum call count.
amendments[].reason_providedCaller-provided reason.
amendments[].amendment_canonical_hashSHA-256 hex hash of the canonical amendment artifact.
amendments[].amendment_signature_b64Signature over the canonical amendment artifact.
amendments[].created_atAmendment creation timestamp.

Other fields have the same meaning as in the POST /v1/workflows response.

Version conflict

Error details are merged into the error object.

{ "error": { "code": "workflow_intent.amendment_version_conflict", "message": "Workflow declaration version does not match if_match_version.", "current_version": 3, "if_match_version": 1 } }
FieldMeaning
error.codeStable error or reason code.
error.messageHuman-readable summary.
error.current_versionCurrent workflow declaration version.
error.if_match_versionVersion supplied by the caller.

POST /v1/workflows/{workflow_id}/complete

Explicitly completes a workflow. Completion recounts attributed calls from permit records, reconciles the stored counter, and sets the status to completed. The route takes no request body.

Response

{ "workflow_id": "invoice-batch-2026-05-13", "status": "completed", "cached_actual_calls": 18433, "authoritative_actual_calls": 18433, "counter_reconciled": true, "completed_at": "2026-05-13T18:30:00Z" }
FieldMeaning
workflow_idCaller-supplied workflow handle.
statuscompleted after successful completion.
cached_actual_callsStored counter after reconciliation (equal to authoritative_actual_calls).
authoritative_actual_callsCount recomputed from permit records.
counter_reconciledtrue when the stored counter already matched the authoritative count; false when it diverged and was corrected.
completed_atCompletion timestamp.

GET /v1/workflows

Lists workflows in the authenticated project, newest first.

Query parameters

ParameterMeaning
statusOptional lifecycle status filter: active, completed, expired, or rejected.
created_at_fromOptional inclusive lower bound for creation time.
created_at_toOptional exclusive upper bound for creation time.
limitOptional page size, 1 to 200. Default 50.
offsetOptional number of records to skip. Default 0.

Response

{ "items": [ { "id": "0e9d4c3a-5b6f-4a1e-9c2d-7f8e9a0b1c2d", "workflow_id": "invoice-batch-2026-05-13", "decision": "accepted", "status": "active", "version": 2, "expected_calls": 15000, "max_calls": 20000, "cached_actual_calls": 9412, "declared_at": "2026-05-13T14:21:00Z", "expires_at": "2026-05-14T14:21:00Z", "amendments": null } ], "limit": 50, "offset": 0, "total": 1 }
FieldMeaning
itemsPage of workflow declarations, each in the POST /v1/workflows response shape with amendments: null.
limitPage size applied.
offsetOffset applied.
totalTotal matching declarations.

GET /v1/workflows/{workflow_id}

Fetches one workflow declaration with its current state and amendment history. The response has the same shape as the amend response: the declaration fields plus amendments as an array (empty when there are none). Returns 404 with not_found when the workflow does not exist in the project.

There is no separate drift object. Compare cached_actual_calls with expected_calls and max_calls; drift transitions are recorded as workflow_intent.drift_detected chain events.

Workflow headers on governed requests

Subsequent governed requests are associated with a workflow by sending:

X-Keel-Workflow-Id: <workflow_id>

See Header Convention for direct API and orchestrator guidance.

Last updated on Edit this page on GitHubÂ