Skip to content
Customers & sales

Customers and groups

Create and update customers, addresses, and groups from your CRM or ERP.

Sync Andrea Ramos from your CRM: look her up by email, create her if she doesn't exist, keep her data current, and add her to the "Wholesale" group.

Requirements

  • An Admin token in ECOMIQ_TOKEN. See Quickstart.
  • The customer:read, customer:create, and customer:update permissions for customers and addresses; customer_group:read, customer_group:create, and customer_group:update for groups.
  • A public HTTPS URL, to receive customer.created.

The JavaScript and Python examples use this function, which sends the token and throws when the response isn't 2xx:

ecomiq.js
export async function ecomiq(method, path, body) {
  const init = {
    method,
    headers: {
      Authorization: `Bearer ${process.env.ECOMIQ_TOKEN}`,
      "Content-Type": "application/json",
    },
  };
  if (body !== undefined) init.body = JSON.stringify(body);

  const response = await fetch(`https://api.ecomiq.pe/api${path}`, init);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  }
  if (response.status === 204) return null;
  return response.json();
}

Search by email

Email is unique within the store, case-insensitive. search matches email, first name, and last name, including partial matches.

curl -G https://api.ecomiq.pe/api/v1/customers \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  --data-urlencode "search=andrea.ramos@example.com"
Response
{
  "data": [],
  "hasNextPage": false,
  "nextCursor": null,
  "totalCount": 0
}

Compare the full email in the results. If you find the customer, store their id and update them; otherwise, create them.

Create a customer

Send email, firstName, lastName, customerType, and countryIsoCode. Use metadata to store your CRM ID.

customer.json
{
  "email": "andrea.ramos@example.com",
  "firstName": "Andrea",
  "lastName": "Ramos",
  "customerType": "individual",
  "countryIsoCode": "PE",
  "phone": "+51987654321",
  "identificationType": "DNI",
  "identificationNumber": "72654321",
  "consent": true,
  "metadata": { "crmId": "CRM-1042" }
}
curl -X POST https://api.ecomiq.pe/api/v1/customers \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  --data @customer.json
Response
{
  "id": "7204558912004325301",
  "status": "pendingVerification",
  "isVerified": false
}

Store the id as a string in your CRM. An individual customer starts in pendingVerification. consent: true means they agreed to receive marketing email. An identity document can belong to only one customer in the store.

Create a business

Send customerType: "business" and the legal name in entityName. firstName and lastName belong to the contact person.

curl -X POST https://api.ecomiq.pe/api/v1/customers \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "purchasing@example.com",
    "firstName": "Lucía",
    "lastName": "Torres",
    "customerType": "business",
    "countryIsoCode": "PE",
    "phone": "+5115550142",
    "identificationType": "RUC",
    "identificationNumber": "20123456789",
    "entityName": "Comercial Lima SAC"
  }'

The business starts in pendingApproval. Approve it to move it to active; the response is 204:

curl -X POST https://api.ecomiq.pe/api/v1/customers/7204558912004325302/approve \
  -H "Authorization: Bearer $ECOMIQ_TOKEN"

Update a customer

PATCH changes only the fields you send and returns 204. null clears an optional field. metadata replaces the stored object.

curl -X PATCH https://api.ecomiq.pe/api/v1/customers/7204558912004325301 \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "andrea.ramos.p@example.com",
    "phone": "+51912345678"
  }'

Consent resets

Changing email sets consent to false, and changing phone sets smsConsent to false.

If the customer has a userId, their account is linked and you can't change the email here.

Replace the tags

tags replaces every tag: to add one, send the current list plus the new tag. [] or null clears them. You can send up to 100 tags of up to 100 characters; extra whitespace and duplicates are removed.

curl -X PATCH https://api.ecomiq.pe/api/v1/customers/7204558912004325301 \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "tags": ["wholesale", "lima"] }'

Save an address

The first address becomes the default; with isDefault: true, the new one does. The 201 response includes its id.

curl -X POST https://api.ecomiq.pe/api/v1/customers/7204558912004325301/addresses \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "addressKey": "home",
    "countryIsoCode": "PE",
    "addressLine": "Av. Larco 1234",
    "department": "Lima",
    "province": "Lima",
    "district": "Miraflores",
    "phone": "+51987654321",
    "isDefault": true
  }'

To edit it, send the full address to PUT /v1/customers/{customerId}/addresses/{addressId}: fields you omit are cleared.

Create a manual group

A manual group holds the customers you pick. The name is unique within the store.

curl -X POST https://api.ecomiq.pe/api/v1/customers/groups \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Wholesale", "type": "manual" }'

Add members by ID:

curl -X POST https://api.ecomiq.pe/api/v1/customers/groups/7204558912004325310/members \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "customerIds": ["7204558912004325301", "7204558912004325302"] }'
Response
{
  "customerGroupId": "7204558912004325310",
  "addedCount": 2,
  "existingCount": 0,
  "requestedCount": 2
}

Customers already in the group count toward existingCount, so you can repeat the call. To remove members, send their IDs to POST /v1/customers/groups/{id}/members/remove; the customer isn't deleted.

Create a dynamic group

A dynamic group includes the customers who meet its conditions: all of them with match: "all", any of them with "any". It needs at least one condition and doesn't accept manual members.

Field (field)OperatorsValue
amountSpent, orderCountatLeast, atMost, betweennumbers
lastOrderDate, firstOrderDate, createdDate, abandonedCheckoutDatewithinLastDays, moreThanDaysAgo, betweenDatesnumbers (days) or dates
paymentMethodin (used), notIn (never used), lastOrderInvalues: method codes
department, province, districtin, notInvalues: IDs from /v1/geo/areas
countryin, notInvalues: ISO codes
productPurchased, categoryPurchased, salesChannel, group, tag, status, customerType, emailSubscription, smsSubscriptionin, notInvalues

amountSpent is in the store currency, net of refunds. Several values in one condition mean "any of them."

curl -X POST https://api.ecomiq.pe/api/v1/customers/groups \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Frequent customers",
    "type": "dynamic",
    "conditions": {
      "match": "all",
      "conditions": [
        { "field": "orderCount", "operator": "atLeast", "numbers": [3], "dates": null, "values": null },
        { "field": "paymentMethod", "operator": "in", "numbers": null, "dates": null, "values": ["yape", "plin"] }
      ]
    }
  }'

To test the rules without saving the group, send match and conditions at the root of the body to POST /v1/customers/groups/preview:

curl -X POST https://api.ecomiq.pe/api/v1/customers/groups/preview \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "match": "all",
    "conditions": [
      { "field": "orderCount", "operator": "atLeast", "numbers": [3], "dates": null, "values": null }
    ],
    "includeSample": true,
    "limit": 10
  }'

The response includes count (matching customers), total (customers in the store), and sample. limit ranges from 1 to 20 (default 10); with includeSample: false, sample comes back empty.

List the members

groupId on GET /v1/customers and GET /v1/customers/count evaluates the group's rules live; for a manual group, it returns its members. Other filters combine with AND:

GET /v1/customers?groupId=7204558912004325311&minOrderCount=3
GET /v1/customers/count?groupId=7204558912004325311&minOrderCount=3

Discounts and campaigns use stored memberships, as do the group condition and the memberOfGroupIds and excludeMemberOfGroupIds filters. They're computed when the group is created and recomputed with each order. customerCount shows the latest computation; lastMaterializedOn is null until the first one.

To export or edit the same selection in a spreadsheet, send those filters in the body of POST /v1/customers/exports or in filters on POST /v1/customers/spreadsheet. In JSON, status, excludeStatus, customerType, and excludeCustomerType are plural: statuses, excludeStatuses, customerTypes, and excludeCustomerTypes.

PUT replaces the group

PUT /v1/customers/groups/{id} expects every field. If you omit type, the group becomes manual. With isActive: false, it stops being recomputed.

Receive customer.created

customer.created arrives for every new customer, including the ones your CRM creates. Register your URL:

curl -X POST https://api.ecomiq.pe/api/v1/webhooks/endpoints \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Customers to CRM",
    "url": "https://crm.example.com/webhooks/ecomiq",
    "eventTypes": ["customer.created"],
    "secret": "whsec_replace_with_a_random_secret"
  }'

Verify the signature and respond 2xx as in Incoming orders. The body is a CustomerWebhookModel with the customer ID.

Delivery is at least once. If the ID is already in your CRM, ignore the event; otherwise, read the customer and record them:

customer-created.js
export async function handleCustomerCreated(customerId) {
  if (await crmHasCustomer(customerId)) return;

  const customer = await ecomiq("GET", `/v1/customers/${customerId}`);
  await createCrmContact(customer);
}

crmHasCustomer and createCrmContact are functions in your CRM.

Errors

codeStatusWhat to do
Common.Validation400Check errors: a field is missing or has the wrong format.
CustomerGroup.DynamicMembership400The group is dynamic: change its conditions, not its members.
Common.Forbidden403Add the missing permission to your app.
Customer.NotFound404The customer doesn't exist in the store.
CustomerGroup.CustomersNotFound404None of the customerIds belong to the store.
Customer.EmailAlreadyExists409Another customer uses that email: look them up and update them.
Customer.IdentificationAlreadyExists409Another customer has that identity document.
Customer.LinkedAccountEmail409The customer has an account: they change the email from it.
Customers.ApprovalRequiresPending409Only a customer in pendingApproval can be approved.
CustomerGroup.AlreadyExists409A group with that name already exists.

Next steps

Last updated:

On this page