diff --git a/docs/src/modules/ROOT/images/quickstart/service/openapi.png b/docs/src/modules/ROOT/images/quickstart/service/openapi.png
new file mode 100644
index 00000000000..af2b48ceba4
Binary files /dev/null and b/docs/src/modules/ROOT/images/quickstart/service/openapi.png differ
diff --git a/docs/src/modules/ROOT/nav.adoc b/docs/src/modules/ROOT/nav.adoc
index 79a8334dd48..e7acc945c7d 100644
--- a/docs/src/modules/ROOT/nav.adoc
+++ b/docs/src/modules/ROOT/nav.adoc
@@ -2,16 +2,39 @@
* xref:planning-ai-concepts.adoc[leveloffset=+1]
* Getting started
** xref:quickstart/overview.adoc[leveloffset=+1]
-** xref:quickstart/hello-world/hello-world-quickstart.adoc[leveloffset=+1]
-** xref:quickstart/quarkus/quarkus-quickstart.adoc[leveloffset=+1]
-** xref:quickstart/spring-boot/spring-boot-quickstart.adoc[leveloffset=+1]
-** xref:quickstart/quarkus-vehicle-routing/quarkus-vehicle-routing-quickstart.adoc[leveloffset=+1]
+** Embed as a library
+*** xref:quickstart/hello-world/hello-world-quickstart.adoc[Hello World Guide]
+*** xref:quickstart/quarkus/quarkus-quickstart.adoc[Quarkus Guide]
+*** xref:quickstart/spring-boot/spring-boot-quickstart.adoc[Spring Boot Guide]
+** xref:quickstart/service/getting-started.adoc[Run as a service (Preview)]
+* Example use cases
+** xref:quickstart/quarkus-vehicle-routing/quarkus-vehicle-routing-quickstart.adoc[Vehicle Routing (Guide)]
+** https://github.com/TimefoldAI/timefold-quickstarts#-employee-scheduling[Employee Scheduling]
+** https://github.com/TimefoldAI/timefold-quickstarts#-maintenance-scheduling[Maintenance Scheduling]
+** https://github.com/TimefoldAI/timefold-quickstarts#-food-packaging[Food Packaging]
+** https://github.com/TimefoldAI/timefold-quickstarts#-order-picking[Order Picking]
+** https://github.com/TimefoldAI/timefold-quickstarts#-school-timetabling[School Timetabling]
+** https://github.com/TimefoldAI/timefold-quickstarts#-facility-location-problem[Facility Location]
+** https://github.com/TimefoldAI/timefold-quickstarts#-conference-scheduling[Conference Scheduling]
+** https://github.com/TimefoldAI/timefold-quickstarts#-bed-allocation-scheduling[Bed Allocation]
+** https://github.com/TimefoldAI/timefold-quickstarts#-flight-crew-scheduling[Flight Crew Scheduling]
+** https://github.com/TimefoldAI/timefold-quickstarts#-meeting-scheduling[Meeting Scheduling]
+** https://github.com/TimefoldAI/timefold-quickstarts#-task-assigning[Task Assigning]
+** https://github.com/TimefoldAI/timefold-quickstarts#-project-job-scheduling[Project Job Scheduling]
+** https://github.com/TimefoldAI/timefold-quickstarts#-sports-league-scheduling[Sports League Scheduling]
+** https://github.com/TimefoldAI/timefold-quickstarts#-tournament-scheduling[Tournament Scheduling]
* Using Timefold Solver
** xref:using-timefold-solver/overview.adoc[leveloffset=+1]
** xref:using-timefold-solver/configuration.adoc[leveloffset=+1]
** xref:using-timefold-solver/modeling-planning-problems.adoc[leveloffset=+1]
** xref:using-timefold-solver/running-the-solver.adoc[leveloffset=+1]
** xref:using-timefold-solver/benchmarking-and-tweaking.adoc[leveloffset=+1]
+** xref:service/overview.adoc[Building a service (Preview)]
+*** xref:service/rest-api.adoc[leveloffset=+1]
+*** xref:service/modeling-changes.adoc[leveloffset=+1]
+*** xref:service/constraint-weights.adoc[leveloffset=+1]
+*** xref:service/demo-data.adoc[leveloffset=+1]
+*** xref:service/exposing-metrics.adoc[leveloffset=+1]
* Constraints and score
** xref:constraints-and-score/overview.adoc[leveloffset=+1]
** xref:constraints-and-score/score-calculation.adoc[leveloffset=+1]
diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/constraint-configuration.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/constraint-configuration.adoc
index 47e1a0f7e82..f833a34b043 100644
--- a/docs/src/modules/ROOT/pages/constraints-and-score/constraint-configuration.adoc
+++ b/docs/src/modules/ROOT/pages/constraints-and-score/constraint-configuration.adoc
@@ -4,6 +4,11 @@
:sectnums:
:icons: font
+:relevance: library-and-service
+:notes: service module has some different patterns to deal with this.
+
+
+
Deciding the correct xref:constraints-and-score/overview.adoc#scoreConstraintWeight[weight] and
xref:constraints-and-score/overview.adoc#scoreLevel[level] for each constraint is not easy.
It often involves negotiating with different stakeholders and their priorities.
diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/overview.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/overview.adoc
index afdef7c9384..dc823e99603 100644
--- a/docs/src/modules/ROOT/pages/constraints-and-score/overview.adoc
+++ b/docs/src/modules/ROOT/pages/constraints-and-score/overview.adoc
@@ -5,7 +5,7 @@
:doctype: book
:sectnums:
:icons: font
-
+:relevance: library-and-service
[#scoreTerminology]
== Score terminology
diff --git a/docs/src/modules/ROOT/pages/design-patterns/design-patterns.adoc b/docs/src/modules/ROOT/pages/design-patterns/design-patterns.adoc
index 19bac9bc983..c3ddf300d82 100644
--- a/docs/src/modules/ROOT/pages/design-patterns/design-patterns.adoc
+++ b/docs/src/modules/ROOT/pages/design-patterns/design-patterns.adoc
@@ -4,6 +4,10 @@
:sectnums:
:icons: font
+:relevance: library-and-service
+:notes: Most of this should be in the "how to model" reference. Design patterns is too weak a term.
+
+
[#designPatternsIntroduction]
== Design patterns introduction
diff --git a/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc b/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc
index abd6616f569..6b02a3f9e08 100644
--- a/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc
+++ b/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc
@@ -1,6 +1,7 @@
= FAQ
:doctype: book
:icons: font
+:relevance: library-and-service
== How is Timefold Solver Licensed?
diff --git a/docs/src/modules/ROOT/pages/integration/integration.adoc b/docs/src/modules/ROOT/pages/integration/integration.adoc
index ba7135e4d0b..78c4192c8b2 100644
--- a/docs/src/modules/ROOT/pages/integration/integration.adoc
+++ b/docs/src/modules/ROOT/pages/integration/integration.adoc
@@ -3,6 +3,10 @@
:doctype: book
:sectnums:
:icons: font
+:relevance: library-and-service
+:notes: Relevant, needs some rewrites. Also should include the "Getting Started" for Quarkus (raw) and Spring
+
+
[#integrationOverview]
diff --git a/docs/src/modules/ROOT/pages/introduction.adoc b/docs/src/modules/ROOT/pages/introduction.adoc
index bb7b7b14776..b6a0c614284 100644
--- a/docs/src/modules/ROOT/pages/introduction.adoc
+++ b/docs/src/modules/ROOT/pages/introduction.adoc
@@ -3,6 +3,8 @@
:doctype: book
:sectnums:
:icons: font
+:relevance: library-and-service
+:notes: be adjusted to clarify difference.
[#whatIsTimefold]
= Introduction
diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc
index 6574d6e72bd..0f69677bff7 100644
--- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc
+++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc
@@ -7,6 +7,9 @@
:doctype: book
:sectnums:
:icons: font
+:relevance: library-and-service
+:notes: Relevant, but some of this can move to a "shared" section instead.
+
== The basics
diff --git a/docs/src/modules/ROOT/pages/quickstart/overview.adoc b/docs/src/modules/ROOT/pages/quickstart/overview.adoc
index 8f14e645f92..056c9ccf184 100644
--- a/docs/src/modules/ROOT/pages/quickstart/overview.adoc
+++ b/docs/src/modules/ROOT/pages/quickstart/overview.adoc
@@ -5,6 +5,8 @@
use-cases-and-examples/use-cases-and-examples.adoc, \
overview-quickstarts.adoc
:imagesdir: ../..
+:relevance: library-and-service
+:icons: font
[TIP]
====
@@ -12,26 +14,108 @@ This documentation covers our Open Source solver to build a model from scratch.
We also provide off-the-shelf models to solve common planning problems. https://docs.timefold.ai/[These can be found here.]
====
-Each _quick start guide_ gets you up and running with Timefold Solver quickly.
-Pick one that aligns with your requirements:
+Timefold Solver can be used in two ways.
+Pick the approach that best fits your architecture:
+
+[cols="1,1", frame=none, grid=none, role="approach-cards"]
+|===
+a|
+[.text-center]
+icon:cubes[4x]
+
+[.text-center]
+*xref:#embedAsALibrary[Embed as a library]*
+
+Add the solver as a dependency and wire it into your application however you like.
+Use Spring Boot, Quarkus... or no framework at all.
+
+a|
+[.text-center]
+icon:server[4x]
+
+[.text-center]
+*xref:#runAsAService[Run as a service]* (Preview)
+
+Deploy a fully isolated optimization service. This opinionated approach reduces the amount of boilerplate code needed to get to a running application.
+
+|===
+
+.Comparison
+[cols="2,5,5", options="header"]
+|===
+| | Embed as a library | Run as a service
+
+| *Best for*
+| Embedding the solver into an existing application
+| Running optimization as a standalone, isolated service that is easier to integrate, orchestrate & scale
+
+| *Ease of use*
+| Requires manual wiring: you manage configuration, lifecycle, and integration
+| Minimal setup: the framework handles REST endpoints and lifecycle for you
+
+| *Integration*
+| You control how the solver is wired into your application. Integrations for Spring Boot and Quarkus available.
+| Opinionated, ready-to-run service built on Quarkus
+
+| *Flexibility*
+| Use any framework or no framework at all
+| Constrained by design, the service only runs optimization
+
+| *Status*
+| Stable
+| Preview
+|===
+
+[#embedAsALibrary]
+== Embed as a library
+
+The library approach gives you the solver core as a dependency.
+You integrate it into your application however you see fit. There are no constraints on your application framework or architecture.
+
+The following getting started guides demonstrate the library approach:
* xref:quickstart/hello-world/hello-world-quickstart.adoc#helloWorldQuickStart[*Hello World*]
-** Build a simple Java or Kotlin application that uses Timefold Solver to optimize a school timetable for students and teachers.
+** The minimal starting point. Build a plain Java or Kotlin application with no additional framework.
* xref:quickstart/quarkus/quarkus-quickstart.adoc#quarkusQuickStart[*Quarkus*]
-** Build a REST application with Java or Kotlin that uses Timefold Solver to optimize a school timetable for students and teachers.
-** https://quarkus.io[Quarkus] is a popular platform in the Java ecosystem that supports native compilation.
+** Build a REST application with Java or Kotlin on top of https://quarkus.io[Quarkus], a popular Java platform that supports native compilation.
* xref:quickstart/spring-boot/spring-boot-quickstart.adoc#springBootQuickStart[*Spring Boot*]
-** Build a REST application with Java or Kotlin that uses Timefold Solver to optimize a school timetable for students and teachers.
-** https://spring.io[Spring Boot] is a popular platform in the Java ecosystem that supports native compilation.
-
-All three quick starts use Timefold Solver to optimize a school timetable for student and teachers:
+** Build a REST application with Java or Kotlin on top of https://spring.io[Spring Boot], a popular Java platform that supports native compilation.
-IMPORTANT: The https://github.com/TimefoldAI/timefold-quickstarts[timefold-quickstarts] repository contains the source code for all these guides and more.
+All three guides optimize a school timetable for students and teachers:
image::quickstart/school-timetabling/schoolTimetablingInputOutput.png[]
-You can also find out
-how to build xref:quickstart/quarkus-vehicle-routing/quarkus-vehicle-routing-quickstart.adoc#vrpQuarkusQuickStart[Vehicle Routing with Quarkus].
+IMPORTANT: The https://github.com/TimefoldAI/timefold-quickstarts[timefold-quickstarts] repository contains the source code for all these guides.
-For other use cases,
+For additional use cases using the library approach,
take a look at https://github.com/TimefoldAI/timefold-quickstarts[the `timefold-quickstarts` GitHub repository].
+
+[#runAsAService]
+== Run as a service (Preview)
+
+[IMPORTANT]
+====
+The service approach is currently in *preview*.
+It is the recommended path going forward, but the API may still change before it reaches general availability.
+====
+
+This is the approach we recommend for integrating Timefold Solver into a larger system,
+and it is the approach we use ourselves when building optimization models at Timefold.
+
+The service has a single responsibility: *running optimization*.
+It receives a dataset, solves it, and returns the result over a well-defined REST API.
+That is all it does.
+
+*Why no database?*
+Deliberately.
+The service does not store datasets, manage user state, or own any persistence layer.
+Your application already has a database; the service does not need one.
+This makes the service stateless, trivially scalable, and completely free of operational concerns like schema migrations or data ownership.
+When optimization is one step in a larger workflow, a stateless service with a clean API slots into any architecture without friction.
+
+*Why opinionated?*
+An opinionated framework means less boilerplate, fewer ways to get it wrong, and a consistent shape that we all can reason about together.
+The library approach gives you full control; this approach gives you a proven path.
+
+To get started, see
+xref:quickstart/service/getting-started.adoc[*Getting started: building a service*].
diff --git a/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc b/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc
new file mode 100644
index 00000000000..8fac39b923f
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/quickstart/service/getting-started.adoc
@@ -0,0 +1,417 @@
+= Getting started: building a service (Preview)
+:description: Getting started with the service module
+:doctype: book
+:sectnums:
+:icons: font
+
+This guide walks you through the process of creating an AI Optimization service.
+
+With this approach, you define your planning model: the domain objects, constraints, and planning solution.
+Timefold Solver takes care of the rest.
+There is no need to wire up a solver manually, manage thread pools, or implement REST endpoints by hand.
+The service exposes a ready-to-use REST API automatically and supports our recommended optimization architecture.
+
+NOTE: The service approach is currently only available in Java.
+
+IMPORTANT: This is a preview feature subject to change.
+
+[sectnums!]
+include::../shared/_whatyoubuild.adoc[]
+
+[sectnums!]
+== Solution source code
+
+In case you get stuck during this Getting Started guide, it might be worth checking the solution project.
+
+This https://github.com/TimefoldAI/timefold-service-getting-started[solution can be found on GitHub].
+
+[sectnums!]
+== Prerequisites
+
+To complete this guide, you need:
+
+* **Tools**
+** {java-version} or higher
+** Maven
+** An IDE of your choice (IntelliJ IDEA, VSCode, ...)
+
+* **Knowledge**
+** Java (Basic)
+** Quarkus (Basic)
+
+== The build file and the dependencies
+
+Create a Maven file and add these dependencies:
+
+[tabs]
+====
+Maven::
++
+--
+Your `pom.xml` file has the following content:
+
+[source,xml,subs=attributes+]
+----
+
+
+ ai.timefold.solver
+ timefold-solver-service-parent
+ {timefold-sdk-version}
+
+
+ 4.0.0
+ org.acme
+ schooltimetabling
+ ${revision}
+ School Timetabling
+ Solution for the Timefold Service getting started example
+
+
+ 1.0.0-SNAPSHOT
+
+
+
+----
+--
+====
+
+include::../shared/school-timetabling/_school-timetabling-model.adoc[leveloffset=+1]
+include::../shared/school-timetabling/_school-timetabling-constraints.adoc[leveloffset=+1]
+
+== Gather the domain objects in a planning solution
+
+A `Timetable` wraps all `Timeslot`, `Room`, and `Lesson` instances of a single dataset.
+Furthermore, because it contains all lessons, each with a specific planning variable state,
+it is known as a _planning solution_.
+
+Extending `AbstractSimpleModel`:
+
+- Configures this class to be (de-)serialized as part of the `ModelInput` and `ModelOutput` of the xref:service/rest-api.adoc[REST API].
+- Implements the `SolverModel` interface using the default HardMediumSoftLongScore.
+
+[NOTE]
+It's ok if you are not familiar with these terms yet. We'll get to them later.
+
+[tabs]
+====
+Java::
++
+--
+Create the `src/main/java/org/acme/schooltimetabling/domain/Timetable.java` class:
+
+[source,java]
+----
+package org.acme.schooltimetabling.domain;
+
+import java.util.List;
+
+import ai.timefold.solver.service.definition.api.AbstractSimpleModel;
+import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
+import ai.timefold.solver.core.api.domain.solution.PlanningScore;
+import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
+import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty;
+import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
+import ai.timefold.solver.core.api.score.buildin.hardmediumsoftlong.HardMediumSoftLongScore;
+import ai.timefold.solver.core.api.solver.change.ConstraintWeightOverrides;
+
+@PlanningSolution
+public class Timetable extends AbstractSimpleModel {
+
+ @ValueRangeProvider
+ @ProblemFactCollectionProperty
+ private List timeslots;
+ @ValueRangeProvider
+ @ProblemFactCollectionProperty
+ private List rooms;
+ @PlanningEntityCollectionProperty
+ private List lessons;
+ public Timetable() {
+ }
+
+ public Timetable(List timeslots, List rooms, List lessons) {
+ this.timeslots = timeslots;
+ this.rooms = rooms;
+ this.lessons = lessons;
+ }
+
+ public List getTimeslots() {
+ return timeslots;
+ }
+
+ public List getRooms() {
+ return rooms;
+ }
+
+ public List getLessons() {
+ return lessons;
+ }
+
+ @Override
+ public ConstraintWeightOverrides getConstraintWeightOverrides() {
+ return ConstraintWeightOverrides.none();
+ }
+
+ public void setConstraintWeightOverrides(ConstraintWeightOverrides constraintWeightOverrides) {
+ // Left empty for now; see constraint weights documentation
+ }
+}
+----
+--
+====
+
+The `Timetable` class has an `@PlanningSolution` annotation,
+so Timefold Solver knows that this class contains all of the input and output data.
+
+Specifically, these classes are the input of the problem:
+
+* The `timeslots` field with all time slots
+** This is a list of problem facts, because they do not change during solving.
+* The `rooms` field with all rooms
+** This is a list of problem facts, because they do not change during solving.
+* The `lessons` field with all lessons
+** This is a list of planning entities, because they change during solving.
+** Of each `Lesson`:
+*** The values of the `timeslot` and `room` fields are typically still `null`, so unassigned.
+They are planning variables.
+*** The other fields, such as `subject`, `teacher` and `studentGroup`, are filled in.
+These fields are problem properties.
+
+However, this class is also the output of the solution:
+
+* The `lessons` field for which each `Lesson` instance has non-null `timeslot` and `room` fields after solving.
+
+NOTE: The `getConstraintWeightOverrides()` and `setConstraintWeightOverrides()` methods are required by the `AbstractSimpleModel` superclass.
+For now they are left as stubs — `none()` means no constraint weights can be overridden per request.
+Configuring this properly is covered in the xref:service/constraint-weights.adoc[Constraint weights] section and is not part of this guide.
+
+=== The value range providers
+include::../shared/school-timetabling/_school-timetabling-solution-value-range-providers.adoc[]
+
+include::../shared/school-timetabling/_school-timetabling-solution-facts-and-entities.adoc[]
+
+== Add the Solver Configuration
+
+Without a termination setting, the solver runs forever.
+You also need to provide some basic metadata about your application.
+
+Create the `src/main/resources/application.properties` file:
+
+[source,properties]
+----
+# The solver runs for 30 seconds by default.
+# Increase this for larger datasets to find a better solution.
+ai.timefold.platform.termination.spent-limit=PT30S
+
+# Application metadata — included in the generated OpenAPI specification
+timefold.application.name=my-model
+timefold.application.version=v1
+timefold.application.contact.email=info@example.com
+timefold.application.contact.name=Your Name
+timefold.application.contact.url=https://example.com
+----
+
+`ai.timefold.platform.termination.spent-limit` sets the default maximum time the solver will run.
+The value uses the https://www.digi.com/resources/documentation/digidocs/90001488-13/reference/r_iso_8601_duration_format.htm[ISO 8601 duration format]: `PT30S` means 30 seconds.
+For production use, consider at least five minutes (`PT5M`) to allow the solver more time to find a better solution.
+
+NOTE: The per-request `config.run.termination.spentLimit` field in the POST body (shown later) overrides this default for individual runs.
+
+`timefold.application.name`, `timefold.application.version`, and the contact fields are *required* metadata.
+They are used to identify your service, validate the JSON schema of requests, and populate the generated OpenAPI specification.
+
+== REST API
+
+The standard way to interact with the optimization service is through a REST API.
+
+Using the service module, most of the REST Endpoints will be automatically generated thanks to the use of the `ModelInput`, `ModelOutput` and `SolverModel` interfaces.
+You didn't have to implement those interfaces in this guide, because they were already implemented for you in the `AbstractSimpleModel`.
+
+To configure the REST API, you need to provide an interface which extends the `ModelRest` interface.
+
+[tabs]
+====
+Java::
++
+--
+Create the `src/main/java/org/acme/schooltimetabling/rest/TimetableResource.java` class:
+
+[source,java]
+----
+package org.acme.schooltimetabling.rest;
+
+import jakarta.ws.rs.Path;
+import ai.timefold.solver.service.rest.api.ModelRest;
+
+@Path("/v1/timetables")
+public interface TimetableResource extends ModelRest {
+}
+----
+--
+====
+
+This is the most basic version of the interface. The SDK will automatically create multiple xref:service/rest-api.adoc[REST API endpoints] for optimization actions.
+The `@Path` annotation is used to configure the base path of all those endpoints.
+
+== Running and manually testing the model
+
+Everything is now configured to run the model.
+
+Start the model in `development mode` by executing:
+
+[source,bash]
+----
+mvn quarkus:dev
+----
+
+You should see the following line in the log on successful start
+
+[source,bash]
+----
+getting-started-school-timetabling on JVM (powered by Quarkus {quarkus-version}) started in 1.552s. Listening on: http://localhost:8080
+----
+
+Open the http://localhost:8080/q/swagger-ui/[Swagger OpenAPI Viewer] to inspect the generated API Endpoints:
+http://localhost:8080/q/swagger-ui/
+
+image:quickstart/service/openapi.png[Screenshot of the Swagger OpenAPI UI showing the automatically generated REST API endpoints]
+
+As shown in the image above, just by extending the `ModelRest` interface, quite a few endpoints were automatically generated.
+We discuss these in more depth in the xref:service/rest-api.adoc[REST API] section.
+
+To showcase that the model is now ready for use we only need 2 of those endpoints:
+
+- `POST /v1/timetables`: To request a problem to be solved. This returns a unique identifier which can be used to request the resulting schedule.
+- `GET /v1/timetables/+{id}+`: Get the calculated schedule with the given identifier.
+
+=== Sending the problem
+
+// TODO: MAKE THIS INTERACTIVE IN THE UI!
+
+[source,bash]
+----
+curl -v -X 'POST' \
+ 'http://localhost:8080/v1/timetables' \
+ -H 'accept: application/json' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "config": {
+ "run": {
+ "termination": {
+ "spentLimit": "PT10S"
+ }
+ }
+ },
+ "modelInput": {
+ "timeslots": [
+ {
+ "dayOfWeek": "MONDAY",
+ "startTime": "13:45:30",
+ "endTime": "14:45:30"
+ }
+ ],
+ "rooms": [
+ {
+ "name": "Room1"
+ }
+ ],
+ "lessons": [
+ {
+ "id": "Lesson1",
+ "subject": "English",
+ "teacher": "Mrs. Joos",
+ "studentGroup": "1st Grade"
+ }
+ ]
+ }
+}'
+----
+
+This call should return a response containing the ID of the started run.
+
+[source,json]
+----
+{
+ "id" : "f401a609-403b-48c4-9487-0baf287e4f72",
+ "name" : "Dataset-2025-06-10T13:03:50.824454+02:00",
+ "submitDateTime" : "2025-06-10T13:03:50.824454+02:00",
+ "solverStatus" : "NOT_SOLVING"
+}
+----
+
+=== Getting the solution
+
+Using the ID received in the previous run, the current status of the run can be retrieved.
+
+[source,bash]
+----
+curl -X 'GET' \
+ 'http://localhost:8080/v1/timetables/{id}' \
+ -H 'accept: application/json'
+----
+
+This call with return the standard xref:service/rest-api.adoc#solvingResponse[response structure].
+[source,json]
+----
+{
+ "metadata": {
+ "id": "f401a609-403b-48c4-9487-0baf287e4f72",
+ "name": "Dataset-2025-06-10T13:03:50.824454+02:00",
+ "submitDateTime": "2025-06-10T13:03:50.824454+02:00",
+ "startDateTime": "2025-06-10T13:03:50.832544+02:00",
+ "activeDateTime": "2025-06-10T13:03:50.833934+02:00",
+ "completeDateTime": "2025-06-10T13:03:50.843229+02:00",
+ "shutdownDateTime": "2025-06-10T13:03:50.844522+02:00",
+ "solverStatus": "SOLVING_COMPLETED",
+ "score": "0hard/0medium/0soft",
+ "validationResult": {
+ "summary": "VALIDATION_NOT_SUPPORTED"
+ }
+ },
+ "modelOutput": {
+ "timeslots": [
+ {
+ "dayOfWeek": "MONDAY",
+ "startTime": "13:45:30.123456789",
+ "endTime": "13:45:30.123456789"
+ }
+ ],
+ "rooms": [
+ {
+ "name": "Room1"
+ }
+ ],
+ "lessons": [
+ {
+ "id": "Lesson1",
+ "subject": "English",
+ "teacher": "Mrs. Joos",
+ "studentGroup": "1st Grade",
+ "timeslot": {
+ "dayOfWeek": "MONDAY",
+ "startTime": "13:45:30.123456789",
+ "endTime": "13:45:30.123456789"
+ },
+ "room": {
+ "name": "Room1"
+ }
+ }
+ ]
+ }
+}
+----
+
+Important elements to observe in the JSON above:
+
+- The `Lesson` object now has an assigned `Room` and `Timeslot`.
+- The `solverStatus` is `SOLVING_COMPLETED`, indicating the solver has finished optimizing.
+- The `score` is `0hard/0medium/0soft`, signaling that no constraints were broken.
+
+[sectnums!]
+== Next
+
+Since this is a "Getting Started" guide, not everything is covered yet.
+
+* Learn about improvements you can make to your model:
+** How to set up the underlying xref:service/modeling-changes.adoc[Timefold Constraint Solver].
+** How to configure your xref:service/rest-api.adoc[REST API] with validations, custom endpoints, etc.
\ No newline at end of file
diff --git a/docs/src/modules/ROOT/pages/quickstart/shared/_whatyoubuild.adoc b/docs/src/modules/ROOT/pages/quickstart/shared/_whatyoubuild.adoc
index 51faa8845be..df2d7715e63 100644
--- a/docs/src/modules/ROOT/pages/quickstart/shared/_whatyoubuild.adoc
+++ b/docs/src/modules/ROOT/pages/quickstart/shared/_whatyoubuild.adoc
@@ -1,11 +1,9 @@
== What you will build
-You will build a REST application that optimizes a school timetable for students and teachers:
+You will build an optimization service that runs locally on your machine, and optimizes a school timetable for students and teachers.
-image::quickstart/school-timetabling/schoolTimetablingScreenshot.png[]
-
-Your service will assign `Lesson` instances to `Timeslot` and `Room` instances automatically
-by using AI to adhere to hard and soft scheduling _constraints_, such as the following examples:
+Your application will assign `Lesson` instances to `Timeslot` and `Room` instances automatically
+by using AI to adhere to hard and soft scheduling _constraints_, for example:
* A room can have at most one lesson at the same time.
* A teacher can teach at most one lesson at the same time.
@@ -18,6 +16,6 @@ Mathematically speaking, school timetabling is an _NP-hard_ problem.
This means it is difficult to scale.
Simply brute force iterating through all possible combinations takes millions of years
for a non-trivial dataset, even on a supercomputer.
-Luckily, AI constraint solvers such as Timefold Solver have advanced algorithms
-that deliver a near-optimal solution in a reasonable amount of time.
+Fortunately, AI constraint solvers such as Timefold Solver have advanced algorithms
+that deliver a near-optimal solution in a reasonable amount of time.
diff --git a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc
index 98fe8c1f011..9018381429b 100644
--- a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc
+++ b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-constraints.adoc
@@ -18,92 +18,7 @@ Hard constraints are weighted against other hard constraints.
Soft constraints are weighted too, against other soft constraints.
*Hard constraints always outweigh soft constraints*, regardless of their respective weights.
-To calculate the score, you could implement an `EasyScoreCalculator` class:
-
-[tabs]
-====
-Java::
-+
---
-[source,java]
-----
-public class TimetableEasyScoreCalculator implements EasyScoreCalculator {
-
- @Override
- public HardSoftScore calculateScore(Timetable timetable) {
- List lessons = timetable.getLessons();
- int hardScore = 0;
- for (Lesson a : lessons) {
- for (Lesson b : lessons) {
- if (a.getTimeslot() != null && a.getTimeslot().equals(b.getTimeslot())
- && a.getId() < b.getId()) {
- // A room can accommodate at most one lesson at the same time.
- if (a.getRoom() != null && a.getRoom().equals(b.getRoom())) {
- hardScore--;
- }
- // A teacher can teach at most one lesson at the same time.
- if (a.getTeacher().equals(b.getTeacher())) {
- hardScore--;
- }
- // A student can attend at most one lesson at the same time.
- if (a.getStudentGroup().equals(b.getStudentGroup())) {
- hardScore--;
- }
- }
- }
- }
- int softScore = 0;
- // Soft constraints are only implemented in the timefold-quickstarts code
- return HardSoftScore.of(hardScore, softScore);
- }
-
-}
-----
---
-
-Kotlin::
-+
---
-[source,kotlin]
-----
-class TimetableEasyScoreCalculator : EasyScoreCalculator {
- override fun calculateScore(solution: Timetable): HardSoftScore {
- val lessons = solution.lessons
- var hardScore = 0
- for (a in lessons) {
- for (b in lessons) {
- if (a.timeslot != null && a.timeslot == b.timeslot && a.id!! < b.id!!) {
- // A room can accommodate at most one lesson at the same time.
- if (a.room != null && a.room == b.room) {
- hardScore--
- }
- // A teacher can teach at most one lesson at the same time.
- if (a.teacher == b.teacher) {
- hardScore--
- }
- // A student can attend at most one lesson at the same time.
- if (a.studentGroup == b.studentGroup) {
- hardScore--
- }
- }
- }
- }
- val softScore = 0
- // Soft constraints are only implemented in the timefold-quickstarts code
- return HardSoftScore.of(hardScore, softScore)
- }
-}
-----
---
-====
-
-
-Unfortunately **that does not scale well**, because it is non-incremental:
-every time a lesson is assigned to a different time slot or room,
-all lessons are re-evaluated to calculate the new score.
-
-Instead, create a `TimetableConstraintProvider` class
-to perform incremental score calculation.
+To calculate the score, implement a `TimetableConstraintProvider` class.
It uses Timefold Solver's xref:constraints-and-score/score-calculation.adoc[Constraint Streams API]
which is inspired by Java Streams and SQL:
diff --git a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-model.adoc b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-model.adoc
index ce3beb65366..52a45494fc7 100644
--- a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-model.adoc
+++ b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-model.adoc
@@ -13,8 +13,7 @@ for example, `Monday 10:30 - 11:30` or `Tuesday 13:30 - 14:30`.
For simplicity's sake, all time slots have the same duration
and there are no time slots during lunch or other breaks.
-A time slot has no date, because a high school schedule just repeats every week.
-So there is no need for xref:responding-to-change/responding-to-change.adoc#continuousPlanning[continuous planning].
+A time slot has no date, because in this example, the high school schedule just repeats every week.
[tabs]
====
@@ -99,7 +98,7 @@ so it is easier to read Timefold Solver's `DEBUG` or `TRACE` log, as shown later
== Room
-The `Room` class represents a location where lessons are taught,
+The `Room` record represents a location where lessons are taught,
for example, `Room A` or `Room B`.
For simplicity's sake, all rooms are without capacity limits
and they can accommodate all lessons.
@@ -115,27 +114,7 @@ Create the `src/main/java/org/acme/schooltimetabling/domain/Room.java` class:
----
package org.acme.schooltimetabling.domain;
-public class Room {
-
- private String name;
-
- public Room() {
- }
-
- public Room(String name) {
- this.name = name;
- }
-
- public String getName() {
- return name;
- }
-
- @Override
- public String toString() {
- return name;
- }
-
-}
+public record Room(String name) { }
----
--
diff --git a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution-facts-and-entities.adoc b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution-facts-and-entities.adoc
new file mode 100644
index 00000000000..c6987775959
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution-facts-and-entities.adoc
@@ -0,0 +1,12 @@
+== The problem fact and planning entity properties
+
+Furthermore, Timefold Solver needs to know which `Lesson` instances it can change
+as well as how to retrieve the `Timeslot` and `Room` instances used for score calculation
+by your `TimetableConstraintProvider`.
+
+The `timeslots` and `rooms` fields have an `@ProblemFactCollectionProperty` annotation,
+so your `TimetableConstraintProvider` can select _from_ those instances.
+
+The `lessons` has an `@PlanningEntityCollectionProperty` annotation,
+so Timefold Solver can change them during solving
+and your `TimetableConstraintProvider` can select _from_ those too.
\ No newline at end of file
diff --git a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution-value-range-providers.adoc b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution-value-range-providers.adoc
new file mode 100644
index 00000000000..8f5e9ea5ba7
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution-value-range-providers.adoc
@@ -0,0 +1,8 @@
+== The value range providers
+
+The `timeslots` field is a value range provider.
+It holds the `Timeslot` instances which Timefold Solver can pick from to assign to the `timeslot` field of `Lesson` instances.
+The `timeslots` field has an `@ValueRangeProvider` annotation to connect the `@PlanningVariable` with the `@ValueRangeProvider`,
+by matching the type of the planning variable with the type returned by the xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueRangeProvider[value range provider].
+
+Following the same logic, the `rooms` field also has an `@ValueRangeProvider` annotation.
diff --git a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution.adoc b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution.adoc
index 474327f637e..30f1660589d 100644
--- a/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution.adoc
+++ b/docs/src/modules/ROOT/pages/quickstart/shared/school-timetabling/_school-timetabling-solution.adoc
@@ -136,24 +136,6 @@ However, this class is also the output of the solution:
* The `lessons` field for which each `Lesson` instance has non-null `timeslot` and `room` fields after solving.
* The `score` field that represents the quality of the output solution, for example, `0hard/-5soft`.
-== The value range providers
+include::_school-timetabling-solution-value-range-providers.adoc[]
-The `timeslots` field is a value range provider.
-It holds the `Timeslot` instances which Timefold Solver can pick from to assign to the `timeslot` field of `Lesson` instances.
-The `timeslots` field has an `@ValueRangeProvider` annotation to connect the `@PlanningVariable` with the `@ValueRangeProvider`,
-by matching the type of the planning variable with the type returned by the xref:using-timefold-solver/modeling-planning-problems.adoc#planningValueRangeProvider[value range provider].
-
-Following the same logic, the `rooms` field also has an `@ValueRangeProvider` annotation.
-
-== The problem fact and planning entity properties
-
-Furthermore, Timefold Solver needs to know which `Lesson` instances it can change
-as well as how to retrieve the `Timeslot` and `Room` instances used for score calculation
-by your `TimetableConstraintProvider`.
-
-The `timeslots` and `rooms` fields have an `@ProblemFactCollectionProperty` annotation,
-so your `TimetableConstraintProvider` can select _from_ those instances.
-
-The `lessons` has an `@PlanningEntityCollectionProperty` annotation,
-so Timefold Solver can change them during solving
-and your `TimetableConstraintProvider` can select _from_ those too.
+include::_school-timetabling-solution-facts-and-entities.adoc[]
\ No newline at end of file
diff --git a/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc b/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc
index 471a36265d2..bbfee7fda4e 100644
--- a/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc
+++ b/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc
@@ -4,6 +4,9 @@
:doctype: book
:sectnums:
:icons: font
+:relevance: library-and-service
+:notes: Wildly different in service module, so potentially split.
+
The problem facts used to create a solution may change before or during the execution of that solution.
Delaying planning in order to lower the risk of problem facts changing is not ideal,
diff --git a/docs/src/modules/ROOT/pages/service/_preview-note.adoc b/docs/src/modules/ROOT/pages/service/_preview-note.adoc
new file mode 100644
index 00000000000..af1e784ba74
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/service/_preview-note.adoc
@@ -0,0 +1,2 @@
+IMPORTANT: This page describes features which are only relevant when running xref:quickstart/overview.adoc[Timefold Solver as a Service]
+The information on these pages may describe functionality which may be changed or even removed in a future release.
\ No newline at end of file
diff --git a/docs/src/modules/ROOT/pages/service/constraint-weights.adoc b/docs/src/modules/ROOT/pages/service/constraint-weights.adoc
new file mode 100644
index 00000000000..7a0913986e8
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/service/constraint-weights.adoc
@@ -0,0 +1,114 @@
+= Constraint weights (optional)
+:description: How to make constraint weights and other parameters user configurable.
+:doctype: book
+:sectnums:
+:icons: font
+
+When implementing a model, it's important to set up the weights of the constraints correctly.
+Having good default weights makes the model easily reusable and is an expression of your modeling expertise.
+
+include::_preview-note.adoc[]
+
+Constraint weights are always an interpretation by the modeler. It might be that the consumer of the model would like to see the constraints weighed differently.
+`ModelConfigOverrides` allows consumers of a model to tailor constraint weights and parameters to their use case.
+
+[NOTE]
+Be careful not to make your model overly configurable as that impacts usability.
+
+== Adjusting constraint weights
+
+Implement the `ModelConfigOverrides` interface. This is a marker interface, meaning it has no methods but can be discovered by the SDK.
+The implementation should have fields that refer to specific constraints using the `@ConstraintReference` annotation.
+To ensure both the constraint and this reference are the same, use a static field to keep the name of the constraint.
+
+.The example ConstraintProvider class.
+[source,java,options="nowrap"]
+----
+public class TimetableConstraintProvider implements ConstraintProvider {
+
+ public static final String TEACHER_CONFLICT = "Teacher conflict";
+ public static final String ROOM_CONFLICT = "Room conflict";
+
+ Constraint roomConflict(ConstraintFactory constraintFactory) {
+ return constraintFactory
+ // constraint implementation excluded
+ .asConstraint(ROOM_CONFLICT);
+ }
+
+ Constraint teacherConflict(ConstraintFactory constraintFactory) {
+ return constraintFactory
+ // constraint implementation excluded
+ .asConstraint(TEACHER_CONFLICT);
+ }
+
+ // other constraints excluded
+}
+----
+
+.The ModelConfigOverrides class.
+[source,java,options="nowrap"]
+----
+public final class TimetableConfigOverrides implements ModelConfigOverrides {
+
+ public static final long DEFAULT_WEIGHT_ZERO = 0L;
+ public static final long DEFAULT_WEIGHT_ONE = 1L;
+
+ @ConstraintReference(TimetableConstraintProvider.TEACHER_CONFLICT)
+ private long teacherConflictWeight = DEFAULT_WEIGHT_ONE;
+
+ @ConstraintReference(TimetableConstraintProvider.ROOM_CONFLICT)
+ private long roomConflictWeight = DEFAULT_WEIGHT_ONE;
+
+ // getter/setter excluded
+
+}
+----
+
+The default constraint weight for these constraints is `1`. This can now be overridden by the consumer by passing in the model overrides object in a request.
+For example, to make the Teacher conflict 10 times more impactful, override the weight to 10:
+
+.Example Request to change the weight.
+[source,json]
+----
+{
+ "config": {
+ "run": {
+ "name": "run name",
+
+ },
+ "model": {
+ "overrides": {
+ "teacherConflictWeight": 10
+ }
+ }
+ },
+ "modelInput" :
+}
+----
+
+[NOTE]
+Only allow weight overrides if it makes sense.
+Usually, it doesn't make sense to allow weight overrides for _hard_ constraints.
+
+Next, in the xref:./rest-api.adoc#modelConverter[model converter], make sure to map these overrides to a solver specific `ConstraintWeightOverrides` object that must be on the `@PlanningSolution` class.
+
+.As part of the ModelConverter
+[source,java,options="nowrap"]
+----
+TimetableConfigOverrides modelConfigOverrides = modelConfig.overrides();
+
+ConstraintWeightOverrides constraintWeightOverrides = ConstraintWeightOverrides.of(
+ Map.ofEntries(
+ Map.entry(TimetableConstraintProvider.TEACHER_CONFLICT,
+ HardMediumSoftLongScore.ofHard(modelConfigOverrides.getTeacherConflictWeight())),
+ Map.entry(TimetableConstraintProvider.ROOM_CONFLICT,
+ HardMediumSoftLongScore.ofSoft(modelConfigOverrides.getRoomConflictWeight()))
+ )
+);
+
+solverModel.setConstraintWeightOverrides(constraintWeightOverrides);
+----
+
+For more information, see xref:../constraints-and-score/constraint-configuration.adoc#constraintConfiguration[Adjusting constraints at runtime].
+
+//TODO === Adjusting Model Parameters https://github.com/TimefoldAI/timefold-solver/issues/2347
\ No newline at end of file
diff --git a/docs/src/modules/ROOT/pages/service/demo-data.adoc b/docs/src/modules/ROOT/pages/service/demo-data.adoc
new file mode 100644
index 00000000000..d2113334dfd
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/service/demo-data.adoc
@@ -0,0 +1,80 @@
+= Demo data (optional)
+:description: How to configure the model to generate Demo Data
+:doctype: book
+:sectnums:
+:icons: font
+
+Providing example datasets is a good way to help consumers understand the expected input of your model.
+This page describes how to provide dedicated support for exposing demo data.
+
+include::_preview-note.adoc[]
+
+// TODO To be simplified with: https://github.com/TimefoldAI/timefold-models-sdk/issues/550
+== Generating demo data
+
+To provide consumers of your model with example datasets, implement the `DemoDataGenerator` interface.
+This interface requires you to implement 2 methods:
+
+- `demoMetaData()`: returns a list of metadata, listing all the different datasets available.
+- `generateDemoData(String demoDataId)`: generates the dataset with the given id, usually retrieved from the metadata.
+
+[WARNING]
+====
+Implementations of this interface must be dependency free meaning simple instantiation (even with reflection) of this class is enough to generate demo data.
+====
+
+[source,java,options="nowrap"]
+----
+@ApplicationScoped
+public class TimetableDemoDataGenerator implements DemoDataGenerator {
+
+ public enum DemoDataKind {
+ BASIC(
+ new DemoMetaData("BASIC", "SHORT_DESCRIPTION", "LONG_DESCRIPTION", List.of("TAGS"),
+ List.of("Configuration Overrides")),
+ this::generateBasicDemoData // could also delegate to another class instead
+ ),
+ COMPLEX_SET(
+ new DemoMetaData("COMPLEX_SET", "SHORT_DESCRIPTION", "LONG_DESCRIPTION", List.of("TAGS"),
+ List.of("Configuration Overrides")),
+ this::generateComplexSet
+ );
+
+ private DemoMetaData metaData;
+ private Supplier> supplier;
+
+ public DemoMetaData getMetaData() {
+ return metaData;
+ }
+
+ public DemoData getDemoData() {
+ return new DemoData(metaData, supplier.get());
+ }
+
+ public ModelRequest generateBasicDemoData() {
+ // Generate basic request.
+ }
+
+ public ModelRequest generateComplexSet() {
+ // Generate complex request.
+ }
+ }
+
+ @Override
+ public List demoMetaData() {
+ return Stream.of(DemoDataKind.values())
+ .map(demoDataKind -> demoDataKind.getMetaData())
+ .toList();
+ }
+
+ @Override
+ public DemoData generateDemoData(String demoDataId) {
+ return DemoDataKind.fromString(demoDataId).getDemoData();
+ }
+}
+----
+
+With this interface implemented, Timefold Solver will automatically expose these methods as REST endpoints:
+
+- `GET //demo-data`: Retrieve all available demo dataset names.
+- `GET //demo-data/\{name\}`: Retrieve demo dataset with given identifier
diff --git a/docs/src/modules/ROOT/pages/service/exposing-metrics.adoc b/docs/src/modules/ROOT/pages/service/exposing-metrics.adoc
new file mode 100644
index 00000000000..1dd8659cb7f
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/service/exposing-metrics.adoc
@@ -0,0 +1,148 @@
+= Exposing metrics (optional)
+:description: How to expose input and output metrics.
+:doctype: book
+:sectnums:
+:icons: font
+
+Timefold Solver provides a mechanism to collect metrics about both the input submitted to the solver and the output it produces.
+These metrics are automatically exposed through the xref:./rest-api.adoc[REST API] alongside the optimization result, making it straightforward to build dashboards, monitoring screens, and insights views on top of them, without any additional endpoints or custom logic.
+
+include::_preview-note.adoc[]
+
+[#modelInputMetrics]
+== Input metrics
+
+Input metrics provide more information about the data provided to the solver.
+The metrics should be contained in a class or record which implements the `ModelInputMetrics` interface.
+
+[NOTE]
+====
+The input metrics will be exposed through the xref:./rest-api.adoc[REST API], for which an OpenAPI spec will be generated.
+It is therefore necessary to add xref:./rest-api.adoc#openAPISpecification[OpenAPI Specification] annotations to the fields.
+====
+
+.Example for School Timetabling
+[source,java,options="nowrap"]
+----
+public record TimetableInputMetrics(
+ @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "lessons", title = "Lessons",
+ format = DataFormat.Values.NUMBER, description = "The number of lessons submitted in the input dataset.",
+ type = SchemaType.NUMBER, example = "10", readOnly = true) int lessons,
+ @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "timeslots", title = "Timeslots",
+ format = DataFormat.Values.NUMBER, description = "The number of timeslots submitted in the input dataset.",
+ type = SchemaType.NUMBER, example = "30", readOnly = true) int timeslots
+ ) implements ModelInputMetrics {}
+----
+
+Next, the `SolverModel` should implement the `InputMetricsAware` interface and construct the defined `ModelInputMetrics` object.
+
+.Example for School Timetabling
+[source,java,options="nowrap"]
+----
+@PlanningSolution
+public class Timetable implements SolverModel, InputMetricsAware {
+
+ @ProblemFactCollectionProperty
+ @ValueRangeProvider
+ private List timeslots;
+
+ @PlanningEntityCollectionProperty
+ private List lessons;
+
+ @PlanningScore
+ private HardSoftScore score;
+
+ @Override
+ public HardSoftScore getScore() {
+ return score;
+ }
+
+ @Override
+ public TimetableInputMetrics getInputMetrics() {
+ return new TimetableInputMetrics(lessons.size(), timeslots.size());
+ }
+
+ // other Getters/Setters/Constructors excluded
+}
+----
+
+[#modelOutputMetrics]
+== Output metrics
+
+Output metrics provide more information about the result produced by the solver,
+such as solution quality indicators or key performance indicators (KPIs).
+
+Output metrics are calculated after solving completes.
+They should be derived from the solved `SolverModel`, not from the input.
+
+[NOTE]
+====
+Like input metrics, they are exposed through the xref:./rest-api.adoc[REST API] under the `kpis` field of the response.
+It is therefore necessary to add xref:./rest-api.adoc#openAPISpecification[OpenAPI Specification] annotations to the fields.
+====
+
+.Example for School Timetabling
+[source,java,options="nowrap"]
+----
+public record TimetableOutputMetrics(
+ @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "unassignedLessons", title = "Unassigned lessons",
+ format = DataFormat.Values.NUMBER, description = "The number of lessons that could not be assigned a timeslot or room.",
+ type = SchemaType.NUMBER, example = "0", readOnly = true) int unassignedLessons,
+ @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) @Schema(name = "maxConsecutiveLessons", title = "Max consecutive lessons",
+ format = DataFormat.Values.NUMBER, description = "The maximum number of consecutive lessons assigned to any single teacher.",
+ type = SchemaType.NUMBER, example = "3", readOnly = true) int maxConsecutiveLessons
+ ) implements ModelOutputMetrics {}
+----
+
+Next, the `SolverModel` should implement the `OutputMetricsAware` interface and construct the defined `ModelOutputMetrics` object from the solved state.
+
+.Example for School Timetabling
+[source,java,options="nowrap"]
+----
+@PlanningSolution
+public class Timetable implements SolverModel, OutputMetricsAware {
+
+ @ProblemFactCollectionProperty
+ @ValueRangeProvider
+ private List timeslots;
+
+ @PlanningEntityCollectionProperty
+ private List lessons;
+
+ @PlanningScore
+ private HardSoftScore score;
+
+ @Override
+ public HardSoftScore getScore() {
+ return score;
+ }
+
+ @Override
+ public TimetableOutputMetrics getOutputMetrics() {
+ int unassigned = (int) lessons.stream()
+ .filter(l -> l.getTimeslot() == null || l.getRoom() == null)
+ .count();
+ int maxConsecutive = computeMaxConsecutiveLessons(lessons);
+ return new TimetableOutputMetrics(unassigned, maxConsecutive);
+ }
+
+ // other Getters/Setters/Constructors excluded
+}
+----
+
+[#combiningMetrics]
+== Combining input and output metrics
+
+A `SolverModel` can implement both `InputMetricsAware` and `OutputMetricsAware` at the same time.
+
+[source,java,options="nowrap"]
+----
+@PlanningSolution
+public class Timetable implements SolverModel,
+ InputMetricsAware,
+ OutputMetricsAware {
+
+ // fields, getInputMetrics(), getOutputMetrics() excluded
+
+}
+----
diff --git a/docs/src/modules/ROOT/pages/service/modeling-changes.adoc b/docs/src/modules/ROOT/pages/service/modeling-changes.adoc
new file mode 100644
index 00000000000..3789109e869
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/service/modeling-changes.adoc
@@ -0,0 +1,124 @@
+= Service Model and Enricher
+:description: The core of any model is the solver
+:doctype: book
+:sectnums:
+:icons: font
+
+Building Timefold Solver as a service introduces a few concepts which are not relevant when using it as a library when it comes to modeling your problem domain.
+
+include::_preview-note.adoc[]
+
+[#solverModel]
+== SolverModel interface
+
+Your class which is annotated by `@PlanningSolution` should implement the `SolverModel` interface.
+
+.Example for School Timetabling
+[source,java,options="nowrap"]
+----
+@PlanningSolution
+public class Timetable implements SolverModel {
+
+ @ProblemFactCollectionProperty
+ @ValueRangeProvider
+ private List timeslots;
+
+ @PlanningEntityCollectionProperty
+ private List lessons;
+
+ @PlanningScore
+ private HardSoftScore score;
+
+ @Override
+ public HardSoftScore getScore() {
+ return score;
+ }
+
+ // other Getters/Setters/Constructors excluded
+}
+----
+
+In case you don't want control over the `score` class used, you can also extend the `AbstractSimpleModel` as used in the xref:../quickstart/service/getting-started.adoc[getting started guide].
+
+[#solverModelEnrichment]
+== SolverModel enrichment
+
+In some situations, the `SolverModel` must be enriched with additional information.
+This could be external information such as map data or
+smaller enhancements such as pinning entities which occurred in the past.
+
+Enrichers usually pre-calculate fields which would otherwise be calculated in a _ConstraintStream_.
+Especially when the field depends on external information or is difficult to compute, pre-calculating can lead to much faster results.
+
+In this example, we enrich the Timetable PlanningSolution described above by filling in the "isHoliday" field for all Timeslot objects.
+
+.Timeslot class for the School Timetabling example
+[source,java,options="nowrap"]
+----
+
+public class Timeslot {
+
+ private LocalDateTime startTime;
+ private LocalDateTime endTime;
+
+ private boolean isHoliday;
+
+ public void setHoliday(boolean isHoliday) {
+ this.isHoliday = isHoliday;
+ }
+
+ // other Getters/Setters/Constructors excluded
+}
+----
+
+Enrichment of the SolverModel is possible by implementing a `SolverModelEnricher`.
+
+.Timeslot Enricher for the School Timetabling example
+[source,java,options="nowrap"]
+----
+@ApplicationScoped
+public class TimeslotHolidayEnricher implements SolverModelEnricher {
+
+ @Override
+ public Timetable enrich(Timetable solverModel) {
+ for (Timeslot timeslot : solverModel.getTimeslots()) {
+ boolean isHoliday = overlapsWithKnownHoliday(timeslot.getStartTime(), timeslot.getEndTime());
+ timeslot.setHoliday(isHoliday);
+ }
+
+ return solverModel;
+ }
+
+ private boolean overlapsWithKnownHoliday(LocalDateTime start, LocalDateTime end) {
+ //Implementation excluded. Potential call external service / database
+ }
+}
+
+----
+
+Next, register a `SolverModelEnrichmentDirector` implementation.
+This class allows you to determine the order in which enrichers are executed.
+This might be important when 1 of your custom enrichers depends on an enricher provided by Timefold Solver.
+//TODO add link to maps component docs https://github.com/TimefoldAI/timefold-solver/issues/2348
+
+.Timetable Enrichment Director for the School Timetabling example
+[source,java,options="nowrap"]
+----
+@ApplicationScoped
+public class TimetableEnrichmentDirector implements SolverModelEnrichmentDirector {
+
+ private final TimeslotHolidayEnricher timeslotEnricher;
+
+ @Inject
+ public TimetableEnrichmentDirector(TimeslotHolidayEnricher timeslotEnricher) {
+ this.timeslotEnricher = timeslotEnricher;
+ }
+
+ @Override
+ public Timetable enrich(Timetable solverModel) {
+ var enrichedModel = timeslotEnricher.enrich(solverModel);
+ // Additional enrichers.
+ return enrichedModel;
+ }
+}
+----
\ No newline at end of file
diff --git a/docs/src/modules/ROOT/pages/service/overview.adoc b/docs/src/modules/ROOT/pages/service/overview.adoc
new file mode 100644
index 00000000000..2ad7e964139
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/service/overview.adoc
@@ -0,0 +1,30 @@
+[#serviceOverview]
+= Building a service (Preview)
+:description: Overview of running Timefold Solver as a fully isolated optimization service.
+:doctype: book
+:sectnums:
+:icons: font
+
+include::_preview-note.adoc[]
+
+Run dataset optimization as a fully isolated service.
+This opinionated approach builds on https://quarkus.io[Quarkus] and eliminates the boilerplate typically needed to expose a solver over a REST API.
+
+If you haven't done so yet, start with the xref:../quickstart/service/getting-started.adoc[Getting started: building a service] guide.
+
+[#foundationsStillApply]
+== Foundations still apply
+
+Building a service does not replace the core Timefold Solver concepts, it builds on top of them.
+Everything covered in the following sections is still relevant and applies directly to your service model:
+
+xref:../using-timefold-solver/overview.adoc#usingTimefoldSolverOverview[*Using Timefold Solver*]::
+How to model your planning problem, configure the solver, and understand the solving lifecycle.
+This knowledge is required regardless of whether you embed the solver as a library or run it as a service.
+
+xref:../constraints-and-score/overview.adoc#constraintsAndScoreOverview[*Constraints and Score*]::
+How to define hard and soft constraints, choose a score type, and analyze solution quality.
+Constraint streams and score calculation work identically in a service model.
+
+The pages in this section cover only what is *specific* to running the solver as a service:
+the REST API contract, model enrichment, constraint weight overrides, demo data, and metrics.
diff --git a/docs/src/modules/ROOT/pages/service/rest-api.adoc b/docs/src/modules/ROOT/pages/service/rest-api.adoc
new file mode 100644
index 00000000000..5b8cade931c
--- /dev/null
+++ b/docs/src/modules/ROOT/pages/service/rest-api.adoc
@@ -0,0 +1,424 @@
+= REST API
+:description: Information on the automatically generated REST API.
+:doctype: book
+:sectnums:
+:icons: font
+
+The REST API module will automatically generate REST API endpoints so you can interact with the optimization service.
+
+include::_preview-note.adoc[]
+
+== Setup
+
+In order for the API to be automatically generated, Timefold Solver will look for implementations of a couple interfaces.
+The following are expected.
+
+- An implementation of `ModelInput` to define the class which will be mapped from JSON to Java on API requests.
+- An implementation of `ModelOutput` to define the class which will be mapped from Java to JSON on API responses.
+- An interface which extends `ModelRest` to define the root path of the REST API.
+
+For example, in the case of School Timetabling, those classes could look as follows.
+[source,java,options="nowrap"]
+----
+// TimetableDto.java
+public class TimetableDto implements ModelInput, ModelOutput {
+
+ @Schema(required = true, description = "The unique identifier of timetable")
+ private String name;
+ @Schema(required = true, description = "List of timeslots")
+ private List timeslots;
+ @Schema(required = true, description = "List of lessons")
+ private List lessons;
+
+ // Getters, Setters and constructor excluded.
+}
+
+// TimetableSchedulingResource.java
+@Tag(name = "School Timetabling",
+ description = "School timetabling service assigning lessons to timeslots.") // OpenAPI documentation annotation
+@Path("/v1/timetables") //sets the root path
+public interface TimetableSchedulingResource extends ModelRest {
+}
+----
+
+Note how the `ModelInput` and `ModelOutput` interface can be placed on the same class.
+
+Since an xref:#openAPISpecification[OpenAPI specification] will also be automatically generated, it's important to properly document your `ModelInput` and `ModelOutput` classes with OpenAPI annotations.
+
+[#generatedEndpoints]
+== Generated endpoints
+
+The following endpoints are generated by the REST module.
+The is determined by the @Path annotation on the ModelRest interface (`/v1/timetables` in the example above).
+
+- `GET /`: Retrieve all registered optimization datasets
+- `POST /`: Create a new optimization dataset
+- `GET //\{id\}`: Retrieve the (intermediate) result of a dataset optimization run
+- `GET //\{id\}/score-analysis`: Retrieve score analysis of an optimization dataset
+- `DELETE //\{id\}`: Terminate a dataset optimization run
+
+If your model provides xref:./demo-data.adoc[demo data], the following endpoints are also created.
+
+- `GET //demo-data`: Retrieve all available demo dataset names.
+- `GET //demo-data/\{name\}`: Retrieve the demo dataset with the given identifier
+
+// TODO See the xref:consumer-guide.adoc[service consumer guide] for information on how consumers of the service should interact with these API endpoints.
+// TODO https://github.com/TimefoldAI/timefold-solver/issues/2349
+
+[#requestResponseStructure]
+== Request and response structure
+
+The generated endpoints do not accept/return the pure `ModelInput`/`ModelOutput` objects as JSON.
+Instead, a fixed envelope is used for both the requests and responses of solver actions.
+
+=== Structured solving request
+
+A Solving Request always contains at least 2 major parts, the `modelInput` object and a `config` object.
+
+- The `modelInput` object contains the JSON formatted `ModelInput` object defined in Java.
+- The `config` object has two fields: the run and the model.
+
+Additionally, more fields might be added for specific model implementations.
+
+.Example Request
+[source,json]
+----
+{
+ "config": {
+ "run": {
+ "name": "dataset name",
+ "termination": {
+ "spentLimit": "PT5M",
+ "unimprovedSpentLimit": "PT10S"
+ },
+ "maxThreadCount": 1,
+ "tags": []
+ },
+ "model": {
+ "overrides": ""
+ }
+ },
+ "modelInput" : ""
+}
+----
+
+The `run` configuration has four fields:
+
+- `name` - The run name (if empty, it will be generated).
+- `maxThreadCount` - The maximum thread count, which indicates the maximum number of threads to be used for solving.
+If not provided, 1 will be used.
+- `tags` - The tags, which are a set of optional tags to be assigned to the run.
+- `termination` - The termination properties determining how long the solver should run:
+
+* `spentLimit` - The maximum duration to keep the solver running (ISO 8601 Duration).
+If omitted, no maximum duration will be set (platform limits will apply).
+
+* `unimprovedSpentLimit` - The maximum unimproved score duration, i.e. if the score has not improved during this period, the solver will terminate (ISO 8601 Duration).
+If omitted, the https://docs.timefold.ai/timefold-solver/latest/optimization-algorithms/overview#diminishedReturnsTermination[diminished returns termination] will be used.
+
+[TIP]
+The diminished returns termination is the recommended default setting.
+This termination is desirable since it terminates based on the relative rate of improvement, and behaves similarly on different hardware and different problem instances.
+`unimprovedSpentLimit` should be set only when necessary.
+
+The `model` configuration is a model-specific field, that contains additional global model configuration attributes.
+See xref:./constraint-weights.adoc[Model Configuration Overrides] for more information.
+
+[#solvingResponse]
+=== Structured solving response
+
+A Solving Response always contains at least 2 major elements, the `modelOutput` object and a `metadata` object.
+
+- The `modelOutput` object contains the JSON formatted `ModelOutput` object defined in Java.
+- The `metadata` object provides more details about the optimization run.
+
+Additionally, more fields might be added for specific model implementations:
+
+- `inputMetrics`: metrics about the input of the planning problem. See: xref:./exposing-metrics.adoc#modelInputMetrics[Input Metrics]
+- `kpis`: metrics about the output of the planning problem. See: xref:./exposing-metrics.adoc#modelOutputMetrics[Output Metrics]
+
+.Example Response
+[source,json]
+----
+{
+ "metadata": {
+ "id": "dataset-id",
+ "name": "dataset-name",
+ "submitDateTime": "2022-03-10T12:15:50-04:00",
+ "startDateTime": "2022-03-10T12:15:50-04:00",
+ "activeDateTime": "2022-03-10T12:15:50-04:00",
+ "completeDateTime": "2022-03-10T12:15:50-04:00",
+ "shutdownDateTime": "2022-03-10T12:15:50-04:00",
+ "solverStatus": "NOT_SOLVING",
+ "score": "string",
+ "tags": [],
+ "validationResult": {
+ "summary": "VALIDATION_NOT_SUPPORTED",
+ "errors": [],
+ "warnings": []
+ }
+ },
+ "modelOutput": "",
+ "inputMetrics": {
+ "lessons": 200,
+ "timeslots": 50
+ },
+ "kpis": {
+ "unassignedLessons": 0,
+ "maxConsecutiveLessons": 3,
+ "earliestTimeslotStart": "2027-02-02T09:00:00Z",
+ "latestTimeslotStart": "2027-02-05T17:00:00Z"
+ }
+}
+----
+
+The `metadata` object contains a couple of fields:
+
+- `id` - The dataset id, as generated by the Solver.
+- `name` - The dataset name.
+- `solverStatus` - The status of the optimization.
+- `score` - The score of the solution.
+- `tags` - The assigned tags of the dataset.
+- `validationResult` - The result of the xref:#validatingRestInput[validation]. This might signal errors in the original input.
+
+The remaining fields are timestamps for each step in the optimization lifecycle.
+
+The metadata object contains five key timestamps, each marking a distinct phase in the process:
+
+- `submitDateTime`: The moment the dataset is submitted. At this stage, the dataset is queued and has not yet begun solving. The dataset status is `SOLVING_SCHEDULED`.
+- `startDateTime`: The moment the run begins initializing. During this phase, the dataset is not solving yet, but the model is being built and enriched with external data (e.g., incorporating map information for models involving geographical locations). The dataset status is `SOLVING_STARTED`.
+- `activeDateTime`: The moment the solving phase begins. At this stage, the actual problem-solving process starts. The dataset status is `SOLVING_ACTIVE`.
+- `completeDateTime`: The moment the solving phase concludes. At this point, the dataset has finished solving but has not yet undergone any post-processing (e.g., score analysis or waypoint enrichment for geographical models). The dataset status is either `SOLVING_COMPLETED` or `SOLVING_FAILED`.
+- `shutdownDateTime`: The moment the post-processing phase finishes. All tasks, including post-processing, are completed, and the dataset optimization is fully finalized.
+
+[#modelConverter]
+== Decoupling API interaction from the solver domain
+
+While it is possible to add the `ModelInput` and `ModelOutput` directly on the `SolverModel` (the `@PlanningSolution` class used by the solver),
+it's often beneficial to decouple the classes used for the REST API interactions and the classes used for the optimization.
+
+To convert between `ModelInput` / `ModelOutput` and the `SolverModel`, a `ModelConvertor` implementation can be provided.
+
+[source,java,options="nowrap"]
+----
+@ApplicationScoped
+public class TimetableConvertor implements ModelConvertor {
+
+ @Override
+ public Timetable toSolverModel(TimetableDto modelInput, ModelConfig modelConfig,
+ Optional lastModelOutput) {
+ return // Mapping logic
+ }
+
+ @Override
+ public TimetableDto toModelOutput(Timetable solverModel) {
+ return // Mapping logic
+ }
+
+ @Override
+ public TimetableDto applyOutputToInput(TimetableDto modelInput, TimetableDto modelOutput) {
+ return // Mapping logic
+ }
+}
+----
+
+[NOTE]
+====
+This mechanism should only be used for ModelInput -> SolverModel -> ModelOutput mapping.
+For enhancing the SolverModel, use the xref:./modeling-changes.adoc#solverModelEnrichment[SolverModel Enrichment] mechanism instead.
+====
+
+[#validatingRestInput]
+== Validating REST input
+
+By default, validation on the input is based on the xref:./rest-api.adoc#openAPISpecification[OpenAPI Specification annotations].
+Additional validations can be added by implementing the `ModelValidator` interface.
+
+[#timetableValidatorExample]
+[source,java,options="nowrap"]
+----
+@ApplicationScoped
+public class TimetableValidator implements ModelValidator {
+
+ @Override
+ public void validate(ValidationBuilder validationBuilder, TimetableDto input, ModelConfig modelConfig) {
+
+ //validation logic here, simplified example here.
+ if(hasDuplicateTeacherNames(input)) {
+ validationBuilder.addIssue(TimetableValidationIssue.DUPLICATE_TEACHER.asIssueType(), new DuplicateTeacherDetail("Ann"));
+ }
+ }
+}
+
+public enum TimetableValidationIssue {
+
+ DUPLICATE_TEACHER(IssueCode.of("DUPLICATE_TEACHER"), IssueSeverity.ERROR,
+ "Duplicate teacher names found.");
+
+ private final IssueType issueType;
+
+ TimetableValidationIssue(IssueCode code, IssueSeverity severity, String message) {
+ this.issueType = new IssueType(code, severity, message);
+ }
+
+ public IssueType asIssueType() {
+ return issueType;
+ }
+
+ public record DuplicateTeacherDetail(String teacherName) implements IssueDetail {
+ }
+}
+----
+
+The `ValidationBuilder` supports issue types of different severity:
+
+- `IssueSeverity.ERROR`: error level: processing can not continue. Results in a BAD_REQUEST (400) HTTP error.
+- `IssueSeverity.WARNING`: warning level: processing will continue.
+
+All validation information can be accessed either in a simple textual form, or as structured machine-readable objects.
+
+=== Simple validation result in metadata object
+
+The `metadata` object is a part of the model response json and contains the validation result in a textual form to provide an immediate feedback on the input json.
+
+.Validation result in response json.
+[source,json, highlight=13..17]
+----
+{
+ "metadata": {
+ "id": "dataset-id",
+ "name": "dataset-name",
+ "submitDateTime": "2022-03-10T12:15:50-04:00",
+ "startDateTime": "2022-03-10T12:15:50-04:00",
+ "activeDateTime": "2022-03-10T12:15:50-04:00",
+ "completeDateTime": "2022-03-10T12:15:50-04:00",
+ "shutdownDateTime": "2022-03-10T12:15:50-04:00",
+ "solverStatus": "NOT_SOLVING",
+ "score": "string",
+ "tags": [],
+ "validationResult": {
+ "summary": "ERRORS",
+ "errors": ["Duplicate teacher names found."],
+ "warnings": []
+ }
+ },
+ "modelOutput": ""
+}
+----
+
+=== Machine-readable validation result
+
+Additionally, a richer validation result can be obtained by a dedicated REST endpoint:
+
+`GET /v1/timetables/\{id\}/validation-result`
+
+Assuming the xref:timetableValidatorExample[example TimetableValidator], the endpoint provides the following response:
+
+.Validation result available via `GET /v1/timetables/\{id\}/validation-result`.
+[source,json, highlight=13..17]
+----
+{
+ "status" : "ERRORS",
+ "issues" : [ {
+ "id" : 1,
+ "code" : "DUPLICATE_TEACHER",
+ "severity" : "ERROR",
+ "detail" : {
+ "teacherName" : "Ann"
+ }
+ } ]
+}
+----
+
+The machine-readable validation result enables for corrective actions.
+In this example, the timetable can be corrected by removing the duplicate teacher Ann.
+
+[#customEndpoints]
+== Adding custom endpoints
+
+You can add custom endpoints in the `ModelRest` implementation.
+For example, if you want to extend the Timetable REST API, you can add custom https://jakarta.ee/learn/docs/jakartaee-tutorial/current/websvcs/rest/rest.html[Jakarta REST endpoints].
+These endpoints should also be documented appropriately for the automatically generated xref:#openAPISpecification[OpenAPI specification].
+
+[source,java,options="nowrap"]
+----
+@Tag(name = "School Timetabling",
+ description = "School timetabling service assigning lessons to timeslots.") //OpenAPI documentation annotation
+@Path("/v1/timetables") //sets the root path
+public interface TimetableSchedulingResource extends ModelRest {
+
+ @APIResponses(value = {
+ @APIResponse(responseCode = "500", description = "In case of processing errors",
+ content = @Content(mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = ErrorInfo.class))),
+ @APIResponse(responseCode = "200", description = "List of all teachers in all optimization runs.",
+ content = @Content(mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = ListTeachersResponseDto.class))) })
+ @Operation(operationId = "list-teachers-in-solver-model",
+ summary = "Lists all teachers in the solver model.")
+ @GET
+ @Path("/insights/teachers")
+ @Produces(MediaType.APPLICATION_JSON)
+ default Response insightsTeachers() {
+ var teachers = //get the list of teachers
+ return Response.ok(teachers).build();
+ }
+}
+----
+
+[#openAPISpecification]
+== OpenAPI specification
+
+The OpenAPI specification is a machine-readable document that defines the structure, endpoints, request/response formats, and authentication of a RESTful API.
+A specification file is automatically generated from the https://download.eclipse.org/microprofile/microprofile-open-api-1.0/microprofile-openapi-spec.html[MicroProfile OpenAPI annotations] on the Java classes.
+
+[TIP]
+For more info on which annotations are available and how they relate to the generated OpenAPI specification, check out the official https://download.eclipse.org/microprofile/microprofile-open-api-1.0/microprofile-openapi-spec.html[MicroProfile OpenAPI specification here].
+
+// IDEA: We could have a small guide on OpenAPI Specification generation using those annotations instead of linking to another location.
+
+After building the model with `mvn verify`, the generated API spec can be found in the `target` folder: `target/openapi-schema/openapi.json`.
+
+[#deliberateAPIChanges]
+=== Deliberate API changes
+
+As with any API, breaking the REST API in newer versions of your model should be avoided to ensure backward compatibility and prevent disruptions for existing consumers.
+Because Timefold Solver takes care of a lot of boilerplate code, it might be harder to see when an API change was accidentally introduced.
+
+Timefold Solver supports comparing a pre-existing OpenAPI specification file in your project `src/build/openapi.json` with the one generated during the build.
+If there is any difference between the 2 files, the build will fail.
+
+If the API changes are intentional, run the following command which will copy the generated `target/openapi-schema/openapi.json` to `src/build/openapi.json` file.
+
+[source,shell script]
+----
+mvn clean package -Dupdate-api
+----
+
+This approach ensures that any change to the API has to be reflected in the `src/build/openapi.json` file.
+
+[TIP]
+We recommend to keep this file in Version Control, this makes sure the changes made are deliberate (committed and pushed) rather than accidental.
+
+[#customizeSummary]
+=== Customize summary of solver methods
+
+As mentioned before, the REST endpoints for solver methods are automatically generated.
+This means users do not have the option to set the _OpenAPI Annotations_ for those methods.
+
+Timefold Solver does support setting a __custom summary__ through the use of properties in the `application.properties` file.
+This uses the following format:
+
+[source,properties]
+----
+timefold.rest..summary=Your custom summary here
+----
+
+The `` is the name of the method in the API Spec (`operationId` field).
+
+For example, if you want to override the OpenAPI summary for the `schedule` operation, add the following entry in your properties file:
+
+[source,properties]
+----
+timefold.rest.schedule.summary=Request a timetable to be solved asynchronously.
+----
diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/benchmarking-and-tweaking.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/benchmarking-and-tweaking.adoc
index fde020bb5da..b70c097eddc 100644
--- a/docs/src/modules/ROOT/pages/using-timefold-solver/benchmarking-and-tweaking.adoc
+++ b/docs/src/modules/ROOT/pages/using-timefold-solver/benchmarking-and-tweaking.adoc
@@ -4,6 +4,8 @@
:doctype: book
:sectnums:
:icons: font
+:relevance: library-and-service
+:notes: Mostly irrelevant in service module.
[#findTheBestSolverConfiguration]
diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/configuration.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/configuration.adoc
index 81645507ae6..a849a5e6efd 100644
--- a/docs/src/modules/ROOT/pages/using-timefold-solver/configuration.adoc
+++ b/docs/src/modules/ROOT/pages/using-timefold-solver/configuration.adoc
@@ -3,6 +3,9 @@
:doctype: book
:sectnums:
:icons: font
+:relevance: core-only
+:notes: Mostly irrelevant in service module. Also, "configuring" is too broad
+
[#solverConfigurationByXML]
== Solver configuration by XML
diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc
index 9f9e8f514c7..81fbc37e89e 100644
--- a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc
+++ b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc
@@ -4,6 +4,8 @@
:doctype: book
:sectnums:
:icons: font
+:relevance: library-and-service
+:notes: Lots of shared things here, but might need to split up some things.
[TIP]
====
diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/overview.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/overview.adoc
index 84a57094e8c..e2140ad672d 100644
--- a/docs/src/modules/ROOT/pages/using-timefold-solver/overview.adoc
+++ b/docs/src/modules/ROOT/pages/using-timefold-solver/overview.adoc
@@ -4,6 +4,8 @@
:doctype: book
:sectnums:
:icons: font
+:relevance: library-and-service
+:notes: split into both usages
Solving a planning problem with Timefold Solver consists of the following steps:
diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/running-the-solver.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/running-the-solver.adoc
index 318a71d6e27..52dc3694ad4 100644
--- a/docs/src/modules/ROOT/pages/using-timefold-solver/running-the-solver.adoc
+++ b/docs/src/modules/ROOT/pages/using-timefold-solver/running-the-solver.adoc
@@ -4,6 +4,8 @@
:doctype: book
:sectnums:
:icons: font
+:relevance: core-only
+:notes: Too many things the service module does automatically, or with a single configuration.
[#theSolverInterface]
== The `Solver` interface