diff --git a/ci/test_self_hosted_service.sh b/ci/test_self_hosted_service.sh
index c64554883..5fd8c00f6 100755
--- a/ci/test_self_hosted_service.sh
+++ b/ci/test_self_hosted_service.sh
@@ -111,16 +111,16 @@ if [ "$doservertest" -eq 1 ]; then
     run_cli_test "'status': 0" cuopt_sh -s -c "$CLIENT_CERT" -p $CUOPT_SERVER_PORT -f cuopt_problem_data.json
 
     # Success, small LP problem with pure JSON
-    run_cli_test "'status': 1" cuopt_sh -s -c "$CLIENT_CERT" -p $CUOPT_SERVER_PORT -t LP ../../datasets/cuopt_service_data/good_lp.json
+    run_cli_test "'status': 'Optimal'" cuopt_sh -s -c "$CLIENT_CERT" -p $CUOPT_SERVER_PORT -t LP ../../datasets/cuopt_service_data/good_lp.json
 
     # Success, small MILP problem with pure JSON which returns a solution with Optimal status
-    run_cli_test "'status': 1" cuopt_sh -s -c $CLIENT_CERT -p $CUOPT_SERVER_PORT -t LP ../../datasets/mixed_integer_programming/milp_data.json
+    run_cli_test "'status': 'Optimal'" cuopt_sh -s -c $CLIENT_CERT -p $CUOPT_SERVER_PORT -t LP ../../datasets/mixed_integer_programming/milp_data.json
 
     # Succes, small LP problem with mps. Data will be transformed to JSON
-    run_cli_test "'status': 1" cuopt_sh -s -c "$CLIENT_CERT" -p $CUOPT_SERVER_PORT -t LP ../../datasets/linear_programming/good-mps-1.mps
+    run_cli_test "'status': 'Optimal'" cuopt_sh -s -c "$CLIENT_CERT" -p $CUOPT_SERVER_PORT -t LP ../../datasets/linear_programming/good-mps-1.mps
 
     # Succes, small Batch LP problem with mps. Data will be transformed to JSON
-    run_cli_test "'status': 1" cuopt_sh -s -c "$CLIENT_CERT" -p $CUOPT_SERVER_PORT -t LP ../../datasets/linear_programming/good-mps-1.mps ../../datasets/linear_programming/good-mps-1.mps
+    run_cli_test "'status': 'Optimal'" cuopt_sh -s -c "$CLIENT_CERT" -p $CUOPT_SERVER_PORT -t LP ../../datasets/linear_programming/good-mps-1.mps ../../datasets/linear_programming/good-mps-1.mps
 
     # Error, local file mode is not allowed with mps
     run_cli_test "Cannot use local file mode with MPS data" cuopt_sh -s -c "$CLIENT_CERT" -p $CUOPT_SERVER_PORT -t LP -f good-mps-1.mps
@@ -166,8 +166,8 @@ if [ "$doservertest" -eq 1 ]; then
     run_cli_test "Check for status with the following command" cuopt_sh -s -c "$CLIENT_CERT" -p "$CUOPT_SERVER_PORT" -pt 0 ../../datasets/cuopt_service_data/good_lp.json -k
     requestid=$(echo "$cli_test" | tail -1)
     requestid=$(echo ${requestid#cuopt_sh } | sed "s/-p $CUOPT_SERVER_PORT//g" | tr -d "'" | tr -d " ")
-    run_cli_test "'status': 1" cuopt_sh -s -c $CLIENT_CERT -p $CUOPT_SERVER_PORT $requestid -k
-    run_cli_test "'status': 1" cuopt_sh -s -c $CLIENT_CERT -p $CUOPT_SERVER_PORT ../../datasets/cuopt_service_data/good_lp.json -wid $requestid
+    run_cli_test "'status': 'Optimal'" cuopt_sh -s -c $CLIENT_CERT -p $CUOPT_SERVER_PORT $requestid -k
+    run_cli_test "'status': 'Optimal'" cuopt_sh -s -c $CLIENT_CERT -p $CUOPT_SERVER_PORT ../../datasets/cuopt_service_data/good_lp.json -wid $requestid
 
     # Success, larger problem, result comes back in results dir
     run_cli_test "'result_file': 'data.result'" cuopt_sh -s -c "$CLIENT_CERT" -p $CUOPT_SERVER_PORT ../../datasets/cuopt_service_data/service_data_200r.json -o data.result
diff --git a/docs/cuopt/source/cuopt-server/client-api/sh-cli-api.rst b/docs/cuopt/source/cuopt-server/client-api/sh-cli-api.rst
index 443a6b581..28374ed16 100644
--- a/docs/cuopt/source/cuopt-server/client-api/sh-cli-api.rst
+++ b/docs/cuopt/source/cuopt-server/client-api/sh-cli-api.rst
@@ -25,11 +25,6 @@ LP Supporting Classes
     :undoc-members:
     :no-inherited-members:
 
-.. autoclass:: solution.solution.PDLPWarmStartData
-    :members:
-    :undoc-members:
-    :no-inherited-members:
-
 .. autoclass:: data_model.DataModel
     :members:
     :undoc-members:
@@ -38,11 +33,10 @@ LP Supporting Classes
     :members:
     :undoc-members:
 
-.. autoclass:: solution.Solution
+.. autoclass:: cuopt_sh_client.ThinClientSolution
     :members:
     :undoc-members:
 
-
 Self-Hosted Client CLI
 ----------------------
 
diff --git a/docs/cuopt/source/cuopt-server/examples/lp-examples.rst b/docs/cuopt/source/cuopt-server/examples/lp-examples.rst
index 01bb356ff..6d5b780b6 100644
--- a/docs/cuopt/source/cuopt-server/examples/lp-examples.rst
+++ b/docs/cuopt/source/cuopt-server/examples/lp-examples.rst
@@ -115,9 +115,9 @@ Normal mode response:
     {
         "response": {
             "solver_response": {
-                "status": 1,
+                "status": "Optimal",
                 "solution": {
-                    "problem_category": 0,
+                    "problem_category": "LP",
                     "primal_solution": [
                         1.8,
                         0.0
@@ -159,9 +159,9 @@ Batch mode response:
         "response": {
             "solver_response": [
                 {
-                    "status": 1,
+                    "status": "Optimal",
                     "solution": {
-                        "problem_category": 0,
+                        "problem_category": "LP",
                         "primal_solution": [
                             1.8,
                             0.0
@@ -188,9 +188,9 @@ Batch mode response:
                     }
                 },
                 {
-                    "status": 1,
+                    "status": "Optimal",
                     "solution": {
-                        "problem_category": 0,
+                        "problem_category": "LP",
                         "primal_solution": [
                             1.8,
                             0.0
@@ -294,9 +294,9 @@ The response would be as follows:
     {
         "response": {
             "solver_response": {
-                "status": 1,
+                "status": "Optimal",
                 "solution": {
-                    "problem_category": 0,
+                    "problem_category": "LP",
                     "primal_solution": [
                         1.8,
                         0.0
@@ -388,9 +388,9 @@ The response is:
     {
         "response": {
             "solver_response": {
-                "status": 1,
+                "status": "Optimal",
                 "solution": {
-                    "problem_category": 0,
+                    "problem_category": "LP",
                     "primal_solution": [
                         1.8,
                         0.0
@@ -515,23 +515,22 @@ Use a datamodel generated from mps file as input; this yields a solution object
     solution_obj = solution["response"]["solver_response"]["solution"]
 
     # Check Termination Reason
-    # For more detail on termination reasons: checkout `Solution.get_termination_reason()`
-    print("Termination Reason: (1 is Optimal)")
+    print("Termination Reason: ")
     print(solution_status)
 
     # Check found objective value
     print("Objective Value:")
-    print(solution_obj["primal_objective"])
+    print(solution_obj.get_primal_objective())
 
     # Check the MPS parse time
     print(f"Mps Parse time: {parse_time:.3f} sec")
 
     # Check network time (client call - solve time)
-    network_time = network_time - (solution_obj["solver_time"])
+    network_time = network_time - (solution_obj.get_solve_time())
     print(f"Network time: {network_time:.3f} sec")
 
     # Check solver time
-    solve_time = solution_obj["solver_time"]
+    solve_time = solution_obj.get_solve_time()
     print(f"Engine Solve time: {solve_time:.3f} sec")
 
     # Check the total end to end time (mps parsing + network + solve time)
@@ -540,7 +539,7 @@ Use a datamodel generated from mps file as input; this yields a solution object
 
     # Print the found decision variables
     print("Variables Values:")
-    print(solution_obj["vars"])
+    print(solution_obj.get_vars())
 
 
 The response would be as follows:
@@ -625,9 +624,9 @@ Response is as follows:
     {
         "response": {
             "solver_response": {
-                "status": 1,
+                "status": "Optimal",
                 "solution": {
-                    "problem_category": 0,
+                    "problem_category": "LP",
                     "primal_solution": [1.8, 0.0],
                     "dual_solution": [-0.06666666666666668, 0.0],
                     "primal_objective": -0.36000000000000004,
@@ -713,4 +712,4 @@ Aborting a Running Job In CLI
 Please refer to the `MILP Example <milp-examples.html#aborting-a-running-job-in-cli>`_ for more details.
 
 .. note::
-   Please use solver settings while using .mps files.
\ No newline at end of file
+   Please use solver settings while using .mps files.
diff --git a/docs/cuopt/source/cuopt-server/examples/milp-examples.rst b/docs/cuopt/source/cuopt-server/examples/milp-examples.rst
index c29c693a5..be76fb97e 100644
--- a/docs/cuopt/source/cuopt-server/examples/milp-examples.rst
+++ b/docs/cuopt/source/cuopt-server/examples/milp-examples.rst
@@ -86,9 +86,9 @@ The response would be as follows:
     {
         "response": {
             "solver_response": {
-                "status": 1,
+                "status": "Optimal",
                 "solution": {
-                    "problem_category": 1,
+                    "problem_category": "MIP",
                     "primal_solution": [
                         0.0,
                         5000.0
@@ -210,9 +210,9 @@ Incumbent callback response:
     {
         "response": {
             "solver_response": {
-                "status": 1,
+                "status": "Optimal",
                 "solution": {
-                    "problem_category": 1,
+                    "problem_category": "MIP",
                     "primal_solution": [
                         0.0,
                         5000.0
diff --git a/docs/cuopt/source/thirdparty_modeling_languages/index.rst b/docs/cuopt/source/thirdparty_modeling_languages/index.rst
index 8a5024e9e..a6ba110f0 100644
--- a/docs/cuopt/source/thirdparty_modeling_languages/index.rst
+++ b/docs/cuopt/source/thirdparty_modeling_languages/index.rst
@@ -7,7 +7,7 @@ Third-Party Modeling Languages
 AMPL Support
 --------------------------
 
-AMPL can be used with near zero code changes: simply switch to cuOpt as a solver to solve linear and mixed-integer programming problems. Please refer to the `AMPL documentation <https://www.ampl.com/>`_ for more information.
+AMPL can be used with near zero code changes: simply switch to cuOpt as a solver to solve linear and mixed-integer programming problems. Please refer to the `AMPL documentation <https://www.ampl.com/>`_ for more information. Also, see the example notebook in the `colab <https://colab.research.google.com/drive/1eEQik_pae4g_tJQ61QJFlO1fFBXazpBr?usp=sharing>`_.
 
 --------------------------
 PuLP Support
diff --git a/python/cuopt/cuopt/linear_programming/pyproject.toml b/python/cuopt/cuopt/linear_programming/pyproject.toml
index 47c94b139..e3ef785c0 100644
--- a/python/cuopt/cuopt/linear_programming/pyproject.toml
+++ b/python/cuopt/cuopt/linear_programming/pyproject.toml
@@ -55,7 +55,7 @@ cmake.version = "CMakeLists.txt"
 minimum-version = "build-system.requires"
 ninja.make-fallback = false
 sdist.reproducible = true
-wheel.packages=["data_model", "solution", "cuopt_mps_parser"]
+wheel.packages=["data_model", "cuopt_mps_parser"]
 
 [tool.scikit-build.metadata.version]
 provider = "scikit_build_core.metadata.regex"
diff --git a/python/cuopt_self_hosted/cuopt_sh_client/__init__.py b/python/cuopt_self_hosted/cuopt_sh_client/__init__.py
index 6dfc92bf2..38069d79e 100644
--- a/python/cuopt_self_hosted/cuopt_sh_client/__init__.py
+++ b/python/cuopt_self_hosted/cuopt_sh_client/__init__.py
@@ -22,6 +22,7 @@
     mime_type,
     set_log_level,
 )
+from .thin_client_solution import ThinClientSolution
 from .thin_client_solver_settings import (
     PDLPSolverMode,
     SolverMethod,
diff --git a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py
index 03a3e23a3..7418516f9 100644
--- a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py
+++ b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py
@@ -28,9 +28,9 @@
 import msgpack_numpy
 import numpy as np
 import requests
-import solution
 
 from . import _version
+from .thin_client_solution import ThinClientSolution
 from .thin_client_solver_settings import ThinClientSolverSettings
 
 msgpack_numpy.patch()
@@ -173,8 +173,8 @@ def create_solution_obj(solver_response):
         problem_category = sol["problem_category"]
 
         # MILP
-        if problem_category in [1, 2]:
-            solution_obj = solution.Solution(
+        if problem_category in ["MIP", "IP"]:
+            solution_obj = ThinClientSolution(
                 problem_category,
                 sol["vars"],
                 solve_time=sol["solver_time"],
@@ -198,7 +198,7 @@ def create_solution_obj(solver_response):
                 ],
             )
         else:
-            solution_obj = solution.Solution(
+            solution_obj = ThinClientSolution(
                 problem_category,
                 sol["vars"],
                 solve_time=sol["solver_time"],
@@ -716,7 +716,7 @@ def get_LP_solve(
             See the LP documentation for details on solver settings.
         response_type : str
             Choose "dict" if response should be returned as a dictionary or
-            "obj" for Solution object. Defaults to "obj"
+            "obj" for ThinClientSolution object. Defaults to "obj"
         filepath : boolean
             Indicates that cuopt_problem_json_data
             is the relative path of a cuopt data file under the server's
@@ -750,7 +750,7 @@ def get_LP_solve(
             a list of strings. The LP solver will not return any
             incumbent solutions. Default is None.
 
-        Returns: dict or Solution object.
+        Returns: dict or ThinClientSolution object.
         """
 
         if incumbent_callback is not None and not callable(incumbent_callback):
@@ -923,7 +923,7 @@ def repoll(self, data, response_type="obj", delete_solution=True):
             containing the key 'reqId' where the value is the uuid.
         response_type: str
             For LP problem choose "dict" if response should be returned
-            as a dictionary or "obj" for Solution object.
+            as a dictionary or "obj" for ThinClientSolution object.
             Defaults to "obj".
             For VRP problem, response_type is ignored and always
             returns a dict.
diff --git a/python/cuopt_self_hosted/cuopt_sh_client/thin_client_solution.py b/python/cuopt_self_hosted/cuopt_sh_client/thin_client_solution.py
new file mode 100644
index 000000000..1db49a4fc
--- /dev/null
+++ b/python/cuopt_self_hosted/cuopt_sh_client/thin_client_solution.py
@@ -0,0 +1,306 @@
+# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.  # noqa
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+class ThinClientSolution:
+    """
+    A container of LP solver output
+
+    Parameters
+    ----------
+    problem_category : str
+        Whether it is a LP, MIP or IP solution
+    vars : Dict[str, float64]
+        Dictionary mapping each variable (name) to its value.
+    primal_solution : numpy.array
+        Primal solution of the LP problem
+    dual_solution : numpy.array
+        Note: Applicable to only LP
+        Dual solution of the LP problem
+    reduced_cost : numpy.array
+        Note: Applicable to only LP
+        The reduced cost.
+        It contains the dual multipliers for the linear constraints.
+    termination_status: str
+        Termination status .
+    primal_residual: Float64
+        L2 norm of the primal residual: measurement of the primal infeasibility
+    dual_residual: Float64
+        Note: Applicable to only LP
+        L2 norm of the dual residual: measurement of the dual infeasibility
+    primal_objective: Float64
+        Value of the primal objective
+    dual_objective: Float64
+        Note: Applicable to only LP
+        Value of the dual objective
+    gap: Float64
+        Difference between the primal and dual objective
+    nb_iterations: Int
+        Number of iterations the LP solver did before converging
+    mip_gap: float64
+        Note: Applicable to only MILP
+        The relative difference between the best integer objective value
+        found so far and the objective bound. A value of 0.01 means the
+        solution is guaranteed to be within 1% of optimal.
+    solution_bound: float64
+        Note: Applicable to only MILP
+        The best known bound on the optimal objective value.
+        For minimization problems, this is a lower bound on the optimal value.
+        For maximization problems, this is an upper bound.
+    max_constraint_violation: float64
+        Note: Applicable to only MILP
+        The maximum amount by which any constraint is violated in
+        the current solution. Should be close to zero for a feasible solution.
+    max_int_violation: float64
+        Note: Applicable to only MILP
+        The maximum amount by which any integer variable deviates from being
+        an integer. A value of 0 means all integer variables have
+        integral values.
+    max_variable_bound_violation: float64
+        Note: Applicable to only MILP
+        The maximum amount by which any variable violates its upper or
+        lower bounds in the current solution. Should be zero for a
+        feasible solution.
+    presolve_time: float64
+        Note: Applicable to only MILP
+        Time used for pre-solve
+    solve_time: Float64
+        Solve time in seconds
+    solved_by_pdlp: bool
+        Whether the problem was solved by PDLP or Dual Simplex
+    """
+
+    def __init__(
+        self,
+        problem_category,
+        vars,
+        solve_time=0.0,
+        primal_solution=None,
+        dual_solution=None,
+        reduced_cost=None,
+        termination_status="Error",
+        error_status=0,
+        error_message="",
+        primal_residual=0.0,
+        dual_residual=0.0,
+        primal_objective=0.0,
+        dual_objective=0.0,
+        gap=0.0,
+        nb_iterations=0,
+        solved_by_pdlp=None,
+        mip_gap=0.0,
+        solution_bound=0.0,
+        presolve_time=0.0,
+        max_constraint_violation=0.0,
+        max_int_violation=0.0,
+        max_variable_bound_violation=0.0,
+        num_nodes=0,
+        num_simplex_iterations=0,
+    ):
+        self.problem_category = problem_category
+        self.primal_solution = primal_solution
+        self.dual_solution = dual_solution
+        self.termination_status = termination_status
+        self.error_status = error_status
+        self.error_message = error_message
+
+        self.primal_objective = primal_objective
+        self.dual_objective = dual_objective
+        self.solve_time = solve_time
+        self.solved_by_pdlp = solved_by_pdlp
+        self.vars = vars
+        self.lp_stats = {
+            "primal_residual": primal_residual,
+            "dual_residual": dual_residual,
+            "gap": gap,
+            "nb_iterations": nb_iterations,
+        }
+        self.reduced_cost = reduced_cost
+        self.milp_stats = {
+            "mip_gap": mip_gap,
+            "solution_bound": solution_bound,
+            "presolve_time": presolve_time,
+            "max_constraint_violation": max_constraint_violation,
+            "max_int_violation": max_int_violation,
+            "max_variable_bound_violation": max_variable_bound_violation,
+            "num_nodes": num_nodes,
+            "num_simplex_iterations": num_simplex_iterations,
+        }
+
+    def raise_if_milp_solution(self, function_name):
+        if self.problem_category in ("MIP", "IP"):
+            raise AttributeError(
+                f"Attribute {function_name} is not supported for milp solution"
+            )
+
+    def raise_if_lp_solution(self, function_name):
+        if self.problem_category == "LP":
+            raise AttributeError(
+                f"Attribute {function_name} is not supported for lp solution"
+            )
+
+    def get_primal_solution(self):
+        """
+        Returns the primal solution as numpy.array with float64 type.
+        """
+        return self.primal_solution
+
+    def get_dual_solution(self):
+        """
+        Note: Applicable to only LP
+        Returns the dual solution as numpy.array with float64 type.
+        """
+        self.raise_if_milp_solution(__name__)
+        return self.dual_solution
+
+    def get_primal_objective(self):
+        """
+        Returns the primal objective as a float64.
+        """
+        return self.primal_objective
+
+    def get_dual_objective(self):
+        """
+        Note: Applicable to only LP
+        Returns the dual objective as a float64.
+        """
+        self.raise_if_milp_solution(__name__)
+        return self.dual_objective
+
+    def get_termination_status(self):
+        """
+        Returns the termination status.
+        """
+        return self.termination_status
+
+    def get_error_status(self):
+        """
+        Returns the error status as per ErrorStatus.
+        """
+        return self.error_status
+
+    def get_error_message(self):
+        """
+        Returns the error message as per ErrorMessage.
+        """
+        return self.error_message
+
+    def get_solve_time(self):
+        """
+        Returns the engine solve time in seconds as a float64.
+        """
+        return self.solve_time
+
+    def get_solved_by_pdlp(self):
+        """
+        Returns whether the problem was solved by PDLP or Dual Simplex
+        """
+        return self.solved_by_pdlp
+
+    def get_vars(self):
+        """
+        Returns the dictionnary mapping each variable (name) to its value.
+        """
+        return self.vars
+
+    def get_lp_stats(self):
+        """
+        Note: Applicable to only LP
+        Returns the convergence statistics as a dictionary:
+
+        "primal_residual": float64
+          Measurement of the primal infeasibility.
+          This quantity is being reduced until primal tolerance is met
+          (see SolverSettings primal_tolerance).
+
+        "dual_residual": float64,
+          Measurement of the dual infeasibility.
+          This quantity is being reduced until dual tolerance is met
+          (see SolverSettings dual_tolerance).
+
+        "gap": float64
+          Difference between the primal and dual objective.
+          This quantity is being reduced until gap tolerance is met
+          (see SolverSettings gap_tolerance).
+
+        - "nb_iterations": int
+            Number of iterations the LP solver did before converging.
+        """
+
+        self.raise_if_milp_solution(__name__)
+
+        return self.lp_stats
+
+    def get_reduced_cost(self):
+        """
+        Returns the reduced cost as numpy.array with float64 type.
+        """
+        return self.reduced_cost
+
+    def get_milp_stats(self):
+        """
+        Note: Applicable to only MILP
+        Returns the convergence statistics as a dictionary:
+
+        mip_gap: float64
+            The relative difference between the best integer objective value
+            found so far and the objective bound. A value of 0.01 means the
+            solution is guaranteed to be within 1% of optimal.
+
+        presolve_time: float64
+            Time took for pre-solve
+
+        max_constraint_violation: float64
+            The maximum amount by which any constraint is violated
+            in the current solution.
+            Should be close to zero for a feasible solution
+            .
+        max_int_violation: float64
+            The maximum amount by which any integer variable deviates
+            from being an integer. A value of 0 means all integer variables
+            have integral values.
+
+        max_variable_bound_violation: float64
+            The maximum amount by which any variable violates
+            its upper or lower bounds in the current solution.
+            Should be zero for a feasible solution.
+
+        solution_bound: float64
+            The best known bound on the optimal objective value.
+            For minimization problems, this is a lower bound on the optimal
+            value.
+            For maximization problems, this is an upper bound.
+
+        num_nodes: int
+            Number of nodes explored during the MIP solve
+
+        num_simplex_iterations: int
+            Number of simplex iterations performed during the MIP solve
+        """
+
+        self.raise_if_lp_solution(__name__)
+
+        return self.milp_stats
+
+    def get_problem_category(self):
+        """
+        Returns one of the problem category from ProblemCategory
+
+        LP
+        MIP
+        IP
+        """
+
+        return self.problem_category
diff --git a/python/cuopt_server/cuopt_server/tests/test_lp.py b/python/cuopt_server/cuopt_server/tests/test_lp.py
index 449756fe0..8fc85aa3a 100644
--- a/python/cuopt_server/cuopt_server/tests/test_lp.py
+++ b/python/cuopt_server/cuopt_server/tests/test_lp.py
@@ -32,7 +32,7 @@ def validate_lp_result(
     expected_termination_status,
 ):
     sol = res["solution"]
-    assert sol["problem_category"] == ProblemCategory.LP
+    assert sol["problem_category"] == ProblemCategory.LP.name
     assert res["status"] == expected_termination_status
     assert len(sol["lp_statistics"]) > 1
 
@@ -45,7 +45,10 @@ def validate_milp_result(
     expected_termination_status,
 ):
     sol = res["solution"]
-    assert sol["problem_category"] in (ProblemCategory.MIP, ProblemCategory.IP)
+    assert sol["problem_category"] in (
+        ProblemCategory.MIP.name,
+        ProblemCategory.IP.name,
+    )
     assert res["status"] == expected_termination_status
     assert len(sol["milp_statistics"].keys()) > 0
 
@@ -111,17 +114,18 @@ def test_sample_lp(cuoptproc):  # noqa
 
     print(res.json())
     validate_lp_result(
-        res.json()["response"]["solver_response"], LPTerminationStatus.Optimal
+        res.json()["response"]["solver_response"],
+        LPTerminationStatus.Optimal.name,
     )
 
 
 @pytest.mark.parametrize(
     "maximize, scaling, expected_status, heuristics_only",
     [
-        (True, True, MILPTerminationStatus.Optimal, True),
-        (False, True, MILPTerminationStatus.Optimal, False),
-        (True, False, MILPTerminationStatus.Optimal, True),
-        (False, False, MILPTerminationStatus.Optimal, False),
+        (True, True, MILPTerminationStatus.Optimal.name, True),
+        (False, True, MILPTerminationStatus.Optimal.name, False),
+        (True, False, MILPTerminationStatus.Optimal.name, True),
+        (False, False, MILPTerminationStatus.Optimal.name, False),
     ],
 )
 def test_sample_milp(
diff --git a/python/cuopt_server/cuopt_server/tests/test_pdlp_warmstart.py b/python/cuopt_server/cuopt_server/tests/test_pdlp_warmstart.py
index 1e4eb4f88..577bf2fca 100644
--- a/python/cuopt_server/cuopt_server/tests/test_pdlp_warmstart.py
+++ b/python/cuopt_server/cuopt_server/tests/test_pdlp_warmstart.py
@@ -54,7 +54,7 @@ def test_warmstart(cuoptproc):  # noqa
     )
     assert res.status_code == 200
     response = res.json()["response"]["solver_response"]
-    assert response["status"] == 1
+    assert response["status"] == "Optimal"
     solve_1_iter = response["solution"]["lp_statistics"]["nb_iterations"]
 
     settings.set_optimality_tolerance(1e-3)
@@ -69,7 +69,7 @@ def test_warmstart(cuoptproc):  # noqa
     assert res.status_code == 200
     response = res.json()["response"]["solver_response"]
     reqId = res.json()["reqId"]
-    assert response["status"] == 1
+    assert response["status"] == "Optimal"
     solve_2_iter = response["solution"]["lp_statistics"]["nb_iterations"]
 
     res = client.get(
@@ -90,7 +90,7 @@ def test_warmstart(cuoptproc):  # noqa
     )
     assert res.status_code == 200
     response = res.json()["response"]["solver_response"]
-    assert response["status"] == 1
+    assert response["status"] == "Optimal"
     solve_3_iter = response["solution"]["lp_statistics"]["nb_iterations"]
 
     assert solve_3_iter + solve_2_iter == solve_1_iter
diff --git a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
index 5d1618595..fcb9d0764 100644
--- a/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
+++ b/python/cuopt_server/cuopt_server/utils/linear_programming/solver.py
@@ -370,7 +370,7 @@ def create_solution(sol):
             milp_stats = get_if_attribute_is_valid_else_none(
                 sol.get_milp_stats
             )
-            solution["problem_category"] = sol.get_problem_category()
+            solution["problem_category"] = sol.get_problem_category().name
             solution["primal_solution"] = primal_solution
             solution["dual_solution"] = dual_solution
             solution["primal_objective"] = get_if_attribute_is_valid_else_none(
@@ -392,7 +392,7 @@ def create_solution(sol):
             )
 
         res = {
-            "status": status,
+            "status": status.name,
             "solution": solution,
         }
         notes.append(sol.get_termination_reason())
