When to use this endpoint

Load ContractProgramme_route first. Submit the complete ordered days and items with the current revision; old revisions remain available to previously published Visits.

Send operation fields inside one JSON-encoded data object. When data is present it replaces top-level operation fields; otherwise the handler accepts the top-level request. String data is limited to 2 MB. Reserved transport/internal keys are removed. Malformed or non-array JSON becomes an empty input, so validate JSON before sending.

Authentication and permissions

Authenticate with a signed API key or logged-in CRM session; session requests follow dispatcher CSRF checks. API-key requests use the key owner’s identity and permissions.

Requires the Contracts entitlement (features & 64), features2.contractProgrammes and Contracts View. Also requires features2.contractProgrammePlanning. Requires Contracts Edit.

All storage belongs to the authenticated tenant. Contract operations follow the actor’s recursive HR manager/team scope, or the configured TeamAccess Contract/Department policy. Client administrators have broad Contract scope; private Programme and route ownership still applies. Active assignees must be within the permitted team.

The tenant must have the Programme storage upgrade (foundation 803; independent route ownership 804 before planning). Requests never create or repair tables. A missing/incompatible storage failure can return an initialization/storage error or Contracts operation failed. Check the saved state before retrying.

Parameters

ParameterTypeRequiredDescription
programme_idPositive integer or digit stringYesLocal Programme definition ID. It must be active for writes that start runs or save routes, and owned by you or marked as a default.
user_idPositive integer or digit stringYesActive local assignee within your permitted team; use the workspace’s board user choices.
revisionInteger-compatibleYes for existing routesCurrent route revision; zero/omitted for a new route.
daysArray or JSON listYes1–366 ordered day objects; array position supplies day_number, so supplied day numbers are ignored.
days[].capacity_minutesPositive integerNoDefaults to account work-day duration (fallback 480); max 1440. Advisory capacity, not a sum-of-items limit.
days[].itemsArrayNoDefaults to []. At most 5000 items across route. Each needs contract_id and site_id; order determines position. Contract must be active, Site active and linked.
days[].items[].job_idInteger-compatibleNoZero/omitted means recurring Site service. Positive value adopts an existing incomplete ordinary customer Job matching Contract/Site, local user_type=0, unclaimed by Programme provenance or another current route; no duplicate reactive Job in this route.
days[].items[].duration_minutesPositive integer or digit stringNo1–1440 minutes; defaults to Contract default_duration.

Example request

$result = FM_api('ContractProgramme_saveRoute', [
	'data' => json_encode([
		'programme_id' => 410,
		'user_id' => 104,
		'revision' => 0,
		'days' => [
			[
				'capacity_minutes' => 480,
				'items' => [
					[
						'contract_id' => 210,
						'site_id' => 1043,
						'duration_minutes' => 60,
					],
				],
			],
		],
	]),
]);

Example response

{
  "ok": 1,
  "id": 610,
  "revision": 1
}

Errors and unusual responses

ResponseCondition
{"error":"not logged in"}No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs.
{"error":"Programme Contracts is not enabled"}Workspace flag or Contracts entitlement is disabled.
{"error":"Permission denied"}Contracts View or the required action permission is unavailable.
{"error":"This Contracts capability is not enabled"}The operation’s additional feature flag is disabled.
{"error":"Route changed. Reload before saving."}Revision differs from existing route.
{"error":"A reactive Job can occur only once on a route"}Repeated reactive Job ID.
{"error":"Reactive Job is already on another route"}Another current route contains this Job.

Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.

Behaviour and edge cases

  • Each successful save increments route revision, inserts new days/items and retains old revisions. Other actors using the same default Programme/user have independent route records.
  • Recurring Site entries may repeat. Reactive Jobs are one-off and may not exist on another current route, including another creator’s route.
  • Capacity does not reject an overloaded day. Saving neither allocates calendar dates nor creates/adopts Jobs yet. Empty days are allowed; an empty entire route is rejected.
  • An inactive Programme or Contract blocks saving; Site must be non-deleted, active, a child Customer and linked to the Contract. Publication revalidates references.

Side effects

  • The service serializes writes on the tenant owner in a transaction. Validation, persistence, audit or TeamAccess outstanding-Job access failures roll back that transaction. Successful changed writes refresh linked Contract/Customer sync timestamps and shared mobile update markers, then invalidate CRM caches after success. These operations do not create schema or enable account features.
  • Writes route revision/day/item rows and route_saved audit. Previous publications continue using their original route revision.

See it in Fieldmotion

CRM1

No direct caller was found in CRM1 source. This workflow is implemented by the opted-in CRM2 Programme Contracts workspace.

CRM2

In Planning Board drag Sites/reactive Jobs into days, reorder them, then Save Route. Filter for ContractProgramme_saveRoute; the UI sends the whole draft and current revision.

Related endpoints