requiredSkills. The resource carries what it has, in skills. The engine gives the order only to a resource whose skills hold every skill the order requires.
This guide covers requiredSkills and skills, the softer preferredStopTags, and the forbiddenAssignment constraint. Each section states one rule, with a link to the API reference where the exact behavior is written. The last section shows how to check a solution against these rules.
Reserve an order for qualified resources
A boiler repair needs a technician with a gas certification. Write the requirement on the order, and give the certification to the technicians who hold it:tech-certified, even when tech-plain lives next to the customer. In the sandbox, a certified technician 7 km away served the job, and the uncertified technician next door did not.
When no resource qualifies, the engine doesn’t fail. It leaves the stop out of every tour and lists it in unaffectedStopIds. The plan is still optimized, and the solution reports no violation. You need to read unaffectedStopIds to see the gap. See Checking the solution.
See Order.requiredSkills and Resource.skills for the rule.
Vehicles with equipment
The equipment of a vehicle works like the certification of a person. Say that only one van has a refrigerated body. Give that van the skill, and require the skill on the orders that need cold:van-refrigerated, even when van-ambient starts closer. In the sandbox, the engine sent the fresh order to the refrigerated van although the ambient van was next to it.
A requirement doesn’t reserve the resource. An order without requiredSkills can go to any resource, including one that has skills. In the same sandbox test, the refrigerated van also took the dry order, and the ambient van stayed unused. The default objectives try to use as few resources as possible before they shorten the distance.
How skills match
- Every skill is needed. An order that requires
gas-certifiedandelectrical-certifiedgoes only to a resource that has both. A resource with only one of them can’t serve it. - The match is exact. The text must be identical, case included. A resource with
Gas-Certifieddoesn’t serve an order that requiresgas-certified. Choose one naming habit, such as lowercase words joined by hyphens, so that every system that writes a plan writes the same skill the same way. - Tags are not skills. A string in the
tagsof a resource doesn’t satisfyrequiredSkills. Put it inskills. - The requirement belongs to the order.
requiredSkillsis a field of the order, not of the stop. If you write it on a stop, the API drops it and restricts nothing. Both SDKs refuse it before they send the plan. Withkardinal-ai1.1.0 and@kardinal-ai/sdk1.1.0, the error names the fieldplan.orders[0].stops[0].requiredSkills.
Prefer instead of require
Sometimes a resource should handle a stop when it can, but another resource may cover for it. A senior technician should take a demanding job, and a generalist can still do it. For this, tag the stop and usepreferredStopTags on the resource, instead of requiredSkills:
maximizePreferredStops, after minimizeCosts and minimizeResources. In the sandbox, with the default list, the senior technician took the preferred stop and the plain one together, because one resource serves both. If you write your own objectives list, add maximizePreferredStops yourself.
See Resource.preferredStopTags for the rule.
Barring one resource from a stop
forbiddenAssignment bars one resource tag from one stop tag. It fits when you know one resource that must stay away from one stop, and that resource carries the tag. For example, technician 1 is banned from a site, so the technician carries technician:1 and the stop carries site:restricted:
technician:1. It gives the stop to another resource. When no other resource can serve it, the stop stays unplanned, with no violation.
The rule bars only the resources that carry the tag. A technician that you add later without the tag can serve the site, and the engine doesn’t warn you. See the Modelling pitfall of forbiddenAssignment in the API reference.
For a rule about a kind of resource, write a requirement instead of a ban. Take a city that forbids trucks over 7.5 t in its old town:
- As a ban, you tag every heavy truck
heavy-truck, tag the old-town stops, and add theforbiddenAssignment. A heavy truck added later without its tag can serve the old town. The mistake is silent. - As a requirement, you give
small-vehicleto the vehicles allowed in the old town, and require it on the old-town orders. A vehicle added later without the skill can’t serve them. The mistake is visible: if no other vehicle has the skill, the stop stays inunaffectedStopIdsuntil you add it.
Pinning a stop to one resource
To pin a stop to one resource, see the pattern “Isolate” in Manual assignment and supervision. A pinned stop is an exception to the rules of this guide. Say you pin a stop instate.assignments on a resource that lacks the skill. The engine keeps the stop there. The plan is still optimized, and no stop is unplanned. The tour of that resource has isValid: false and a violation of type skills, with the list lackingSkills. In the sandbox, this held for the three mode and status pairs tested.
A stop pinned on a resource barred by a forbiddenAssignment stays too. The tour then has a violation of type forbiddenAssignment.
Checking the solution
The engine doesn’t report a missing skill as an error. Read these fields of the solution:unaffectedStopIdslists the stops that no resource could serve, for example because none had the skill.tours[].isValidisfalsewhen a hard rule is broken, for example a pinned stop on a resource without the skill.tours[].violationssays which rule. A violation of typeskillslists thelackingSkills. A violation of typeforbiddenAssignmentlists the barred pair.
Example plan
This plan has two technicians.tech-certified holds the skill gas-certified, and tech-plain holds no skill. Each of the three orders shows one rule. The boiler needs a gas certification and sits next to tech-plain. The meter has no requirement. The panel needs an electrical certification that nobody holds.
What the solution shows
The sandbox returns this for the plan, with the offset ofEurope/Paris and with seconds. The other fields are left out.
- The boiler goes to
tech-certified, the only technician with the skill, althoughtech-plainlives next to it. - The meter has no requirement.
tech-certifiedserves it too, andtech-plainstays unused. The default objectives use as few resources as they can. - The panel is in
unaffectedStopIds. Nobody holdselectrical-certified, so no tour has it. The tour is valid and the plan isoptimized: nothing in the solution calls this a failure.
A check in code
These snippets read the solution you got for the example plan. They list the unplanned stops. Then they print, for each tour, if it is valid and which skills it lacks. A violation of typeforbiddenAssignment is printed too.

