# Create and sync customers

> Find, create, update and sync customers with their contacts and addresses, without making duplicates.

Web page: https://developer.yoprint.com/guides/customers-and-contacts/

**Read first:** [Customers](https://developer.yoprint.com/how-yoprint-works/customers.md), for what a customer holds and the defaults it gives an order.

A customer is a company or person you sell to. Each customer has contacts (the people you email and call) and addresses (billing and shipping). Orders and quotes belong to a customer.

Syncing buyers from a store or a CRM? This is the safe order, with no duplicates:

**Diagram:** The upsert matches on the contact's email or phone, so a retry finds the same customer instead of making a second one.

- **1. Upsert by email**: `perform_upsert: true`, the customer's `name`, and a `contact` with the buyer's email. See [Sync without duplicates](#3-sync-without-duplicates)
- **2. Keep the id**: Store the answer's `data.id` with the buyer in your system
- **3. Add the address**: A shipping address on the customer, unless it already has it. See [Manage addresses](#6-manage-addresses)

Send the `name` in the upsert: without it, a new customer has no name. Every other customer field is optional.

## 1. Find a customer

List customers a page at a time:

`GET /v1/api/store/{team_slug}/customer` [List customers](https://developer.yoprint.com/reference/customers/v1-customer-index.md)

Or filter them, for example the customers assigned to you, newest first:

`POST /v1/api/store/{team_slug}/customer/filter` [Search customers with filters](https://developer.yoprint.com/reference/customers/v1-customer-filter.md)

```json
{
  "filters": [{ "key": "assigned_to_me", "operator": "eq", "value": true }],
  "sort": [{ "key": "created_at", "direction": "desc" }]
}
```

See [Filtering and sorting](https://developer.yoprint.com/concepts/filtering-and-sorting.md) for the operators.

To open one customer, with its contacts, addresses, tags and assigned users:

`GET /v1/api/store/{team_slug}/customer/{hash_id}` [Get a customer](https://developer.yoprint.com/reference/customers/v1-customer-show.md)

### Find a customer by email

The public API has no direct lookup by email or phone. Customer lists and filters don't include contacts, and no filter matches an email. Choose one of these:

1. **Keep YoPrint's id.** When you create or upsert a customer, store the answer's `data.id` next to the record in your own system. Later, read the customer by that id. This is the fastest and safest way.
2. **Upsert with only the contact.** Send `perform_upsert: true` and a `contact` with the email, and no other customer fields. When a customer matches, YoPrint changes none of its fields and returns it with its contacts (it still sends `customer.updated`). When nothing matches, YoPrint **creates** a customer, without a name. Use this only when creating is what you want.
3. **Read customers one by one.** List customers, then read each one to see its contacts. This works on any team, but it takes one request per customer.
4. **Export customers to CSV** with the columns `id` and `contact_email`, then look the email up in the file. A customer with several contacts takes several rows. Deleted contacts are left out. See [Export and import data](https://developer.yoprint.com/guides/imports-and-exports.md).

```json
{
  "perform_upsert": true,
  "contact": {
    "first_name": "Alex",
    "email": "alex@example.com",
    "send_invoice": true,
    "is_primary": true
  }
}
```

An upsert never changes the matched contact itself. When an upsert creates a customer, `is_primary` is ignored and the new contact is not made primary. To make it primary, call [Set the primary contact](#5-manage-contacts) after creation. An upsert checks no customer permission, so any member's key can update a matched customer.

## 2. Create a customer

`POST /v1/api/store/{team_slug}/customer` [Create a customer](https://developer.yoprint.com/reference/customers/v1-customer-store.md)

```json
{
  "customer": {
    "name": "Northwind Apparel",
    "tax_exempt": false,
    "website": "https://example.com"
  },
  "contact": [
    {
      "first_name": "Alex",
      "last_name": "Rivera",
      "email": "alex@example.com",
      "phone": "+1 201 555 0123",
      "phone_country": "US",
      "send_invoice": true,
      "is_primary": true
    }
  ],
  "billing": [
    {
      "label": "Head office",
      "address1": "100 Example Street",
      "city": "Springfield",
      "country_code": "US",
      "postcode": "10001",
      "is_primary": true
    }
  ]
}
```

- `customer.name` and `customer.tax_exempt` are required.
- Each contact needs `first_name`, `send_invoice` (whether invoices go to them) and `is_primary`. A `phone` needs `phone_country`, and must be a valid number for that country: YoPrint checks it, so a made-up number such as `+1 555 0100` is refused.
- `billing` and `shipping` are lists of addresses. Each needs `label`, `address1`, `country_code` and `is_primary`.

The new customer is assigned to you, and `customer.created` fires.

## 3. Sync without duplicates

If your system may already have sent a customer, upsert it instead. With `"perform_upsert": true`, YoPrint updates the customer whose contact has the same email or phone, or creates it:

`POST /v1/api/store/{team_slug}/customer/upsert` [Create or update a customer by contact](https://developer.yoprint.com/reference/customers/v1-customer-upsert.md)

```json
{
  "perform_upsert": true,
  "name": "Northwind Apparel",
  "tax_exempt": false,
  "contact": {
    "first_name": "Alex",
    "email": "alex@example.com",
    "send_invoice": true,
    "is_primary": true
  }
}
```

The upsert body is flat (`name` at the top, one `contact` object), unlike create. If no customer matches and a new one is created, `contact.is_primary` is ignored. Use [Set the primary contact](#5-manage-contacts) to make it primary. Read [Retries and duplicates](https://developer.yoprint.com/concepts/retries-and-duplicates.md) for what upsert matches before you rely on it.

## 4. Update a customer

`PUT /v1/api/store/{team_slug}/customer/{hash_id}` [Update a customer](https://developer.yoprint.com/reference/customers/v1-customer-update-put.md)

Send the fields to change at the top level, such as `{ "name": "Northwind Apparel Ltd" }`. Contacts and addresses have their own operations.

## 5. Manage contacts

`GET /v1/api/store/{team_slug}/customer/{hash_id}/contact` [List a customer's contacts](https://developer.yoprint.com/reference/customer-contacts/v1-customer-contact-index.md)

`POST /v1/api/store/{team_slug}/customer/{hash_id}/contact` [Add a contact to a customer](https://developer.yoprint.com/reference/customer-contacts/v1-customer-contact-store.md)

`PUT /v1/api/store/{team_slug}/customer/{hash_id}/contact/{id}` [Update a customer contact](https://developer.yoprint.com/reference/customer-contacts/v1-customer-contact-update-put.md)

To make a contact the customer's main one:

`PATCH /v1/api/store/{team_slug}/customer/{hash_id}/contact/{id}/primary` [Set the primary contact](https://developer.yoprint.com/reference/customer-contacts/v1-customer-contact-primary.md)

To add or update a contact by its email or phone, within one customer:

`POST /v1/api/store/{team_slug}/customer/{hash_id}/contact/upsert` [Create or update a customer contact by email or phone](https://developer.yoprint.com/reference/customer-contacts/v1-customer-contact-upsert.md)

When you only have a contact's id, and not its customer's, read it directly:

`GET /v1/api/store/{team_slug}/contact/{id}` [Get a contact by id](https://developer.yoprint.com/reference/contacts/v1-contact-show.md)

Two live contacts of one customer can't share an email: YoPrint saves emails trimmed and in lower case, and refuses a second contact with the same email with `409` (`customer_portal_duplicate_contact_email`). Other customers may use the same email.

A contact can also give someone access to the Customer Portal. Changing that contact's email moves their portal access, so the update may ask you to choose with `portal_email_scope` or to confirm with `portal_reconnect_confirmed` (a `409` tells you which). Deleting the contact removes their access to this customer. The update operation above explains both fields.

## 6. Manage addresses

An address has a `role`, `billing` or `shipping`:

`POST /v1/api/store/{team_slug}/customer/{hash_id}/address` [Add an address to a customer](https://developer.yoprint.com/reference/customer-addresses/v1-customer-address-store.md)

```json
{
  "role": "shipping",
  "label": "Warehouse",
  "address1": "100 Example Street",
  "city": "Springfield",
  "country_code": "US",
  "postcode": "10001",
  "is_primary": true
}
```

`PATCH /v1/api/store/{team_slug}/customer/{hash_id}/address/{id}/primary` [Set the primary address](https://developer.yoprint.com/reference/customer-addresses/v1-customer-address-primary.md)

## 7. Delete and restore

Deleting moves a customer to the trash. A customer with an open quote or sales order can't be deleted.

`DELETE /v1/api/store/{team_slug}/customer/{hash_id}` [Delete a customer](https://developer.yoprint.com/reference/customers/v1-customer-destroy.md)

`PATCH /v1/api/store/{team_slug}/customer/{hash_id}/restore` [Restore a deleted customer](https://developer.yoprint.com/reference/customers/v1-customer-restore.md)

## Pitfalls

- **Create nests, upsert doesn't.** Create takes `customer` plus lists of `contact`, `billing` and `shipping`. Upsert takes the customer's fields at the top and one `contact`.
- **`is_primary` is ignored when upsert creates a customer.** Call Set the primary contact afterwards to make it primary.
- **Upsert matches on email or phone.** It can match, and overwrite, a different customer than you expect. Always send an email.
- **Upsert overwrites what you send.** Every top-level field you send replaces the matched customer's value. Send only the fields your system owns.
- **Made-up phone numbers are refused.** Use a real-format number, such as `+1 201 555 0123` with `phone_country` `US`.
- **Phones need a country.** Send `phone_country` with every `phone`.
- **Customers synced from Shopify are read-only here.** Change them in Shopify.
- **Send only the fields the reference lists.** Don't copy fields you read back, such as `is_external`, `external_type`, `is_system` or `is_editable`, into a create or an update. Creating, updating or upserting a customer or a contact ignores `is_external` and `external_type`.
- **`customer_portal_link` is always `null`.** YoPrint no longer returns a portal sign-in link for a customer. To turn the portal off for one customer, update it with `"customer_portal_enabled": false`.
- **A narrower role sees fewer customers.** Some roles see only customers assigned to them, and reading any other customer answers `403`. See [Roles and permissions](https://developer.yoprint.com/concepts/roles-and-permissions.md).
