diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md
index 86be3a5dc73..dff8552e97f 100644
--- a/api/openapi/markdown/console-openapi.md
+++ b/api/openapi/markdown/console-openapi.md
@@ -9135,6 +9135,61 @@ Generate a Dify workflow graph from natural language
| 400 | Invalid request parameters | |
| 402 | Provider quota exceeded | |
+### [GET] /workflow-run-archives
+List monthly workflow-run archive metadata for the current workspace
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | Success | **application/json**: [WorkflowRunArchiveListResponse](#workflowrunarchivelistresponse)
|
+
+### [POST] /workflow-run-archives/downloads
+Create or return a temporary workflow-run archive download task
+
+#### Request Body
+
+| Required | Schema |
+| -------- | ------ |
+| Yes | **application/json**: [WorkflowRunArchiveDownloadPayload](#workflowrunarchivedownloadpayload)
|
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 202 | Download task accepted | **application/json**: [WorkflowRunArchiveDownloadTaskResponse](#workflowrunarchivedownloadtaskresponse)
|
+
+### [GET] /workflow-run-archives/downloads/{download_id}
+Get a temporary workflow-run archive download task
+
+#### Parameters
+
+| Name | Located in | Description | Required | Schema |
+| ---- | ---------- | ----------- | -------- | ------ |
+| download_id | path | | Yes | string |
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 200 | Success | **application/json**: [WorkflowRunArchiveDownloadTaskResponse](#workflowrunarchivedownloadtaskresponse)
|
+
+### [GET] /workflow-run-archives/downloads/{download_id}/file
+Redirect to a prepared workflow-run archive ZIP file
+
+#### Parameters
+
+| Name | Located in | Description | Required | Schema |
+| ---- | ---------- | ----------- | -------- | ------ |
+| download_id | path | | Yes | string |
+
+#### Responses
+
+| Code | Description | Schema |
+| ---- | ----------- | ------ |
+| 302 | Redirect to pre-signed archive storage URL | **application/json**: [RedirectResponse](#redirectresponse)
|
+| 409 | Download task is not ready | |
+
### [GET] /workflow/{workflow_run_id}/events
**Get workflow execution events stream after resume**
@@ -21300,6 +21355,71 @@ can reuse its existing handler.
| result | string | | Yes |
| updated_at | integer | | Yes |
+#### WorkflowRunArchiveDownloadPayload
+
+Request body for preparing one monthly workflow-run archive download.
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| month | integer | | Yes |
+| year | integer | | Yes |
+
+#### WorkflowRunArchiveDownloadStatus
+
+Lifecycle state for an asynchronous archive download request.
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| WorkflowRunArchiveDownloadStatus | string | Lifecycle state for an asynchronous archive download request. | |
+
+#### WorkflowRunArchiveDownloadTaskResponse
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| archive_bytes | integer | | Yes |
+| bundle_count | integer | | Yes |
+| created_at | dateTime | | Yes |
+| download_id | string | | Yes |
+| error | string | | No |
+| expires_at | dateTime | | Yes |
+| file_name | string | | No |
+| file_size_bytes | integer | | No |
+| finished_at | string | | No |
+| month | integer | | Yes |
+| started_at | string | | No |
+| status | [WorkflowRunArchiveDownloadStatus](#workflowrunarchivedownloadstatus) | | Yes |
+| updated_at | dateTime | | Yes |
+| year | integer | | Yes |
+
+#### WorkflowRunArchiveListResponse
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| months | [ [WorkflowRunArchiveMonthResponse](#workflowrunarchivemonthresponse) ] | | Yes |
+| summary | [WorkflowRunArchiveSummaryResponse](#workflowrunarchivesummaryresponse) | | Yes |
+
+#### WorkflowRunArchiveMonthResponse
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| archive_bytes | integer | | Yes |
+| bundle_count | integer | | Yes |
+| download_task | [WorkflowRunArchiveDownloadTaskResponse](#workflowrunarchivedownloadtaskresponse) | | No |
+| latest_archived_at | dateTime | | Yes |
+| month | integer | | Yes |
+| row_count | integer | | Yes |
+| workflow_run_count | integer | | Yes |
+| year | integer | | Yes |
+
+#### WorkflowRunArchiveSummaryResponse
+
+| Name | Type | Description | Required |
+| ---- | ---- | ----------- | -------- |
+| archive_bytes | integer | | Yes |
+| archived_month_count | integer | | Yes |
+| latest_archived_at | string | | No |
+| workflow_run_count | integer | | Yes |
+
#### WorkflowRunCountQuery
| Name | Type | Description | Required |
diff --git a/api/tests/unit_tests/services/retention/workflow_run/test_archive_log_service.py b/api/tests/unit_tests/services/retention/workflow_run/test_archive_log_service.py
index f3da2c27b89..149572fa73b 100644
--- a/api/tests/unit_tests/services/retention/workflow_run/test_archive_log_service.py
+++ b/api/tests/unit_tests/services/retention/workflow_run/test_archive_log_service.py
@@ -316,8 +316,11 @@ def test_get_ready_workflow_run_archive_download_task_requires_ready_file() -> N
)
cache = FakeTaskCache(existing_task=ready_task)
- assert get_ready_workflow_run_archive_download_task(
- tenant_id="tenant-1",
- download_id="download-1",
- cache=cast(WorkflowRunArchiveDownloadTaskCache, cache),
- ) == ready_task
+ assert (
+ get_ready_workflow_run_archive_download_task(
+ tenant_id="tenant-1",
+ download_id="download-1",
+ cache=cast(WorkflowRunArchiveDownloadTaskCache, cache),
+ )
+ == ready_task
+ )
diff --git a/eslint-suppressions.json b/eslint-suppressions.json
index eb99f899a63..e322a83bc3f 100644
--- a/eslint-suppressions.json
+++ b/eslint-suppressions.json
@@ -836,11 +836,6 @@
"count": 1
}
},
- "web/app/components/app/log/index.tsx": {
- "react/set-state-in-effect": {
- "count": 1
- }
- },
"web/app/components/app/log/list.tsx": {
"react/set-state-in-effect": {
"count": 6