{
"components": {
"schemas": {
"AccountFormFieldResponse": {
"description": "A form field of an account kind.",
"properties": {
"help": {
"description": "Help text.",
"type": "string"
},
"label": {
"description": "Label.",
"type": "string"
},
"name": {
"description": "Field identifier.",
"type": "string"
},
"secret": {
"description": "Whether the field is a secret (write-only).",
"type": "boolean"
}
},
"required": [
"name",
"label",
"secret",
"help"
],
"type": "object"
},
"AccountKindResponse": {
"description": "A supported account kind.",
"properties": {
"display_name": {
"description": "Human-readable name.",
"type": "string"
},
"fields": {
"description": "Fields of the add form.",
"items": {
"$ref": "#/components/schemas/AccountFormFieldResponse"
},
"type": "array"
},
"id": {
"description": "Kind identifier.",
"type": "string"
}
},
"required": [
"id",
"display_name",
"fields"
],
"type": "object"
},
"AccountState": {
"description": "Derived state of an account, for display.",
"enum": [
"ok",
"near_limit",
"limited",
"token_invalid",
"never_used"
],
"type": "string"
},
"AccountTestResult": {
"description": "Outcome of a live credential test.",
"enum": [
"valid",
"limited",
"unauthorized"
],
"type": "string"
},
"AccountUsagePointResponse": {
"description": "One historical observation of a window.",
"properties": {
"model_scope": {
"description": "Model scope.",
"nullable": true,
"type": "string"
},
"observed_at": {
"description": "When the window was observed.",
"format": "date-time",
"type": "string"
},
"resets_at": {
"description": "When the window resets.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"status": {
"$ref": "#/components/schemas/AccountWindowStatus",
"description": "Provider status."
},
"utilization": {
"description": "Fraction used.",
"format": "double",
"type": "number"
},
"window": {
"description": "Window name.",
"type": "string"
}
},
"required": [
"window",
"utilization",
"status",
"observed_at"
],
"type": "object"
},
"AccountWindowResponse": {
"description": "One usage window of an account.",
"properties": {
"model_scope": {
"description": "Model family the window applies to, `null` for every model.",
"nullable": true,
"type": "string"
},
"observed_at": {
"description": "When the window was observed.",
"format": "date-time",
"type": "string"
},
"resets_at": {
"description": "When the window resets.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"status": {
"$ref": "#/components/schemas/AccountWindowStatus",
"description": "Provider status."
},
"utilization": {
"description": "Fraction used, `0.0..=1.0`.",
"format": "double",
"type": "number"
},
"window": {
"description": "Window name (`five_hour`, `seven_day`).",
"type": "string"
}
},
"required": [
"window",
"utilization",
"status",
"observed_at"
],
"type": "object"
},
"AccountWindowStatus": {
"description": "Status of a usage window, as reported by the provider.\n\n# Examples\n\n```\nuse ironflow_store::entities::AccountWindowStatus;\n\nlet status: AccountWindowStatus = \"allowed_warning\".parse().unwrap();\nassert_eq!(status, AccountWindowStatus::AllowedWarning);\n```",
"enum": [
"allowed",
"allowed_warning",
"rejected"
],
"type": "string"
},
"ApiKeyResponse": {
"description": "API key summary (never includes the hash or raw key).",
"properties": {
"created_at": {
"description": "Creation date.",
"format": "date-time",
"type": "string"
},
"expires_at": {
"description": "Expiration date.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"id": {
"description": "API key ID.",
"format": "uuid",
"type": "string"
},
"is_active": {
"description": "Whether the key is active.",
"type": "boolean"
},
"key_prefix": {
"description": "First characters for identification.",
"type": "string"
},
"last_used_at": {
"description": "Last used date.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"name": {
"description": "Key name.",
"type": "string"
},
"rate_limit_override": {
"description": "Per-key rate limit override (requests per minute), if set.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"scopes": {
"description": "Granted scopes.",
"items": {
"$ref": "#/components/schemas/ApiKeyScope"
},
"type": "array"
}
},
"required": [
"id",
"name",
"key_prefix",
"scopes",
"is_active",
"created_at"
],
"type": "object"
},
"ApiKeyScope": {
"description": "Permission scope for an API key.\n\nEach scope grants access to a specific set of actions.\nA key with no scopes has no permissions.",
"enum": [
"workflows_read",
"runs_read",
"runs_write",
"runs_manage",
"stats_read",
"accounts_read",
"accounts_manage",
"signals_send",
"admin"
],
"type": "string"
},
"ApprovalDelegationResponse": {
"description": "Approval delegation response.",
"properties": {
"created_at": {
"description": "When the delegation was created.",
"format": "date-time",
"type": "string"
},
"from_user_id": {
"description": "User handing over their approval power.",
"format": "uuid",
"type": "string"
},
"id": {
"description": "Delegation ID.",
"format": "uuid",
"type": "string"
},
"to_user_id": {
"description": "User receiving the approval power.",
"format": "uuid",
"type": "string"
},
"valid_from": {
"description": "Start of the validity window (inclusive).",
"format": "date-time",
"type": "string"
},
"valid_until": {
"description": "End of the validity window (exclusive).",
"format": "date-time",
"type": "string"
},
"workflow_filter": {
"description": "Glob on the workflow name. `None` means every workflow.",
"nullable": true,
"type": "string"
}
},
"required": [
"id",
"from_user_id",
"to_user_id",
"valid_from",
"valid_until",
"created_at"
],
"type": "object"
},
"ApprovalEscalatedEvent": {
"description": "Payload of the `Event::ApprovalEscalated` event.\n\nEmitted every time an approval gate misses its SLA deadline, including the\nrepeated firings of a bare `Notify`/`Escalate` policy and the final\n\"chain exhausted\" notice. The audit log persists it verbatim, so the whole\nescalation history of a gate is reconstructable from it.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::ApprovalEscalatedEvent;\nuse uuid::Uuid;\n\nlet payload = ApprovalEscalatedEvent {\n run_id: Uuid::now_v7(),\n step_id: Uuid::now_v7(),\n step_name: \"prod-gate\".to_string(),\n stage: 0,\n policy: \"auto_reject\".to_string(),\n action: \"rejected\".to_string(),\n reason: \"approval deadline of 3600s expired\".to_string(),\n assignee: None,\n at: Utc::now(),\n};\nassert_eq!(payload.policy, \"auto_reject\");\n```",
"properties": {
"action": {
"description": "What the escalation did, for the audit log.",
"type": "string"
},
"assignee": {
"description": "Assignee after the escalation, when it reassigned the gate.",
"nullable": true,
"type": "string"
},
"at": {
"description": "When the escalation ran.",
"format": "date-time",
"type": "string"
},
"policy": {
"description": "Policy applied, e.g. `\"auto_reject\"`, `\"notify\"`, `\"escalate\"`.",
"type": "string"
},
"reason": {
"description": "Why it fired, e.g. `\"approval deadline of 3600s expired\"`.",
"type": "string"
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"stage": {
"description": "Escalation stage that fired (0-based index into the policy chain).",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"step_id": {
"description": "Approval step identifier.",
"format": "uuid",
"type": "string"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
}
},
"required": [
"run_id",
"step_id",
"step_name",
"stage",
"policy",
"action",
"reason",
"at"
],
"type": "object"
},
"ApprovalGrantedEvent": {
"description": "Payload of the `Event::ApprovalGranted` event.\n\nPublished for every vote cast on a gate. A vote with\n`approvals_received < approvals_required` is recorded but does not resolve\nthe gate: the run stays `AwaitingApproval` until enough distinct approvers\nvoted.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::ApprovalGrantedEvent;\nuse uuid::Uuid;\n\nlet payload = ApprovalGrantedEvent {\n run_id: Uuid::now_v7(),\n step_id: Some(Uuid::now_v7()),\n approved_by: \"alice\".to_string(),\n approvals_received: 1,\n approvals_required: 2,\n requirement: None,\n at: Utc::now(),\n};\nassert!(payload.approvals_received < payload.approvals_required);\n```",
"properties": {
"approvals_received": {
"description": "Distinct approvals recorded on the gate, this one included.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"approvals_required": {
"description": "Distinct approvals needed to resolve the gate.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"approved_by": {
"description": "User who approved (ID or username).",
"type": "string"
},
"at": {
"description": "When the approval was granted.",
"format": "date-time",
"type": "string"
},
"requirement": {
"$ref": "#/components/schemas/ApprovalRequirement",
"description": "Approvers the handler required, if any.",
"nullable": true
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "Approval step identifier. `None` in events recorded before it existed.",
"format": "uuid",
"nullable": true,
"type": "string"
}
},
"required": [
"run_id",
"approved_by",
"at"
],
"type": "object"
},
"ApprovalRejectedEvent": {
"description": "Payload of the `Event::ApprovalRejected` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::ApprovalRejectedEvent;\nuse uuid::Uuid;\n\nlet payload = ApprovalRejectedEvent {\n run_id: Uuid::now_v7(),\n step_id: Some(Uuid::now_v7()),\n rejected_by: \"bob\".to_string(),\n requirement: None,\n at: Utc::now(),\n};\nassert_eq!(payload.rejected_by, \"bob\");\n```",
"properties": {
"at": {
"description": "When the rejection occurred.",
"format": "date-time",
"type": "string"
},
"rejected_by": {
"description": "User who rejected (ID or username).",
"type": "string"
},
"requirement": {
"$ref": "#/components/schemas/ApprovalRequirement",
"description": "Approvers the handler required, if any.",
"nullable": true
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "Approval step identifier. `None` in events recorded before it existed.",
"format": "uuid",
"nullable": true,
"type": "string"
}
},
"required": [
"run_id",
"rejected_by",
"at"
],
"type": "object"
},
"ApprovalRequestedEvent": {
"description": "Payload of the `Event::ApprovalRequested` event.\n\nPublished when an approval gate opens. Carries the approvers the handler\nrequired, if it set any.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::ApprovalRequestedEvent;\nuse uuid::Uuid;\n\nlet payload = ApprovalRequestedEvent {\n run_id: Uuid::now_v7(),\n step_id: Uuid::now_v7(),\n message: \"Deploy to prod?\".to_string(),\n requirement: None,\n at: Utc::now(),\n};\nassert_eq!(payload.message, \"Deploy to prod?\");\n```",
"properties": {
"at": {
"description": "When the approval was requested.",
"format": "date-time",
"type": "string"
},
"message": {
"description": "Message displayed to reviewers.",
"type": "string"
},
"requirement": {
"$ref": "#/components/schemas/ApprovalRequirement",
"description": "Approvers the handler required. `None` for a gate opened without\napprovers: one approval resolves it.",
"nullable": true
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "Approval step identifier.",
"format": "uuid",
"type": "string"
}
},
"required": [
"run_id",
"step_id",
"message",
"at"
],
"type": "object"
},
"ApprovalRequirement": {
"description": "The approval requirement recorded when a gate opened.\n\nA gate opened without approvers carries no requirement at all; the default\nvalue (one approval from anyone allowed to answer the gate) applies.\n\n# Examples\n\n```\nuse ironflow_store::entities::ApprovalRequirement;\n\nlet requirement = ApprovalRequirement {\n reason: Some(\"amount > 10k\".to_string()),\n required_approvers: 2,\n approver_groups: vec![\"finance\".to_string()],\n};\nassert!(!requirement.is_satisfied_by(1));\nassert!(requirement.is_satisfied_by(2));\nassert!(!requirement.allows_everyone());\n```",
"properties": {
"approver_groups": {
"description": "Groups whose members may vote. Empty means anyone allowed to answer\nthe gate may vote.",
"items": {
"type": "string"
},
"type": "array"
},
"reason": {
"description": "Why the handler asked for these approvers, for the audit trail. Never\nevaluated.",
"nullable": true,
"type": "string"
},
"required_approvers": {
"description": "Number of distinct approvals needed to resolve the gate.",
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
"required": [
"required_approvers"
],
"type": "object"
},
"ArtifactResponse": {
"description": "An artifact as exposed by the REST API.\n\nThe storage key is deliberately absent: it is internal plumbing, and callers\naddress an artifact by `(run, step, name)` through the download route.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_api::entities::ArtifactResponse;\nuse uuid::Uuid;\n\nlet response = ArtifactResponse {\n id: Uuid::now_v7(),\n step_id: Uuid::now_v7(),\n name: \"report.html\".to_string(),\n content_type: \"text/html\".to_string(),\n size_bytes: 142,\n sha256: \"0\".repeat(64),\n created_at: Utc::now(),\n};\nassert_eq!(response.name, \"report.html\");\n```",
"properties": {
"content_type": {
"description": "MIME type served on download.",
"type": "string"
},
"created_at": {
"description": "When the artifact was recorded.",
"format": "date-time",
"type": "string"
},
"id": {
"description": "Unique artifact identifier.",
"format": "uuid",
"type": "string"
},
"name": {
"description": "File name, unique within the step.",
"type": "string"
},
"sha256": {
"description": "Lowercase hex SHA-256 of the content.",
"type": "string"
},
"size_bytes": {
"description": "Size of the content, in bytes.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"step_id": {
"description": "The step that produced it.",
"format": "uuid",
"type": "string"
}
},
"required": [
"id",
"step_id",
"name",
"content_type",
"size_bytes",
"sha256",
"created_at"
],
"type": "object"
},
"AuditLogEntry": {
"description": "A persisted audit log entry capturing a domain event.\n\nEach entry records the full event payload as JSON alongside\ndenormalized contextual IDs for efficient filtering.\n\n# Examples\n\n```\nuse ironflow_store::entities::{AuditLogEntry, EventKind};\nuse uuid::Uuid;\nuse chrono::Utc;\nuse serde_json::json;\n\nlet entry = AuditLogEntry {\n id: Uuid::now_v7(),\n event_type: EventKind::RunStatusChanged,\n payload: json!({\"run_id\": \"...\"}),\n run_id: Some(Uuid::now_v7()),\n step_id: None,\n user_id: None,\n created_at: Utc::now(),\n};\nassert_eq!(entry.event_type, EventKind::RunStatusChanged);\n```",
"properties": {
"created_at": {
"description": "When the event was recorded.",
"format": "date-time",
"type": "string"
},
"event_type": {
"$ref": "#/components/schemas/EventKind",
"description": "Event type."
},
"id": {
"description": "Unique entry ID (UUID v7).",
"format": "uuid",
"type": "string"
},
"payload": {
"description": "Full serialized event payload."
},
"run_id": {
"description": "Associated run ID, if the event is run-scoped.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"step_id": {
"description": "Associated step ID, if the event is step-scoped.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"user_id": {
"description": "Associated user ID, if the event is user-scoped.",
"format": "uuid",
"nullable": true,
"type": "string"
}
},
"required": [
"id",
"event_type",
"payload",
"created_at"
],
"type": "object"
},
"CancelRunResponse": {
"allOf": [
{
"$ref": "#/components/schemas/RunResponse",
"description": "The cancelled run."
},
{
"properties": {
"cancelled_descendants": {
"description": "Sub-workflow runs below it that this request cancelled, oldest first.\nEmpty when none was still active.",
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
}
},
"required": [
"cancelled_descendants"
],
"type": "object"
}
],
"description": "Response of `POST /api/v1/runs/:id/cancel`: the cancelled run, with the\nsub-workflow runs cancelled along with it.\n\nThe run's fields stay at the top level, as before the descendants were\nlisted, so existing clients read the same document."
},
"ChangePasswordRequest": {
"description": "Change password request body.",
"properties": {
"new_password": {
"description": "New password: 12 to 128 characters, not a common password, not\ncontaining the email or username, not repetitive.",
"type": "string"
},
"old_password": {
"description": "Current password.",
"type": "string"
}
},
"required": [
"old_password",
"new_password"
],
"type": "object"
},
"ConcurrencyLimit": {
"description": "Membership of a run in a concurrency group, with the limit the run accepts.\n\nA run is only moved to `Running` while fewer than `limit` root runs\ncarrying `group` are running. Each run is compared against its own limit,\nso two runs of the same group may carry different limits.\n\n# Examples\n\n```\nuse ironflow_store::entities::ConcurrencyLimit;\n\nlet limit = ConcurrencyLimit::new(\"repo:acme/api\", 2);\nassert_eq!(limit.group, \"repo:acme/api\");\nassert_eq!(limit.limit, 2);\n```",
"properties": {
"group": {
"description": "Name of the concurrency group (1 to 255 bytes).",
"type": "string"
},
"limit": {
"description": "Maximum number of root runs of this group running at once (at least 1).",
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
"required": [
"group",
"limit"
],
"type": "object"
},
"ConditionResponse": {
"description": "Outcome of a branch condition as recorded by the planner.",
"properties": {
"expression": {
"description": "Expression the handler declared, when the planner knows one.",
"nullable": true,
"type": "string"
},
"reason": {
"description": "Why the step is skipped, or why the condition cannot be evaluated.",
"nullable": true,
"type": "string"
},
"state": {
"description": "Condition state: `evaluated`, `skipped` or `unevaluable`.",
"type": "string"
},
"value": {
"description": "What the expression evaluated to, for an `evaluated` condition.",
"nullable": true,
"type": "boolean"
}
},
"required": [
"state"
],
"type": "object"
},
"CreateApiKeyRequest": {
"description": "Request body for creating an API key.",
"properties": {
"expires_at": {
"description": "Optional expiration date (ISO 8601).",
"format": "date-time",
"nullable": true,
"type": "string"
},
"name": {
"description": "Human-readable name for this key.",
"type": "string"
},
"rate_limit_override": {
"description": "Optional per-key rate limit override (requests per minute).\nWhen set, the server uses this value instead of the global rate\nlimit for requests authenticated with this key.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"scopes": {
"description": "Scopes to grant.",
"items": {
"$ref": "#/components/schemas/ApiKeyScope"
},
"type": "array"
}
},
"required": [
"name",
"scopes"
],
"type": "object"
},
"CreateApiKeyResponse": {
"description": "Response returned when creating an API key.\nThe raw key is only shown once.",
"properties": {
"created_at": {
"description": "Creation date.",
"format": "date-time",
"type": "string"
},
"expires_at": {
"description": "Expiration date.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"id": {
"description": "API key ID.",
"format": "uuid",
"type": "string"
},
"key": {
"description": "The full raw API key (only returned at creation time).",
"type": "string"
},
"key_prefix": {
"description": "First characters for identification.",
"type": "string"
},
"name": {
"description": "Key name.",
"type": "string"
},
"rate_limit_override": {
"description": "Per-key rate limit override (requests per minute), if set.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"scopes": {
"description": "Granted scopes.",
"items": {
"$ref": "#/components/schemas/ApiKeyScope"
},
"type": "array"
}
},
"required": [
"id",
"key",
"key_prefix",
"name",
"scopes",
"created_at"
],
"type": "object"
},
"CreateApprovalDelegationRequest": {
"description": "Create delegation request body.\n\nThe delegator is always the authenticated caller: a delegation can only be\ngranted by the person handing over their own approval power.",
"properties": {
"to_user_id": {
"description": "User receiving the delegated approval power.",
"format": "uuid",
"type": "string"
},
"valid_from": {
"description": "Start of the window. Defaults to now when omitted.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"valid_until": {
"description": "End of the window (exclusive).",
"format": "date-time",
"type": "string"
},
"workflow_filter": {
"description": "Optional glob on the workflow name, e.g. `\"deploy-*\"`. `None` = all workflows.",
"nullable": true,
"type": "string"
}
},
"required": [
"to_user_id",
"valid_until"
],
"type": "object"
},
"CreateProviderAccountRequest": {
"description": "Request body to add a Provider Account. Admin only.",
"properties": {
"alert_threshold": {
"description": "Utilization from which the account is shown as near its limit, in `(0, 1]` (default 0.8).",
"format": "double",
"nullable": true,
"type": "number"
},
"display_name": {
"description": "Human-readable name, defaults to `name`.",
"nullable": true,
"type": "string"
},
"enabled": {
"description": "Whether the account may be selected (default `true`).",
"nullable": true,
"type": "boolean"
},
"expires_at": {
"description": "When the credential expires (default: now + 365 days).",
"format": "date-time",
"nullable": true,
"type": "string"
},
"kind": {
"description": "Account kind (see `GET /provider-accounts/kinds`).",
"type": "string"
},
"max_concurrency": {
"description": "Maximum concurrent steps (>= 1).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"name": {
"description": "Unique slug, immutable (`^[a-z0-9][a-z0-9-]{0,62}$`).",
"type": "string"
},
"plan": {
"description": "Subscription plan, informative (`pro`, `max`).",
"nullable": true,
"type": "string"
},
"priority": {
"description": "Priority, lower is preferred (default 100).",
"format": "int32",
"nullable": true,
"type": "integer"
},
"tags": {
"description": "Tags, each a slug.",
"items": {
"type": "string"
},
"nullable": true,
"type": "array"
},
"token": {
"description": "Credential, write-only (e.g. a `claude setup-token` token).",
"type": "string"
}
},
"required": [
"name",
"kind",
"token"
],
"type": "object"
},
"CreateRunRequest": {
"description": "Request to trigger a workflow.\n\n# Examples\n\n```\nuse ironflow_api::entities::CreateRunRequest;\nuse serde_json::json;\n\nlet req = CreateRunRequest {\n workflow: \"deploy\".to_string(),\n payload: Some(json!({\"env\": \"prod\"})),\n labels: None,\n scheduled_at: None,\n max_retries: Some(2),\n max_cost_usd: None,\n concurrency_key: Some(\"issue:12\".to_string()),\n concurrency_limits: Vec::new(),\n};\nassert_eq!(req.workflow, \"deploy\");\n```",
"properties": {
"concurrency_key": {
"description": "Optional exclusivity key, at most 255 bytes.\n\nWhile a non-terminal run (pending, running, sleeping, retrying,\nawaiting approval) holds the same key, the request is refused with\n`409 CONCURRENCY_CONFLICT` naming that run. The key is released when\nthe run completes, fails, ends with a warning or is cancelled.",
"nullable": true,
"type": "string"
},
"concurrency_limits": {
"description": "Concurrency groups the run belongs to, each with its own limit.\n\nThe run is always created, but a worker only starts it once, for every\ngroup, fewer root runs of that group than its `limit` are running.\nUntil then it stays pending, and later runs of other groups go ahead.\nEach group is non-empty, at most 255 bytes and listed once; each limit\nis at least 1. Empty means no limit.",
"items": {
"$ref": "#/components/schemas/ConcurrencyLimit"
},
"type": "array"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"description": "Optional key-value labels for categorization and filtering.",
"nullable": true,
"propertyNames": {
"type": "string"
},
"type": "object"
},
"max_cost_usd": {
"description": "Optional cumulative cost cap for this run, in USD.\n\nOverrides the workflow default and the server default. `None` falls back\nto those. Must be zero or positive.",
"format": "double",
"nullable": true,
"type": "number"
},
"max_retries": {
"description": "How many times the run may be replayed automatically after a transient\nfailure. Defaults to `0`, meaning no automatic retry.\n\nEach retry waits an exponential backoff (30 s, 2 min, 8 min, capped at\n15 min) before the run is replayed from the start. Failures that cannot\nsucceed on replay -- an unknown workflow, an invalid payload, an\nexhausted agent budget, a rejected approval, a manual cancellation --\nconsume no attempt.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"payload": {
"additionalProperties": {},
"description": "Optional input payload for the workflow.",
"nullable": true,
"propertyNames": {
"type": "string"
},
"type": "object"
},
"scheduled_at": {
"description": "Optional deferred execution time. `None` means run immediately.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"workflow": {
"description": "The workflow name to trigger.",
"type": "string"
}
},
"required": [
"workflow"
],
"type": "object"
},
"CreateScheduleRequest": {
"description": "Create schedule request body.",
"properties": {
"cron_expression": {
"description": "Cron expression (6-field format, e.g. `\"0 */5 * * * *\"`).",
"type": "string"
},
"inputs": {
"description": "JSON payload for the workflow. Defaults to `{}`."
},
"workflow_name": {
"description": "Name of the workflow to trigger.",
"type": "string"
}
},
"required": [
"workflow_name",
"cron_expression"
],
"type": "object"
},
"CreateUserRequest": {
"description": "Request body for creating a user (admin only).",
"properties": {
"email": {
"description": "Email address.",
"type": "string"
},
"is_admin": {
"description": "Whether the new user should be an admin.",
"type": "boolean"
},
"password": {
"description": "Plaintext password: 12 to 128 characters, not a common password, not\ncontaining the email or username, not repetitive.",
"type": "string"
},
"username": {
"description": "Display username.",
"type": "string"
}
},
"required": [
"email",
"username",
"password",
"is_admin"
],
"type": "object"
},
"CreatedBy": {
"description": "The author of a run, as exposed by the API.\n\nAlways present on a [`RunResponse`](crate::entities::RunResponse): a run with\nno recorded author (including every run created before authorship tracking)\nreports [`CreatedByKind::System`] with a label derived from its trigger, so\nclients never have to handle a missing value.\n\n# Examples\n\n```\nuse ironflow_api::entities::{CreatedBy, CreatedByKind};\n\n// Built from a Run via `CreatedBy::from(&run)`.\nlet system = CreatedBy {\n kind: CreatedByKind::System,\n id: None,\n label: \"manual\".to_string(),\n};\nassert_eq!(system.id, None);\n```",
"properties": {
"id": {
"description": "Identifier of the principal: the user ID, the API key ID, or `None` for\n[`CreatedByKind::System`].",
"format": "uuid",
"nullable": true,
"type": "string"
},
"kind": {
"$ref": "#/components/schemas/CreatedByKind",
"description": "What kind of principal triggered the run."
},
"label": {
"description": "Human-readable label. Never empty.\n\nThe username for a user, `\"key-name (username)\"` for an API key, and the\ntrigger description for a system run.",
"type": "string"
}
},
"required": [
"kind",
"label"
],
"type": "object"
},
"CreatedByKind": {
"description": "What kind of principal triggered a run.",
"enum": [
"user",
"api_key",
"system"
],
"type": "string"
},
"Event": {
"description": "A domain event emitted by the ironflow system.\n\nCovers the full lifecycle: runs, steps, approvals, and authentication.\nSubscribers receive these via [`EventPublisher`](super::EventPublisher)\nand pattern-match on the variants they care about.\n\nEach variant wraps a dedicated payload struct. The serialized form stays\nflat: the `type` discriminant sits next to the payload fields, so\n`{\"type\":\"run_created\",\"run_id\":...}` round-trips unchanged.\n\n# Examples\n\n```\nuse std::collections::HashMap;\nuse ironflow_engine::notify::{Event, RunStatusChangedEvent};\nuse ironflow_store::models::RunStatus;\nuse uuid::Uuid;\n\nlet event = Event::RunStatusChanged(RunStatusChangedEvent {\n run_id: Uuid::now_v7(),\n workflow_name: \"deploy\".to_string(),\n from: RunStatus::Running,\n to: RunStatus::Completed,\n error: None,\n cost_usd: rust_decimal::Decimal::ZERO,\n duration_ms: 5000,\n labels: HashMap::new(),\n at: chrono::Utc::now(),\n});\nassert_eq!(event.event_type(), \"run_status_changed\");\n```",
"oneOf": [
{
"allOf": [
{
"$ref": "#/components/schemas/RunCreatedEvent",
"description": "A new run was created (status: Pending)."
},
{
"properties": {
"type": {
"enum": [
"run_created"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A new run was created (status: Pending)."
},
{
"allOf": [
{
"$ref": "#/components/schemas/RunStatusChangedEvent",
"description": "A run changed status."
},
{
"properties": {
"type": {
"enum": [
"run_status_changed"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A run changed status."
},
{
"allOf": [
{
"$ref": "#/components/schemas/RunFailedEvent",
"description": "A run transitioned to [`Failed`](ironflow_store::models::RunStatus::Failed).\n\nThis is a convenience event emitted alongside [`RunStatusChanged`](Event::RunStatusChanged)\nwhen the target status is `Failed`. Subscribe to this instead of\n`RUN_STATUS_CHANGED` when you only care about failures."
},
{
"properties": {
"type": {
"enum": [
"run_failed"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A run transitioned to [`Failed`](ironflow_store::models::RunStatus::Failed).\n\nThis is a convenience event emitted alongside [`RunStatusChanged`](Event::RunStatusChanged)\nwhen the target status is `Failed`. Subscribe to this instead of\n`RUN_STATUS_CHANGED` when you only care about failures."
},
{
"allOf": [
{
"$ref": "#/components/schemas/RunBudgetExceededEvent",
"description": "A run was stopped because it reached its cumulative cost cap.\n\nEmitted when the engine refuses an agent step that would cross the run's\n`max_cost_usd`. The run transitions to\n[`Cancelled`](ironflow_store::models::RunStatus::Cancelled) and the step\nis never launched, so the reported spend is what the run had already\nconsumed."
},
{
"properties": {
"type": {
"enum": [
"run_budget_exceeded"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A run was stopped because it reached its cumulative cost cap.\n\nEmitted when the engine refuses an agent step that would cross the run's\n`max_cost_usd`. The run transitions to\n[`Cancelled`](ironflow_store::models::RunStatus::Cancelled) and the step\nis never launched, so the reported spend is what the run had already\nconsumed."
},
{
"allOf": [
{
"$ref": "#/components/schemas/RetryForcedEvent",
"description": "A manual retry was forced despite a handler version mismatch.\n\nEmitted when a caller passes `force=true` on a retry where the\nhandler version differs from the run's recorded version. This is\nan audit event: it means the new code will execute on the old\npayload without the handler explicitly declaring compatibility."
},
{
"properties": {
"type": {
"enum": [
"retry_forced"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A manual retry was forced despite a handler version mismatch.\n\nEmitted when a caller passes `force=true` on a retry where the\nhandler version differs from the run's recorded version. This is\nan audit event: it means the new code will execute on the old\npayload without the handler explicitly declaring compatibility."
},
{
"allOf": [
{
"$ref": "#/components/schemas/StepCompletedEvent",
"description": "A step completed successfully."
},
{
"properties": {
"type": {
"enum": [
"step_completed"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A step completed successfully."
},
{
"allOf": [
{
"$ref": "#/components/schemas/StepFailedEvent",
"description": "A step failed."
},
{
"properties": {
"type": {
"enum": [
"step_failed"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A step failed."
},
{
"allOf": [
{
"$ref": "#/components/schemas/ApprovalRequestedEvent",
"description": "A run is waiting for human approval."
},
{
"properties": {
"type": {
"enum": [
"approval_requested"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A run is waiting for human approval."
},
{
"allOf": [
{
"$ref": "#/components/schemas/ApprovalGrantedEvent",
"description": "A run was approved by a human."
},
{
"properties": {
"type": {
"enum": [
"approval_granted"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A run was approved by a human."
},
{
"allOf": [
{
"$ref": "#/components/schemas/ApprovalRejectedEvent",
"description": "A run was rejected by a human."
},
{
"properties": {
"type": {
"enum": [
"approval_rejected"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A run was rejected by a human."
},
{
"allOf": [
{
"$ref": "#/components/schemas/ApprovalEscalatedEvent",
"description": "An approval gate missed its SLA deadline and an escalation policy ran."
},
{
"properties": {
"type": {
"enum": [
"approval_escalated"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "An approval gate missed its SLA deadline and an escalation policy ran."
},
{
"allOf": [
{
"$ref": "#/components/schemas/LogLineEvent",
"description": "A log line emitted during step execution.\n\nPushed by the worker in real time so that SSE clients can stream\nstep output as it happens, without waiting for step completion."
},
{
"properties": {
"type": {
"enum": [
"log_line"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A log line emitted during step execution.\n\nPushed by the worker in real time so that SSE clients can stream\nstep output as it happens, without waiting for step completion."
},
{
"allOf": [
{
"$ref": "#/components/schemas/UserSignedInEvent",
"description": "A user signed in."
},
{
"properties": {
"type": {
"enum": [
"user_signed_in"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A user signed in."
},
{
"allOf": [
{
"$ref": "#/components/schemas/UserSignedUpEvent",
"description": "A new user signed up."
},
{
"properties": {
"type": {
"enum": [
"user_signed_up"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A new user signed up."
},
{
"allOf": [
{
"$ref": "#/components/schemas/UserSignedOutEvent",
"description": "A user signed out."
},
{
"properties": {
"type": {
"enum": [
"user_signed_out"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A user signed out."
},
{
"allOf": [
{
"$ref": "#/components/schemas/ProviderAccountUpdatedEvent",
"description": "A Provider Account was created, updated, deleted or had its token replaced."
},
{
"properties": {
"type": {
"enum": [
"provider_account.updated"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A Provider Account was created, updated, deleted or had its token replaced."
},
{
"allOf": [
{
"$ref": "#/components/schemas/ProviderAccountUsageUpdatedEvent",
"description": "New usage windows were recorded for a Provider Account."
},
{
"properties": {
"type": {
"enum": [
"provider_account.usage_updated"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "New usage windows were recorded for a Provider Account."
},
{
"allOf": [
{
"$ref": "#/components/schemas/SignalAwaitedEvent",
"description": "A run started waiting for a signal."
},
{
"properties": {
"type": {
"enum": [
"signal_awaited"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A run started waiting for a signal."
},
{
"allOf": [
{
"$ref": "#/components/schemas/SignalReceivedEvent",
"description": "A signal was received."
},
{
"properties": {
"type": {
"enum": [
"signal_received"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A signal was received."
}
]
},
"EventKind": {
"description": "Strongly-typed event kind matching domain event variants.\n\nSerializes to/from `snake_case` strings (e.g. `\"run_status_changed\"`).\n\n# Examples\n\n```\nuse ironflow_store::entities::EventKind;\n\nlet kind: EventKind = \"run_status_changed\".parse().unwrap();\nassert_eq!(kind, EventKind::RunStatusChanged);\nassert_eq!(kind.as_str(), \"run_status_changed\");\n```",
"enum": [
"run_created",
"run_status_changed",
"run_failed",
"run_budget_exceeded",
"step_completed",
"step_failed",
"approval_requested",
"approval_granted",
"approval_rejected",
"approval_escalated",
"log_line",
"user_signed_in",
"user_signed_up",
"user_signed_out",
"secrets_rotated",
"retry_forced",
"provider_account.updated",
"provider_account.usage_updated",
"signal_awaited",
"signal_received"
],
"type": "string"
},
"ExecutionPlanResponse": {
"description": "The execution plan of one workflow for one input payload.",
"properties": {
"estimated_duration_ms": {
"description": "Sum of the step estimates, counting each parallel wave once.",
"format": "int64",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"incomplete_reason": {
"description": "Why the plan stopped early, when it did.",
"nullable": true,
"type": "string"
},
"max_depth": {
"description": "Sub-workflow expansion depth used for this plan.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"steps": {
"description": "Steps the run is expected to create, in execution order.",
"items": {
"$ref": "#/components/schemas/PlannedStepResponse"
},
"type": "array"
},
"truncated": {
"description": "`true` when the step cap or the depth limit cut the plan short.",
"type": "boolean"
},
"workflow": {
"description": "Workflow the plan was built for.",
"type": "string"
}
},
"required": [
"workflow",
"steps",
"max_depth",
"truncated"
],
"type": "object"
},
"GetRunLogsQuery": {
"description": "Query parameters for listing run logs.",
"properties": {
"cursor": {
"description": "Cursor for pagination (last entry ID from previous page).",
"format": "uuid",
"nullable": true,
"type": "string"
},
"limit": {
"description": "Number of entries to return (default: 100, max: 1000).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"step_id": {
"description": "Filter by step ID.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"stream": {
"$ref": "#/components/schemas/LogStream",
"description": "Filter by output stream (`stdout`, `stderr`, `system`).",
"nullable": true
}
},
"type": "object"
},
"HistoryGranularity": {
"description": "Time bucket granularity for historical statistics.\n\nControls the size of each time bucket in the response.\n\n# Examples\n\n```\nuse ironflow_store::entities::HistoryGranularity;\n\nlet gran = HistoryGranularity::OneDay;\nassert_eq!(gran.to_string(), \"1d\");\nassert_eq!(gran.pg_interval(), \"day\");\n```",
"enum": [
"1h",
"1d",
"1w"
],
"type": "string"
},
"HistoryPeriod": {
"description": "Time period for historical statistics queries.\n\nControls how far back the query reaches from the current time.\n\n# Examples\n\n```\nuse ironflow_store::entities::HistoryPeriod;\n\nlet period = HistoryPeriod::SevenDays;\nassert_eq!(period.to_string(), \"7d\");\n```",
"enum": [
"24h",
"7d",
"30d",
"90d"
],
"type": "string"
},
"KeyVersionsResponse": {
"description": "How the configured key ring lines up with the stored secrets.",
"properties": {
"active": {
"description": "Version used to encrypt new secrets.",
"format": "int32",
"type": "integer"
},
"configured": {
"description": "Versions present in the configured key ring.",
"items": {
"format": "int32",
"type": "integer"
},
"type": "array"
},
"in_use": {
"description": "Versions actually used by stored secrets.",
"items": {
"format": "int32",
"type": "integer"
},
"type": "array"
},
"missing": {
"description": "Versions used by stored secrets but absent from the key ring.\nNon-empty means some secrets are unreadable.",
"items": {
"format": "int32",
"type": "integer"
},
"type": "array"
},
"retirable": {
"description": "Versions that can be removed from the key ring safely.",
"items": {
"format": "int32",
"type": "integer"
},
"type": "array"
}
},
"required": [
"active",
"configured",
"in_use",
"missing",
"retirable"
],
"type": "object"
},
"ListApprovalDelegationsQuery": {
"description": "Query parameters for listing delegations.\n\nBoth user filters are honoured for an admin only; a non-admin always sees\nexactly the delegations they granted or received. Pagination applies to\neveryone.",
"properties": {
"from_user_id": {
"description": "Only delegations granted by this user.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"page": {
"description": "Page number (1-based, defaults to 1).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"per_page": {
"description": "Items per page (defaults to 20, max 100).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"to_user_id": {
"description": "Only delegations received by this user.",
"format": "uuid",
"nullable": true,
"type": "string"
}
},
"type": "object"
},
"ListAuditLogsQuery": {
"description": "Query parameters for listing audit log entries.",
"properties": {
"event_type": {
"$ref": "#/components/schemas/EventKind",
"description": "Filter by event type (e.g. `run_status_changed`).",
"nullable": true
},
"from": {
"description": "Filter entries created at or after this timestamp.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"page": {
"description": "Page number (1-based, default: 1).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"per_page": {
"description": "Items per page (default: 50, max: 100).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"run_id": {
"description": "Filter by run ID.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"to": {
"description": "Filter entries created at or before this timestamp.",
"format": "date-time",
"nullable": true,
"type": "string"
}
},
"type": "object"
},
"ListRunsQuery": {
"description": "Query parameters for listing runs.",
"properties": {
"concurrency_group": {
"description": "Filter by concurrency group: only runs that belong to this group.",
"nullable": true,
"type": "string"
},
"created_by": {
"description": "Filter by author: the user ID that triggered the run.\n\nAlso matches runs triggered by one of that user's API keys.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"has_steps": {
"description": "Filter by step presence (only applies to completed/cancelled runs).\nNon-terminal runs (pending, running, etc.) are always included.\nWhen `true`, only return completed/cancelled runs that have steps.\nWhen `false`, only return completed/cancelled runs without steps.",
"nullable": true,
"type": "boolean"
},
"label": {
"description": "Filter by labels. Comma-separated `key:value` pairs.",
"nullable": true,
"type": "string"
},
"page": {
"description": "Page number (1-based).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"per_page": {
"description": "Items per page.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"status": {
"$ref": "#/components/schemas/RunStatus",
"description": "Filter by run status.",
"nullable": true
},
"workflow": {
"description": "Filter by workflow name.",
"nullable": true,
"type": "string"
}
},
"type": "object"
},
"ListSignalsQuery": {
"description": "Query parameters for listing signals.",
"properties": {
"key": {
"description": "Only signals with this exact key.",
"nullable": true,
"type": "string"
},
"name": {
"description": "Only signals with this exact name.",
"nullable": true,
"type": "string"
},
"page": {
"description": "Page number (1-based, defaults to 1).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"per_page": {
"description": "Items per page (defaults to 20, max 100).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
}
},
"type": "object"
},
"ListUsersQuery": {
"description": "Query parameters for listing users.",
"properties": {
"page": {
"description": "Page number (1-based, defaults to 1).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"per_page": {
"description": "Items per page (defaults to 20, max 100).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
}
},
"type": "object"
},
"ListWorkflowsQuery": {
"description": "Query parameters for listing workflows.",
"properties": {
"category": {
"description": "Optional case-insensitive partial match on the category path\n(e.g. `etl` matches `Data/ETL` and `data/etl/nightly`).\n\nPass `__uncategorized__` to list only workflows without any\ncategory.",
"nullable": true,
"type": "string"
},
"name": {
"description": "Optional case-insensitive partial match on workflow name.",
"nullable": true,
"type": "string"
}
},
"type": "object"
},
"LogCursorMeta": {
"description": "Cursor-based pagination metadata for log entries.",
"properties": {
"has_more": {
"description": "Whether more entries exist after this page.",
"type": "boolean"
},
"next_cursor": {
"description": "Cursor to pass for the next page. `None` when there are no more entries.",
"format": "uuid",
"nullable": true,
"type": "string"
}
},
"required": [
"has_more"
],
"type": "object"
},
"LogEntry": {
"description": "A persisted log line from step execution.\n\nEach entry records a single line of output alongside its metadata.\nEntries are ordered by [`id`](LogEntry::id) (UUID v7, time-ordered).\n\n# Examples\n\n```\nuse ironflow_store::entities::{LogEntry, LogStream};\nuse uuid::Uuid;\nuse chrono::Utc;\n\nlet entry = LogEntry {\n id: Uuid::now_v7(),\n run_id: Uuid::now_v7(),\n step_id: Uuid::now_v7(),\n step_name: \"build\".to_string(),\n stream: LogStream::Stdout,\n line: \"Compiling ironflow v0.1.0\".to_string(),\n created_at: Utc::now(),\n};\nassert_eq!(entry.stream, LogStream::Stdout);\n```",
"properties": {
"created_at": {
"description": "When the line was recorded.",
"format": "date-time",
"type": "string"
},
"id": {
"description": "Unique entry ID (UUID v7, time-ordered).",
"format": "uuid",
"type": "string"
},
"line": {
"description": "The log line content.",
"type": "string"
},
"run_id": {
"description": "Run that produced this log line.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "Step that produced this log line.",
"format": "uuid",
"type": "string"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
},
"stream": {
"$ref": "#/components/schemas/LogStream",
"description": "Output stream."
}
},
"required": [
"id",
"run_id",
"step_id",
"step_name",
"stream",
"line",
"created_at"
],
"type": "object"
},
"LogLineEvent": {
"description": "Payload of the `Event::LogLine` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::{LogLineEvent, LogStream};\nuse uuid::Uuid;\n\nlet payload = LogLineEvent {\n id: Uuid::now_v7(),\n run_id: Uuid::now_v7(),\n step_id: Uuid::now_v7(),\n step_name: \"build\".to_string(),\n stream: LogStream::Stdout,\n line: \"Compiling ironflow v0.1.0\".to_string(),\n at: Utc::now(),\n};\nassert_eq!(payload.line, \"Compiling ironflow v0.1.0\");\n```",
"properties": {
"at": {
"description": "When the line was emitted.",
"format": "date-time",
"type": "string"
},
"id": {
"description": "Persisted entry identifier (UUID v7, time-ordered).\n\nMatches the `id` of the log entry later stored for this line, so clients\ncan de-duplicate the live SSE stream against the persisted history\nfetched from `GET /runs/:id/logs`.\n\nDefaults to the nil UUID when absent, so a payload emitted by an older\nproducer still deserializes instead of dropping the whole event.",
"format": "uuid",
"type": "string"
},
"line": {
"description": "The log line content.",
"type": "string"
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "Step identifier.",
"format": "uuid",
"type": "string"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
},
"stream": {
"$ref": "#/components/schemas/LogStream",
"description": "Output stream."
}
},
"required": [
"run_id",
"step_id",
"step_name",
"stream",
"line",
"at"
],
"type": "object"
},
"LogStream": {
"description": "Output stream for a log line.\n\n# Examples\n\n```\nuse ironflow_store::entities::LogStream;\n\nlet stream: LogStream = \"stdout\".parse().unwrap();\nassert_eq!(stream.as_str(), \"stdout\");\n```",
"enum": [
"stdout",
"stderr",
"system"
],
"type": "string"
},
"MeResponse": {
"description": "Current user profile response.",
"properties": {
"created_at": {
"description": "When the user account was created.",
"format": "date-time",
"type": "string"
},
"email": {
"description": "Email address.",
"type": "string"
},
"is_admin": {
"description": "Admin flag.",
"type": "boolean"
},
"user_id": {
"description": "User ID.",
"format": "uuid",
"type": "string"
},
"username": {
"description": "Display username.",
"type": "string"
}
},
"required": [
"user_id",
"email",
"username",
"is_admin",
"created_at"
],
"type": "object"
},
"PlanWorkflowRequest": {
"description": "Request body for building a workflow execution plan.",
"properties": {
"estimate_durations": {
"description": "Estimate step durations from run history. Defaults to `true`.",
"nullable": true,
"type": "boolean"
},
"max_depth": {
"description": "How deep sub-workflows are expanded. Defaults to 3, capped at 10.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"payload": {
"additionalProperties": {},
"description": "Input payload the plan is computed for. Defaults to `{}`.",
"nullable": true,
"propertyNames": {
"type": "string"
},
"type": "object"
}
},
"type": "object"
},
"PlannedStepResponse": {
"description": "One step the planner expects the run to create.",
"properties": {
"condition": {
"$ref": "#/components/schemas/ConditionResponse",
"description": "Branch condition recorded for this step, when the handler declared one.",
"nullable": true
},
"depends_on": {
"description": "Names of the steps this one runs after.",
"items": {
"type": "string"
},
"type": "array"
},
"depth": {
"description": "Sub-workflow nesting depth; `0` for the top-level workflow.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"estimated_duration_ms": {
"description": "Average duration of this step in past completed runs, in milliseconds.",
"format": "int64",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"kind": {
"description": "Step kind, or the name of a custom operation.",
"type": "string"
},
"name": {
"description": "Step name as the handler declares it.",
"type": "string"
},
"parallel_group": {
"description": "Parallel wave this step belongs to, when it runs concurrently.",
"nullable": true,
"type": "string"
},
"workflow": {
"description": "Workflow that owns this step.",
"type": "string"
}
},
"required": [
"name",
"kind",
"workflow",
"depth",
"depends_on"
],
"type": "object"
},
"ProviderAccountChange": {
"description": "What happened to a Provider Account in a\n[`ProviderAccountUpdatedEvent`].\n\n# Examples\n\n```\nuse ironflow_engine::notify::ProviderAccountChange;\n\nassert_eq!(ProviderAccountChange::TokenReplaced.as_str(), \"token_replaced\");\n```",
"enum": [
"created",
"updated",
"deleted",
"token_replaced"
],
"type": "string"
},
"ProviderAccountResponse": {
"description": "A Provider Account, without its credential.",
"properties": {
"alert_threshold": {
"description": "Alert threshold.",
"format": "double",
"type": "number"
},
"auth_failed_at": {
"description": "When the provider last rejected the credential.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"created_at": {
"description": "Creation timestamp.",
"format": "date-time",
"type": "string"
},
"created_by": {
"description": "Creating user.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"display_name": {
"description": "Human-readable name.",
"type": "string"
},
"enabled": {
"description": "Whether the account may be selected.",
"type": "boolean"
},
"expires_at": {
"description": "When the credential expires.",
"format": "date-time",
"type": "string"
},
"id": {
"description": "Account ID.",
"format": "uuid",
"type": "string"
},
"kind": {
"description": "Account kind.",
"type": "string"
},
"max_concurrency": {
"description": "Maximum concurrent steps, `null` for unlimited.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"name": {
"description": "Unique slug.",
"type": "string"
},
"plan": {
"description": "Subscription plan.",
"nullable": true,
"type": "string"
},
"priority": {
"description": "Priority, lower is preferred.",
"format": "int32",
"type": "integer"
},
"state": {
"$ref": "#/components/schemas/AccountState",
"description": "Derived state."
},
"tags": {
"description": "Tags.",
"items": {
"type": "string"
},
"type": "array"
},
"updated_at": {
"description": "Last update timestamp.",
"format": "date-time",
"type": "string"
},
"windows": {
"description": "Latest windows.",
"items": {
"$ref": "#/components/schemas/AccountWindowResponse"
},
"type": "array"
}
},
"required": [
"id",
"name",
"display_name",
"kind",
"enabled",
"priority",
"tags",
"alert_threshold",
"expires_at",
"created_at",
"updated_at",
"state",
"windows"
],
"type": "object"
},
"ProviderAccountTestResponse": {
"description": "Response of `POST /provider-accounts/{id}/test`.",
"properties": {
"result": {
"$ref": "#/components/schemas/AccountTestResult",
"description": "Outcome."
},
"windows": {
"description": "Windows reported by the provider.",
"items": {
"$ref": "#/components/schemas/AccountWindowResponse"
},
"type": "array"
}
},
"required": [
"result",
"windows"
],
"type": "object"
},
"ProviderAccountUpdatedEvent": {
"description": "Payload of the `Event::ProviderAccountUpdated` event.\n\nNever carries the credential.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::{ProviderAccountChange, ProviderAccountUpdatedEvent};\nuse uuid::Uuid;\n\nlet payload = ProviderAccountUpdatedEvent {\n account_id: Uuid::now_v7(),\n name: \"perso\".to_string(),\n change: ProviderAccountChange::Created,\n at: Utc::now(),\n};\nassert_eq!(payload.name, \"perso\");\n```",
"properties": {
"account_id": {
"description": "Account identifier.",
"format": "uuid",
"type": "string"
},
"at": {
"description": "When the change happened.",
"format": "date-time",
"type": "string"
},
"change": {
"$ref": "#/components/schemas/ProviderAccountChange",
"description": "What changed."
},
"name": {
"description": "Account name.",
"type": "string"
}
},
"required": [
"account_id",
"name",
"change",
"at"
],
"type": "object"
},
"ProviderAccountUsageResponse": {
"description": "Current windows and history of an account.",
"properties": {
"account_id": {
"description": "Account ID.",
"format": "uuid",
"type": "string"
},
"history": {
"description": "Observations over the requested period, oldest first.",
"items": {
"$ref": "#/components/schemas/AccountUsagePointResponse"
},
"type": "array"
},
"name": {
"description": "Account name.",
"type": "string"
},
"windows": {
"description": "Latest windows.",
"items": {
"$ref": "#/components/schemas/AccountWindowResponse"
},
"type": "array"
}
},
"required": [
"account_id",
"name",
"windows",
"history"
],
"type": "object"
},
"ProviderAccountUsageUpdatedEvent": {
"description": "Payload of the `Event::ProviderAccountUsageUpdated` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::ProviderAccountUsageUpdatedEvent;\nuse uuid::Uuid;\n\nlet payload = ProviderAccountUsageUpdatedEvent {\n account_id: Uuid::now_v7(),\n name: \"perso\".to_string(),\n windows: Vec::new(),\n at: Utc::now(),\n};\nassert!(payload.windows.is_empty());\n```",
"properties": {
"account_id": {
"description": "Account identifier.",
"format": "uuid",
"type": "string"
},
"at": {
"description": "When the windows were recorded.",
"format": "date-time",
"type": "string"
},
"name": {
"description": "Account name.",
"type": "string"
},
"windows": {
"description": "Current windows of the account.",
"items": {
"$ref": "#/components/schemas/ProviderAccountWindow"
},
"type": "array"
}
},
"required": [
"account_id",
"name",
"windows",
"at"
],
"type": "object"
},
"ProviderAccountWindow": {
"description": "Latest reading of one usage window of an account.",
"properties": {
"account_id": {
"description": "Account the window belongs to.",
"format": "uuid",
"type": "string"
},
"model_scope": {
"description": "Model family the window applies to, `None` for every model.",
"nullable": true,
"type": "string"
},
"observed_at": {
"description": "When the window was observed.",
"format": "date-time",
"type": "string"
},
"resets_at": {
"description": "When the window resets.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"status": {
"$ref": "#/components/schemas/AccountWindowStatus",
"description": "Provider status."
},
"utilization": {
"description": "Fraction used, `0.0..=1.0`.",
"format": "double",
"type": "number"
},
"window": {
"description": "Window name (`five_hour`, `seven_day`).",
"type": "string"
}
},
"required": [
"account_id",
"window",
"utilization",
"status",
"observed_at"
],
"type": "object"
},
"RejectHumanInputRequest": {
"description": "Request body for rejecting a human input step.",
"properties": {
"reason": {
"description": "Why the input is refused. Passed to the handler.",
"nullable": true,
"type": "string"
}
},
"type": "object"
},
"RejectedRunResponse": {
"description": "A waiting run whose payload schema the signal did not match.",
"properties": {
"error": {
"description": "Why the payload was refused.",
"type": "string"
},
"run_id": {
"description": "Run that keeps waiting.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "The signal step that keeps waiting.",
"format": "uuid",
"type": "string"
}
},
"required": [
"run_id",
"step_id",
"error"
],
"type": "object"
},
"ResumedRunResponse": {
"description": "A run resumed by a signal.",
"properties": {
"run_id": {
"description": "Run whose waiting step the signal resolved.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "The resolved signal step.",
"format": "uuid",
"type": "string"
}
},
"required": [
"run_id",
"step_id"
],
"type": "object"
},
"RetryForcedEvent": {
"description": "Payload of the `Event::RetryForced` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::RetryForcedEvent;\nuse uuid::Uuid;\n\nlet payload = RetryForcedEvent {\n run_id: Uuid::now_v7(),\n workflow_name: \"deploy\".to_string(),\n original_version: \"1\".to_string(),\n current_version: \"2\".to_string(),\n at: Utc::now(),\n};\nassert_eq!(payload.current_version, \"2\");\n```",
"properties": {
"at": {
"description": "When the forced retry occurred.",
"format": "date-time",
"type": "string"
},
"current_version": {
"description": "Current version of the handler.",
"type": "string"
},
"original_version": {
"description": "Version stored on the original run.",
"type": "string"
},
"run_id": {
"description": "The new run created by the forced retry.",
"format": "uuid",
"type": "string"
},
"workflow_name": {
"description": "Workflow name.",
"type": "string"
}
},
"required": [
"run_id",
"workflow_name",
"original_version",
"current_version",
"at"
],
"type": "object"
},
"RotateSecretsRequest": {
"description": "Request body for rotating a batch of secrets to another key version.\n\nRotation is driven one batch per call so a long rotation never becomes a\nlong HTTP request: the client loops, carrying `after_id` forward.",
"properties": {
"after_id": {
"description": "Resume after this secret ID. Omit to start from the beginning.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"batch_size": {
"description": "Secrets to process in this batch. Defaults to 100, clamped to 1000.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"to_version": {
"description": "Target key version. Defaults to the server's active version.",
"format": "int32",
"nullable": true,
"type": "integer"
}
},
"type": "object"
},
"RotateSecretsResponse": {
"description": "Outcome of one rotation batch.",
"properties": {
"failed": {
"description": "Secrets skipped because they could not be decrypted.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"last_id": {
"description": "Highest secret ID seen in this batch. Pass it back as `after_id` to\ncontinue; `null` means there is nothing left to do.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"remaining": {
"description": "Secrets left on another key version after this batch.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"rotated": {
"description": "Secrets successfully re-encrypted in this batch.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"to_version": {
"description": "Key version the batch re-encrypted towards.",
"format": "int32",
"type": "integer"
}
},
"required": [
"to_version",
"rotated",
"failed",
"remaining"
],
"type": "object"
},
"RunBudgetExceededEvent": {
"description": "Payload of the `Event::RunBudgetExceeded` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::RunBudgetExceededEvent;\nuse rust_decimal::Decimal;\nuse uuid::Uuid;\n\nlet payload = RunBudgetExceededEvent {\n run_id: Uuid::now_v7(),\n workflow_name: \"deploy\".to_string(),\n limit_usd: Decimal::new(200, 2),\n spent_usd: Decimal::new(180, 2),\n step_budget_usd: Decimal::new(50, 2),\n at: Utc::now(),\n};\nassert_eq!(payload.limit_usd, Decimal::new(200, 2));\n```",
"properties": {
"at": {
"description": "When the refusal occurred.",
"format": "date-time",
"type": "string"
},
"limit_usd": {
"description": "The configured cost cap in USD.",
"format": "double",
"type": "number"
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"spent_usd": {
"description": "Cost already consumed when the cap was reached, in USD.",
"format": "double",
"type": "number"
},
"step_budget_usd": {
"description": "Declared budget of the refused step, in USD.",
"format": "double",
"type": "number"
},
"workflow_name": {
"description": "Workflow name.",
"type": "string"
}
},
"required": [
"run_id",
"workflow_name",
"limit_usd",
"spent_usd",
"step_budget_usd",
"at"
],
"type": "object"
},
"RunCreatedEvent": {
"description": "Payload of the `Event::RunCreated` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::RunCreatedEvent;\nuse uuid::Uuid;\n\nlet payload = RunCreatedEvent {\n run_id: Uuid::now_v7(),\n workflow_name: \"deploy\".to_string(),\n at: Utc::now(),\n};\nassert_eq!(payload.workflow_name, \"deploy\");\n```",
"properties": {
"at": {
"description": "When the run was created.",
"format": "date-time",
"type": "string"
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"workflow_name": {
"description": "Workflow name.",
"type": "string"
}
},
"required": [
"run_id",
"workflow_name",
"at"
],
"type": "object"
},
"RunDetailResponse": {
"description": "Run detail response — includes steps and payload.",
"properties": {
"active_descendant_count": {
"description": "Sub-workflow runs below this run, at any depth, that are not finished:\ncancelling the run cancels them too.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"payload": {
"description": "Input payload that triggered this run."
},
"run": {
"$ref": "#/components/schemas/RunResponse",
"description": "The run."
},
"steps": {
"description": "Associated steps, ordered by position.",
"items": {
"$ref": "#/components/schemas/StepResponse"
},
"type": "array"
}
},
"required": [
"run",
"steps",
"payload",
"active_descendant_count"
],
"type": "object"
},
"RunFailedEvent": {
"description": "Payload of the `Event::RunFailed` event.\n\n# Examples\n\n```\nuse std::collections::HashMap;\n\nuse chrono::Utc;\nuse ironflow_engine::notify::RunFailedEvent;\nuse rust_decimal::Decimal;\nuse uuid::Uuid;\n\nlet payload = RunFailedEvent {\n run_id: Uuid::now_v7(),\n workflow_name: \"deploy\".to_string(),\n error: Some(\"step crashed\".to_string()),\n cost_usd: Decimal::ZERO,\n duration_ms: 3000,\n labels: HashMap::new(),\n at: Utc::now(),\n};\nassert_eq!(payload.error.as_deref(), Some(\"step crashed\"));\n```",
"properties": {
"at": {
"description": "When the failure occurred.",
"format": "date-time",
"type": "string"
},
"cost_usd": {
"description": "Aggregated cost in USD at the time of failure.",
"format": "double",
"type": "number"
},
"duration_ms": {
"description": "Aggregated duration in milliseconds at the time of failure.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"error": {
"description": "Error message.",
"nullable": true,
"type": "string"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"description": "Labels of the run at the time of the failure.",
"propertyNames": {
"type": "string"
},
"type": "object"
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"workflow_name": {
"description": "Workflow name.",
"type": "string"
}
},
"required": [
"run_id",
"workflow_name",
"cost_usd",
"duration_ms",
"at"
],
"type": "object"
},
"RunResponse": {
"description": "Run response DTO — public API representation of a run.\n\nMaps from the internal [`Run`] model, exposing only necessary fields.\n\n# Examples\n\n```\nuse ironflow_store::models::{Run, RunStatus, TriggerKind};\nuse ironflow_api::entities::RunResponse;\n```",
"properties": {
"capacity_wait_kind": {
"description": "Provider kind the run is waiting capacity for, set only while the\nrun is `sleeping` because every targeted Provider Account is rate\nlimited. `scheduled_at` then holds when the run wakes.",
"nullable": true,
"type": "string"
},
"completed_at": {
"description": "When execution completed.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"concurrency_key": {
"description": "Exclusivity key held by this run until it reaches a terminal state,\nwhen one was supplied.",
"nullable": true,
"type": "string"
},
"concurrency_limits": {
"description": "Concurrency groups the run belongs to, with the limit it was created\nwith. Omitted when the run belongs to no group.",
"items": {
"$ref": "#/components/schemas/ConcurrencyLimit"
},
"type": "array"
},
"cost_usd": {
"description": "Aggregated cost in USD.",
"format": "double",
"type": "number"
},
"created_at": {
"description": "When created.",
"format": "date-time",
"type": "string"
},
"created_by": {
"$ref": "#/components/schemas/CreatedBy",
"description": "Who triggered the run. Always present."
},
"duration_ms": {
"description": "Total duration in milliseconds.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"error": {
"description": "Optional error message.",
"nullable": true,
"type": "string"
},
"handler_version": {
"description": "Version of the handler that created this run.",
"nullable": true,
"type": "string"
},
"id": {
"description": "Unique run identifier.",
"format": "uuid",
"type": "string"
},
"idempotency_key": {
"description": "Idempotency key that produced this run, when one was supplied.",
"nullable": true,
"type": "string"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"description": "User-defined key-value labels.",
"propertyNames": {
"type": "string"
},
"type": "object"
},
"max_cost_usd": {
"description": "Cumulative cost cap for this run, in USD. `None` means no cap.",
"format": "double",
"nullable": true,
"type": "number"
},
"max_retries": {
"description": "Maximum allowed retries.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"output": {
"description": "Typed output the handler set with `WorkflowContext::set_output`.\n\nWritten when the run ends (completed, warning, failed or cancelled).\nOmitted when the handler set no output."
},
"retry_count": {
"description": "Number of times retried.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"scheduled_at": {
"description": "Scheduled execution time. `None` means the run executed immediately.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"started_at": {
"description": "When execution started.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"status": {
"$ref": "#/components/schemas/RunStatus",
"description": "Current status."
},
"trigger": {
"$ref": "#/components/schemas/TriggerKind",
"description": "How the run was triggered."
},
"updated_at": {
"description": "When last updated.",
"format": "date-time",
"type": "string"
},
"workflow_name": {
"description": "Workflow name.",
"type": "string"
}
},
"required": [
"id",
"workflow_name",
"status",
"trigger",
"retry_count",
"max_retries",
"cost_usd",
"duration_ms",
"created_at",
"updated_at",
"created_by"
],
"type": "object"
},
"RunStatus": {
"description": "Status of a workflow run, forming a finite state machine.\n\nValid transitions:\n- `Pending` -> `Running`, `Cancelled`\n- `Running` -> `Pending` (worker lease expired), `Completed`, `Failed`, `Warning`, `Retrying`, `Cancelled`, `AwaitingApproval`, `Sleeping`\n- `Retrying` -> `Running`, `Failed`, `Cancelled`\n- `AwaitingApproval` -> `Running`, `Pending` (requeued for a worker under `ExecutionMode::Workers`), `Failed`, `Cancelled`\n- `Sleeping` -> `Pending` (wake-up timer elapsed), `Cancelled`\n\nTerminal states (`Completed`, `Failed`, `Warning`, `Cancelled`) are idempotent:\ntransitioning to the same terminal state is a no-op, not an error.\n\n# Examples\n\n```\nuse ironflow_store::entities::RunStatus;\n\nassert!(RunStatus::Pending.can_transition_to(&RunStatus::Running));\nassert!(!RunStatus::Pending.can_transition_to(&RunStatus::Completed));\nassert!(!RunStatus::Completed.can_transition_to(&RunStatus::Running));\nassert!(RunStatus::Running.can_transition_to(&RunStatus::AwaitingApproval));\nassert!(RunStatus::Running.can_transition_to(&RunStatus::Warning));\n// A run whose worker lease expired goes back to the queue:\nassert!(RunStatus::Running.can_transition_to(&RunStatus::Pending));\nassert!(RunStatus::AwaitingApproval.can_transition_to(&RunStatus::Running));\nassert!(RunStatus::AwaitingApproval.can_transition_to(&RunStatus::Pending));\nassert!(RunStatus::Running.can_transition_to(&RunStatus::Sleeping));\nassert!(RunStatus::Sleeping.can_transition_to(&RunStatus::Pending));\nassert!(RunStatus::Sleeping.can_transition_to(&RunStatus::Cancelled));\n// Terminal-to-same is idempotent:\nassert!(RunStatus::Failed.can_transition_to(&RunStatus::Failed));\nassert!(RunStatus::Completed.can_transition_to(&RunStatus::Completed));\nassert!(RunStatus::Warning.can_transition_to(&RunStatus::Warning));\nassert!(RunStatus::Cancelled.can_transition_to(&RunStatus::Cancelled));\n```",
"enum": [
"pending",
"running",
"completed",
"failed",
"retrying",
"cancelled",
"awaiting_approval",
"warning",
"sleeping"
],
"type": "string"
},
"RunStatusChangedEvent": {
"description": "Payload of the `Event::RunStatusChanged` event.\n\n# Examples\n\n```\nuse std::collections::HashMap;\n\nuse chrono::Utc;\nuse ironflow_engine::notify::RunStatusChangedEvent;\nuse ironflow_store::models::RunStatus;\nuse rust_decimal::Decimal;\nuse uuid::Uuid;\n\nlet payload = RunStatusChangedEvent {\n run_id: Uuid::now_v7(),\n workflow_name: \"deploy\".to_string(),\n from: RunStatus::Running,\n to: RunStatus::Completed,\n error: None,\n cost_usd: Decimal::ZERO,\n duration_ms: 5000,\n labels: HashMap::new(),\n at: Utc::now(),\n};\nassert_eq!(payload.to, RunStatus::Completed);\n```",
"properties": {
"at": {
"description": "When the transition occurred.",
"format": "date-time",
"type": "string"
},
"cost_usd": {
"description": "Aggregated cost in USD at the time of transition.",
"format": "double",
"type": "number"
},
"duration_ms": {
"description": "Aggregated duration in milliseconds at the time of transition.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"error": {
"description": "Error message (when transitioning to Failed).",
"nullable": true,
"type": "string"
},
"from": {
"$ref": "#/components/schemas/RunStatus",
"description": "Previous status."
},
"labels": {
"additionalProperties": {
"type": "string"
},
"description": "Labels of the run at the time of the transition.",
"propertyNames": {
"type": "string"
},
"type": "object"
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"to": {
"$ref": "#/components/schemas/RunStatus",
"description": "New status."
},
"workflow_name": {
"description": "Workflow name.",
"type": "string"
}
},
"required": [
"run_id",
"workflow_name",
"from",
"to",
"cost_usd",
"duration_ms",
"at"
],
"type": "object"
},
"ScheduleResponse": {
"description": "Schedule list/detail response.",
"properties": {
"created_at": {
"description": "When the schedule was created.",
"format": "date-time",
"type": "string"
},
"created_by_user_id": {
"description": "User who created the schedule. `None` for handler-declared schedules.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"cron_expression": {
"description": "Cron expression.",
"type": "string"
},
"disabled_at": {
"description": "When the schedule was disabled. `None` means active.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"id": {
"description": "Schedule ID.",
"format": "uuid",
"type": "string"
},
"inputs": {
"description": "JSON payload passed to the workflow."
},
"last_error": {
"description": "Why Ironflow disabled the schedule on its own (e.g. its next trigger\ncannot be computed). `None` when paused by a user or never disabled.",
"nullable": true,
"type": "string"
},
"last_triggered_at": {
"description": "When the schedule last created a run.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"next_trigger_at": {
"description": "When the schedule will next fire. Always set on an active schedule.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"source": {
"$ref": "#/components/schemas/ScheduleSource",
"description": "Where this schedule was created (`handler` or `api`)."
},
"updated_at": {
"description": "When the schedule was last updated.",
"format": "date-time",
"type": "string"
},
"workflow_name": {
"description": "Name of the workflow to trigger.",
"type": "string"
}
},
"required": [
"id",
"workflow_name",
"cron_expression",
"inputs",
"source",
"created_at",
"updated_at"
],
"type": "object"
},
"ScheduleSource": {
"description": "Where a schedule was created.\n\n`Handler` schedules are declared in code via [`WorkflowHandler::schedule()`]\nand synced to the database at startup. `Api` schedules are created by users\nthrough the REST API, CLI, or dashboard.\n\n# Examples\n\n```\nuse ironflow_store::entities::ScheduleSource;\n\nlet source = ScheduleSource::Handler;\nassert_eq!(source.as_str(), \"handler\");\n\nlet parsed: ScheduleSource = \"api\".parse().unwrap();\nassert_eq!(parsed, ScheduleSource::Api);\n```",
"enum": [
"handler",
"api"
],
"type": "string"
},
"ScopeEntry": {
"description": "A scope entry with its machine name and human-readable label.",
"properties": {
"description": {
"description": "Short description.",
"type": "string"
},
"label": {
"description": "Human-readable label (e.g. \"Runs Read\").",
"type": "string"
},
"value": {
"description": "Machine-readable scope value (e.g. \"runs_read\").",
"type": "string"
}
},
"required": [
"value",
"label",
"description"
],
"type": "object"
},
"SecretResponse": {
"description": "Response DTO for a secret (never exposes the value).",
"properties": {
"created_at": {
"description": "Creation timestamp.",
"format": "date-time",
"type": "string"
},
"id": {
"description": "Secret ID.",
"format": "uuid",
"type": "string"
},
"key": {
"description": "Secret key.",
"type": "string"
},
"updated_at": {
"description": "Last update timestamp.",
"format": "date-time",
"type": "string"
}
},
"required": [
"id",
"key",
"created_at",
"updated_at"
],
"type": "object"
},
"SendSignalRequest": {
"description": "Send signal request body.",
"properties": {
"idempotency_id": {
"description": "Deduplication ID (1-255 characters): a second signal with the same ID\nis neither stored nor delivered.",
"nullable": true,
"type": "string"
},
"key": {
"description": "Occurrence key, e.g. a commit SHA (1-255 characters).",
"type": "string"
},
"name": {
"description": "Signal name, e.g. `\"ci.pipeline_finished\"` (1-255 characters).",
"type": "string"
},
"payload": {
"description": "JSON payload, validated against the schema of each waiting step.",
"type": "object"
}
},
"required": [
"name",
"key",
"payload"
],
"type": "object"
},
"SetSecretRequest": {
"description": "Request body for creating or updating a secret.",
"properties": {
"key": {
"description": "Secret key (namespaced, e.g. `workflows/inbox/gmail_token`).",
"type": "string"
},
"value": {
"description": "Secret value (plaintext, will be encrypted at rest).",
"type": "string"
}
},
"required": [
"key",
"value"
],
"type": "object"
},
"SignInRequest": {
"description": "Sign-in request body.",
"properties": {
"email": {
"description": "Email address.",
"type": "string"
},
"password": {
"description": "Plaintext password.",
"type": "string"
}
},
"required": [
"email",
"password"
],
"type": "object"
},
"SignUpRequest": {
"description": "Sign-up request body.",
"properties": {
"email": {
"description": "Email address.",
"type": "string"
},
"password": {
"description": "Plaintext password: 12 to 128 characters, not a common password, not\ncontaining the email or username, not repetitive.",
"type": "string"
},
"username": {
"description": "Display username.",
"type": "string"
}
},
"required": [
"email",
"username",
"password"
],
"type": "object"
},
"SignalAwaitedEvent": {
"description": "Payload of the `Event::SignalAwaited` event.\n\nEmitted when a run suspends on `ctx.wait_for_signal` and goes `Sleeping`.\n\n# Examples\n\n```\nuse chrono::{TimeDelta, Utc};\nuse ironflow_engine::notify::SignalAwaitedEvent;\nuse uuid::Uuid;\n\nlet now = Utc::now();\nlet payload = SignalAwaitedEvent {\n run_id: Uuid::now_v7(),\n step_id: Uuid::now_v7(),\n step_name: \"wait-ci\".to_string(),\n name: \"ci.pipeline_finished\".to_string(),\n key: \"4f2a9c1\".to_string(),\n deadline_at: now + TimeDelta::hours(1),\n at: now,\n};\nassert_eq!(payload.key, \"4f2a9c1\");\n```",
"properties": {
"at": {
"description": "When the run started waiting.",
"format": "date-time",
"type": "string"
},
"deadline_at": {
"description": "When the wait times out.",
"format": "date-time",
"type": "string"
},
"key": {
"description": "Awaited occurrence key.",
"type": "string"
},
"name": {
"description": "Awaited signal name.",
"type": "string"
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "The signal step the run waits on.",
"format": "uuid",
"type": "string"
},
"step_name": {
"description": "Name of the signal step.",
"type": "string"
}
},
"required": [
"run_id",
"step_id",
"step_name",
"name",
"key",
"deadline_at",
"at"
],
"type": "object"
},
"SignalDeliveryResponse": {
"description": "Result of sending a signal.",
"properties": {
"duplicate": {
"description": "`true` when the idempotency ID was already used: nothing was delivered.",
"type": "boolean"
},
"rejected": {
"description": "Waiting runs that rejected the payload and keep waiting.",
"items": {
"$ref": "#/components/schemas/RejectedRunResponse"
},
"type": "array"
},
"resumed": {
"description": "Runs the signal resumed.",
"items": {
"$ref": "#/components/schemas/ResumedRunResponse"
},
"type": "array"
},
"signal_id": {
"description": "ID of the stored signal (the pre-existing one on a duplicate).",
"format": "uuid",
"type": "string"
}
},
"required": [
"signal_id",
"duplicate",
"resumed",
"rejected"
],
"type": "object"
},
"SignalReceivedEvent": {
"description": "Payload of the `Event::SignalReceived` event.\n\nEmitted once per stored signal; a duplicate (same idempotency ID) emits\nnothing.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::SignalReceivedEvent;\nuse uuid::Uuid;\n\nlet payload = SignalReceivedEvent {\n signal_id: Uuid::now_v7(),\n name: \"ci.pipeline_finished\".to_string(),\n key: \"4f2a9c1\".to_string(),\n resumed_runs: vec![Uuid::now_v7()],\n at: Utc::now(),\n};\nassert_eq!(payload.resumed_runs.len(), 1);\n```",
"properties": {
"at": {
"description": "When the signal was received.",
"format": "date-time",
"type": "string"
},
"key": {
"description": "Occurrence key.",
"type": "string"
},
"name": {
"description": "Signal name.",
"type": "string"
},
"resumed_runs": {
"description": "Runs whose waiting step the signal resolved.",
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"signal_id": {
"description": "Stored signal identifier.",
"format": "uuid",
"type": "string"
}
},
"required": [
"signal_id",
"name",
"key",
"resumed_runs",
"at"
],
"type": "object"
},
"SignalResponse": {
"description": "A stored signal.",
"properties": {
"id": {
"description": "Signal ID.",
"format": "uuid",
"type": "string"
},
"idempotency_id": {
"description": "Deduplication ID given by the sender.",
"nullable": true,
"type": "string"
},
"key": {
"description": "Occurrence key.",
"type": "string"
},
"name": {
"description": "Signal name.",
"type": "string"
},
"payload": {
"description": "JSON payload.",
"type": "object"
},
"received_at": {
"description": "When the signal was received.",
"format": "date-time",
"type": "string"
}
},
"required": [
"id",
"name",
"key",
"payload",
"received_at"
],
"type": "object"
},
"StatsHistoryBucketResponse": {
"description": "One time bucket in the history response.\n\nRuns are assigned to the bucket of their creation time and counted under\ntheir current status. Each status has its own counter, so the counters add\nup to the number of runs created in the bucket.\n\n# Examples\n\n```\nuse ironflow_api::entities::StatsHistoryBucketResponse;\n```",
"properties": {
"avg_duration_ms": {
"description": "Average duration in milliseconds.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"awaiting_approval": {
"description": "Number of runs created in this bucket and currently awaiting approval.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"cancelled": {
"description": "Number of runs created in this bucket and currently cancelled.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"completed": {
"description": "Number of runs created in this bucket and currently completed.\nStrict: runs in the `warning` state are counted in `warning`.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"failed": {
"description": "Number of runs created in this bucket and currently failed.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"p95_duration_ms": {
"description": "95th percentile duration in milliseconds.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"pending": {
"description": "Number of runs created in this bucket and currently pending.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"retrying": {
"description": "Number of runs created in this bucket and currently retrying.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"running": {
"description": "Number of runs created in this bucket and currently running.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"sleeping": {
"description": "Number of runs created in this bucket and currently sleeping.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"success_rate_percent": {
"description": "Success rate: (completed + warning) / (completed + warning + failed),\nas a percentage. `null` when the bucket has no completed, warning or\nfailed run.",
"format": "double",
"nullable": true,
"type": "number"
},
"time": {
"description": "Start of the time bucket.",
"format": "date-time",
"type": "string"
},
"total_cost_usd": {
"description": "Total cost in USD.",
"format": "double",
"type": "number"
},
"warning": {
"description": "Number of runs created in this bucket and currently in `warning`.",
"format": "int64",
"minimum": 0,
"type": "integer"
}
},
"required": [
"time",
"completed",
"warning",
"failed",
"cancelled",
"pending",
"running",
"retrying",
"awaiting_approval",
"sleeping",
"avg_duration_ms",
"p95_duration_ms",
"total_cost_usd"
],
"type": "object"
},
"StatsHistoryResponse": {
"description": "Time-bucketed historical statistics response.\n\n# Examples\n\n```\nuse ironflow_api::entities::StatsHistoryResponse;\n```",
"properties": {
"buckets": {
"description": "Every bucket of the period, zero-filled, sorted by time ascending.",
"items": {
"$ref": "#/components/schemas/StatsHistoryBucketResponse"
},
"type": "array"
},
"granularity": {
"$ref": "#/components/schemas/HistoryGranularity",
"description": "The granularity of each bucket."
},
"period": {
"$ref": "#/components/schemas/HistoryPeriod",
"description": "The period that was queried."
},
"workflow": {
"description": "Workflow name filter, if applied.",
"nullable": true,
"type": "string"
}
},
"required": [
"period",
"granularity",
"buckets"
],
"type": "object"
},
"StatsResponse": {
"description": "Aggregate statistics response.\n\nComputed from all runs in the store.\n\n# Examples\n\n```\nuse ironflow_api::entities::StatsResponse;\n```",
"properties": {
"active_runs": {
"description": "Number of active runs: pending, running, retrying, awaiting approval\nor sleeping.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"awaiting_approval_runs": {
"description": "Number of runs awaiting approval. A subset of `active_runs`.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"cancelled_runs": {
"description": "Number of cancelled runs.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"completed_runs": {
"description": "Number of completed runs.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"failed_runs": {
"description": "Number of failed runs.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"success_rate_percent": {
"description": "Success rate: completed / (completed + failed), as a percentage.",
"format": "double",
"type": "number"
},
"total_cost_usd": {
"description": "Aggregated cost across all runs in USD.",
"format": "double",
"type": "number"
},
"total_duration_ms": {
"description": "Aggregated duration across all runs in milliseconds.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"total_runs": {
"description": "Total number of runs.",
"format": "int64",
"minimum": 0,
"type": "integer"
}
},
"required": [
"total_runs",
"completed_runs",
"failed_runs",
"cancelled_runs",
"active_runs",
"awaiting_approval_runs",
"success_rate_percent",
"total_cost_usd",
"total_duration_ms"
],
"type": "object"
},
"StepAccountResponse": {
"description": "Readable identity of the Provider Account a step ran under.\n\nDeliberately minimal: it never carries the credential, the secret key or\nany scheduling data, since it is readable by anyone who can read the run.\n\n# Examples\n\n```\nuse ironflow_api::entities::StepAccountResponse;\nuse uuid::Uuid;\n\nlet account = StepAccountResponse {\n id: Uuid::now_v7(),\n name: \"perso\".to_string(),\n display_name: \"Compte perso\".to_string(),\n};\nassert_eq!(account.name, \"perso\");\n```",
"properties": {
"display_name": {
"description": "Human-readable name.",
"type": "string"
},
"id": {
"description": "Account ID.",
"format": "uuid",
"type": "string"
},
"name": {
"description": "Unique slug.",
"type": "string"
}
},
"required": [
"id",
"name",
"display_name"
],
"type": "object"
},
"StepApproval": {
"description": "One vote cast on an approval gate.\n\nVotes are unique per [`user_id`](StepApproval::user_id): an API key votes as\nits owner, and a user voting twice is ignored by the store.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_store::entities::StepApproval;\nuse uuid::Uuid;\n\nlet vote = StepApproval {\n user_id: Uuid::now_v7(),\n approved_by: \"alice\".to_string(),\n at: Utc::now(),\n};\nassert_eq!(vote.approved_by, \"alice\");\n```",
"properties": {
"approved_by": {
"description": "Display name of the voter at vote time.",
"type": "string"
},
"at": {
"description": "When the vote was cast.",
"format": "date-time",
"type": "string"
},
"user_id": {
"description": "The user who voted.",
"format": "uuid",
"type": "string"
}
},
"required": [
"user_id",
"approved_by",
"at"
],
"type": "object"
},
"StepCompletedEvent": {
"description": "Payload of the `Event::StepCompleted` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::StepCompletedEvent;\nuse ironflow_store::models::StepKind;\nuse rust_decimal::Decimal;\nuse uuid::Uuid;\n\nlet payload = StepCompletedEvent {\n run_id: Uuid::now_v7(),\n step_id: Uuid::now_v7(),\n step_name: \"build\".to_string(),\n kind: StepKind::Shell,\n duration_ms: 1200,\n cost_usd: Decimal::ZERO,\n at: Utc::now(),\n};\nassert_eq!(payload.step_name, \"build\");\n```",
"properties": {
"at": {
"description": "When the step completed.",
"format": "date-time",
"type": "string"
},
"cost_usd": {
"description": "Step cost in USD.",
"format": "double",
"type": "number"
},
"duration_ms": {
"description": "Step duration in milliseconds.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"kind": {
"description": "Step operation kind.",
"type": "string"
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "Step identifier.",
"format": "uuid",
"type": "string"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
}
},
"required": [
"run_id",
"step_id",
"step_name",
"kind",
"duration_ms",
"cost_usd",
"at"
],
"type": "object"
},
"StepFailedEvent": {
"description": "Payload of the `Event::StepFailed` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::StepFailedEvent;\nuse ironflow_store::models::StepKind;\nuse uuid::Uuid;\n\nlet payload = StepFailedEvent {\n run_id: Uuid::now_v7(),\n step_id: Uuid::now_v7(),\n step_name: \"build\".to_string(),\n kind: StepKind::Shell,\n error: \"exit code 1\".to_string(),\n at: Utc::now(),\n};\nassert_eq!(payload.error, \"exit code 1\");\n```",
"properties": {
"at": {
"description": "When the step failed.",
"format": "date-time",
"type": "string"
},
"error": {
"description": "Error message.",
"type": "string"
},
"kind": {
"description": "Step operation kind.",
"type": "string"
},
"run_id": {
"description": "Run identifier.",
"format": "uuid",
"type": "string"
},
"step_id": {
"description": "Step identifier.",
"format": "uuid",
"type": "string"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
}
},
"required": [
"run_id",
"step_id",
"step_name",
"kind",
"error",
"at"
],
"type": "object"
},
"StepResponse": {
"description": "Step response DTO — public API representation of a step.\n\n# Examples\n\n```\nuse ironflow_store::models::Step;\nuse ironflow_api::entities::StepResponse;\n```",
"properties": {
"account": {
"$ref": "#/components/schemas/StepAccountResponse",
"description": "Readable identity of the Provider Account the step ran under.\n\n`None` when the step has no account or the account was deleted since\n(`account_id` is then still set). Readable by anyone who can read the\nrun; no other account data is exposed here.",
"nullable": true
},
"account_id": {
"description": "Provider Account the agent step ran under, if any.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"approval_assignee": {
"description": "Who the approval is currently assigned to.\n\nSerialized as a prefixed string: `user:{name}` or `group:{name}`.",
"nullable": true,
"type": "string"
},
"approval_deadline_at": {
"description": "When this approval gate expires, if it carries an SLA deadline.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"approval_requirement": {
"$ref": "#/components/schemas/ApprovalRequirement",
"description": "Approvers the workflow handler required when the gate opened. `None`\nfor a gate opened without approvers.",
"nullable": true
},
"approval_seconds_remaining": {
"description": "Seconds left before the gate escalates. Clamped at 0, `None` when the\nstep has no deadline.",
"format": "int64",
"nullable": true,
"type": "integer"
},
"approvals": {
"description": "Votes cast on the approval gate so far, at most one per user.",
"items": {
"$ref": "#/components/schemas/StepApproval"
},
"type": "array"
},
"approvals_required": {
"description": "Distinct approvals the gate needs: the requirement's count, `1` for an\napproval step without rules, `None` for any other step kind.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"artifacts": {
"description": "Files this step produced, downloadable through the artifact route.\n\nEmpty when the step produced none or when artifact storage is not\nconfigured on the server.",
"items": {
"$ref": "#/components/schemas/ArtifactResponse"
},
"type": "array"
},
"attempt": {
"description": "Which run attempt produced this step (1-based).\n\nA run retried twice exposes steps with `attempt` 1, 2 and 3. Steps from\nearlier attempts are kept so a failed attempt stays inspectable.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"cache_creation_input_tokens": {
"description": "Input tokens written to the prompt cache (agent steps).",
"format": "int64",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"cache_read_input_tokens": {
"description": "Input tokens served from the prompt cache (agent steps).",
"format": "int64",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"completed_at": {
"description": "When execution completed.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"cost_usd": {
"description": "Cost in USD.",
"format": "double",
"type": "number"
},
"created_at": {
"description": "When created.",
"format": "date-time",
"type": "string"
},
"debug_messages": {
"description": "Verbose conversation trace for agent steps (thinking blocks, tool\ncalls, tool results, per-turn usage). `None` when verbose mode was\noff or the step is not an agent step."
},
"dependencies": {
"description": "IDs of steps this step depends on (direct dependencies).",
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
},
"duration_ms": {
"description": "Execution duration in milliseconds.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"environment_id": {
"description": "Persistent environment the agent step ran in, if any. Pass it to\n`Agent::resume_environment` to continue a later step in the same\nworkspace.",
"nullable": true,
"type": "string"
},
"error": {
"description": "Optional error message.",
"nullable": true,
"type": "string"
},
"id": {
"description": "Unique step identifier.",
"format": "uuid",
"type": "string"
},
"input": {
"additionalProperties": {},
"description": "Input configuration.",
"nullable": true,
"propertyNames": {
"type": "string"
},
"type": "object"
},
"input_tokens": {
"description": "Uncached input token count (agent steps).",
"format": "int64",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"kind": {
"description": "Step operation type.",
"type": "string"
},
"name": {
"description": "Step name.",
"type": "string"
},
"output": {
"additionalProperties": {},
"description": "Step output.",
"nullable": true,
"propertyNames": {
"type": "string"
},
"type": "object"
},
"output_tokens": {
"description": "Output token count (agent steps).",
"format": "int64",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"position": {
"description": "Execution order (0-based).",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"run_id": {
"description": "Parent run ID.",
"format": "uuid",
"type": "string"
},
"started_at": {
"description": "When execution started.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"status": {
"$ref": "#/components/schemas/StepStatus",
"description": "Current status."
},
"trace_id": {
"description": "Deterministic trace ID for log correlation.",
"format": "uuid",
"type": "string"
},
"updated_at": {
"description": "When updated.",
"format": "date-time",
"type": "string"
}
},
"required": [
"id",
"trace_id",
"run_id",
"name",
"kind",
"position",
"status",
"attempt",
"duration_ms",
"cost_usd",
"created_at",
"updated_at",
"dependencies"
],
"type": "object"
},
"StepStatus": {
"description": "Status of an individual step within a run.\n\n# Examples\n\n```\nuse ironflow_store::entities::StepStatus;\n\nassert!(!StepStatus::Pending.is_terminal());\nassert!(StepStatus::Completed.is_terminal());\n```",
"enum": [
"pending",
"running",
"completed",
"failed",
"skipped",
"awaiting_approval",
"rejected"
],
"type": "string"
},
"SubWorkflowDetail": {
"description": "Sub-workflow detail included in the workflow response.",
"properties": {
"description": {
"description": "Human-readable description.",
"type": "string"
},
"name": {
"description": "Sub-workflow name.",
"type": "string"
},
"source_code": {
"description": "Optional Rust source code of the handler.",
"nullable": true,
"type": "string"
}
},
"required": [
"name",
"description"
],
"type": "object"
},
"TriggerKind": {
"description": "How a run was triggered.\n\n# Examples\n\n```\nuse ironflow_store::entities::TriggerKind;\n\nlet trigger = TriggerKind::Manual;\nlet json = serde_json::to_string(&trigger).unwrap();\nassert!(json.contains(\"manual\"));\n```",
"oneOf": [
{
"description": "Triggered manually (CLI or programmatic call).",
"properties": {
"kind": {
"enum": [
"manual"
],
"type": "string"
}
},
"required": [
"kind"
],
"type": "object"
},
{
"description": "Triggered by an incoming webhook.",
"properties": {
"kind": {
"enum": [
"webhook"
],
"type": "string"
},
"path": {
"description": "The webhook path that received the request.",
"type": "string"
}
},
"required": [
"path",
"kind"
],
"type": "object"
},
{
"description": "Triggered by a cron schedule.",
"properties": {
"kind": {
"enum": [
"cron"
],
"type": "string"
},
"schedule": {
"description": "The cron expression that fired.",
"type": "string"
}
},
"required": [
"schedule",
"kind"
],
"type": "object"
},
{
"description": "Triggered via the REST API.",
"properties": {
"kind": {
"enum": [
"api"
],
"type": "string"
}
},
"required": [
"kind"
],
"type": "object"
},
{
"description": "Retry of a previously failed run.",
"properties": {
"kind": {
"enum": [
"retry"
],
"type": "string"
},
"parent_run_id": {
"description": "The original run that failed.",
"format": "uuid",
"type": "string"
}
},
"required": [
"parent_run_id",
"kind"
],
"type": "object"
},
{
"description": "Replay of a previously finished run on the current handler version.",
"properties": {
"kind": {
"enum": [
"replay"
],
"type": "string"
},
"original_run_id": {
"description": "The original run that was replayed.",
"format": "uuid",
"type": "string"
}
},
"required": [
"original_run_id",
"kind"
],
"type": "object"
},
{
"description": "Triggered by a parent workflow as a sub-workflow step.",
"properties": {
"kind": {
"enum": [
"workflow"
],
"type": "string"
}
},
"required": [
"kind"
],
"type": "object"
},
{
"description": "Triggered by a message consumed from a NATS subject.",
"properties": {
"kind": {
"enum": [
"nats"
],
"type": "string"
},
"subject": {
"description": "The NATS subject the message was consumed from.",
"type": "string"
}
},
"required": [
"subject",
"kind"
],
"type": "object"
},
{
"description": "Triggered by an internal run event (workflow chaining).",
"properties": {
"event_kind": {
"description": "The event kind that fired (e.g. `\"run_failed\"`).",
"type": "string"
},
"kind": {
"enum": [
"run_event"
],
"type": "string"
},
"source_run_id": {
"description": "The run whose event triggered this run.",
"format": "uuid",
"type": "string"
}
},
"required": [
"source_run_id",
"event_kind",
"kind"
],
"type": "object"
},
{
"description": "Triggered by a polling probe detecting new data.",
"properties": {
"kind": {
"enum": [
"polling"
],
"type": "string"
},
"probe": {
"description": "Name of the probe that fired (e.g. `\"http\"`, `\"sql\"`).",
"type": "string"
}
},
"required": [
"probe",
"kind"
],
"type": "object"
}
]
},
"UpdateProviderAccountRequest": {
"additionalProperties": false,
"description": "Request body to update a Provider Account. All fields optional; `name`\nand `kind` are immutable.",
"properties": {
"alert_threshold": {
"description": "New alert threshold in `(0, 1]`.",
"format": "double",
"nullable": true,
"type": "number"
},
"display_name": {
"description": "New display name.",
"nullable": true,
"type": "string"
},
"enabled": {
"description": "Enable or disable.",
"nullable": true,
"type": "boolean"
},
"expires_at": {
"description": "New expiry of the credential.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"max_concurrency": {
"description": "New max concurrency; `null` removes the limit.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"plan": {
"description": "New plan; `null` clears it.",
"nullable": true,
"type": "string"
},
"priority": {
"description": "New priority.",
"format": "int32",
"nullable": true,
"type": "integer"
},
"tags": {
"description": "New tags (replaces the list).",
"items": {
"type": "string"
},
"nullable": true,
"type": "array"
},
"token": {
"description": "Replacement credential, write-only. Checked against the provider.",
"nullable": true,
"type": "string"
}
},
"type": "object"
},
"UpdateRoleRequest": {
"description": "Request body for updating a user's role (admin only).",
"properties": {
"is_admin": {
"description": "New admin status.",
"type": "boolean"
}
},
"required": [
"is_admin"
],
"type": "object"
},
"UpdateScheduleRequest": {
"description": "Update schedule request body. All fields optional.",
"properties": {
"cron_expression": {
"description": "New cron expression.",
"nullable": true,
"type": "string"
},
"inputs": {
"description": "New inputs payload."
}
},
"type": "object"
},
"UpdateSecretRequest": {
"description": "Request body for updating a secret value.",
"properties": {
"value": {
"description": "New secret value (plaintext, will be encrypted at rest).",
"type": "string"
}
},
"required": [
"value"
],
"type": "object"
},
"UpdateUserGroupsRequest": {
"description": "Request body for replacing a user's group memberships (admin only).\n\nGroup names are trimmed and deduplicated. Each must be 1 to 64 characters\nfrom `[A-Za-z0-9_.-]`, with at most 50 groups. An empty list removes the\nuser from every group.",
"properties": {
"groups": {
"description": "The complete new set of groups.",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"groups"
],
"type": "object"
},
"UserGroupsResponse": {
"description": "Response DTO for a user's group memberships.",
"properties": {
"groups": {
"description": "Groups the user belongs to, sorted by name.",
"items": {
"type": "string"
},
"type": "array"
},
"user_id": {
"description": "User ID.",
"format": "uuid",
"type": "string"
}
},
"required": [
"user_id",
"groups"
],
"type": "object"
},
"UserResponse": {
"description": "Response DTO for a user (never exposes password hash).",
"properties": {
"created_at": {
"description": "Creation timestamp.",
"format": "date-time",
"type": "string"
},
"email": {
"description": "Email address.",
"type": "string"
},
"id": {
"description": "User ID.",
"format": "uuid",
"type": "string"
},
"is_admin": {
"description": "Whether the user is an admin.",
"type": "boolean"
},
"updated_at": {
"description": "Last update timestamp.",
"format": "date-time",
"type": "string"
},
"username": {
"description": "Display username.",
"type": "string"
}
},
"required": [
"id",
"email",
"username",
"is_admin",
"created_at",
"updated_at"
],
"type": "object"
},
"UserSignedInEvent": {
"description": "Payload of the `Event::UserSignedIn` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::UserSignedInEvent;\nuse uuid::Uuid;\n\nlet payload = UserSignedInEvent {\n user_id: Uuid::now_v7(),\n username: \"alice\".to_string(),\n at: Utc::now(),\n};\nassert_eq!(payload.username, \"alice\");\n```",
"properties": {
"at": {
"description": "When the sign-in occurred.",
"format": "date-time",
"type": "string"
},
"user_id": {
"description": "User identifier.",
"format": "uuid",
"type": "string"
},
"username": {
"description": "Username.",
"type": "string"
}
},
"required": [
"user_id",
"username",
"at"
],
"type": "object"
},
"UserSignedOutEvent": {
"description": "Payload of the `Event::UserSignedOut` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::UserSignedOutEvent;\nuse uuid::Uuid;\n\nlet user_id = Uuid::now_v7();\nlet payload = UserSignedOutEvent {\n user_id,\n at: Utc::now(),\n};\nassert_eq!(payload.user_id, user_id);\n```",
"properties": {
"at": {
"description": "When the sign-out occurred.",
"format": "date-time",
"type": "string"
},
"user_id": {
"description": "User identifier.",
"format": "uuid",
"type": "string"
}
},
"required": [
"user_id",
"at"
],
"type": "object"
},
"UserSignedUpEvent": {
"description": "Payload of the `Event::UserSignedUp` event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::UserSignedUpEvent;\nuse uuid::Uuid;\n\nlet payload = UserSignedUpEvent {\n user_id: Uuid::now_v7(),\n username: \"alice\".to_string(),\n at: Utc::now(),\n};\nassert_eq!(payload.username, \"alice\");\n```",
"properties": {
"at": {
"description": "When the sign-up occurred.",
"format": "date-time",
"type": "string"
},
"user_id": {
"description": "User identifier.",
"format": "uuid",
"type": "string"
},
"username": {
"description": "Username.",
"type": "string"
}
},
"required": [
"user_id",
"username",
"at"
],
"type": "object"
},
"WorkflowAgentStepTokensUsedEvent": {
"description": "Payload of the `WorkflowEvent::AgentStepTokensUsed` workflow event.\n\n# Examples\n\n```\nuse ironflow_engine::notify::WorkflowAgentStepTokensUsedEvent;\nuse rust_decimal::Decimal;\n\nlet payload = WorkflowAgentStepTokensUsedEvent {\n step_name: \"review\".to_string(),\n tokens: 15_000,\n cost_usd: Decimal::new(42, 4),\n};\nassert_eq!(payload.tokens, 15_000);\n```",
"properties": {
"cost_usd": {
"description": "Estimated cost in USD.",
"format": "double",
"type": "number"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
},
"tokens": {
"description": "Total tokens consumed.",
"format": "int64",
"minimum": 0,
"type": "integer"
}
},
"required": [
"step_name",
"tokens",
"cost_usd"
],
"type": "object"
},
"WorkflowApprovalRequiredEvent": {
"description": "Payload of the `WorkflowEvent::ApprovalRequired` workflow event.\n\n# Examples\n\n```\nuse ironflow_engine::notify::WorkflowApprovalRequiredEvent;\nuse uuid::Uuid;\n\nlet payload = WorkflowApprovalRequiredEvent {\n step_name: \"prod-gate\".to_string(),\n step_index: 3,\n approval_id: Uuid::now_v7(),\n};\nassert_eq!(payload.step_name, \"prod-gate\");\n```",
"properties": {
"approval_id": {
"description": "Identifier of the approval gate.",
"format": "uuid",
"type": "string"
},
"step_index": {
"description": "Zero-based position in the workflow.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
}
},
"required": [
"step_name",
"step_index",
"approval_id"
],
"type": "object"
},
"WorkflowDetailResponse": {
"description": "Workflow detail response.",
"properties": {
"category": {
"description": "Optional `/`-separated category path used to group workflows.",
"nullable": true,
"type": "string"
},
"compatible_versions": {
"description": "Versions accepted for replay without `force`.",
"items": {
"type": "string"
},
"type": "array"
},
"default_labels": {
"additionalProperties": {
"type": "string"
},
"description": "Labels automatically applied to every run of this workflow.",
"propertyNames": {
"type": "string"
},
"type": "object"
},
"default_max_cost_usd": {
"description": "Default cumulative cost cap applied to runs of this workflow, in USD.\n\nOverridden by a cap supplied at run creation. `None` means the workflow\ndeclares no default and falls back to the server default (if any).",
"format": "double",
"nullable": true,
"type": "number"
},
"description": {
"description": "Human-readable description.",
"type": "string"
},
"input_schema": {
"description": "JSON Schema describing the expected input payload."
},
"name": {
"description": "Workflow name.",
"type": "string"
},
"schedule": {
"description": "Optional 6-field cron expression for automatic execution.",
"nullable": true,
"type": "string"
},
"source_code": {
"description": "Optional Rust source code of the handler.",
"nullable": true,
"type": "string"
},
"sub_workflows": {
"description": "Sub-workflows invoked by this handler (recursive, depth-limited).",
"items": {
"$ref": "#/components/schemas/SubWorkflowDetail"
},
"type": "array"
},
"version": {
"description": "Current handler version.",
"nullable": true,
"type": "string"
}
},
"required": [
"name",
"description",
"sub_workflows"
],
"type": "object"
},
"WorkflowEvent": {
"description": "A granular step-level event for real-time workflow monitoring.\n\nUnlike [`Event`](super::Event) which covers the full system lifecycle\n(runs, auth, audit), `WorkflowEvent` tracks individual step transitions\nwithin a single run. Serialized with a `type` discriminant for UI\nconsumption.\n\nEach variant wraps a dedicated payload struct; the serialized form stays\nflat, with `type` sitting next to the payload fields.\n\n# Examples\n\n```\nuse ironflow_engine::notify::{WorkflowEvent, WorkflowStepStartedEvent};\nuse chrono::Utc;\n\nlet event = WorkflowEvent::StepStarted(WorkflowStepStartedEvent {\n step_name: \"deploy\".to_string(),\n step_index: 0,\n timestamp: Utc::now(),\n});\nassert_eq!(event.event_type(), \"step_started\");\n\nlet json = serde_json::to_string(&event)?;\nassert!(json.contains(\"\\\"type\\\":\\\"step_started\\\"\"));\n# Ok::<(), serde_json::Error>(())\n```",
"oneOf": [
{
"allOf": [
{
"$ref": "#/components/schemas/WorkflowStepStartedEvent",
"description": "A step began execution."
},
{
"properties": {
"type": {
"enum": [
"step_started"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A step began execution."
},
{
"allOf": [
{
"$ref": "#/components/schemas/WorkflowStepCompletedEvent",
"description": "A step completed successfully."
},
{
"properties": {
"type": {
"enum": [
"step_completed"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A step completed successfully."
},
{
"allOf": [
{
"$ref": "#/components/schemas/WorkflowStepFailedEvent",
"description": "A step failed."
},
{
"properties": {
"type": {
"enum": [
"step_failed"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A step failed."
},
{
"allOf": [
{
"$ref": "#/components/schemas/WorkflowApprovalRequiredEvent",
"description": "A step requires human approval before the run can continue."
},
{
"properties": {
"type": {
"enum": [
"approval_required"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A step requires human approval before the run can continue."
},
{
"allOf": [
{
"$ref": "#/components/schemas/WorkflowInputRequiredEvent",
"description": "A step waits for a typed human input before the run can continue."
},
{
"properties": {
"type": {
"enum": [
"input_required"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A step waits for a typed human input before the run can continue."
},
{
"allOf": [
{
"$ref": "#/components/schemas/WorkflowAgentStepTokensUsedEvent",
"description": "Token usage report for an agent step."
},
{
"properties": {
"type": {
"enum": [
"agent_step_tokens_used"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "Token usage report for an agent step."
}
]
},
"WorkflowInputRequiredEvent": {
"description": "Payload of the `WorkflowEvent::InputRequired` workflow event.\n\n# Examples\n\n```\nuse ironflow_engine::notify::WorkflowInputRequiredEvent;\nuse serde_json::json;\nuse uuid::Uuid;\n\nlet payload = WorkflowInputRequiredEvent {\n run_id: Uuid::now_v7(),\n step_id: Uuid::now_v7(),\n step_name: \"clarify\".to_string(),\n step_index: 2,\n message: \"Answer the questions\".to_string(),\n schema: json!({\"type\": \"object\"}),\n};\nassert_eq!(payload.step_name, \"clarify\");\n```",
"properties": {
"message": {
"description": "Message displayed to the person answering.",
"type": "string"
},
"run_id": {
"description": "The run waiting for the input.",
"format": "uuid",
"type": "string"
},
"schema": {
"description": "JSON schema the answer must match.",
"type": "object"
},
"step_id": {
"description": "Identifier of the human input step; the answer is posted to it.",
"format": "uuid",
"type": "string"
},
"step_index": {
"description": "Zero-based position in the workflow.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
}
},
"required": [
"run_id",
"step_id",
"step_name",
"step_index",
"message",
"schema"
],
"type": "object"
},
"WorkflowStepCompletedEvent": {
"description": "Payload of the `WorkflowEvent::StepCompleted` workflow event.\n\n# Examples\n\n```\nuse ironflow_engine::notify::WorkflowStepCompletedEvent;\n\nlet payload = WorkflowStepCompletedEvent {\n step_name: \"deploy\".to_string(),\n step_index: 1,\n duration_ms: 5000,\n output_summary: Some(\"deployed v1.2.3\".to_string()),\n};\nassert_eq!(payload.duration_ms, 5000);\n```",
"properties": {
"duration_ms": {
"description": "Step duration in milliseconds.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"output_summary": {
"description": "Optional summary of the step output.",
"nullable": true,
"type": "string"
},
"step_index": {
"description": "Zero-based position in the workflow.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
}
},
"required": [
"step_name",
"step_index",
"duration_ms"
],
"type": "object"
},
"WorkflowStepFailedEvent": {
"description": "Payload of the `WorkflowEvent::StepFailed` workflow event.\n\n# Examples\n\n```\nuse ironflow_engine::notify::WorkflowStepFailedEvent;\n\nlet payload = WorkflowStepFailedEvent {\n step_name: \"test\".to_string(),\n step_index: 2,\n error: \"exit code 1\".to_string(),\n duration_ms: 3000,\n};\nassert_eq!(payload.error, \"exit code 1\");\n```",
"properties": {
"duration_ms": {
"description": "Step duration in milliseconds.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"error": {
"description": "Error description.",
"type": "string"
},
"step_index": {
"description": "Zero-based position in the workflow.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
}
},
"required": [
"step_name",
"step_index",
"error",
"duration_ms"
],
"type": "object"
},
"WorkflowStepStartedEvent": {
"description": "Payload of the `WorkflowEvent::StepStarted` workflow event.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::WorkflowStepStartedEvent;\n\nlet payload = WorkflowStepStartedEvent {\n step_name: \"build\".to_string(),\n step_index: 0,\n timestamp: Utc::now(),\n};\nassert_eq!(payload.step_index, 0);\n```",
"properties": {
"step_index": {
"description": "Zero-based position in the workflow.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"step_name": {
"description": "Human-readable step name.",
"type": "string"
},
"timestamp": {
"description": "When the step started.",
"format": "date-time",
"type": "string"
}
},
"required": [
"step_name",
"step_index",
"timestamp"
],
"type": "object"
},
"WorkflowSummary": {
"description": "Summary entry returned by `GET /api/v1/workflows`.",
"properties": {
"category": {
"description": "Optional `/`-separated category path.",
"nullable": true,
"type": "string"
},
"name": {
"description": "Workflow name (unique identifier).",
"type": "string"
},
"schedule": {
"description": "Optional 6-field cron expression for automatic execution.",
"nullable": true,
"type": "string"
},
"version": {
"description": "Current handler version.",
"nullable": true,
"type": "string"
}
},
"required": [
"name"
],
"type": "object"
}
},
"securitySchemes": {
"Bearer": {
"bearerFormat": "JWT",
"scheme": "bearer",
"type": "http"
}
}
},
"info": {
"contact": {
"name": "Thomas Tartrau"
},
"description": "REST API for the ironflow workflow engine",
"license": {
"identifier": "MIT",
"name": "MIT"
},
"title": "Ironflow REST API",
"version": "1.0.0"
},
"openapi": "3.0.3",
"paths": {
"/api/v1/api-keys": {
"get": {
"operationId": "list_api_keys",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/ApiKeyResponse"
},
"type": "array"
}
}
},
"description": "List of API keys"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List all API keys for the authenticated user.",
"tags": [
"api-keys"
]
},
"post": {
"description": "# Errors\n\n- 400 if the name is empty or scopes are invalid",
"operationId": "create_api_key",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateApiKeyRequest"
}
}
},
"description": "API key configuration",
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateApiKeyResponse"
}
}
},
"description": "API key created successfully"
},
"400": {
"description": "Invalid input"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden (member trying to assign forbidden scopes)"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Create a new API key for the authenticated user.",
"tags": [
"api-keys"
]
}
},
"/api/v1/api-keys/scopes": {
"get": {
"description": "Admins get all scopes, members only get read-only scopes.",
"operationId": "available_scopes",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/ScopeEntry"
},
"type": "array"
}
}
},
"description": "List of available scopes"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Return the list of scopes the current user is allowed to assign to API keys.",
"tags": [
"api-keys"
]
}
},
"/api/v1/api-keys/{id}": {
"delete": {
"description": "# Errors\n\n- 404 if the key does not exist or belongs to another user",
"operationId": "delete_api_key",
"parameters": [
{
"description": "API key ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"204": {
"description": "API key deleted successfully"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "API key not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Delete an API key owned by the authenticated user.",
"tags": [
"api-keys"
]
}
},
"/api/v1/approval-delegations": {
"get": {
"description": "An admin sees every active delegation and may narrow the result with the\nquery parameters. A non-admin always sees exactly the delegations they\ngranted plus the ones they received, and the user filters are ignored --\nthey must not become a way to enumerate other people's delegations.\n\nExpired and not-yet-started delegations are never returned: the store filters\nthem out at read time.\n\n# Errors\n\n- 401 if not authenticated",
"operationId": "list_approval_delegations",
"parameters": [
{
"description": "Only delegations granted by this user.",
"in": "query",
"name": "from_user_id",
"required": false,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Only delegations received by this user.",
"in": "query",
"name": "to_user_id",
"required": false,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Page number (1-based, defaults to 1).",
"in": "query",
"name": "page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
{
"description": "Items per page (defaults to 20, max 100).",
"in": "query",
"name": "per_page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/ApprovalDelegationResponse"
},
"type": "array"
}
}
},
"description": "Paginated list of active delegations"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List the active approval delegations visible to the caller, paginated.",
"tags": [
"approval-delegations"
]
},
"post": {
"description": "The delegator is always the caller: a user hands over their own approval\npower, never someone else's. Any authenticated user may do so.\n\n# Errors\n\n- 400 if the target is the caller, the window is inverted, the workflow\n filter is not a valid glob, or the target user does not exist\n- 401 if not authenticated",
"operationId": "create_approval_delegation",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateApprovalDelegationRequest"
}
}
},
"description": "Delegation definition",
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApprovalDelegationResponse"
}
}
},
"description": "Delegation created"
},
"400": {
"description": "Invalid input"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Create an approval delegation.",
"tags": [
"approval-delegations"
]
}
},
"/api/v1/approval-delegations/{id}": {
"delete": {
"description": "Only the delegator who granted it, or an admin, may revoke a delegation.\nExpired rows are still revocable: they are readable by ID even though they\nno longer appear in the list.\n\n# Errors\n\n- 401 if not authenticated\n- 403 if the caller is neither the delegator nor an admin\n- 404 if the delegation does not exist",
"operationId": "delete_approval_delegation",
"parameters": [
{
"description": "Delegation ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Delegation revoked"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Delegation not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Revoke an approval delegation.",
"tags": [
"approval-delegations"
]
}
},
"/api/v1/audit-logs": {
"get": {
"description": "Admin-only. Returns a paginated list of persisted domain events\nfor compliance review and post-mortem debugging.",
"operationId": "list_audit_logs",
"parameters": [
{
"description": "Filter by event type (e.g. `run_status_changed`).",
"in": "query",
"name": "event_type",
"required": false,
"schema": {
"$ref": "#/components/schemas/EventKind"
}
},
{
"description": "Filter by run ID.",
"in": "query",
"name": "run_id",
"required": false,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Filter entries created at or after this timestamp.",
"in": "query",
"name": "from",
"required": false,
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"description": "Filter entries created at or before this timestamp.",
"in": "query",
"name": "to",
"required": false,
"schema": {
"format": "date-time",
"type": "string"
}
},
{
"description": "Page number (1-based, default: 1).",
"in": "query",
"name": "page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
{
"description": "Items per page (default: 50, max: 100).",
"in": "query",
"name": "per_page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/AuditLogEntry"
},
"type": "array"
}
}
},
"description": "List of audit log entries with pagination"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden - admin only"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List audit log entries with optional filtering and pagination.",
"tags": [
"audit"
]
}
},
"/api/v1/auth/me": {
"get": {
"description": "# Errors\n\n- 401 if no valid token is provided\n- 404 if the user no longer exists in the store",
"operationId": "me",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MeResponse"
}
}
},
"description": "Current user profile"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Return the current user's profile.",
"tags": [
"auth"
]
}
},
"/api/v1/auth/password": {
"patch": {
"description": "Verifies the old password, hashes the new one, and persists it.\n\n# Errors\n\n- 400 if old password is incorrect, or `WEAK_PASSWORD` if the new one\n breaks the password policy\n- 401 if no valid token is provided\n- 404 if the user no longer exists in the store",
"operationId": "change_password",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChangePasswordRequest"
}
}
},
"description": "Old and new passwords",
"required": true
},
"responses": {
"200": {
"description": "Password changed successfully"
},
"400": {
"description": "Invalid old password, or new password breaks the strength policy (WEAK_PASSWORD)"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Change the authenticated user's password.",
"tags": [
"auth"
]
}
},
"/api/v1/auth/refresh": {
"post": {
"description": "# Errors\n\n- 401 if the refresh token is missing, invalid, or expired",
"operationId": "refresh",
"responses": {
"204": {
"description": "Access token refreshed successfully, new cookies set"
},
"401": {
"description": "Invalid or expired refresh token"
}
},
"summary": "Refresh the access token using a valid refresh token from cookies.",
"tags": [
"auth"
]
}
},
"/api/v1/auth/sign-in": {
"post": {
"description": "Returns access and refresh tokens on success, and sets HttpOnly cookies.\n\n# Errors\n\n- 401 if email not found or password is wrong",
"operationId": "sign_in",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignInRequest"
}
}
},
"description": "Sign in credentials",
"required": true
},
"responses": {
"204": {
"description": "User authenticated successfully, cookies set"
},
"401": {
"description": "Invalid email or password"
}
},
"summary": "Authenticate a user with email and password.",
"tags": [
"auth"
]
}
},
"/api/v1/auth/sign-out": {
"post": {
"operationId": "sign_out",
"responses": {
"200": {
"description": "User signed out successfully, cookies cleared"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Sign out the current user by clearing auth cookies.",
"tags": [
"auth"
]
}
},
"/api/v1/auth/sign-up": {
"post": {
"description": "The answer is the same `204` whether the email was free or already\nregistered, and it never carries a session: the client signs in next\nwith the same credentials. Without that, the status code or the presence\nof session cookies would tell anyone which emails have an account.\n\nA taken username is still reported (`409`). It is checked before the\nemail, so that answer says nothing about the email.\n\n# Errors\n\n- 400 if the email or username is invalid\n- 400 `WEAK_PASSWORD` if the password breaks the password policy\n- 409 if the username is already taken",
"operationId": "sign_up",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignUpRequest"
}
}
},
"description": "Sign up credentials",
"required": true
},
"responses": {
"204": {
"description": "Request accepted. Same answer whether the email was free or already registered; no session is issued, sign in next"
},
"400": {
"description": "Invalid email or username, or password breaks the strength policy (WEAK_PASSWORD)"
},
"409": {
"description": "Username already taken"
}
},
"summary": "Register a new user with email and password.",
"tags": [
"auth"
]
}
},
"/api/v1/health-check": {
"get": {
"operationId": "health_check",
"responses": {
"200": {
"description": "Service is healthy"
}
},
"summary": "Health check handler. Always returns 200 OK.",
"tags": [
"health"
]
}
},
"/api/v1/provider-accounts": {
"get": {
"description": "The response never includes the credential.",
"operationId": "list_provider_accounts",
"parameters": [
{
"description": "Only accounts of this kind.",
"in": "query",
"name": "kind",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Page number (1-based).",
"in": "query",
"name": "page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
{
"description": "Items per page (max 100).",
"in": "query",
"name": "per_page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/ProviderAccountResponse"
},
"type": "array"
}
}
},
"description": "Provider accounts listed"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List Provider Accounts with their latest windows. Admin only.",
"tags": [
"provider-accounts"
]
},
"post": {
"description": "The token is checked against the provider before anything is stored: a\nmalformed or rejected token gets 422 and leaves nothing behind. A token\nanswered with 429 is stored and shown as limited until its window resets.\nThe response never includes the token.",
"operationId": "create_provider_account",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateProviderAccountRequest"
}
}
},
"description": "Account and its credential",
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderAccountResponse"
}
}
},
"description": "Provider account created"
},
"400": {
"description": "Invalid input or unknown kind"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"409": {
"description": "Name already taken"
},
"422": {
"description": "Credential rejected"
},
"502": {
"description": "Provider unreachable"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Add a Provider Account. Admin only.",
"tags": [
"provider-accounts"
]
}
},
"/api/v1/provider-accounts/kinds": {
"get": {
"operationId": "list_account_kinds",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/AccountKindResponse"
},
"type": "array"
}
}
},
"description": "Supported kinds"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List the account kinds this server supports, with their form fields. Admin only.",
"tags": [
"provider-accounts"
]
}
},
"/api/v1/provider-accounts/{id}": {
"delete": {
"description": "Its windows and usage history go with it; the steps that ran under it\nkeep their history but lose the link to the account.",
"operationId": "delete_provider_account",
"parameters": [
{
"description": "Account UUID or name",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Provider account deleted"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Provider account not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Delete a Provider Account and its credential. Admin only.",
"tags": [
"provider-accounts"
]
},
"get": {
"operationId": "get_provider_account",
"parameters": [
{
"description": "Account UUID or name",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderAccountResponse"
}
}
},
"description": "Provider account"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Provider account not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Get a Provider Account by UUID or name. Admin only.",
"tags": [
"provider-accounts"
]
},
"patch": {
"description": "`name` and `kind` are immutable. A new `token` is checked against the\nprovider before it replaces the stored one. The response never includes\nthe token.",
"operationId": "update_provider_account",
"parameters": [
{
"description": "Account UUID or name",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateProviderAccountRequest"
}
}
},
"description": "Fields to change",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderAccountResponse"
}
}
},
"description": "Provider account updated"
},
"400": {
"description": "Invalid input"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Provider account not found"
},
"422": {
"description": "Credential rejected"
},
"502": {
"description": "Provider unreachable"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Update a Provider Account. Admin only.",
"tags": [
"provider-accounts"
]
}
},
"/api/v1/provider-accounts/{id}/test": {
"post": {
"description": "`result` is `valid`, `limited` or `unauthorized`; an `unauthorized`\naccount is marked as having an invalid token until it is replaced.",
"operationId": "test_provider_account",
"parameters": [
{
"description": "Account UUID or name",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderAccountTestResponse"
}
}
},
"description": "Credential checked"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Provider account not found"
},
"422": {
"description": "No credential stored"
},
"502": {
"description": "Provider unreachable"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Check the stored credential against the provider and record the windows\nit reports. Admin only.",
"tags": [
"provider-accounts"
]
}
},
"/api/v1/provider-accounts/{id}/usage": {
"get": {
"operationId": "provider_account_usage",
"parameters": [
{
"description": "Account UUID or name",
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
},
{
"description": "History depth in days, 1 to 90 (default 30).",
"in": "query",
"name": "days",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProviderAccountUsageResponse"
}
}
},
"description": "Usage of the account"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Provider account not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Current windows of a Provider Account and their history over `days`\n(default 30, at most 90). Admin only.",
"tags": [
"provider-accounts"
]
}
},
"/api/v1/runs": {
"get": {
"description": "# Query Parameters\n\n- `workflow` — Filter by workflow name (optional)\n- `status` — Filter by run status (optional)\n- `created_by` — Filter by author user ID (optional). Also matches runs\n triggered by one of that user's API keys.\n- `concurrency_group` - Filter by concurrency group (optional)\n- `page` — Page number, 1-based (default: 1)\n- `per_page` — Items per page (default: 20, max: 100)",
"operationId": "list_runs",
"parameters": [
{
"description": "Filter by workflow name.",
"in": "query",
"name": "workflow",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Filter by run status.",
"in": "query",
"name": "status",
"required": false,
"schema": {
"$ref": "#/components/schemas/RunStatus"
}
},
{
"description": "Filter by step presence (only applies to completed/cancelled runs).\nNon-terminal runs (pending, running, etc.) are always included.\nWhen `true`, only return completed/cancelled runs that have steps.\nWhen `false`, only return completed/cancelled runs without steps.",
"in": "query",
"name": "has_steps",
"required": false,
"schema": {
"type": "boolean"
}
},
{
"description": "Filter by labels. Comma-separated `key:value` pairs.",
"in": "query",
"name": "label",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Filter by author: the user ID that triggered the run.\n\nAlso matches runs triggered by one of that user's API keys.",
"in": "query",
"name": "created_by",
"required": false,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Filter by concurrency group: only runs that belong to this group.",
"in": "query",
"name": "concurrency_group",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Page number (1-based).",
"in": "query",
"name": "page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
{
"description": "Items per page.",
"in": "query",
"name": "per_page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/RunResponse"
},
"type": "array"
}
}
},
"description": "List of runs with pagination"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List runs with optional filtering and pagination.",
"tags": [
"runs"
]
},
"post": {
"description": "Returns 201 Created with the newly enqueued run.\n\nAn optional `Idempotency-Key` header makes the call safe to replay: the same\nkey returns the run it already produced with 200 OK instead of enqueueing a\nsecond one. A key reused with a different workflow or payload is rejected with\n409 Conflict. Keys stay bound for 24 hours, after which they are released.\n\nAn optional `concurrency_key` in the body makes the run exclusive: while a\nnon-terminal run holds the same key, the call is refused with 409\n`CONCURRENCY_CONFLICT` naming that run.\n\nOptional `concurrency_limits` in the body put the run in concurrency groups:\nit is created at once but a worker only starts it while, for each group,\nfewer root runs of that group than its limit are running.\n\n# Errors\n\nReturns [`ApiError::Forbidden`] for non-admin callers.\nReturns [`ApiError::BadRequest`] if the workflow is unknown, the body is\ninvalid (including malformed `concurrency_limits`), or the `Idempotency-Key`\nheader is malformed.\nReturns [`ApiError::IdempotencyKeyConflict`] if the key is bound to a\ndifferent request.\nReturns [`ApiError::ConcurrencyConflict`] if a non-terminal run already\nholds the requested `concurrency_key`.\nReturns [`ApiError::MonthlyBudgetExceeded`] if the global monthly cost quota\nis exhausted.",
"operationId": "create_run",
"parameters": [
{
"description": "Optional key making the call safe to replay. At most 255 printable ASCII characters, valid for 24 hours.",
"in": "header",
"name": "Idempotency-Key",
"required": false,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateRunRequest"
}
}
},
"description": "Workflow to trigger",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunResponse"
}
}
},
"description": "Idempotency key replayed: the existing run is returned"
},
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunResponse"
}
}
},
"description": "Run created successfully"
},
"400": {
"description": "Unknown workflow, invalid body (including malformed concurrency_limits) or malformed Idempotency-Key"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"409": {
"description": "Idempotency key already used with a different request (IDEMPOTENCY_KEY_CONFLICT), or concurrency key held by an active run (CONCURRENCY_CONFLICT)"
},
"429": {
"description": "Monthly cost quota exhausted"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Trigger a workflow by name.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}": {
"get": {
"description": "Returns 404 if the run does not exist.",
"operationId": "get_run",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunDetailResponse"
}
}
},
"description": "Run details with steps"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Run not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Get a run by ID, including all its steps and dependency edges.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/approve": {
"post": {
"description": "Records the caller's vote on the open gate. Votes are counted per user: an\nAPI key votes as its owner, and an admin's vote counts as one vote like any\nother. Once the gate holds as many distinct approvals as its\n[`ApprovalRequirement`](ironflow_store::models::ApprovalRequirement)\nrequires (one for a gate opened without approvers), the run transitions from\n`AwaitingApproval` back to `Running` and resumes. Until then the response\nreturns the run still `AwaitingApproval`, and the gate keeps its SLA timer.\n\nReturns 400 if the run is not in `AwaitingApproval` state, 403 if the\ncaller may not vote on the gate, and 409 if the caller already approved it.",
"operationId": "approve_run",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunResponse"
}
}
},
"description": "Approval recorded. The run is `running` once enough distinct approvals were collected, and still `awaiting_approval` when more approvals are required"
},
"400": {
"description": "Run not awaiting approval"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Run not found"
},
"409": {
"description": "Caller already approved this gate"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Approve a run that is awaiting human approval.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/cancel": {
"post": {
"description": "The run and its active descendants move to `Cancelled`, their open steps\nare closed and their concurrency keys released (see\n[`Engine::cancel_run`](ironflow_engine::engine::Engine::cancel_run)). The\nresponse lists the descendants cancelled. Cancelling a child whose chain\nis suspended wakes its root, whose `Workflow` step then fails (or, with\n`allow_failure`, completes with the `Cancelled` child).\n\nCancelling a run already `Cancelled` succeeds and cancels only what was\nstill active below it. Returns 400 for a run that finished otherwise.",
"operationId": "cancel_run",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CancelRunResponse"
}
}
},
"description": "Run cancelled, with the sub-runs cancelled along"
},
"400": {
"description": "Run cannot be cancelled"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Run not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Cancel a run that has not finished, with every sub-workflow run below it.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/events": {
"get": {
"description": "Streams [`WorkflowEvent`]s for a specific workflow run in real time.\nSupports optional filtering via `?types=step_started,step_completed`.\n\nEach SSE message has:\n- `event:` set to the event type (e.g. `step_started`)\n- `data:` JSON-serialized event payload\n\nA keep-alive comment is sent every 30 seconds.\n\n# Errors\n\nReturns 401 if the request is not authenticated.\nReturns 404 if the run does not exist.",
"operationId": "run_events",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Comma-separated workflow event types to filter (e.g. step_started,step_completed)",
"in": "query",
"name": "types",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "SSE stream of workflow events"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Run not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "`GET /api/v1/runs/{id}/events` -- per-run Server-Sent Events stream.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/logs": {
"get": {
"description": "Returns log entries ordered by time (UUID v7 ascending). Use the\n`cursor` query parameter with the last entry's `id` to fetch the\nnext page.",
"operationId": "get_run_logs",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Filter by step ID.",
"in": "query",
"name": "step_id",
"required": false,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Filter by output stream (`stdout`, `stderr`, `system`).",
"in": "query",
"name": "stream",
"required": false,
"schema": {
"$ref": "#/components/schemas/LogStream"
}
},
{
"description": "Cursor for pagination (last entry ID from previous page).",
"in": "query",
"name": "cursor",
"required": false,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Number of entries to return (default: 100, max: 1000).",
"in": "query",
"name": "limit",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/LogEntry"
},
"type": "array"
}
}
},
"description": "Log entries with cursor-based pagination"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Run not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Retrieve persisted log lines for a run with cursor-based pagination.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/reject": {
"post": {
"description": "Transitions the run from `AwaitingApproval` to `Failed`. A single rejection\nfrom anyone allowed to vote on the gate vetoes it, even after partial\napprovals.\nReturns 400 if the run is not in `AwaitingApproval` state.",
"operationId": "reject_run",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunResponse"
}
}
},
"description": "Run rejected successfully"
},
"400": {
"description": "Run not awaiting approval"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Run not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Reject a run that is awaiting human approval.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/replay": {
"post": {
"description": "Creates a new `Pending` run with `TriggerKind::Replay` pointing to the\noriginal, always on the current handler version. The original run is not modified.",
"operationId": "replay_run",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunResponse"
}
}
},
"description": "Run replay created successfully"
},
"400": {
"description": "Run cannot be replayed"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Run not found or workflow not registered"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Replay a finished run on the current handler version.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/retry": {
"post": {
"description": "Creates a new `Pending` run with `TriggerKind::Retry` pointing to the\noriginal. Returns 400 if the run is not in a retryable state, 409 if an\nautomatic retry is already armed, 409 if the handler version has\nchanged since the original run (pass `?force=true` to override), and 409\n`CONCURRENCY_CONFLICT` if another active run took the original's\nconcurrency key since.",
"operationId": "retry_run",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Force retry despite handler version mismatch",
"in": "query",
"name": "force",
"required": false,
"schema": {
"type": "boolean"
}
}
],
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunResponse"
}
}
},
"description": "Run retry created successfully"
},
"400": {
"description": "Run cannot be retried"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Run not found"
},
"409": {
"description": "Version mismatch, automatic retry already armed, or concurrency key held by an active run (CONCURRENCY_CONFLICT)"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Retry a failed run.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/steps/{step_id}/artifacts/{name}": {
"get": {
"description": "Streams the stored bytes with the MIME type recorded at upload time. The\nartifact must belong to both the run and the step named in the path, so an\nid guessed from another run resolves to a 404 rather than a leak.\n\nRequires the same permission as reading the run itself.",
"operationId": "download_artifact",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Step ID",
"in": "path",
"name": "step_id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Artifact name",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/octet-stream": {}
},
"description": "Artifact content"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Run, step or artifact not found"
},
"501": {
"description": "Artifact storage is not configured"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Download one artifact produced by a step.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/steps/{step_id}/input": {
"post": {
"description": "The answer is validated against the schema stored on the step. The caller\nmust be allowed to resolve the step like an approval gate (admin, approver\ngroups, assignee or delegation). The first valid answer wins: the step\ncompletes, the run moves from `AwaitingApproval` back to `Running` and\nresumes, and the handler receives the typed answer.\n\n# Errors\n\n- 400 if the step is not a human input or is not awaiting input\n- 403 if the caller may not answer the step\n- 404 if the run or the step does not exist\n- 409 if the input was already answered or rejected\n- 422 if the answer does not match the schema",
"operationId": "submit_human_input",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Step ID",
"in": "path",
"name": "step_id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Answer matching the JSON schema stored on the step",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunResponse"
}
}
},
"description": "Answer recorded, the run resumes"
},
"400": {
"description": "Step is not a human input awaiting input"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Run or step not found"
},
"409": {
"description": "Input already answered or rejected"
},
"422": {
"description": "Answer does not match the schema"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Answer a human input step with a value matching its JSON schema.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/steps/{step_id}/reject": {
"post": {
"description": "The step is marked `Rejected` with the reason, the run moves from\n`AwaitingApproval` back to `Running` and resumes: the handler receives a\n`HumanInputRejected` error and decides what happens next. The body is\noptional; without a reason, one naming the caller is recorded.\n\n# Errors\n\n- 400 if the step is not a human input or is not awaiting input\n- 403 if the caller may not resolve the step\n- 404 if the run or the step does not exist\n- 409 if the input was already answered or rejected",
"operationId": "reject_human_input",
"parameters": [
{
"description": "Run ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Step ID",
"in": "path",
"name": "step_id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RejectHumanInputRequest"
}
}
},
"description": "Optional rejection reason",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RunResponse"
}
}
},
"description": "Input rejected, the run resumes"
},
"400": {
"description": "Step is not a human input awaiting input"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Run or step not found"
},
"409": {
"description": "Input already answered or rejected"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Reject a human input step.",
"tags": [
"runs"
]
}
},
"/api/v1/schedules": {
"get": {
"description": "# Errors\n\n- 401 if not authenticated",
"operationId": "list_schedules",
"parameters": [
{
"description": "Page number (1-based)",
"in": "query",
"name": "page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
{
"description": "Items per page",
"in": "query",
"name": "per_page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/ScheduleResponse"
},
"type": "array"
}
}
},
"description": "Paginated schedules"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List all schedules, paginated.",
"tags": [
"schedules"
]
},
"post": {
"description": "# Errors\n\n- 400 if validation fails or cron expression is invalid\n- 400 if the workflow is not registered\n- 401 if not authenticated",
"operationId": "create_schedule",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateScheduleRequest"
}
}
},
"description": "Schedule definition",
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleResponse"
}
}
},
"description": "Schedule created"
},
"400": {
"description": "Invalid input"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Create a new schedule.",
"tags": [
"schedules"
]
}
},
"/api/v1/schedules/{id}": {
"delete": {
"description": "Handler-declared schedules (`source = handler`) cannot be deleted via the\nAPI -- they are managed by the code and reconciled at startup.\n\n# Errors\n\n- 401 if not authenticated\n- 403 if the schedule is handler-declared\n- 404 if the schedule does not exist",
"operationId": "delete_schedule",
"parameters": [
{
"description": "Schedule ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Schedule deleted"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Cannot delete handler-declared schedule"
},
"404": {
"description": "Schedule not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Delete a schedule by ID.",
"tags": [
"schedules"
]
},
"get": {
"description": "# Errors\n\n- 401 if not authenticated\n- 404 if the schedule does not exist",
"operationId": "get_schedule",
"parameters": [
{
"description": "Schedule ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleResponse"
}
}
},
"description": "Schedule detail"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Schedule not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Get a schedule by ID.",
"tags": [
"schedules"
]
}
},
"/api/v1/schedules/{id}/pause": {
"post": {
"description": "# Errors\n\n- 401 if not authenticated\n- 404 if the schedule does not exist",
"operationId": "pause_schedule",
"parameters": [
{
"description": "Schedule ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleResponse"
}
}
},
"description": "Schedule paused"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Schedule not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Pause a schedule (set enabled = false).",
"tags": [
"schedules"
]
}
},
"/api/v1/schedules/{id}/resume": {
"post": {
"description": "The next trigger time is recomputed from the cron expression.\n\n# Errors\n\n- 401 if not authenticated\n- 404 if the schedule does not exist",
"operationId": "resume_schedule",
"parameters": [
{
"description": "Schedule ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleResponse"
}
}
},
"description": "Schedule resumed"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Schedule not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Resume a schedule (clear `disabled_at` and `last_error`).",
"tags": [
"schedules"
]
}
},
"/api/v1/schedules/{id}/trigger": {
"post": {
"description": "# Errors\n\n- 401 if not authenticated\n- 404 if the schedule does not exist",
"operationId": "trigger_schedule",
"parameters": [
{
"description": "Schedule ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScheduleResponse"
}
}
},
"description": "Run created from schedule"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Schedule not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Trigger a schedule manually, creating a run immediately.",
"tags": [
"schedules"
]
}
},
"/api/v1/secrets": {
"get": {
"description": "Returns key, id, and timestamps. Never returns encrypted or decrypted values.",
"operationId": "list_secrets",
"parameters": [
{
"description": "Filter by key prefix (e.g. `workflows/inbox/`).",
"in": "query",
"name": "prefix",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Page number (1-based, default 1).",
"in": "query",
"name": "page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
{
"description": "Items per page (default 50, max 100).",
"in": "query",
"name": "per_page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/SecretResponse"
},
"type": "array"
}
}
},
"description": "Secrets listed"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List secret metadata. Admin only.",
"tags": [
"secrets"
]
},
"post": {
"description": "If the key already exists, the value is replaced. The response\nnever includes the secret value.",
"operationId": "create_secret",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SetSecretRequest"
}
}
},
"description": "Secret key and value",
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SecretResponse"
}
}
},
"description": "Secret created or updated"
},
"400": {
"description": "Invalid input"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Create or update a secret. Admin only.",
"tags": [
"secrets"
]
}
},
"/api/v1/secrets/key-versions": {
"get": {
"description": "This is what tells an operator whether an old key can be dropped from\n`IRONFLOW_SECRET_KEYS` without breaking the next startup: a version listed\nin `retirable` is configured, not active, and used by no secret.\n\nA non-empty `missing` means some stored secrets cannot be decrypted with\nthe current configuration -- the server refuses to start in that state.\n\n# Errors\n\n- 401 if the caller is not authenticated\n- 403 if the caller is not an admin",
"operationId": "secret_key_versions",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/KeyVersionsResponse"
}
}
},
"description": "Key ring status"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Report how the configured key ring lines up with the stored secrets.",
"tags": [
"secrets"
]
}
},
"/api/v1/secrets/rotate": {
"post": {
"description": "One call processes one batch, so a rotation over a large stock never\nbecomes a single long request. The client loops, passing `last_id` back as\n`after_id`, until `remaining` reaches zero or `last_id` comes back null.\n\nThe operation is idempotent: secrets already on the target version are\nskipped, so an interrupted rotation can simply be restarted.\n\n# Errors\n\n- 400 if the target version is not in the configured key ring\n- 401 if the caller is not authenticated\n- 403 if the caller is not an admin",
"operationId": "rotate_secrets",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RotateSecretsRequest"
}
}
},
"description": "Target key version, batch size, and resume cursor",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RotateSecretsResponse"
}
}
},
"description": "Batch rotated"
},
"400": {
"description": "Invalid target key version"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Re-encrypt one batch of secrets towards a key version. Admin only.",
"tags": [
"secrets"
]
}
},
"/api/v1/secrets/{key}": {
"delete": {
"description": "# Errors\n\n- 403 if the caller is not an admin\n- 404 if the secret key does not exist",
"operationId": "delete_secret",
"parameters": [
{
"description": "Secret key",
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "Secret deleted"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Secret not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Delete a secret by key. Admin only.",
"tags": [
"secrets"
]
},
"put": {
"description": "The key is taken from the URL path. The response never includes\nthe secret value.\n\n# Errors\n\n- 403 if the caller is not an admin\n- 404 if the secret key does not exist\n- 400 if the value is invalid",
"operationId": "update_secret",
"parameters": [
{
"description": "Secret key",
"in": "path",
"name": "key",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateSecretRequest"
}
}
},
"description": "New secret value",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SecretResponse"
}
}
},
"description": "Secret updated"
},
"400": {
"description": "Invalid input"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Secret not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Update a secret's value. Admin only.",
"tags": [
"secrets"
]
}
},
"/api/v1/signals": {
"get": {
"description": "Any signed-in user may list signals. An API key needs the `runs_read`\nscope.\n\n# Errors\n\n- 401 if not authenticated\n- 403 if the API key lacks `runs_read`",
"operationId": "list_signals",
"parameters": [
{
"description": "Only signals with this exact name.",
"in": "query",
"name": "name",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Only signals with this exact key.",
"in": "query",
"name": "key",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Page number (1-based, defaults to 1).",
"in": "query",
"name": "page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
{
"description": "Items per page (defaults to 20, max 100).",
"in": "query",
"name": "per_page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/SignalResponse"
},
"type": "array"
}
}
},
"description": "Paginated list of signals"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Insufficient scope"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List received signals, newest first, paginated.",
"tags": [
"signals"
]
},
"post": {
"description": "The signal is stored, then every run waiting for its `(name, key)` with a\npayload schema it matches is resumed. A waiting run whose schema the\npayload does not match keeps waiting and is listed under `rejected`. The\nsignal is stored even when nobody waits for it yet, so a run that opens its\nwait later still finds it.\n\nSending again with the same `idempotency_id` returns 200 with\n`duplicate: true` and delivers nothing.\n\n# Errors\n\n- 400 if `name` or `key` is empty or longer than 255 characters, or if\n `idempotency_id` is empty or longer than 255 characters\n- 401 if not authenticated\n- 403 if the caller is not an admin, or the API key lacks `signals_send`",
"operationId": "send_signal",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SendSignalRequest"
}
}
},
"description": "Signal to deliver",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SignalDeliveryResponse"
}
}
},
"description": "Signal stored and delivered"
},
"400": {
"description": "Invalid signal"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden or insufficient scope"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Send a signal.",
"tags": [
"signals"
]
}
},
"/api/v1/stats": {
"get": {
"description": "Accepts the same filtering query parameters as `GET /api/v1/runs`\n(`workflow`, `status`, `has_steps`, `label`, `created_by`,\n`concurrency_group`). `page` and `per_page` are ignored.",
"operationId": "get_stats",
"parameters": [
{
"description": "Filter by workflow name.",
"in": "query",
"name": "workflow",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Filter by run status.",
"in": "query",
"name": "status",
"required": false,
"schema": {
"$ref": "#/components/schemas/RunStatus"
}
},
{
"description": "Filter by step presence (only applies to completed/cancelled runs).\nNon-terminal runs (pending, running, etc.) are always included.\nWhen `true`, only return completed/cancelled runs that have steps.\nWhen `false`, only return completed/cancelled runs without steps.",
"in": "query",
"name": "has_steps",
"required": false,
"schema": {
"type": "boolean"
}
},
{
"description": "Filter by labels. Comma-separated `key:value` pairs.",
"in": "query",
"name": "label",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Filter by author: the user ID that triggered the run.\n\nAlso matches runs triggered by one of that user's API keys.",
"in": "query",
"name": "created_by",
"required": false,
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"description": "Filter by concurrency group: only runs that belong to this group.",
"in": "query",
"name": "concurrency_group",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Page number (1-based).",
"in": "query",
"name": "page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
{
"description": "Items per page.",
"in": "query",
"name": "per_page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StatsResponse"
}
}
},
"description": "Aggregate statistics"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Get aggregate statistics across runs matching the filter.",
"tags": [
"stats"
]
}
},
"/api/v1/stats/history": {
"get": {
"description": "Returns run counts for every status, the success rate, duration metrics\nand cost per time bucket. Runs are bucketed by creation time and counted\nunder their current status. Buckets use UTC boundaries (weeks start on\nMonday) and cover the whole period: buckets without runs are zero-filled.\n\nAccepts `period` and `granularity`, plus the same filters as\n`GET /api/v1/stats` and `GET /api/v1/runs` (`workflow` as a\ncase-insensitive substring, `status`, `has_steps`, `label`, `created_by`).",
"operationId": "get_stats_history",
"parameters": [
{
"description": "Filter by workflow name (case-insensitive substring match, same as\n`GET /api/v1/runs`). Omit to aggregate all workflows.",
"in": "query",
"name": "workflow",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Time period to query. Defaults to `7d`.",
"in": "query",
"name": "period",
"required": false,
"schema": {
"$ref": "#/components/schemas/HistoryPeriod"
}
},
{
"description": "Bucket granularity. Auto-derived from period when omitted.",
"in": "query",
"name": "granularity",
"required": false,
"schema": {
"$ref": "#/components/schemas/HistoryGranularity"
}
},
{
"description": "Filter by run status.",
"in": "query",
"name": "status",
"required": false,
"schema": {
"$ref": "#/components/schemas/RunStatus"
}
},
{
"description": "Filter by step presence (only applies to completed/cancelled runs).\nNon-terminal runs (pending, running, etc.) are always included.\nWhen `true`, only count completed/cancelled runs that have steps.\nWhen `false`, only count completed/cancelled runs without steps.",
"in": "query",
"name": "has_steps",
"required": false,
"schema": {
"type": "boolean"
}
},
{
"description": "Filter by labels. Comma-separated `key:value` pairs.",
"in": "query",
"name": "label",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Filter by author: the user ID that triggered the run.\n\nAlso matches runs triggered by one of that user's API keys.",
"in": "query",
"name": "created_by",
"required": false,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StatsHistoryResponse"
}
}
},
"description": "Historical statistics"
},
"400": {
"description": "Invalid period, granularity or filter"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Get time-bucketed historical statistics for trend charts.",
"tags": [
"stats"
]
}
},
"/api/v1/users": {
"get": {
"description": "# Errors\n\n- 403 if the caller is not an admin",
"operationId": "list_users",
"parameters": [
{
"description": "Page number (1-based, defaults to 1).",
"in": "query",
"name": "page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
{
"description": "Items per page (defaults to 20, max 100).",
"in": "query",
"name": "per_page",
"required": false,
"schema": {
"format": "int32",
"minimum": 0,
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/UserResponse"
},
"type": "array"
}
}
},
"description": "Paginated list of users"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden (not an admin)"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List all users with pagination. Admin only.",
"tags": [
"users"
]
},
"post": {
"description": "# Errors\n\n- 403 if the caller is not an admin\n- 400 if input validation fails, `WEAK_PASSWORD` if the password breaks\n the password policy\n- 409 if email or username is already taken",
"operationId": "create_user",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateUserRequest"
}
}
},
"description": "User account details",
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserResponse"
}
}
},
"description": "User created successfully"
},
"400": {
"description": "Invalid input"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden (not an admin)"
},
"409": {
"description": "Email or username already taken"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Create a new user account. Admin only.",
"tags": [
"users"
]
}
},
"/api/v1/users/{id}": {
"delete": {
"description": "# Errors\n\n- 403 if the caller is not an admin\n- 400 if trying to delete self\n- 404 if the user does not exist",
"operationId": "delete_user",
"parameters": [
{
"description": "User ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"204": {
"description": "User deleted successfully"
},
"400": {
"description": "Cannot delete self"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden (not an admin)"
},
"404": {
"description": "User not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Delete a user account. Admin only.",
"tags": [
"users"
]
}
},
"/api/v1/users/{id}/groups": {
"get": {
"description": "# Errors\n\n- 403 if the caller is not an admin\n- 404 if the user does not exist",
"operationId": "get_user_groups",
"parameters": [
{
"description": "User ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserGroupsResponse"
}
}
},
"description": "User groups"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden (not an admin)"
},
"404": {
"description": "User not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List the groups a user belongs to. Admin only.",
"tags": [
"users"
]
},
"put": {
"description": "Names are trimmed, deduplicated and returned sorted. An empty list removes\nthe user from every group.\n\n# Errors\n\n- 400 if a group name is empty, longer than 64 characters, uses a\n character outside `[A-Za-z0-9_.-]`, or if more than 50 groups are given\n- 403 if the caller is not an admin\n- 404 if the user does not exist",
"operationId": "update_user_groups",
"parameters": [
{
"description": "User ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateUserGroupsRequest"
}
}
},
"description": "Complete new set of groups",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserGroupsResponse"
}
}
},
"description": "User groups replaced"
},
"400": {
"description": "Invalid group name or too many groups"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden (not an admin)"
},
"404": {
"description": "User not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Replace the groups a user belongs to. Admin only.",
"tags": [
"users"
]
}
},
"/api/v1/users/{id}/role": {
"patch": {
"description": "# Errors\n\n- 403 if the caller is not an admin\n- 400 if trying to change own role\n- 404 if the user does not exist",
"operationId": "update_role",
"parameters": [
{
"description": "User ID",
"in": "path",
"name": "id",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateRoleRequest"
}
}
},
"description": "New role assignment",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserResponse"
}
}
},
"description": "User role updated successfully"
},
"400": {
"description": "Cannot change own role"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden (not an admin)"
},
"404": {
"description": "User not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Update a user's admin role. Admin only.",
"tags": [
"users"
]
}
},
"/api/v1/workflows": {
"get": {
"description": "# Query Parameters\n\n- `name` — Case-insensitive partial match on workflow name (optional)\n- `category` — Case-insensitive partial match on category path, or\n [`UNCATEGORIZED_FILTER`] to filter only uncategorized workflows\n (optional)",
"operationId": "list_workflows",
"parameters": [
{
"description": "Optional case-insensitive partial match on workflow name.",
"in": "query",
"name": "name",
"required": false,
"schema": {
"type": "string"
}
},
{
"description": "Optional case-insensitive partial match on the category path\n(e.g. `etl` matches `Data/ETL` and `data/etl/nightly`).\n\nPass `__uncategorized__` to list only workflows without any\ncategory.",
"in": "query",
"name": "category",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/WorkflowSummary"
},
"type": "array"
}
}
},
"description": "List of workflow summaries"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List registered workflows, optionally filtered by name and category.",
"tags": [
"workflows"
]
}
},
"/api/v1/workflows/{name}": {
"get": {
"description": "# Errors\n\n- 404 if the workflow is not registered",
"operationId": "get_workflow",
"parameters": [
{
"description": "Workflow name",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WorkflowDetailResponse"
}
}
},
"description": "Workflow details"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Workflow not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Get details about a registered workflow.",
"tags": [
"workflows"
]
}
},
"/api/v1/workflows/{name}/plan": {
"post": {
"description": "# Errors\n\n- 400 if `max_depth` is out of range or the payload is not a JSON object\n- 404 if the workflow is not registered",
"operationId": "plan_workflow",
"parameters": [
{
"description": "Workflow name",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PlanWorkflowRequest"
}
}
},
"description": "Input payload and planning options",
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExecutionPlanResponse"
}
}
},
"description": "Execution plan"
},
"400": {
"description": "Invalid payload or max_depth"
},
"401": {
"description": "Unauthorized"
},
"404": {
"description": "Workflow not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Build a workflow execution plan without running it.",
"tags": [
"workflows"
]
}
}
},
"tags": [
{
"description": "Health check endpoints",
"name": "health"
},
{
"description": "Workflow run management",
"name": "runs"
},
{
"description": "Workflow definitions",
"name": "workflows"
},
{
"description": "Aggregated statistics",
"name": "stats"
},
{
"description": "Authentication and authorization",
"name": "auth"
},
{
"description": "API key management",
"name": "api-keys"
},
{
"description": "User management (admin only)",
"name": "users"
},
{
"description": "Encrypted secret management (admin only)",
"name": "secrets"
},
{
"description": "Provider Accounts: AI provider credentials and usage limits (admin only)",
"name": "provider-accounts"
},
{
"description": "Audit log (admin only)",
"name": "audit"
},
{
"description": "Run/step log persistence and retrieval",
"name": "logs"
},
{
"description": "Schedule management",
"name": "schedules"
},
{
"description": "Approval delegation for absent approvers",
"name": "approval-delegations"
},
{
"description": "Signals: external messages that resume waiting runs",
"name": "signals"
}
]
}