{"record":{"id":"f2320fee8f42e020","repo":"microsoft/aspire","slug":"the-apipath-must-contain-only-url-safe-path-characters","errorCode":null,"errorMessage":"The apiPath must contain only URL-safe path characters (alphanumeric, '/', '-', '_'). Invalid character: '{c}'","messagePattern":"The apiPath must contain only URL-safe path characters \\(alphanumeric, '/', '-', '_'\\)\\. Invalid character: '(.+?)'","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs","lineNumber":3199,"sourceCode":"            {\n                // If we can't read the config, skip the check — the Docker build will surface the error.\n            }\n\n            return;\n        }\n\n        throw new InvalidOperationException(\n            \"No Next.js configuration file found. AddNextJsApp expects one of: \" +\n            string.Join(\", \", s_nextConfigFileNames));\n    }\n\n    private static void ValidateApiPath(string apiPath)\n    {\n        foreach (var c in apiPath)\n        {\n            if (!char.IsAsciiLetterOrDigit(c) && c is not '/' and not '-' and not '_')\n            {\n                throw new ArgumentException($\"The apiPath must contain only URL-safe path characters (alphanumeric, '/', '-', '_'). Invalid character: '{c}'\", nameof(apiPath));\n            }\n        }\n    }\n\n    /// <summary>\n    /// Walks up from <paramref name=\"startDirectory\"/> to find the nearest <c>node_modules</c> directory.\n    /// </summary>\n    private static string? FindNearestNodeModules(string startDirectory)\n    {\n        var current = Path.GetFullPath(startDirectory);\n        while (current is not null)\n        {\n            var candidate = Path.Join(current, \"node_modules\");\n            if (Directory.Exists(candidate))\n            {\n                return candidate;\n            }\n","sourceCodeStart":3181,"sourceCodeEnd":3217,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs#L3181-L3217","documentation":"ValidateApiPath enforces that the apiPath argument to AddNextJsApp contains only ASCII letters, digits, '/', '-', and '_', because it becomes part of a URL route and is embedded in generated configuration. Any other character (spaces, dots, query chars, Unicode) throws an ArgumentException naming the offending character.","triggerScenarios":"Passing an apiPath containing characters outside [A-Za-z0-9/-_], e.g. \"/api/v1.0\", \"api data\", \"/api?x=1\", or a Unicode-containing path.","commonSituations":"Including version dots or query strings in the API path; trailing/leading whitespace; copy-pasted paths with encoded characters (%20) or Windows separators (\\).","solutions":["Rewrite the apiPath using only letters, digits, '/', '-', and '_' (e.g. \"/api/v1-data\").","Percent-encode or remove special characters; move query parameters out of the path.","Trim whitespace from the path before passing it."],"exampleFix":"// before\nbuilder.AddNextJsApp(\"web\", \"./web\", options => options.ApiPath = \"/api/v1.0\");\n// after\nbuilder.AddNextJsApp(\"web\", \"./web\", options => options.ApiPath = \"/api/v1-0\");","handlingStrategy":"validation","validationCode":"static bool IsSafeApiPath(string? path) =>\n    !string.IsNullOrEmpty(path) && path.All(c => char.IsAsciiLetterOrDigit(c) || c is '/' or '-' or '_');","typeGuard":"bool IsSafeApiPath(string? path) => !string.IsNullOrEmpty(path) && path.All(c => char.IsAsciiLetterOrDigit(c) || c is '/' or '-' or '_');","tryCatchPattern":"try { builder.AddNextJsApp(\"web\", \"./web\", o => o.ApiPath = apiPath); } catch (ArgumentException ex) when (ex.Message.Contains(\"URL-safe\")) { /* sanitize apiPath */ }","preventionTips":["Sanitize user/team-supplied paths with a whitelist regex before use.","Never embed query strings, dots, or spaces in ApiPath.","Add unit tests for path validation in your build pipeline."],"tags":["javascript","nextjs","validation","url"],"backgroundTag":"invalid-argument-format","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}