diff --git a/frontend/src/components/executions/nav.tsx b/frontend/src/components/executions/nav.tsx
index d07bcb4e5..f81d2c616 100644
--- a/frontend/src/components/executions/nav.tsx
+++ b/frontend/src/components/executions/nav.tsx
@@ -96,11 +96,12 @@ export function WorkflowExecutionNav({
         {workflowExecutions.map((execution, index) => (
           <HoverCard openDelay={10} closeDelay={10} key={index}>
             <Link
-              href={`${baseUrl}/executions/${execution.id.split(":")[1]}`}
+              href={`${baseUrl}/executions/${parseExecutionId(execution.id)[1]}`}
               className={cn(
                 buttonVariants({ variant: "default", size: "sm" }),
                 "justify-start bg-background text-muted-foreground shadow-none hover:cursor-default hover:bg-gray-100",
-                execution.id.split(":")[1] === executionId && "bg-gray-200"
+                parseExecutionId(execution.id)[1] === executionId &&
+                  "bg-gray-200"
               )}
             >
               <div className="flex items-center">
@@ -152,7 +153,7 @@ export function WorkflowExecutionNav({
                   <Label className="text-xs text-muted-foreground">
                     Execution ID
                   </Label>
-                  <span>{execution.id.split(":")[1]}</span>
+                  <span>{parseExecutionId(execution.id)[1]}</span>
                 </div>
                 <div className="flex flex-col">
                   <Label className="text-xs text-muted-foreground">
@@ -238,3 +239,23 @@ export function getExecutionStatusIcon(
       throw new Error("Invalid status")
   }
 }
+
+/**
+ * Get the execution ID from a full execution ID
+ * @param fullExecutionId
+ * @returns the execution ID
+ *
+ * Example:
+ * - "wf-123:1234567890" -> ["wf-123", "1234567890"]
+ * - "wf-123:1234567890:1" -> ["wf-123", "1234567890:1"]
+ */
+function parseExecutionId(fullExecutionId: string): [string, string] {
+  // Split at most once from the left, keeping any remaining colons in the second part
+  const splitIndex = fullExecutionId.indexOf(":")
+  if (splitIndex === -1) {
+    throw new Error("Invalid execution ID format - missing colon separator")
+  }
+  const workflowId = fullExecutionId.slice(0, splitIndex)
+  const executionId = fullExecutionId.slice(splitIndex + 1)
+  return [workflowId, executionId]
+}
diff --git a/tracecat/workflow/executions/dependencies.py b/tracecat/workflow/executions/dependencies.py
new file mode 100644
index 000000000..c06f15d6c
--- /dev/null
+++ b/tracecat/workflow/executions/dependencies.py
@@ -0,0 +1,14 @@
+import urllib.parse
+from typing import Annotated
+
+from fastapi import Depends
+
+from tracecat.workflow.executions.models import ExecutionOrScheduleID
+
+
+def unquote_dep(execution_id: ExecutionOrScheduleID) -> ExecutionOrScheduleID:
+    return urllib.parse.unquote(execution_id)
+
+
+UnquotedExecutionOrScheduleID = Annotated[ExecutionOrScheduleID, Depends(unquote_dep)]
+"""Dependency for an unquoted execution or schedule ID."""
diff --git a/tracecat/workflow/executions/models.py b/tracecat/workflow/executions/models.py
index 509d1343f..c73d6ffc0 100644
--- a/tracecat/workflow/executions/models.py
+++ b/tracecat/workflow/executions/models.py
@@ -20,7 +20,7 @@
     RunActionInput,
     TriggerInputs,
 )
-from tracecat.identifiers import WorkflowExecutionID, WorkflowID
+from tracecat.identifiers import WorkflowExecutionID, WorkflowID, WorkflowScheduleID
 from tracecat.types.auth import Role
 from tracecat.workflow.management.models import GetWorkflowDefinitionActivityInputs
 
@@ -35,6 +35,8 @@
 ]
 """Mapped literal types for workflow execution statuses."""
 
+ExecutionOrScheduleID = WorkflowExecutionID | WorkflowScheduleID
+
 
 class EventHistoryType(StrEnum):
     """The event types we care about."""
diff --git a/tracecat/workflow/executions/router.py b/tracecat/workflow/executions/router.py
index 8db77b748..cd190b5fb 100644
--- a/tracecat/workflow/executions/router.py
+++ b/tracecat/workflow/executions/router.py
@@ -12,14 +12,15 @@
 from sqlmodel import select
 from sqlmodel.ext.asyncio.session import AsyncSession
 
-from tracecat import identifiers
 from tracecat.auth.credentials import authenticate_user_for_workspace
 from tracecat.db.engine import get_async_session
 from tracecat.db.schemas import WorkflowDefinition
 from tracecat.dsl.common import DSLInput
+from tracecat.identifiers import WorkflowID
 from tracecat.logger import logger
 from tracecat.types.auth import Role
 from tracecat.types.exceptions import TracecatValidationError
+from tracecat.workflow.executions.dependencies import UnquotedExecutionOrScheduleID
 from tracecat.workflow.executions.models import (
     CreateWorkflowExecutionParams,
     CreateWorkflowExecutionResponse,
@@ -36,7 +37,7 @@
 async def list_workflow_executions(
     role: Annotated[Role, Depends(authenticate_user_for_workspace)],
     # Filters
-    workflow_id: identifiers.WorkflowID | None = Query(None),
+    workflow_id: WorkflowID | None = Query(None),
 ) -> list[WorkflowExecutionResponse]:
     """List all workflow executions."""
     with logger.contextualize(role=role):
@@ -54,7 +55,7 @@ async def list_workflow_executions(
 @router.get("/{execution_id}", tags=["workflow-executions"])
 async def get_workflow_execution(
     role: Annotated[Role, Depends(authenticate_user_for_workspace)],
-    execution_id: identifiers.WorkflowExecutionID | identifiers.WorkflowScheduleID,
+    execution_id: UnquotedExecutionOrScheduleID,
 ) -> WorkflowExecutionResponse:
     """Get a workflow execution."""
     with logger.contextualize(role=role):
@@ -66,7 +67,7 @@ async def get_workflow_execution(
 @router.get("/{execution_id}/history", tags=["workflow-executions"])
 async def list_workflow_execution_event_history(
     role: Annotated[Role, Depends(authenticate_user_for_workspace)],
-    execution_id: identifiers.WorkflowExecutionID | identifiers.WorkflowScheduleID,
+    execution_id: UnquotedExecutionOrScheduleID,
 ) -> list[EventHistoryResponse]:
     """Get a workflow execution."""
     with logger.contextualize(role=role):
@@ -126,7 +127,7 @@ async def create_workflow_execution(
 )
 async def cancel_workflow_execution(
     role: Annotated[Role, Depends(authenticate_user_for_workspace)],
-    execution_id: identifiers.WorkflowExecutionID | identifiers.WorkflowScheduleID,
+    execution_id: UnquotedExecutionOrScheduleID,
 ) -> None:
     """Get a workflow execution."""
     with logger.contextualize(role=role):
@@ -150,7 +151,7 @@ async def cancel_workflow_execution(
 )
 async def terminate_workflow_execution(
     role: Annotated[Role, Depends(authenticate_user_for_workspace)],
-    execution_id: identifiers.WorkflowExecutionID | identifiers.WorkflowScheduleID,
+    execution_id: UnquotedExecutionOrScheduleID,
     params: TerminateWorkflowExecutionParams,
 ) -> None:
     """Get a workflow execution."""