Skip to content

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}:

GET https://{server}/api/entity/customers/01_C100001

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

GET https://{server}/api/entity/customers/new

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.