Customers¶
The Customers entity provides full CRUD access to P21 customer master records. A customer record is the root object for all customer-facing business activity — orders, invoices, accounts receivable, and pricing all reference the customer master.
Endpoints¶
| Method | URL | Description |
|---|---|---|
GET | /entity/customers/ | Get all customers (ArrayOfCustomer) |
POST | /entity/customers/ | Create a new customer |
GET | /entity/customers/{companyId}_{customerId} | Get a single customer |
PUT | /entity/customers/{companyId}_{customerId} | Update a customer |
GET | /entity/customers/new | Get a blank customer with defaults populated |
GET | /entity/customers/ping | Health check |
Compound Key¶
The URL key segment is {companyId}_{customerId}:
Both components are required. The underscore _ is the separator, not a hyphen.
Field Reference¶
Root-Level Fields¶
| Field | Type | Description |
|---|---|---|
company_id | string | Company identifier (e.g., 01) |
customer_id | string | Unique customer identifier |
customer_name | string | Full customer name |
address1 | string | Primary address line 1 |
address2 | string | Address line 2 |
city | string | City |
state | string | State or province code |
zip | string | Postal/ZIP code |
phone | string | Primary phone number |
fax | string | Fax number |
credit_limit | decimal | Credit limit amount in the customer's currency |
currency_id | string | Currency code (e.g., USD, CAD) |
terms_id | string | Payment terms identifier (e.g., NET30) |
territory_id | string | Sales territory assignment |
tax_code | string | Tax jurisdiction code |
date_last_modified | datetime | Timestamp of last record modification |
Nested Collections¶
The Customer object contains several nested collections that represent related records:
| Collection | Description |
|---|---|
CustomerAddress | Alternate ship-to addresses |
CustomerSalesreps | Sales representative assignments |
CustomerTerms | Payment term overrides by condition |
CustomerEDITransactions | EDI transaction configuration |
CustomerDealerTypes | Dealer type classifications |
CustomerRestrictedClasses | Restricted product class rules |
Nested collections on creation
When creating a customer, you do not need to populate all nested collections. Provide only the collections relevant to your use case. Omitted collections will be initialized empty.
XML Examples¶
GET — Single Customer Response¶
<Customer xmlns="http://www.epicor.com/entity">
<company_id>01</company_id>
<customer_id>C100001</customer_id>
<customer_name>Acme Corp</customer_name>
<address1>123 Main St</address1>
<address2>Suite 400</address2>
<city>Memphis</city>
<state>TN</state>
<zip>38101</zip>
<phone>901-555-1234</phone>
<fax>901-555-1235</fax>
<credit_limit>50000.00</credit_limit>
<currency_id>USD</currency_id>
<terms_id>NET30</terms_id>
<territory_id>SOUTHEAST</territory_id>
<tax_code>TN_MEM</tax_code>
<date_last_modified>2025-11-14T08:32:00</date_last_modified>
<CustomerSalesreps>
<CustomerSalesrep>
<salesrep_id>SR001</salesrep_id>
<commission_pct>3.50</commission_pct>
</CustomerSalesrep>
</CustomerSalesreps>
<CustomerAddress>
<CustomerShipTo>
<ship_to_id>WAREHOUSE</ship_to_id>
<address1>456 Dock Rd</address1>
<city>Memphis</city>
<state>TN</state>
<zip>38102</zip>
</CustomerShipTo>
</CustomerAddress>
</Customer>
GET — New Customer Template¶
The response includes all fields with P21-generated defaults. Use this as the base document for customer creation.
POST — Create Customer (Request Body)¶
<Customer xmlns="http://www.epicor.com/entity">
<company_id>01</company_id>
<customer_id>C100099</customer_id>
<customer_name>New Customer LLC</customer_name>
<address1>789 Commerce Dr</address1>
<city>Nashville</city>
<state>TN</state>
<zip>37201</zip>
<phone>615-555-9000</phone>
<credit_limit>25000.00</credit_limit>
<currency_id>USD</currency_id>
<terms_id>NET30</terms_id>
<territory_id>SOUTHEAST</territory_id>
</Customer>
Always fetch /new before creating
The snippet above shows only a partial field set for illustration. In practice, always start from the /new template response. It includes system-required fields (date stamps, default flags, etc.) that are not visible here but are required by P21 business rules.
C# Examples¶
Get a Customer¶
public async Task<Customer> GetCustomerAsync(string companyId, string customerId)
{
var url = $"{_baseUrl}/entity/customers/{companyId}_{customerId}";
var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var xml = await response.Content.ReadAsStringAsync();
var serializer = new XmlSerializer(typeof(Customer));
using var reader = new StringReader(xml);
return (Customer)serializer.Deserialize(reader);
}
Get New Customer Template¶
public async Task<Customer> GetNewCustomerTemplateAsync()
{
var response = await _httpClient.GetAsync($"{_baseUrl}/entity/customers/new");
response.EnsureSuccessStatusCode();
var xml = await response.Content.ReadAsStringAsync();
var serializer = new XmlSerializer(typeof(Customer));
using var reader = new StringReader(xml);
return (Customer)serializer.Deserialize(reader);
}
Create a Customer¶
public async Task<Customer> CreateCustomerAsync(CustomerCreateRequest request)
{
// Step 1: Always fetch the template first
var template = await GetNewCustomerTemplateAsync();
// Step 2: Populate required fields from your request
template.customer_id = request.CustomerId;
template.customer_name = request.CustomerName;
template.address1 = request.Address1;
template.city = request.City;
template.state = request.State;
template.zip = request.Zip;
template.phone = request.Phone;
template.credit_limit = request.CreditLimit;
template.currency_id = request.CurrencyId;
template.terms_id = request.TermsId;
template.territory_id = request.TerritoryId;
// Step 3: Serialize and POST
var serializer = new XmlSerializer(typeof(Customer));
using var writer = new StringWriter();
serializer.Serialize(writer, template);
var xml = writer.ToString();
var content = new StringContent(xml, Encoding.UTF8, "application/xml");
var response = await _httpClient.PostAsync($"{_baseUrl}/entity/customers/", content);
response.EnsureSuccessStatusCode();
// Step 4: Deserialize and return created record
var responseXml = await response.Content.ReadAsStringAsync();
using var reader = new StringReader(responseXml);
return (Customer)serializer.Deserialize(reader);
}
Update a Customer¶
public async Task<Customer> UpdateCustomerAsync(string companyId, string customerId,
Action<Customer> applyChanges)
{
// Fetch the current record first
var customer = await GetCustomerAsync(companyId, customerId);
// Apply changes via a delegate
applyChanges(customer);
// Serialize and PUT
var serializer = new XmlSerializer(typeof(Customer));
using var writer = new StringWriter();
serializer.Serialize(writer, customer);
var xml = writer.ToString();
var content = new StringContent(xml, Encoding.UTF8, "application/xml");
var url = $"{_baseUrl}/entity/customers/{companyId}_{customerId}";
var response = await _httpClient.PutAsync(url, content);
response.EnsureSuccessStatusCode();
var responseXml = await response.Content.ReadAsStringAsync();
using var reader = new StringReader(responseXml);
return (Customer)serializer.Deserialize(reader);
}
Notes and Common Pitfalls¶
Customer ID conventions
Customer IDs are conventionally prefixed with C (e.g., C100001) in most P21 installations, but this is a convention only — P21 does not enforce it. Check your installation's numbering scheme before assigning IDs programmatically. Many installations use auto-number sequences.
Credit limit currency
credit_limit is stored in the customer's assigned currency (currency_id). If your integration works with multiple currencies, convert credit limit values appropriately before comparison.
Updating nested collections
When PUTting a customer record, the nested collections (CustomerSalesreps, CustomerAddress, etc.) are included in the payload. If you fetch a record, modify a root field, and PUT it back, the nested collection state in your payload determines the final state. Do not accidentally clear nested collections by omitting them from an update payload — always start from the fetched record.