diff --git a/src/main/java/io/github/randomcodespace/iq/api/GlobalExceptionHandler.java b/src/main/java/io/github/randomcodespace/iq/api/GlobalExceptionHandler.java
new file mode 100644
index 00000000..dd06aaca
--- /dev/null
+++ b/src/main/java/io/github/randomcodespace/iq/api/GlobalExceptionHandler.java
@@ -0,0 +1,71 @@
+package io.github.randomcodespace.iq.api;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
+import org.springframework.context.annotation.Profile;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Uniform error envelope for the REST API: {@code {"code","message","request_id"}}
+ * with the appropriate HTTP status. Stack traces and class names never reach the
+ * response body — only logged at WARN with the {@code request_id} so on-call can
+ * correlate.
+ *
+ * Active in the {@code serving} profile only.
+ */
+@RestControllerAdvice
+@Profile("serving")
+public class GlobalExceptionHandler {
+
+ private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+ @ExceptionHandler(ResponseStatusException.class)
+ public ResponseEntity