Schedule of Rates
Add a Schedule of Rates row
/a/ScheduleOfRates_add
Create a Schedule of Rates row with the legacy CRM1 payload and nominal-account resolution.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Trimmed nonblank rate name. Duplicate names are accepted. |
code / description | String | No | Trimmed stored values; default to blank. |
cost | Number | No | Base customer price after floating-point conversion; defaults to 0. |
stock_used | Native array or JSON string | No | Normally [[stockId, quantity], ...]. Strings are decoded and the result is re-encoded; malformed JSON is stored as JSON null. Omission becomes []. |
labour_used | Native array or JSON string | No | Legacy [[userId, hours], ...] or newer labour objects. Strings are decoded and re-encoded; malformed or omitted input is stored as JSON null. |
nominal_code | String | No | Trimmed account nominal code. The first tenant account with an exact code wins. An unknown nonblank code is retained with nominal ID 0. |
nominal_id | Integer string | No | Tenant 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
| Response | Condition |
|---|---|
{"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 legacynominal(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 applymeta.customerCosts, the tenant settingsor-child-gets-parent-price, and the customer’s price band later.
Side effects
- Inserts one tenant row and advances the
scheduleOfRatescache marker. - Fires the tenant-configurable
SOR_newevent 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
ScheduleOfRates_setTaxadds the legacy tax metadata after creation.ScheduleOfRate_createDatais CRM2’s create route.