Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
  • Platform
Try models
  • Field Service Routing
  • Visit service constraints
  • Visit profit

Field Service Routing

    • Introduction
    • Getting started: Hello world
    • User guide
      • Terminology
      • Use case guide
      • Planning AI concepts
      • Integration
      • Constraints
      • Understanding the API
      • Demo datasets
      • Input datasets
        • Model configuration
        • Model input
        • Planning window
        • Time zones and daylight-saving time (DST)
      • Routing with Timefold’s maps service
      • Input validation
      • Model response
      • Output datasets
        • Metadata
        • Model output
        • Input metrics
        • Key performance indicators (KPIs)
      • Key performance indicators (KPIs)
      • Metrics and optimization goals
      • Visualizations
    • Vehicle resource constraints
      • Shift hours and overtime
      • Lunch breaks and personal appointments
      • Fairness
      • Route optimization
      • Technician costs
      • Technician ratings
      • Technician coverage area
    • Visit service constraints
      • Time windows and opening hours
      • Skills
      • Visit dependencies
      • Multi-vehicle visits
      • Multi-day schedules and movable visits
      • Priority visits and optional visits
      • Visit service level agreement (SLA)
      • Duration added for first visit on location
      • Visit profit
      • Visit requirements and tags
        • Visit requirements
        • Tags
    • Manual intervention
    • Recommendations
      • Visit time window recommendations
      • Visit group time window recommendations
    • Real-time planning
      • Real-time planning: extended visit
      • Real-time planning: reassignment
      • Real-time planning: emergency visit
      • Real-time planning: no show
      • Real-time planning: technician ill
      • Real-time planning: pinning visits
    • Real-time planning with patches
      • Real-time planning: extended visit (using patches)
      • Real-time planning: reassignment (using patches)
      • Real-time planning: emergency visit (using patches)
      • Real-time planning: no show (using patches)
      • Real-time planning: technician ill (using patches)
      • Real-time planning: pinning visits (using patches)
    • Scenarios
      • Long-running visits
      • Configuring labor law compliance
    • Changelog
    • Upgrade to the latest version
    • Feature requests

Visit profit

Field service companies often have more work available than their technicians can complete in a day. When capacity is limited, it matters which visits get scheduled.

By assigning a profit value to a visit, Timefold can prioritize visits that generate the most revenue. When a technician’s shift fills up, lower-profit visits are left unscheduled in favor of higher-profit visits.

This is particularly useful when visits are optional, visits whose time window extends beyond the planning window, so they can be deferred to a future day without a hard constraint violation.

  • 1. Fixed profit
  • 2. Example: prioritizing profitable work
  • 3. Maximize profit constraint

1. Fixed profit

Learn how to configure an API Key to run the examples in this guide:
  1. Log in to Timefold Platform: app.timefold.ai.

  2. From the Dashboard, click your tenant, and from the drop-down menu select Manage tenant, then choose API Keys.

  3. Create a new API key or use an existing one. Ensure the list of models for the API key contains the current model.

In the examples, replace <API_KEY> with the API Key you just copied.

A visit’s profit property contains a fixedProfit field representing the revenue gained when that visit is completed.

{
  "visits": [
    {
      "id": "Boiler replacement",
      "location": [33.77301, -84.43838],
      "serviceDuration": "PT2H",
      "profit": {
        "fixedProfit": 500
      }
    }
  ]
}

The fixedProfit is a flat amount of profit attributed to a visit, regardless of how long the visit takes or which technician completes it.

Timefold rewards scheduling a visit with fixedProfit set. The higher the value, the stronger the incentive to include that visit in the plan.

When a shift has room for only some of the available optional visits, Timefold selects the combination that maximizes the total profit across all scheduled visits.

2. Example: prioritizing profitable work

A plumbing company has one technician available on the morning of February 1st, 2027. The shift allows two hours of service (from 09:00 to 11:00), but there are three optional visits on the backlog, each taking one hour.

The boiler replacement (500) and the pipe repair (300) is the most profitable visit and is scheduled. The low-value faucet check (50) is left unscheduled.

  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and making the following API call:
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans [email protected]
{
  "config": {
    "run": {
      "name": "Visit profit example"
    }
  },
  "modelInput": {
    "planningWindow": {
      "startDate": "2027-02-01T00:00:00Z",
      "endDate": "2027-02-02T00:00:00Z"
    },
    "vehicles": [
      {
        "id": "Bob",
        "shifts": [
          {
            "id": "Bob-2027-02-01",
            "startLocation": [33.77301, -84.43838],
            "endLocation": [33.77301, -84.43838],
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxLastVisitDepartureTime": "2027-02-01T11:00:00Z"
          }
        ]
      }
    ],
    "visits": [
      {
        "id": "Boiler replacement",
        "location": [33.77301, -84.43838],
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-03T17:00:00Z"
          }
        ],
        "serviceDuration": "PT1H",
        "profit": {
          "fixedProfit": 500
        }
      },
      {
        "id": "Pipe repair",
        "location": [33.77301, -84.43838],
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-03T17:00:00Z"
          }
        ],
        "serviceDuration": "PT1H",
        "profit": {
          "fixedProfit": 300
        }
      },
      {
        "id": "Faucet check",
        "location": [33.77301, -84.43838],
        "timeWindows": [
          {
            "minStartTime": "2027-02-01T09:00:00Z",
            "maxEndTime": "2027-02-03T17:00:00Z"
          }
        ],
        "serviceDuration": "PT1H",
        "profit": {
          "fixedProfit": 50
        }
      }
    ]
  }
}
To request the solution, locate the ID from the response to the post operation and append it to the following API call:
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/field-service-routing/v1/route-plans/<ID>
{
  "metadata": {
    "id": "ID",
    "name": "Visit profit example",
    "submitDateTime": "2025-02-17T10:15:28.589040604Z",
    "startDateTime": "2025-02-17T10:15:34.610314505Z",
    "activeDateTime": "2025-02-17T10:15:34.87683868Z",
    "completeDateTime": "2025-02-17T10:20:35.024965784Z",
    "shutdownDateTime": "2025-02-17T10:20:35.299293025Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/0medium/3800000soft",
    "tags": [
      "system.profile:default"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "vehicles": [
      {
        "id": "Bob",
        "shifts": [
          {
            "id": "Bob-2027-02-01",
            "startTime": "2027-02-01T09:00:00Z",
            "itinerary": [
              {
                "id": "Pipe repair",
                "arrivalTime": "2027-02-01T09:00:00Z",
                "startServiceTime": "2027-02-01T09:00:00Z",
                "departureTime": "2027-02-01T10:00:00Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT0S",
                "travelDistanceMetersFromPreviousStandstill": 0,
                "minStartTravelTime": "2027-02-01T00:00:00Z",
                "kind": "VISIT"
              },
              {
                "id": "Boiler replacement",
                "arrivalTime": "2027-02-01T10:00:00Z",
                "startServiceTime": "2027-02-01T10:00:00Z",
                "departureTime": "2027-02-01T11:00:00Z",
                "effectiveServiceDuration": "PT1H",
                "travelTimeFromPreviousStandstill": "PT0S",
                "travelDistanceMetersFromPreviousStandstill": 0,
                "minStartTravelTime": "2027-02-01T00:00:00Z",
                "kind": "VISIT"
              }
            ],
            "metrics": {
              "totalServiceDuration": "PT2H",
              "totalBreakDuration": "PT0S",
              "totalWaitingTime": "PT0S",
              "totalTravelTime": "PT0S",
              "travelTimeFromStartLocationToFirstVisit": "PT0S",
              "travelTimeBetweenVisits": "PT0S",
              "travelTimeFromLastVisitToEndLocation": "PT0S",
              "totalTravelDistanceMeters": 0,
              "travelDistanceFromStartLocationToFirstVisitMeters": 0,
              "travelDistanceBetweenVisitsMeters": 0,
              "travelDistanceFromLastVisitToEndLocationMeters": 0,
              "endLocationArrivalTime": "2027-02-01T11:00:00Z",
              "overtime": "PT0S",
              "availableOvertime": "PT0S",
              "totalProfit": 800
            }
          }
        ],
        "metrics": {
          "activatedShifts": 1,
          "assignedVisits": 2,
          "totalShiftDuration": "PT2H",
          "totalServiceDuration": "PT2H",
          "totalTravelTime": "PT0S",
          "totalTravelDistanceMeters": 0,
          "totalBreakTime": "PT0S",
          "totalWaitingTime": "PT0S",
          "totalOvertime": "PT0S",
          "availableOvertime": "PT0S",
          "totalProfit": 800
        }
      }
    ],
    "unassignedVisits": [
      "Faucet check"
    ]
  },
  "inputMetrics": {
    "vehicles": 1,
    "vehicleShifts": 1,
    "visits": 3,
    "mandatoryVisits": 0,
    "optionalVisits": 3,
    "pinnedVisits": 0,
    "visitsWithSla": 0,
    "visitGroups": 0,
    "visitDependencies": 0,
    "excludedVisits": 0,
    "movableVisits": 3
  },
  "kpis": {
    "averageTravelTimePerVisit": "PT0S",
    "totalTravelTime": "PT0S",
    "travelTimeFromStartLocationToFirstVisit": "PT0S",
    "travelTimeBetweenVisits": "PT0S",
    "travelTimeFromLastVisitToEndLocation": "PT0S",
    "averageTravelDistanceMetersPerVisit": 0,
    "totalTravelDistanceMeters": 0,
    "travelDistanceFromStartLocationToFirstVisitMeters": 0,
    "travelDistanceBetweenVisitsMeters": 0,
    "travelDistanceFromLastVisitToEndLocationMeters": 0,
    "totalUnassignedVisits": 1,
    "totalAssignedVisits": 2,
    "assignedMandatoryVisits": 0,
    "unassignedMandatoryVisits": 0,
    "assignedOptionalVisits": 2,
    "unassignedOptionalVisits": 1,
    "totalActivatedVehicles": 1,
    "workingTimeFairnessPercentage": 100.0,
    "totalOvertime": "PT0S",
    "availableOvertime": "PT0S",
    "totalProfit": 800
  }
}

modelOutput contains the two most profitable visits in Bob’s shift itinerary. The unassignedVisits list contains Faucet check, the least profitable visit that could not fit within the shift’s capacity.

3. Maximize profit constraint

The Maximize profit soft constraint rewards each scheduled visit that has a positive fixedProfit. The reward is proportional to the profit value, so a visit worth 500 contributes ten times more to the score than a visit worth 50.

This means that when choosing which optional visits to schedule, Timefold naturally selects the combination that yields the highest total revenue, even when the individually most profitable visit is a worse fit than a pair of shorter, moderately profitable visits.

fixedProfit must be zero or greater. Negative values are rejected with a validation error.

Every soft constraint has a weight that can be configured to change the relative importance of the constraint compared to other constraints.

Learn about constraint weights.

Next

  • See the full API spec or try the online API.

  • Learn more about field service routing from our YouTube playlist.

  • Read Priority visits and optional visits to learn how to make visits optional so that less profitable work can be deferred.

  • Read Technician costs to learn how to minimize the cost of a visit alongside maximizing profit.

  • © 2026 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default