When to use this endpoint

Use Customer_edit when reproducing the CRM1 main customer form and its exact snake-case field contract.

For a configurable CRM2 layout use Customer_updateData. Do not mix the two naming schemes: for example, this route expects account_number, while the mapped route expects accountNumber.

Authentication and permissions

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

The lookup and any write use the authenticated tenant; an ID from another tenant cannot be addressed through this call.

Parameters

ParameterTypeRequiredDescription
idInteger stringYesExisting non-deleted customer ID.
nameStringNoTrimmed, maximum 178 bytes. When duplicates are disallowed, an exact active duplicate is changed to the first available suffix beginning at (2).
contact_name, account_number, industry, mobile, landlineStringsNoTrimmed direct fields. Limits are 82, 58, unlimited, 40, and 38 bytes respectively. A blank contact number can be rejected by tenant settings.
emailStringNoTrimmed, maximum 64 bytes. Nonblank values must pass PHP email validation; blank clears the stored field.
street_address1, street_address2, town, city, county, state, postcode, countryStringsNoTrimmed address fields. Limits: town/city 40, county 299, state 255, postcode 20, and country 3 bytes; address-line length is not checked here.
latitude, longitudeNumbersNoConverted to floats. Address changes can trigger geocoding when submitted coordinates did not themselves change; submitting both as zero also asks for geocoding.
parent_idInteger stringNoNegative values become 0. Setting the customer as its own parent adds an item to errors; other parent IDs are not validated by this handler.
account_manager, type, department_id, is_flagged, price_bandInteger stringsNoConverted to nonnegative integers. type is also sent to Customer_setType before the main validation/update; department_id is sent to its setter again afterward.
billing_targetInteger stringNoConverted to an integer, including supported negative target modes. The referenced customer is not validated here.
callbackStringNoTrimmed; a 16-character value gets :00. Blank stores SQL null. No date-format validation is performed.
credit_termsStringNoTrimmed and updated only when shorter than 256 bytes. An overlong value is silently ignored rather than returned as an error.
accounts_name / accounts_numberStringsNoAccountant name/telephone, trimmed. Values over 100/32 bytes are silently ignored.
notesStringNoStored verbatim except a value beginning [{' has all single quotes replaced with double quotes for legacy JSON compatibility.
custom_valuesJSON object/array or JSON stringNoComplete replacement custom-values object. Changes are compared by key and stored with arrays forced to JSON object form.
account_number_sage / account_sage, email_accounting, email_extraStringNoLegacy aliases stored in metadata as account_sage, email_accounting, and email_extra.
default_nominal, default_taxcode, discountAny JSON-encodable scalarNoStored in customer metadata without semantic validation by this route.
default_invoice_due_dateInteger string or blankNoBlank removes the override; otherwise it must be one of 0, 7, 14, 21, 28, 30, 45, 60, 90. Invalid values are reported in the response errors array while other valid fields can still save.
do_not_sms / do_not_email_invoicesBoolean-like stringNoOnly on, true, or 1 stores integer 1; every other value stores 0 in metadata.
noredirectAnyNoSuppresses the legacy browser Location header when an HTTP referrer exists.
is_activeAnyNoExplicitly ignored by this handler. Use the enable/disable endpoints for lifecycle changes.

Example request

$result = FM_api('Customer_edit', [
	'id' => '1042',
	'name' => 'Northwind Service Centre',
	'email' => 'service@example.test',
	'country' => 'GBR',
	'default_invoice_due_date' => '30',
	'noredirect' => '1',
]);

Example response

{
  "ok": 1,
  "id": 1042,
  "errors": [],
  "updates": {
    "email": "\"service@example.test\"",
    "meta": "\"{...}\""
  }
}

updates exposes internal SQL assignment fragments, not clean business values. Treat it as diagnostic legacy output; use a read endpoint to obtain final stored data.

ok: 1 does not imply an empty errors array. Some field errors are accumulated while other fields are applied.

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":"no customer with that ID exists"}The ID is missing, zero, unknown, or soft-deleted.
{"error":"Contact Number was not set"}customers-require-landlines is enabled and submitted landline is blank or omitted.
{"error":"That landline is already in use by ..."}customer-landline-unique is enabled and another active customer has the exact trimmed number. The message contains CRM HTML links.
{"error":"<field> has a maximum length of <n>"}A field with an explicit limit is too long. Byte length is used.
errors: ["invalid email address"]A nonblank submitted email is invalid; other accepted fields can still be saved.
errors: ["Cannot set Customer as its own Parent."]parent_id equals id; other accepted fields can still be saved.
errors: ["default_invoice_due_date must be one of: ..."]The invoice override is outside the fixed legacy list; other accepted fields can still be saved.

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

Behaviour and edge cases

  • Only recognized switch cases are updated; unknown parameters are ignored. There is no pagination.
  • Duplicate contact-number checks use exact stored text among non-deleted customers. Duplicate-name checks are case-insensitive and controlled by customer-name-allow-duplicate.
  • Address changes are geocoded synchronously when submitted coordinates are stale/unchanged. A geocoding miss preserves old coordinates and does not fail the edit.
  • Clearing email updates the row but does not add that clear to the local changes collection, so this route omits the corresponding customer-history and per-field last-edited entry.
  • If nothing changes, the endpoint still fires Customer_edit and returns ok: 1 with updates: []. Changed rows use an object because the PHP array then has field-name keys.

Side effects

  • Updates the customer and cache, writes one customer_changes row per tracked changed field, and updates last_edited_all. Oversized change JSON is replaced by a placeholder.
  • Records address/parent history and refreshes job/customer-parent caches. A name-only change can reset cached job address details.
  • May call external geocoding, execute configured customer-type email triggers, set department history, and fire Customer_edit.
  • With an HTTP referrer and no noredirect, emits a redirect to the CRM1 customer edit page.

See it in Fieldmotion

CRM1

Open a customer, edit the main form, and save. The page submits to Customer_edit; filter Network requests by that name. Inline type controls can also call its setter before the main save.

CRM2

No direct production CRM2 caller was found. CRM2 configurable customer layouts use Customer_updateData; batch import can reach this route internally through Customers_edit.

Related endpoints