diff --git a/.gitignore b/.gitignore
index ff8262c..ba1194e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -480,3 +480,5 @@ FodyWeavers.xsd
*/data_source/other
.fake
.idea
+
+*.feature.cs
\ No newline at end of file
diff --git a/src/Core/API/API.Specs/API.Specs.csproj b/src/Core/API/API.Specs/API.Specs.csproj
new file mode 100644
index 0000000..ba032cb
--- /dev/null
+++ b/src/Core/API/API.Specs/API.Specs.csproj
@@ -0,0 +1,37 @@
+
+
+ net10.0
+ enable
+ enable
+ false
+ API.Specs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Core/API/API.Specs/Features/NotFound.feature b/src/Core/API/API.Specs/Features/NotFound.feature
new file mode 100644
index 0000000..68806c8
--- /dev/null
+++ b/src/Core/API/API.Specs/Features/NotFound.feature
@@ -0,0 +1,10 @@
+Feature: NotFound API
+ As a client of the API
+ I want consistent 404 responses
+ So that consumers can handle missing routes
+
+ Scenario: GET error 404 returns NotFound message
+ Given the API is running
+ When I GET "/error/404"
+ Then the response status code should be 404
+ And the response JSON should have "message" equal "Route not found."
diff --git a/src/Core/API/API.Specs/Steps/ApiSteps.cs b/src/Core/API/API.Specs/Steps/ApiSteps.cs
new file mode 100644
index 0000000..a7fb6b3
--- /dev/null
+++ b/src/Core/API/API.Specs/Steps/ApiSteps.cs
@@ -0,0 +1,51 @@
+using System.Net;
+using System.Net.Http.Json;
+using Reqnroll;
+using FluentAssertions;
+
+namespace API.Specs.Steps;
+
+[Binding]
+public class ApiSteps
+{
+ private readonly TestApiFactory _factory;
+ private HttpClient? _client;
+ private HttpResponseMessage? _response;
+
+ public ApiSteps()
+ {
+ _factory = new TestApiFactory();
+ }
+
+ [Given("the API is running")]
+ public void GivenTheApiIsRunning()
+ {
+ _client = _factory.CreateClient();
+ }
+
+ // No user service assumptions needed for 404 tests
+
+ [When("I GET {string}")]
+ public async Task WhenIGet(string path)
+ {
+ _client.Should().NotBeNull("API client must be initialized");
+ _response = await _client!.GetAsync(path);
+ }
+
+ [Then("the response status code should be {int}")]
+ public void ThenStatusCodeShouldBe(int expected)
+ {
+ _response.Should().NotBeNull();
+ ((int)_response!.StatusCode).Should().Be(expected);
+ }
+
+ [Then("the response JSON should have {string} equal {string}")]
+ public async Task ThenResponseJsonShouldHaveFieldEqual(string field, string expected)
+ {
+ _response.Should().NotBeNull();
+ var dict = await _response!.Content.ReadFromJsonAsync>();
+ dict.Should().NotBeNull();
+ dict!.TryGetValue(field, out var value).Should().BeTrue();
+ (value?.ToString()).Should().Be(expected);
+ }
+}
diff --git a/src/Core/API/API.Specs/reqnroll.json b/src/Core/API/API.Specs/reqnroll.json
new file mode 100644
index 0000000..2511d20
--- /dev/null
+++ b/src/Core/API/API.Specs/reqnroll.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://raw.githubusercontent.com/reqnroll/Reqnroll/main/Reqnroll.Configuration/reqnroll.schema.json",
+ "language": {
+ "feature": "en-US"
+ },
+ "bindingCulture": {
+ "name": "en-US"
+ },
+ "trace": {
+ "level": "Verbose"
+ },
+ "runtime": {
+ "stopAtFirstError": false
+ }
+}