When to use this endpoint

Use ScheduleOfRates_add when reproducing CRM1’s create dialog or when the nominal code/ID should be resolved against tenant accounts.

CRM2 uses ScheduleOfRate_createData, whose field names and metadata handling differ.

Authentication and permissions

The request must establish tenant context with signed API credentials or a logged-in CRM session. The handler checks for the tenant database prefix rather than calling the normal login guard.

No additional page or role permission is checked. The CRM screens apply their own client-side Schedule of Rates permissions.

Parameters

ParameterTypeRequiredDescription
nameStringYesTrimmed nonblank rate name. Duplicate names are accepted.
code / descriptionStringNoTrimmed stored values; default to blank.
costNumberNoBase customer price after floating-point conversion; defaults to 0.
stock_usedNative array or JSON stringNoNormally [[stockId, quantity], ...]. Strings are decoded and the result is re-encoded; malformed JSON is stored as JSON null. Omission becomes [].
labour_usedNative array or JSON stringNoLegacy [[userId, hours], ...] or newer labour objects. Strings are decoded and re-encoded; malformed or omitted input is stored as JSON null.
nominal_codeStringNoTrimmed account nominal code. The first tenant account with an exact code wins. An unknown nonblank code is retained with nominal ID 0.
nominal_idInteger stringNoTenant account ID. If only a valid ID is supplied, its canonical code is stored. If both inputs resolve to different accounts, the request fails.

Example request

$created = FM_api('ScheduleOfRates_add', [
	'name' => 'Annual service',
	'code' => 'AS-01',
	'description' => 'Annual preventative service',
	'cost' => 150,
	'nominal_code' => '4000',
	'stock_used' => [[74, 1]],
	'labour_used' => [[18, 2]],
]);

Example response

{
  "ok": 1,
  "id": 430001
}

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: name"}name is absent or blank after trimming.
{"error":"nominal_id and nominal_code refer to different accounts"}Both values resolve, but not to the same account.

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

Behaviour and edge cases

  • The metadata object is replaced at creation with nominal_id, nominal_code, and legacy nominal (the numeric ID). Tax, asset types, price bands, customer prices, and archive state cannot be supplied here.
  • Current server time is written to last_edited. Duplicate name/code combinations are allowed.
  • These endpoints store the base price in cost; they do not calculate a customer’s final price. Job/quote consumers can apply meta.customerCosts, the tenant setting sor-child-gets-parent-price, and the customer’s price band later.

Side effects

  • Inserts one tenant row and advances the scheduleOfRates cache marker.
  • Fires the tenant-configurable SOR_new event with the new rate ID. Any configured listeners run synchronously and can add integration-specific side effects.

See it in Fieldmotion

CRM1

Open Stock → Schedule of Rates and use Create. CRM1 sends stock as ID/quantity pairs and labour as user-ID/hour pairs, then sets tax in a second request.

CRM2

CRM2 does not call this route; it uses the singular mapped create endpoint.

Related endpoints