HttpRequestException: Response status code does not indicate success: 404 (Not Found). comes from EnsureSuccessStatusCode(): the server answered, and it answered 404 for the URL your client built. Read the status, the requested URI and the body before you throw; in the test below that output showed the wrong part of the URL at once.

Replace the bare EnsureSuccessStatusCode() with a check of IsSuccessStatusCode that prints RequestMessage.RequestUri and the response body, then fix the path it shows.

The error

PS ...\Cli> dotnet run --no-build -- 404
Unhandled exception. System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at Program.<Main>$(String[] args) in ...\Cli\Program.cs:line 57
   at Program.<Main>(String[] args)

The code that threw:

using var http = new HttpClient { BaseAddress = new Uri("http://127.0.0.1:15801/api/v1/") };
using var resp = await http.GetAsync("patient");
resp.EnsureSuccessStatusCode();

Why it happens

GetAsync does not throw for an error status; it returns the response. The exception is raised by EnsureSuccessStatusCode(), as the first line of the stack trace shows, and its message carries only the number and the reason phrase. It does not say which URL was requested or what the server wrote back, and those two are what tell you whether the path is wrong, the record does not exist, or the route was never mapped.

Here the route on the test server is /api/v1/patients and the client asked for patient, singular. The exception alone does not show that.

The fix

First make the failure readable:

using var http = new HttpClient { BaseAddress = new Uri("http://127.0.0.1:15801/api/v1/") };
using var resp = await http.GetAsync("patient");
if (!resp.IsSuccessStatusCode)
{
    var body = await resp.Content.ReadAsStringAsync();
    Console.WriteLine($"{(int)resp.StatusCode} {resp.ReasonPhrase} for {resp.RequestMessage!.RequestUri}");
    Console.WriteLine("Body: " + body);
    return;
}
PS ...\Cli> dotnet run --no-build -- 404-fixed
404 Not Found for http://127.0.0.1:15801/api/v1/patient
Body: 404 for /api/v1/patient

The output names the requested URI, and the misspelt segment is visible at once. With the path corrected to "patients", the same client and base address returned the list:

PS ...\Cli> dotnet run --no-build -- fixed
200 from /api/v1/patients: Maria Garcia, David Chen

Keep the readable check in place after the fix: the next 404 will then print its URI and body too, instead of only the status line. If the requested URI looks right but has lost a segment such as v1, check the slashes on BaseAddress and on the path.

How it was reproduced

A console app (dotnet new console, no packages) on .NET SDK 10.0.403 called a minimal API (dotnet new web) on http://127.0.0.1:15801. The API maps GET /api/v1/patients and a fallback that answers 404 with the text 404 for and the requested path. The client asked for /api/v1/patient and called EnsureSuccessStatusCode() on the response.

Frequently asked

How do I see the response body when EnsureSuccessStatusCode throws?
Check IsSuccessStatusCode first and read resp.Content.ReadAsStringAsync() before throwing. The exception message itself carried only the status code and reason phrase in the test.
Does HttpClient GetAsync throw on 404?
No. In the test GetAsync returned the 404 response, and the exception came from the EnsureSuccessStatusCode call on the next line.
How do I find which URL HttpClient actually requested?
Print response.RequestMessage.RequestUri. In the test it showed http://127.0.0.1:15801/api/v1/patient, which exposed the missing s in the path.

More decoded errors in the Fixes category. The slash rules for BaseAddress, tested against the same server, are in An invalid request URI was provided.