diff --git a/services/bff/Bff.Api/Bff.Api.csproj b/services/bff/Bff.Api/Bff.Api.csproj
index cb4f665..1187014 100644
--- a/services/bff/Bff.Api/Bff.Api.csproj
+++ b/services/bff/Bff.Api/Bff.Api.csproj
@@ -11,6 +11,7 @@
+
diff --git a/services/bff/Bff.Api/Program.cs b/services/bff/Bff.Api/Program.cs
index dcadd14..45e6d13 100644
--- a/services/bff/Bff.Api/Program.cs
+++ b/services/bff/Bff.Api/Program.cs
@@ -3,6 +3,7 @@ using System.Text.Json;
using System.Text.Json.Serialization;
using Bff.Api;
using Microsoft.AspNetCore.Authentication.JwtBearer;
+using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
@@ -18,7 +19,16 @@ builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation(o => o.Filter = ctx => ctx.Request.Path != "/health")
.AddHttpClientInstrumentation()
- .AddOtlpExporter());
+ .AddOtlpExporter())
+ // OpenTelemetry metrics (S-16c, ADR-0023): the golden signals for the request path —
+ // http.server.request.duration (traffic/errors/latency) + http.client.* for the downstream hops,
+ // plus the built-in System.Runtime meter for saturation (GC, CPU, thread pool). Prometheus scrapes
+ // these from /metrics (mapped below); no OTLP push for metrics, so no collector hop (ADR-0023).
+ .WithMetrics(metrics => metrics
+ .AddAspNetCoreInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddMeter("System.Runtime")
+ .AddPrometheusExporter());
var keycloakAuthority = builder.Configuration["Keycloak:Authority"]
?? throw new InvalidOperationException("Missing configuration 'Keycloak:Authority'");
@@ -82,6 +92,9 @@ app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/health");
+
+// Prometheus scrape endpoint (S-16c): exposes the OTel metrics above in Prometheus text format.
+app.MapPrometheusScrapingEndpoint();
app.MapOpenApi();
// Self-service submit: requires a valid digid token; the bsn comes from the token, not the body,