{
"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"
},
"AgentRoleStatsEntryResponse": {
"description": "Cost, executions and failures of one agent role.\n\n# Examples\n\n```\nuse ironflow_api::entities::AgentRoleStatsEntryResponse;\nuse ironflow_store::entities::AgentRoleStats;\nuse rust_decimal::Decimal;\n\nlet entry = AgentRoleStatsEntryResponse::from(AgentRoleStats {\n agent_role: \"Reviewer\".to_string(),\n executions: 3,\n failed: 1,\n total_cost_usd: Decimal::new(42, 2),\n total_duration_ms: 1200,\n});\nassert_eq!(entry.agent_role, \"Reviewer\");\n```",
"properties": {
"agent_role": {
"description": "Name of the role (`AgentRole::name`).",
"type": "string"
},
"executions": {
"description": "Number of agent steps the role ran, each attempt included.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"failed": {
"description": "Steps of the role that failed.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"total_cost_usd": {
"description": "Total cost in USD of the steps of the role.",
"format": "double",
"type": "number"
},
"total_duration_ms": {
"description": "Total execution time in milliseconds of the steps of the role.",
"format": "int64",
"minimum": 0,
"type": "integer"
}
},
"required": [
"agent_role",
"executions",
"failed",
"total_cost_usd",
"total_duration_ms"
],
"type": "object"
},
"AgentRoleStatsResponse": {
"description": "Cost, executions and failures per agent role over a period, all workflows\ncombined.\n\n# Examples\n\n```\nuse ironflow_api::entities::AgentRoleStatsResponse;\nuse ironflow_store::entities::HistoryPeriod;\n\nlet response = AgentRoleStatsResponse {\n period: HistoryPeriod::SevenDays,\n roles: Vec::new(),\n};\nassert!(response.roles.is_empty());\n```",
"properties": {
"period": {
"$ref": "#/components/schemas/HistoryPeriod",
"description": "The period that was queried."
},
"roles": {
"description": "One entry per role that ran an agent step in the period, sorted by\ntotal cost descending, then by role name.",
"items": {
"$ref": "#/components/schemas/AgentRoleStatsEntryResponse"
},
"type": "array"
}
},
"required": [
"period",
"roles"
],
"type": "object"
},
"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 user_id: Some(Uuid::now_v7()),\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"
},
"user_id": {
"description": "ID of the user who approved. `None` for a system decision (timeout\nescalation) and in events recorded before it existed. `approved_by`\nstays the display label.",
"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 user_id: Some(Uuid::now_v7()),\n reason: None,\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"
},
"reason": {
"description": "Why the decision was refused, when the rejection carries one (human\ninput rejection). `None` for a gate rejection.",
"nullable": true,
"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"
},
"user_id": {
"description": "ID of the user who rejected. `None` for a system decision (timeout\nescalation) and in events recorded before it existed. `rejected_by`\nstays the display label.",
"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."
},
"CatchupPolicy": {
"description": "What a schedule does with the occurrences it missed while no server was\nfiring it (downtime, a long deploy, a stalled ticker).\n\n# Examples\n\n```\nuse ironflow_store::entities::CatchupPolicy;\n\nassert_eq!(CatchupPolicy::default(), CatchupPolicy::Latest);\nassert_eq!(CatchupPolicy::All.as_ref(), \"all\");\n\nlet parsed: CatchupPolicy = \"skip\".parse().unwrap();\nassert_eq!(parsed, CatchupPolicy::Skip);\n```",
"enum": [
"latest",
"all",
"skip"
],
"type": "string"
},
"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 priority: None,\n concurrency_limits: Vec::new(),\n worker_tags: vec![\"gpu\".to_string()],\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"
},
"priority": {
"description": "Optional queue priority, from -100 to 100.\n\nWorkers pick the pending run with the highest priority first, then the\noldest among equal priorities. `None` falls back to the workflow\ndefault, itself `0` unless the handler declares one. A running run is\nnever preempted, and a low priority run is not aged: a steady flow of\nhigher priority runs can delay it indefinitely.",
"format": "int32",
"nullable": true,
"type": "integer"
},
"scheduled_at": {
"description": "Optional deferred execution time. `None` means run immediately.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"worker_tags": {
"description": "Tags a worker must carry to take this run, added to the ones the\nworkflow requires.\n\nThe run stays pending until a worker carrying all of them asks for\nwork. Each tag is 1 to 64 bytes of ASCII letters, digits and\n`- _ . : / =`; at most 32 tags. Empty means only the workflow's own\nrequirements apply.",
"items": {
"type": "string"
},
"type": "array"
},
"workflow": {
"description": "The workflow name to trigger.",
"type": "string"
}
},
"required": [
"workflow"
],
"type": "object"
},
"CreateScheduleRequest": {
"description": "Create schedule request body.",
"properties": {
"catchup": {
"$ref": "#/components/schemas/CatchupPolicy",
"description": "What the schedule does with the occurrences it missed while no server\nfired it: `latest` (default) runs the most recent one, `all` runs each\nof them, `skip` runs none.",
"nullable": true
},
"catchup_max": {
"description": "Most runs created to catch up under `catchup = all`, from 1 to 1000.\nDefaults to 10.",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"catchup_window_secs": {
"description": "How far back, in seconds, a missed occurrence is still caught up, from\n60 to 2592000 (30 days). Defaults to 86400 (one day).",
"format": "int32",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"cron_expression": {
"description": "Cron expression (6-field format, e.g. `\"0 */5 * * * *\"`).",
"type": "string"
},
"inputs": {
"description": "JSON payload for the workflow. Defaults to `{}`."
},
"overlap": {
"$ref": "#/components/schemas/OverlapPolicy",
"description": "What the schedule does when an occurrence comes while one of its runs\nis still active: `allow` (default) starts another run, `skip` drops the\noccurrence.",
"nullable": true
},
"priority": {
"description": "Queue priority given to every run the schedule creates, from -100 to\n100. Defaults to the priority the workflow handler declares.\n\nA higher priority run is picked first by workers. A running run is\nnever preempted, and a low priority run is not aged.",
"format": "int32",
"nullable": true,
"type": "integer"
},
"timezone": {
"description": "IANA timezone the cron expression is evaluated in, e.g.\n`\"Europe/Paris\"`. Defaults to `\"UTC\"`.",
"nullable": true,
"type": "string"
},
"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."
},
{
"allOf": [
{
"$ref": "#/components/schemas/ScheduleOccurrencesMissedEvent",
"description": "A cron schedule dropped occurrences instead of starting a run for them.\n\nEmitted by the schedule ticker once per schedule, tick and reason."
},
{
"properties": {
"type": {
"enum": [
"schedule_occurrences_missed"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A cron schedule dropped occurrences instead of starting a run for them.\n\nEmitted by the schedule ticker once per schedule, tick and reason."
},
{
"allOf": [
{
"$ref": "#/components/schemas/WorkflowPausedEvent",
"description": "A workflow was paused: its new runs are held until it is resumed."
},
{
"properties": {
"type": {
"enum": [
"workflow_paused"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A workflow was paused: its new runs are held until it is resumed."
},
{
"allOf": [
{
"$ref": "#/components/schemas/WorkflowResumedEvent",
"description": "A paused workflow was resumed."
},
{
"properties": {
"type": {
"enum": [
"workflow_resumed"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "A paused workflow was resumed."
}
]
},
"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",
"schedule_occurrences_missed",
"workflow_paused",
"workflow_resumed"
],
"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"
},
"priority": {
"description": "Filter by priority: only runs with exactly this priority.",
"format": "int32",
"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"
},
"OverlapPolicy": {
"description": "What a schedule does when an occurrence comes while one of its runs is\nstill active.\n\n# Examples\n\n```\nuse ironflow_store::entities::OverlapPolicy;\n\nassert_eq!(OverlapPolicy::default(), OverlapPolicy::Allow);\nassert_eq!(OverlapPolicy::Skip.as_ref(), \"skip\");\n\nlet parsed: OverlapPolicy = \"allow\".parse().unwrap();\nassert_eq!(parsed, OverlapPolicy::Allow);\n```",
"enum": [
"allow",
"skip"
],
"type": "string"
},
"PauseRunResponse": {
"allOf": [
{
"$ref": "#/components/schemas/RunResponse",
"description": "The paused run. Its `resume_status` is the state it returns to."
},
{
"properties": {
"paused_descendants": {
"description": "Sub-workflow runs below it that this request paused, oldest first.",
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
}
},
"required": [
"paused_descendants"
],
"type": "object"
}
],
"description": "Response of `POST /api/v1/runs/:id/pause`: the paused run, with the\nsub-workflow runs paused along with it."
},
"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"
},
"ResumeRunResponse": {
"allOf": [
{
"$ref": "#/components/schemas/RunResponse",
"description": "The resumed run, in the state it was paused from."
},
{
"properties": {
"resumed_descendants": {
"description": "Sub-workflow runs below it that this request resumed, oldest first.",
"items": {
"format": "uuid",
"type": "string"
},
"type": "array"
}
},
"required": [
"resumed_descendants"
],
"type": "object"
}
],
"description": "Response of `POST /api/v1/runs/:id/resume`: the resumed run, with the\nsub-workflow runs resumed along with it."
},
"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"
},
"worker_routing": {
"$ref": "#/components/schemas/WorkerRouting",
"description": "Workers seen recently that could take the run. Only filled while the\nrun waits in the queue (`pending` or `retrying`), so a run no worker\ncan take is visible instead of waiting in silence.",
"nullable": true
}
},
"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"
},
"lease_recoveries": {
"description": "Number of times the run was requeued after its worker lost its lease. Separate from `retry_count`: it does not consume `max_retries`.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"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."
},
"priority": {
"description": "Queue priority, from -100 to 100. Workers pick the pending run with\nthe highest priority first, then the oldest among equal priorities.\n`0` is the default.",
"format": "int32",
"type": "integer"
},
"resume_status": {
"$ref": "#/components/schemas/RunStatus",
"description": "State a `paused` run returns to when it is resumed. Omitted in every\nother state.",
"nullable": true
},
"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"
},
"worker_tags": {
"description": "Tags a worker must carry to take this run, sorted. Empty when any\nworker can take it.",
"items": {
"type": "string"
},
"type": "array"
},
"workflow_name": {
"description": "Workflow name.",
"type": "string"
}
},
"required": [
"id",
"workflow_name",
"status",
"trigger",
"retry_count",
"max_retries",
"lease_recoveries",
"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- `Pending`, `Retrying`, `Sleeping`, `AwaitingApproval`, `Running` -> `Paused` (operator pause)\n- `Paused` -> `Pending`, `Running`, `Retrying`, `Sleeping`, `AwaitingApproval` (resume),\n `Failed` (rejection decided while paused), `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));\nassert!(RunStatus::Running.can_transition_to(&RunStatus::Paused));\nassert!(RunStatus::Paused.can_transition_to(&RunStatus::Pending));\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",
"paused"
],
"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"
},
"ScheduleMissReason": {
"description": "Why an occurrence of a schedule created no run.\n\n# Examples\n\n```\nuse ironflow_store::entities::ScheduleMissReason;\n\nlet reason = ScheduleMissReason::Overlap;\nassert_eq!(reason.to_string(), \"overlap\");\nassert_eq!(serde_json::to_string(&reason).unwrap(), \"\\\"overlap\\\"\");\n```",
"enum": [
"outside_window",
"catchup_max",
"superseded",
"catchup_skip",
"overlap"
],
"type": "string"
},
"ScheduleOccurrencesMissedEvent": {
"description": "Payload of the `Event::ScheduleOccurrencesMissed` event.\n\nA cron schedule skipped some of its occurrences: the server was down\nlonger than the catch-up window allows, the catch-up policy dropped them,\nor the previous run was still active under an overlap `skip` policy.\n\n# Examples\n\n```\nuse chrono::{TimeDelta, Utc};\nuse ironflow_engine::notify::ScheduleOccurrencesMissedEvent;\nuse ironflow_store::models::ScheduleMissReason;\nuse uuid::Uuid;\n\nlet now = Utc::now();\nlet payload = ScheduleOccurrencesMissedEvent {\n schedule_id: Uuid::now_v7(),\n workflow_name: \"nightly-report\".to_string(),\n reason: ScheduleMissReason::OutsideWindow,\n count: 3,\n first: now - TimeDelta::hours(3),\n last: now - TimeDelta::hours(1),\n at: now,\n};\nassert_eq!(payload.count, 3);\n```",
"properties": {
"at": {
"description": "When the ticker noticed the miss.",
"format": "date-time",
"type": "string"
},
"count": {
"description": "Number of missed occurrences.",
"format": "int64",
"minimum": 0,
"type": "integer"
},
"first": {
"description": "Earliest missed occurrence.",
"format": "date-time",
"type": "string"
},
"last": {
"description": "Latest missed occurrence.",
"format": "date-time",
"type": "string"
},
"reason": {
"$ref": "#/components/schemas/ScheduleMissReason",
"description": "Why the occurrences were not run."
},
"schedule_id": {
"description": "Schedule that missed the occurrences.",
"format": "uuid",
"type": "string"
},
"workflow_name": {
"description": "Workflow the schedule starts.",
"type": "string"
}
},
"required": [
"schedule_id",
"workflow_name",
"reason",
"count",
"first",
"last",
"at"
],
"type": "object"
},
"ScheduleResponse": {
"description": "Schedule list/detail response.",
"properties": {
"catchup": {
"$ref": "#/components/schemas/CatchupPolicy",
"description": "What the schedule does with the occurrences it missed while no server\nfired it."
},
"catchup_max": {
"description": "Most runs created to catch up under `catchup = all`.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"catchup_window_secs": {
"description": "How far back, in seconds, a missed occurrence is still caught up.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"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"
},
"overlap": {
"$ref": "#/components/schemas/OverlapPolicy",
"description": "What the schedule does when an occurrence comes while one of its runs\nis still active."
},
"priority": {
"description": "Queue priority given to every run the schedule creates, from -100 to 100.",
"format": "int32",
"type": "integer"
},
"source": {
"$ref": "#/components/schemas/ScheduleSource",
"description": "Where this schedule was created (`handler` or `api`)."
},
"timezone": {
"description": "IANA timezone the cron expression is evaluated in.",
"type": "string"
},
"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",
"priority",
"catchup",
"catchup_max",
"catchup_window_secs",
"overlap",
"timezone",
"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 sender_run_id: None,\n sender_step_id: None,\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"
},
"sender_run_id": {
"description": "The run that sent the signal with `ctx.send_signal`. `None` for a\nsignal sent through the API or the engine directly.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"sender_step_id": {
"description": "The send step of the sending run. `None` when `sender_run_id` is.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"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"
},
"paused": {
"description": "Number of runs created in this bucket and currently paused.",
"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",
"paused",
"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"
},
"agent_role": {
"description": "Name of the `AgentRole` the agent step was created from; null for any\nother step, an agent step built by hand, or a step stored before roles\nwere recorded.",
"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"
},
"session_id": {
"description": "Claude Code session the agent step ran in, if any. A step interrupted\nby a lost lease resumes it on its next execution.",
"nullable": true,
"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"
},
"schedule_id": {
"description": "Schedule that created the run. `None` on runs created before it\nwas recorded.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"scheduled_for": {
"description": "Occurrence the run covers. `None` for a manual trigger of the\nschedule and on older runs.",
"format": "date-time",
"nullable": true,
"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"
},
"WorkerRouting": {
"description": "Workers the API saw recently, as seen from one queued run.\n\nCounted from the pick requests received in the last few minutes by this\nAPI process: with several replicas, each one only counts the workers that\npolled it.\n\n# Examples\n\n```\nuse ironflow_api::entities::WorkerRouting;\n\nlet routing = WorkerRouting {\n seen_workers: 2,\n eligible_workers: 0,\n};\nassert!(routing.eligible_workers < routing.seen_workers);\n```",
"properties": {
"eligible_workers": {
"description": "Among them, the workers that registered the run's workflow and carry\nevery tag it requires.",
"format": "int32",
"minimum": 0,
"type": "integer"
},
"seen_workers": {
"description": "Workers that asked for a run recently.",
"format": "int32",
"minimum": 0,
"type": "integer"
}
},
"required": [
"seen_workers",
"eligible_workers"
],
"type": "object"
},
"WorkflowAgentStepResumedEvent": {
"description": "Payload of the `WorkflowEvent::AgentStepResumed` workflow event.\n\nPublished when an agent step interrupted by a lost lease is re-executed\non the Claude Code session of the interrupted attempt instead of starting\nfrom scratch.\n\n# Examples\n\n```\nuse ironflow_engine::notify::WorkflowAgentStepResumedEvent;\nuse chrono::Utc;\n\nlet payload = WorkflowAgentStepResumedEvent {\n step_name: \"review\".to_string(),\n step_index: 2,\n session_id: \"0192f0c1-7d2e-7a4b-9c3d-1e2f3a4b5c6d\".to_string(),\n timestamp: Utc::now(),\n};\nassert_eq!(payload.step_index, 2);\n```",
"properties": {
"session_id": {
"description": "Claude Code session the step resumes.",
"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"
},
"timestamp": {
"description": "When the step resumed.",
"format": "date-time",
"type": "string"
}
},
"required": [
"step_name",
"step_index",
"session_id",
"timestamp"
],
"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"
},
"paused_at": {
"description": "When the workflow was paused: its queued runs are not picked up until\nit is resumed. Omitted when the workflow is not paused.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"priority": {
"description": "Default queue priority of the runs of this workflow, from -100 to 100.\nClamped like the run creation path. Always present, 0 when the handler\ndeclares none.",
"format": "int32",
"type": "integer"
},
"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",
"priority"
],
"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."
},
{
"allOf": [
{
"$ref": "#/components/schemas/WorkflowAgentStepResumedEvent",
"description": "An interrupted agent step resumed its Claude Code session."
},
{
"properties": {
"type": {
"enum": [
"agent_step_resumed"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
],
"description": "An interrupted agent step resumed its Claude Code session."
}
]
},
"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"
},
"WorkflowPauseResponse": {
"description": "Response of `POST /api/v1/workflows/:name/pause` and\n`POST /api/v1/workflows/:name/resume`.\n\n# Examples\n\n```\nuse ironflow_api::entities::WorkflowPauseResponse;\n\nlet response = WorkflowPauseResponse::resumed(\"deploy\");\nassert!(response.paused_at.is_none());\n```",
"properties": {
"paused_at": {
"description": "When the workflow was paused: its queued runs are not picked up until\nit is resumed. Omitted when the workflow is not paused.",
"format": "date-time",
"nullable": true,
"type": "string"
},
"paused_by": {
"description": "User who paused the workflow, when known.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"workflow_name": {
"description": "Workflow name.",
"type": "string"
}
},
"required": [
"workflow_name"
],
"type": "object"
},
"WorkflowPausedEvent": {
"description": "Payload of the `Event::WorkflowPaused` event.\n\nEmitted on every pause request, including the pause of a workflow that\nwas already paused: the request is a decision of its actor.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::WorkflowPausedEvent;\nuse uuid::Uuid;\n\nlet payload = WorkflowPausedEvent {\n workflow_name: \"deploy\".to_string(),\n user_id: Some(Uuid::now_v7()),\n at: Utc::now(),\n};\nassert_eq!(payload.workflow_name, \"deploy\");\n```",
"properties": {
"at": {
"description": "When the workflow was paused.",
"format": "date-time",
"type": "string"
},
"user_id": {
"description": "ID of the user who paused the workflow. `None` when no user is behind\nthe call.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"workflow_name": {
"description": "Paused workflow.",
"type": "string"
}
},
"required": [
"workflow_name",
"at"
],
"type": "object"
},
"WorkflowResumedEvent": {
"description": "Payload of the `Event::WorkflowResumed` event.\n\nEmitted only when the workflow was paused: resuming a workflow that is\nnot paused changes nothing and emits nothing.\n\n# Examples\n\n```\nuse chrono::Utc;\nuse ironflow_engine::notify::WorkflowResumedEvent;\nuse uuid::Uuid;\n\nlet payload = WorkflowResumedEvent {\n workflow_name: \"deploy\".to_string(),\n user_id: Some(Uuid::now_v7()),\n at: Utc::now(),\n};\nassert_eq!(payload.workflow_name, \"deploy\");\n```",
"properties": {
"at": {
"description": "When the workflow was resumed.",
"format": "date-time",
"type": "string"
},
"user_id": {
"description": "ID of the user who resumed the workflow. `None` when no user is behind\nthe call.",
"format": "uuid",
"nullable": true,
"type": "string"
},
"workflow_name": {
"description": "Resumed workflow.",
"type": "string"
}
},
"required": [
"workflow_name",
"at"
],
"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"
},
"paused_at": {
"description": "When the workflow was paused: its queued runs are not picked up until\nit is resumed. Omitted when the workflow is not paused.",
"format": "date-time",
"nullable": true,
"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 longer than 255 characters (after trimming), 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 assigning forbidden scopes or a rate_limit_override)"
}
},
"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- `priority` - Filter by exact priority, from -100 to 100 (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": "Filter by priority: only runs with exactly this priority.",
"in": "query",
"name": "priority",
"required": false,
"schema": {
"format": "int32",
"type": "integer"
}
},
{
"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\nOptional `worker_tags` in the body are added to the tags the workflow\nrequires: only a worker carrying all of them takes the run.\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 `worker_tags`), or the\n`Idempotency-Key` header 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 worker_tags) 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": "While the run waits in the queue, `worker_routing` counts the workers seen\nrecently and those able to take it, so a run no worker can take shows up.\n\nReturns 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\nA run an operator paused while it awaited approval can be approved too: the\nvote is recorded and the run stays `paused`, resuming to `pending` once the\noperator resumes it.\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}/pause": {
"post": {
"description": "A queued, sleeping or waiting run is no longer picked or woken. A running\nrun has its step in flight interrupted and starts no further step; the\nresume executes the interrupted step again (see\n[`Engine::pause_run`](ironflow_engine::engine::Engine::pause_run)). The\nrun keeps the state it was paused from in `resume_status`.\n\nReturns 400 for a run that already finished, a run already paused, or a\nsub-workflow run: pause its root run instead.",
"operationId": "pause_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/PauseRunResponse"
}
}
},
"description": "Run paused, with the sub-runs paused along"
},
"400": {
"description": "Run cannot be paused"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Run not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Pause a run that has not finished, with every sub-workflow run below it.",
"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.\nA run paused while it awaited approval is rejected the same way.\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.\n\nReturns 429 `MONTHLY_BUDGET_EXCEEDED` if the global monthly cost quota is exhausted.",
"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"
},
"429": {
"description": "Monthly cost quota exhausted (MONTHLY_BUDGET_EXCEEDED)"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Replay a finished run on the current handler version.",
"tags": [
"runs"
]
}
},
"/api/v1/runs/{id}/resume": {
"post": {
"description": "The run returns to the state it was paused from: a queued run is picked\nagain, a sleeping run waits for its deadline, a run waiting for an\napproval keeps waiting. A run paused while it executed goes back to\n`pending` and replays from the step where it stopped.\n\nReturns 400 for a run that is not paused or a sub-workflow run.",
"operationId": "resume_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/ResumeRunResponse"
}
}
},
"description": "Run resumed, with the sub-runs resumed along"
},
"400": {
"description": "Run is not paused"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Run not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Resume a paused run, with the sub-workflow runs paused below it.",
"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.\n\nReturns 429 `MONTHLY_BUDGET_EXCEEDED` if the global monthly cost quota is exhausted.",
"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)"
},
"429": {
"description": "Monthly cost quota exhausted (MONTHLY_BUDGET_EXCEEDED)"
}
},
"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\nThe rejection is published as an `approval_rejected` audit event carrying\nthe caller's label (`rejected_by`), user id and the reason.\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 `priority` is outside `-100..=100`\n- 400 if `catchup_max` is outside `1..=1000` or `catchup_window_secs` is\n outside `60..=2592000`\n- 400 if the workflow is not registered\n- 401 if not authenticated\n- 422 if `timezone` is not an IANA timezone name, or `catchup` or `overlap`\n is not a known policy",
"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, cron, priority or catch-up bounds"
},
"401": {
"description": "Unauthorized"
},
"422": {
"description": "Unknown timezone, catch-up or overlap policy"
}
},
"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 in the\nschedule timezone. Occurrences that fell during the pause are never caught\nup, whatever the catch-up policy.\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": "The run carries the schedule id and no `scheduled_for` occurrence.\n\n# Errors\n\n- 401 if not authenticated\n- 404 if the schedule does not exist\n- 409 if the schedule has `overlap = skip` and one of its runs is still\n active",
"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"
},
"409": {
"description": "Overlap skip: a run of the schedule is still active"
}
},
"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, the API key lacks `signals_send`, or\n its owner is not an admin",
"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": "Filter by priority: only runs with exactly this priority.",
"in": "query",
"name": "priority",
"required": false,
"schema": {
"format": "int32",
"type": "integer"
}
},
{
"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/agent-roles": {
"get": {
"description": "Aggregates the agent steps created during the period (`24h`, `7d`, `30d`\nor `90d`, default `7d`) by the `AgentRole` they were created from, all\nworkflows combined. Each step record counts as one execution, retried\nattempts included. Agent steps without a role are left out.",
"operationId": "get_agent_role_stats",
"parameters": [
{
"description": "Time period to query: `24h`, `7d`, `30d` or `90d`. Defaults to `7d`.",
"in": "query",
"name": "period",
"required": false,
"schema": {
"$ref": "#/components/schemas/HistoryPeriod"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AgentRoleStatsResponse"
}
}
},
"description": "Statistics per agent role"
},
"400": {
"description": "Invalid period"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Get the cost, executions and failures of every agent role over a period.",
"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`,\n`concurrency_group`).",
"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"
}
},
{
"description": "Filter by concurrency group: only runs that belong to this group.",
"in": "query",
"name": "concurrency_group",
"required": false,
"schema": {
"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/steps": {
"get": {
"description": "# Query Parameters\n\n- `agent_role` - Name of the `AgentRole`, matched exactly (required, at\n most 128 bytes)\n- `page` - Page number, 1-based (default: 1)\n- `per_page` - Items per page (default: 20, max: 100)\n\nSteps are sorted newest first. Steps stored before roles were recorded have\nno role and are never listed.",
"operationId": "list_steps",
"parameters": [
{
"description": "Name of the `AgentRole` whose agent steps to list, across every run and\nworkflow. Matched exactly. Required.",
"in": "query",
"name": "agent_role",
"required": true,
"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 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/StepResponse"
},
"type": "array"
}
}
},
"description": "Agent steps of the role with pagination"
},
"400": {
"description": "Missing, blank or too long agent_role"
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "List the agent steps of one agent role, across every run and workflow.",
"tags": [
"runs"
]
}
},
"/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}/pause": {
"post": {
"description": "Runs already executing are left alone (pause them one by one with\n`POST /api/v1/runs/{id}/pause`), and new runs are still created: they\nwait in the queue until the workflow is resumed. Pausing a workflow\nalready paused succeeds and keeps its first `paused_at`. Every pause\npublishes a `workflow_paused` audit event carrying the caller's user id.",
"operationId": "pause_workflow",
"parameters": [
{
"description": "Workflow name",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WorkflowPauseResponse"
}
}
},
"description": "Workflow paused"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Workflow not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Pause a workflow: workers stop picking its queued runs.",
"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"
]
}
},
"/api/v1/workflows/{name}/resume": {
"post": {
"description": "Resuming a workflow that is not paused succeeds and changes nothing. A\nresume of a paused workflow publishes a `workflow_resumed` audit event\ncarrying the caller's user id.",
"operationId": "resume_workflow",
"parameters": [
{
"description": "Workflow name",
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WorkflowPauseResponse"
}
}
},
"description": "Workflow resumed"
},
"401": {
"description": "Unauthorized"
},
"403": {
"description": "Forbidden"
},
"404": {
"description": "Workflow not found"
}
},
"security": [
{
"Bearer": []
}
],
"summary": "Resume a paused workflow: workers pick its queued runs again.",
"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"
}
]
}