diff --git a/Controllers/WorkflowFilesController.cs b/Controllers/WorkflowFilesController.cs
new file mode 100644
index 0000000..ce3c0f1
--- /dev/null
+++ b/Controllers/WorkflowFilesController.cs
@@ -0,0 +1,278 @@
+using System.Diagnostics;
+using Microsoft.AspNetCore.Mvc;
+using w4c_workflows.Filters;
+using w4c_workflows.Services;
+
+namespace w4c_workflows.Controllers;
+
+///
+/// Workflow file management endpoints. Provides CRUD operations for the
+/// workflow YAML definitions and their sibling code files (shell scripts,
+/// JS, Python, etc.) stored in the tenant's private Forgejo repo.
+///
+/// Requires the Forgejo-backed mode to be active (Forgejo:AdminToken
+/// configured). In filesystem-only mode these endpoints return 503.
+///
+[ApiController]
+[Route("api/workflow-files")]
+public class WorkflowFilesController : ControllerBase
+{
+ private readonly WorkflowSourceFactory _sourceFactory;
+ private readonly ILogger _logger;
+
+ public WorkflowFilesController(
+ WorkflowSourceFactory sourceFactory,
+ ILogger logger)
+ {
+ _sourceFactory = sourceFactory;
+ _logger = logger;
+ }
+
+ private string TenantId => (string?)HttpContext.Items["TenantId"]
+ ?? throw new InvalidOperationException("TenantId not resolved by auth middleware");
+
+ ///
+ /// The user's Forgejo login, resolved from the tenant ID by the
+ /// WorkflowSourceMiddleware. Used for repo path lookups since repos
+ /// are per-user: {login}/workflows-{login}.
+ ///
+ private string ForgejoLogin => (string?)HttpContext.Items["ForgejoLogin"]
+ ?? throw new InvalidOperationException("ForgejoLogin not resolved by WorkflowSourceMiddleware");
+
+ /// Returns the Forgejo repo info for this tenant's workflow files.
+ [HttpGet("repo")]
+ [RequireScope("read")]
+ public IActionResult GetRepoInfo()
+ {
+ if (_sourceFactory.Forgejo == null)
+ return StatusCode(503, new { error = "Forgejo-backed workflow mode is not configured." });
+
+ var login = ForgejoLogin;
+ var cloneDir = _sourceFactory.Forgejo.TenantCloneDir(login);
+ var fullName = ForgejoWorkflowRepoService.RepoFullNameForLogin(login);
+ var exists = Directory.Exists(Path.Combine(cloneDir, ".git"));
+
+ return Ok(new
+ {
+ repoFullName = fullName,
+ cloneDir,
+ cloned = exists,
+ forgejoUrl = $"{_sourceFactory.Forgejo.GetType().GetProperty("ForgejoBase")?.GetValue(_sourceFactory.Forgejo) ?? "https://forgejo.wiz4chat.com"}/{fullName}",
+ });
+ }
+
+ ///
+ /// Lists all files in the tenant's workflow repo (recursive, excluding .git).
+ /// Returns relative paths and basic file info.
+ ///
+ [HttpGet]
+ [RequireScope("read")]
+ public IActionResult ListFiles([FromQuery] string? path = null)
+ {
+ if (_sourceFactory.Forgejo == null)
+ return StatusCode(503, new { error = "Forgejo-backed workflow mode is not configured." });
+
+ var cloneDir = _sourceFactory.Forgejo.TenantCloneDir(ForgejoLogin);
+ if (!Directory.Exists(cloneDir))
+ return Ok(new { files = Array.Empty