When to use this endpoint

Use after selecting rate records and quantities in the Resources rate widget. Send the complete desired list, not a delta.

Call Job_updatePrices afterward when sell totals must immediately reflect the changed rates; this handler does not do so.

Authentication and permissions

Authenticate with signed API-key fields or a logged-in CRM session. The handler performs no additional page or role-permission check.

Job and form IDs refer to the authenticated account. Cross-account writes occur only through the stored outsourcing relationship on the routes that explicitly mirror changes.

The dispatcher also applies account/user and IP restrictions. A CRM session carrying a CSRF token must supply it through X-FMCSRF or _csrf; missing/incorrect tokens can be rejected before this handler runs.

Parameters

ParameterTypeRequiredDescription
idInteger stringYesPositive physical job ID.
valJSON array stringYesComplete rate list. [] clears it. Nonempty input must have sor_id on row 0. Normal rows contain sor_id and numeric amt, with optional price, stock_cost, labour_cost and cost. Missing price defaults to zero through compatibility conversion, not the rate’s catalog sell price.

Example request

$result = FM_api('Job_setScheduleOfRates', [
	'id'=>'1042', 'val'=>json_encode([['sor_id'=>205,'amt'=>2,'price'=>45.50]]),
]);

Example response

{"ok":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":"missing or invalid parameter: id"}Nonpositive ID.
{"error":"missing parameter: val"}val is absent/null.
{"error":"invalid parameter: val"}Decoded input is not an array or a nonempty list lacks row-0 sor_id.
{"error":"Unable to save job"}The primary database save failed. Successful-save history and subsequent follow-ups are not run.
{"error":"job does not exist"}The job does not exist or is soft-deleted; no answer or resource write is performed.

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

Behaviour and edge cases

  • Writes both legacy tuple schedule_of_rates and canonical schedule_of_rates2 JSON. The rate parser skips rows without positive numeric rate IDs/numeric amounts; full row validation is not performed at the endpoint boundary.
  • When absent, stock_cost/labour_cost/cost are enriched from rate components: current stock average costs and rate labour totals. Existing supplied unit-cost fields are preserved. sor_cost sums unit cost×amount and rounds to three decimals.
  • No active-rate existence check is imposed. Missing rate components contribute zero unless explicit costs were supplied. Existing cost snapshots are not automatically refreshed to later catalog costs.
  • Requires an active job before computing and saving rate snapshots. Missing/deleted jobs return job does not exist. No pagination, batch limit or concurrency guard.

Side effects

  • Checked active-job rate/cost/timestamp/edit-marker UPDATE, meetings-cache invalidation and generic job history only after a successful save.
  • No Job_updateCosts/Job_updatePrices follow-up, stock deduction, invoice write or outsourced mirror is performed by this handler.

See it in Fieldmotion

CRM1

Open the job Resources/Costs page and edit schedule-of-rates rows. pageJobCosts.js debounces changes for 200 ms and sends the full list to Job_setScheduleOfRates.

CRM2

No direct canonical or legacy caller was found in CRM2’s source. Its layout-driven resource editing is a separate save contract. Its Costs page explicitly finishes with Job_updatePrices rather than directly calling this rate setter.

Related endpoints