# Partially update a customer

`PATCH /accounting/customers/{id}`

Group: Accounting › Customers · Operation ID: `updateCustomer`

Writes only the fields the request body carries. Everything omitted is left exactly as it was, so a caller never has to read the customer first in order to change one field. The response is empty; read the updated record with GET accounting/customers/:id.

Omission and clearing are two different requests on the nullable fields (partnerType, invoiceEmail, mainEmail, segment, exemptionNumber): pass the key with a null value to clear it, or leave it out entirely to keep the current value. A cleared field is simply absent from the next read, the same as a customer that never had one set. `name`, `customerType`, `businessType`, `taxExempt` and `defaultCurrency` have no equivalent clear — they are required on the underlying record.

`entityId` is not part of this request: the owning legal entity is fixed at creation and cannot move — create a new customer under the right entity instead. Changing `name` and/or `customerType` is still checked against the (name, entityId, customerType) uniqueness the customer directory enforces; a collision fails with DUPLICATE_CUSTOMER.

customerType 'endCustomer' is rejected with END_CUSTOMER_NOT_SUPPORTED for the same reason CustomerCreateEndpointV1 rejects it — no field in this API's contract can carry the required partner link.

Addresses can be written two ways, and the request may use one or the other, not both. `addresses` replaces the whole set: send the complete list you want stored, or an empty array to remove them all — every address is recreated, so the addressIds change. `createAddresses`, `updateAddresses` and `deleteAddresses` instead edit the set in place, leaving anything they do not name untouched and keeping its addressId; `updateAddresses` and `deleteAddresses` identify an address by the addressId the read endpoints return, and fail with ADDRESS_NOT_FOUND when it is not on this customer. A deleted address is only detached from the customer — the record survives, since an invoice may still point at it. Each type keeps exactly one default throughout. `externalIdentifiers` follow the full create/update/delete split: `createExternalIdentifiers` adds a fieldName not already present, `updateExternalIdentifiers` rewrites one that is, and `deleteExternalIdentifiers` removes by fieldName; referencing a fieldName in the wrong bucket fails with EXTERNAL_IDENTIFIER_NOT_FOUND or DUPLICATE_EXTERNAL_IDENTIFIER.

An unknown id fails with NOT_FOUND. The call runs in a transaction, so a failure leaves the customer untouched.

[Open in the interactive reference](https://docs.api.lensing.ai/?op=updateCustomer)

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Api-Version` | header | `"v1"` | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. |
| `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. |
| `id` | path | string | yes | Platform id (a UUID) of the customer to update; bound from the :id path segment. |

## Request body

Required, `application/json`: updateCustomerInput.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | New customer display name. Omit to leave unchanged. |
| `customerType` | string | no | New customer type. One of 'default', 'partner', 'endCustomer' (the last rejected — see END_CUSTOMER_NOT_SUPPORTED). Cannot be changed once the customer appears on a sales order. Omit to leave unchanged. |
| `partnerType` | string or null | no | New partner type, relevant when customerType is 'partner'. One of 'reseller', 'thirdPartyPayer'. Omit to leave unchanged; pass null to clear. |
| `businessType` | string | no | New business type. One of 'company', 'individual'. Omit to leave unchanged. |
| `invoiceEmail` | string or null | no | New invoice email. Omit to leave unchanged; pass null to clear. |
| `mainEmail` | string or null | no | New main contact email. Omit to leave unchanged; pass null to clear. |
| `taxExempt` | boolean | no | New tax-exempt flag. Omit to leave unchanged. Setting this to false also clears exemptionNumber. |
| `defaultCurrency` | string | no | New default currency, as a 3-letter ISO 4217 code. Omit to leave unchanged. Cannot be cleared — every customer carries a currency. |
| `segment` | string or null | no | New market segment. One of 'SMB', 'MidMarket', 'Enterprise'. Ignored when the customer's effective businessType is 'individual'. Omit to leave unchanged; pass null to clear. |
| `exemptionNumber` | string or null | no | New tax-exemption certificate number. Only storable while the customer is tax exempt: supplying one when taxExempt is, or becomes, false fails with EXEMPTION_NUMBER_REQUIRES_TAX_EXEMPT. Omit to leave unchanged; pass null to clear. |
| `paymentTermCode` | string | no | Payment-term code applied to this customer's invoices. Fails with UNKNOWN_REFERENCE when no such payment term exists. |
| `parentId` | string | no | Customer UUID of this customer's parent in the business-partner hierarchy. Fails with UNKNOWN_REFERENCE when no such customer exists. |
| `partnerId` | string | no | Customer UUID of the partner this customer buys through. Fails with UNKNOWN_REFERENCE when no such customer exists. |
| `salesChannel` | string or null | no | Channel the customer was won through. A SalesChannel member. Omit to leave unchanged; pass null to clear. |
| `additionalContactEmail` | string or null | no | Further contact addresses, beyond the invoice and main contacts. Omit to leave unchanged; pass null to clear. |
| `mainContactName` | string or null | no | Name of the customer's main contact person. Omit to leave unchanged; pass null to clear. |
| `preferredLanguage` | string or null | no | Language the customer prefers correspondence in. A PreferredLanguage member. Omit to leave unchanged; pass null to clear. |
| `crmCustomerId` | string or null | no | The customer's identifier in the CRM system it was sourced from. Omit to leave unchanged; pass null to clear. |
| `businessIdentificationNumber` | string or null | no | Government-issued business registration number. Omit to leave unchanged; pass null to clear. |
| `invoiceConsolidation` | string or null | no | How invoices are consolidated for this customer. An InvoiceConsolidation member. Omit to leave unchanged; pass null to clear. |
| `invoiceDeliveryMode` | string or null | no | How invoices reach the customer. An InvoiceDeliveryMode member. Omit to leave unchanged; pass null to clear. |
| `isExemptFromCreditCardFee` | boolean | no | Whether the customer is exempt from credit-card surcharges. |
| `billableTravelExpenseInvoiceDetail` | string | no | Whether billable travel and expense items appear itemised or consolidated. An InvoiceDetail member. |
| `status` | string | no | Lifecycle status of the customer record. A CustomerStatus member. |
| `taxExemptCode` | string or null | no | Code behind the tax exemption. Omit to leave unchanged; pass null to clear. |
| `taxRegistrations` | array of object | no | Tax registrations to record for this customer. Upserted by (country, type): an entry matching an existing registration updates it, anything else is added; registrations left out are untouched. |
| `taxRegistrations[].country` | string | yes | ISO 3166-1 country code the registration applies in. |
| `taxRegistrations[].taxNumber` | string | yes | Official tax identification number issued by the tax authority. |
| `taxRegistrations[].type` | string | yes | Kind of tax identification number. One of 'tax', 'salesTax', 'VAT', 'GST', 'EIN', 'TIN'. |
| `taxRegistrations[].effectiveDate` | string (date) | no | Date the registration becomes valid for tax calculations (YYYY-MM-DD). |
| `taxRegistrations[].isActive` | boolean | no | Whether the registration is used for tax calculations. Defaults to true. |
| `payment` | object | no | Payment-provider link for this customer. `externalPaymentMethodId` requires an `externalCustomerId`, either in the same request or already stored. |
| `payment.provider` | string | no | Payment provider name, e.g. 'Stripe'. Required when any other payment field is supplied. |
| `payment.externalCustomerId` | string | no | The customer's identifier in the payment provider's system, e.g. a Stripe customer id. |
| `payment.externalPaymentMethodId` | string | no | The customer's default payment-method identifier in the provider's system, e.g. a Stripe payment method id. |
| `addresses` | array of object | no | The customer's addresses. The array REPLACES the whole address set across both types: omit the field to leave every address unchanged, or send an empty array to remove them all. |
| `addresses[].type` | string | yes | Which address this is. One of 'billTo', 'soldTo'. |
| `addresses[].attention` | string | no | Person or department correspondence at this address is directed to. |
| `addresses[].isDefault` | boolean | no | Marks this entry as its type's default. At most one per type; when a type has none marked, its first entry becomes the default. |
| `addresses[].line1` | string | yes | Street address line 1: number, street name, or PO box. |
| `addresses[].line2` | string | no | Apartment, suite, unit, building, floor, etc. |
| `addresses[].city` | string | yes | City or locality. |
| `addresses[].stateProvince` | string | no | State, province, or region. |
| `addresses[].country` | string | yes | Country, as a name or ISO 3166-1 code. |
| `addresses[].zipCode` | string | yes | Postal or ZIP code. |
| `createAddresses` | array of object | no | Addresses to add, on top of the ones the customer already has. Cannot be combined with `addresses`. |
| `createAddresses[].type` | string | yes | Which address this is. One of 'billTo', 'soldTo'. |
| `createAddresses[].attention` | string | no | Person or department correspondence at this address is directed to. |
| `createAddresses[].isDefault` | boolean | no | Marks this entry as its type's default, demoting the previous holder. At most one per type per request. |
| `createAddresses[].line1` | string | yes | Street address line 1: number, street name, or PO box. |
| `createAddresses[].line2` | string | no | Apartment, suite, unit, building, floor, etc. |
| `createAddresses[].city` | string | yes | City or locality. |
| `createAddresses[].stateProvince` | string | no | State, province, or region. |
| `createAddresses[].country` | string | yes | Country, as a name or ISO 3166-1 code. |
| `createAddresses[].zipCode` | string | yes | Postal or ZIP code. |
| `updateAddresses` | array of object | no | Addresses to rewrite, each identified by the addressId the read endpoints return. Every field but addressId is optional: an omitted one keeps its stored value. Rejected with ADDRESS_NOT_FOUND if the addressId is not on this customer. Cannot be combined with `addresses`. |
| `updateAddresses[].addressId` | string | yes | Id of the address record to rewrite (a UUID), as returned by GET accounting/customers/:id. |
| `updateAddresses[].type` | string | no | Moves the address to a different set. One of 'billTo', 'soldTo'. |
| `updateAddresses[].attention` | string | no | Person or department correspondence at this address is directed to. |
| `updateAddresses[].isDefault` | boolean | no | Marks this entry as its type's default, demoting the previous holder. |
| `updateAddresses[].line1` | string | no | Street address line 1: number, street name, or PO box. |
| `updateAddresses[].line2` | string | no | Apartment, suite, unit, building, floor, etc. |
| `updateAddresses[].city` | string | no | City or locality. |
| `updateAddresses[].stateProvince` | string | no | State, province, or region. |
| `updateAddresses[].country` | string | no | Country, as a name or ISO 3166-1 code. |
| `updateAddresses[].zipCode` | string | no | Postal or ZIP code. |
| `deleteAddresses` | array of string | no | addressIds of addresses to remove from this customer. The address record itself is kept, since an invoice may still reference it; only the customer's link to it goes. Rejected with ADDRESS_NOT_FOUND if the addressId is not on this customer. Cannot be combined with `addresses`. |
| `createExternalIdentifiers` | array of object | no | External identifiers to add. Rejected with DUPLICATE_EXTERNAL_IDENTIFIER if the fieldName is already present on the record. |
| `createExternalIdentifiers[].fieldName` | string | yes |  |
| `createExternalIdentifiers[].value` | string | yes |  |
| `createExternalIdentifiers[].externalUrl` | string | no |  |
| `updateExternalIdentifiers` | array of object | no | External identifiers to rewrite, matched by fieldName. Rejected with EXTERNAL_IDENTIFIER_NOT_FOUND if the fieldName is not already present. |
| `updateExternalIdentifiers[].fieldName` | string | yes |  |
| `updateExternalIdentifiers[].value` | string | yes |  |
| `updateExternalIdentifiers[].externalUrl` | string | no |  |
| `deleteExternalIdentifiers` | array of string | no | fieldNames of external identifiers to remove. Rejected with EXTERNAL_IDENTIFIER_NOT_FOUND if a name is not present. |

Example:

```json
{
  "name": "<name>",
  "customerType": "<customerType>",
  "partnerType": "<partnerType>",
  "businessType": "<businessType>",
  "invoiceEmail": "ap@example-robotics.com",
  "mainEmail": "<mainEmail>",
  "taxExempt": true,
  "defaultCurrency": "<defaultCurrency>",
  "segment": "MidMarket",
  "exemptionNumber": "<exemptionNumber>",
  "paymentTermCode": "<paymentTermCode>",
  "parentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "partnerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "salesChannel": "<salesChannel>",
  "additionalContactEmail": "<additionalContactEmail>",
  "mainContactName": "<mainContactName>",
  "preferredLanguage": "<preferredLanguage>",
  "crmCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "businessIdentificationNumber": "<businessIdentificationNumber>",
  "invoiceConsolidation": "<invoiceConsolidation>",
  "invoiceDeliveryMode": "<invoiceDeliveryMode>",
  "isExemptFromCreditCardFee": true,
  "billableTravelExpenseInvoiceDetail": "<billableTravelExpenseInvoiceDetail>",
  "status": "<status>",
  "taxExemptCode": "<taxExemptCode>",
  "taxRegistrations": [
    {
      "country": "<country>",
      "taxNumber": "<taxNumber>",
      "type": "<type>",
      "effectiveDate": "2026-01-15",
      "isActive": true
    }
  ],
  "payment": {
    "provider": "<provider>",
    "externalCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "externalPaymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  },
  "addresses": [
    {
      "type": "<type>",
      "attention": "<attention>",
      "isDefault": true,
      "line1": "<line1>",
      "line2": "<line2>",
      "city": "<city>",
      "stateProvince": "<stateProvince>",
      "country": "<country>",
      "zipCode": "<zipCode>"
    }
  ],
  "createAddresses": [
    {
      "type": "<type>",
      "attention": "<attention>",
      "isDefault": true,
      "line1": "<line1>",
      "line2": "<line2>",
      "city": "<city>",
      "stateProvince": "<stateProvince>",
      "country": "<country>",
      "zipCode": "<zipCode>"
    }
  ],
  "updateAddresses": [
    {
      "addressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "type": "<type>",
      "attention": "<attention>",
      "isDefault": true,
      "line1": "<line1>",
      "line2": "<line2>",
      "city": "<city>",
      "stateProvince": "<stateProvince>",
      "country": "<country>",
      "zipCode": "<zipCode>"
    }
  ],
  "deleteAddresses": [
    "<deleteAddresses>"
  ],
  "createExternalIdentifiers": [
    {
      "fieldName": "<fieldName>",
      "value": "<value>",
      "externalUrl": "<externalUrl>"
    }
  ],
  "updateExternalIdentifiers": [
    {
      "fieldName": "<fieldName>",
      "value": "<value>",
      "externalUrl": "<externalUrl>"
    }
  ],
  "deleteExternalIdentifiers": [
    "<deleteExternalIdentifiers>"
  ]
}
```

## Response

`200` Success

## Response headers

- `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`.

## Errors

| Code | Status |
| --- | --- |
| `NOT_FOUND` | 400 |
| `INVALID_FIELD_VALUE` | 400 |
| `MULTIPLE_DEFAULT_ADDRESSES` | 400 |
| `DUPLICATE_CUSTOMER` | 400 |
| `END_CUSTOMER_NOT_SUPPORTED` | 400 |
| `UNKNOWN_EXTERNAL_IDENTIFIER` | 400 |
| `DUPLICATE_EXTERNAL_IDENTIFIER` | 400 |
| `EXTERNAL_IDENTIFIER_NOT_FOUND` | 400 |
| `ADDRESS_NOT_FOUND` | 400 |
| `CUSTOMER_TYPE_CHANGE_NOT_ALLOWED` | 400 |
| `UNKNOWN_REFERENCE` | 400 |
| `UNKNOWN_PAYMENT_PROVIDER` | 400 |
| `PAYMENT_METHOD_REQUIRES_CUSTOMER_ID` | 400 |
| `EXEMPTION_NUMBER_REQUIRES_TAX_EXEMPT` | 400 |
| `IDEMPOTENCY_KEY_CONFLICT` | 409 |
| `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 |

## Statuses

- `400` — Invalid request or business failure. Codes: NOT_FOUND, INVALID_FIELD_VALUE, MULTIPLE_DEFAULT_ADDRESSES, DUPLICATE_CUSTOMER, END_CUSTOMER_NOT_SUPPORTED, UNKNOWN_EXTERNAL_IDENTIFIER, DUPLICATE_EXTERNAL_IDENTIFIER, EXTERNAL_IDENTIFIER_NOT_FOUND, ADDRESS_NOT_FOUND, CUSTOMER_TYPE_CHANGE_NOT_ALLOWED, UNKNOWN_REFERENCE, UNKNOWN_PAYMENT_PROVIDER, PAYMENT_METHOD_REQUIRES_CUSTOMER_ID, EXEMPTION_NUMBER_REQUIRES_TAX_EXEMPT
- `401` — Missing or invalid access token
- `403` — Not authorized
- `404` — Not found
- `409` — The Idempotency-Key is still being processed by another request.
- `422` — The Idempotency-Key was already used with a different request payload.
- `429` — Too many requests
- `500` — Internal server error
