# How to manage a customer (https://developer.godaddy.com/en/docs/api-users/commerce/manage-orders-and-customers/customer)

***

title: How to manage a customer
description: Create, read, update customer profiles within a store.
agentNotes:
permissions: \[]
scopes: \["commerce.customer:read", "commerce.customer:create", "commerce.customer:update"]
idempotent: false
destructive: false
failureRecovery: "GET operations are safe to retry. For POST and PATCH, verify the current customer state before retrying to avoid duplicate creates or conflicting updates."
related:
apis:

* title: "Customer API reference"
  href: "/docs/references/rest/customers/customer"
  guides:
* title: "Manage a store"
  href: "/docs/api-users/commerce/set-up-a-store"
* title: "Process an order"
  href: "/docs/api-users/commerce/manage-orders-and-customers"
  concepts:
* title: "About the Commerce API"
  href: "/docs/api-users/commerce"
* title: "Authentication"
  href: "/docs/api-users/auth"
* title: "Rate limits"
  href: "/docs/api-users/rate-limits"

***

## Overview

Customers are profiles associated with one or more orders in a store. The Customer API is a REST API. All operations use standard HTTP methods against store-scoped endpoints under `v1`. The following article provides examples of how to use the Customer API to manage customers in your store.

## Prerequisites

The following prerequisites are required before you can manage customers:

* a GoDaddy account with an active commerce store
* a [Personal Access Token (PAT)](https://developer.godaddy.com/docs/api-users/auth) with the scopes for the operations you need (`commerce.customer:read` for reads; `commerce.customer:create` or `:update` for writes)
* your `storeId`

  Go to [Your stores](https://developer.godaddy.com/docs/api-users/commerce#your-stores) to find your store ID.

## List customers

`GET /v1/commerce/stores/{storeId}/customers` retrieves all customers for a store. Deleted customers are excluded by default.

The following procedure lists customers in your store.

1. Retrieve a list of customers:

   ```bash tab="curl"
   curl -s "https://api.godaddy.com/v1/commerce/stores/${STORE_ID}/customers" \
     -H "Authorization: Bearer $GODADDY_PAT"
   ```

   ```js tab="Node"
   const token = process.env.GODADDY_PAT;
   const storeId = process.env.STORE_ID;

   const res = await fetch(
     `https://api.godaddy.com/v1/commerce/stores/${storeId}/customers`,
     { headers: { Authorization: `Bearer ${token}` } }
   );
   const customers = await res.json();
   console.log(customers);
   ```

   ```python tab="Python"
   import os, requests

   token = os.environ["GODADDY_PAT"]
   store_id = os.environ["STORE_ID"]

   res = requests.get(
       f"https://api.godaddy.com/v1/commerce/stores/{store_id}/customers",
       headers={"Authorization": f"Bearer {token}"},
   )
   res.raise_for_status()
   print(res.json())
   ```

   ```go tab="Go"
   package main

   import (
       "encoding/json"
       "fmt"
       "net/http"
       "os"
   )

   func main() {
       token := os.Getenv("GODADDY_PAT")
       storeId := os.Getenv("STORE_ID")

       url := fmt.Sprintf("https://api.godaddy.com/v1/commerce/stores/%s/customers", storeId)
       req, _ := http.NewRequest("GET", url, nil)
       req.Header.Set("Authorization", "Bearer "+token)

       res, err := http.DefaultClient.Do(req)
       if err != nil { panic(err) }
       defer res.Body.Close()

       // The response is an object: { customers: [...], links: [...] }.
       var response map[string]any
       json.NewDecoder(res.Body).Decode(&response)
       fmt.Println(response["customers"])
   }
   ```

2. Review the response:

   ```json
   {
     "customers": [
       {
         "customerId": "<CUSTOMER_UUID>",
         "firstName": "<CUSTOMER_FIRST_NAME>",
         "lastName": "<CUSTOMER_LAST_NAME>",
         "emails": [{ "email": "<CUSTOMER_EMAIL_ADDRESS>", "default": true }],
         "phones": [{ "phone": "<CUSTOMER_PHONE_NUMBER>", "default": true }],
         "createdAt": "<ISO8601_CREATED_TIMESTAMP>",
         "updatedAt": "<ISO8601_UPDATED_TIMESTAMP>"
       }
     ],
     "links": [
       {
         "rel": "self",
         "href": "https://api.godaddy.com/v1/commerce/stores/<STORE_ID>/customers?page=1",
         "method": "GET"
       }
     ]
   }
   ```

   Each customer is identified by `customerId`. Contact details (`emails`, `phones`) are arrays, not scalar fields — access them as `customer.emails[0].email`, not `customer.email`. The `links` array contains HATEOAS pagination cursors.

   Pass `totalRequired=true` to include `totalItems` and `totalPages` in the response. The returned `links` echo the current request (e.g. the `self` link above reflects the page you fetched). To page forward, prefer the `pageToken` from the previous response over incrementing the `page` param.

## Create a customer

`POST /v1/commerce/stores/{storeId}/customers` creates a new customer or de-duplicates an existing one using channel data. If the supplied channel data matches an existing customer, the existing record is updated rather than a new one created.

The request body has two top-level fields: a required `source` (the application creating the customer, e.g. `COMMERCE`) and a nested `customer` object holding the profile. Creation is **asynchronous** — a successful call returns `202 Accepted` with a `links` array rather than the created record.

Send emails and phones as the `emails` and `phones` arrays, each with an `email` / `phone` sub-field. A top-level scalar `email` or `phone` is accepted with a `202` but **silently discarded** — the customer is created with no contact info. Always confirm with a follow-up GET.

The following procedure creates a customer profile.

1. Create a customer with their contact details:

   ```bash tab="curl"
   curl -s -X POST "https://api.godaddy.com/v1/commerce/stores/${STORE_ID}/customers" \
     -H "Authorization: Bearer $GODADDY_PAT" \
     -H "Content-Type: application/json" \
     -d '{
       "source": "COMMERCE",
       "customer": {
         "firstName": "<CUSTOMER_FIRST_NAME>",
         "lastName": "<CUSTOMER_LAST_NAME>",
         "emails": [{ "email": "<CUSTOMER_EMAIL_ADDRESS>", "default": true }],
         "phones": [{ "phone": "<CUSTOMER_PHONE_NUMBER>", "default": true }]
       }
     }'
   ```

   ```js tab="Node"
   const token = process.env.GODADDY_PAT;
   const storeId = process.env.STORE_ID;

   const res = await fetch(
     `https://api.godaddy.com/v1/commerce/stores/${storeId}/customers`,
     {
       method: "POST",
       headers: {
         Authorization: `Bearer ${token}`,
         "Content-Type": "application/json",
       },
       body: JSON.stringify({
         source: "COMMERCE",
         customer: {
           firstName: "<CUSTOMER_FIRST_NAME>",
           lastName: "<CUSTOMER_LAST_NAME>",
           emails: [{ email: "<CUSTOMER_EMAIL_ADDRESS>", default: true }],
           phones: [{ phone: "<CUSTOMER_PHONE_NUMBER>", default: true }],
         },
       }),
     }
   );
   // 202 Accepted: parse the customerId out of the rel:"customer" link.
   const { links } = await res.json();
   const customerHref = links.find((l) => l.rel === "customer")?.href;
   const customerId = customerHref?.split("/").pop();
   console.log(customerId);
   ```

   ```python tab="Python"
   import os, requests

   token = os.environ["GODADDY_PAT"]
   store_id = os.environ["STORE_ID"]

   res = requests.post(
       f"https://api.godaddy.com/v1/commerce/stores/{store_id}/customers",
       headers={
           "Authorization": f"Bearer {token}",
           "Content-Type": "application/json",
       },
       json={
           "source": "COMMERCE",
           "customer": {
               "firstName": "<CUSTOMER_FIRST_NAME>",
               "lastName": "<CUSTOMER_LAST_NAME>",
               "emails": [{"email": "<CUSTOMER_EMAIL_ADDRESS>", "default": True}],
               "phones": [{"phone": "<CUSTOMER_PHONE_NUMBER>", "default": True}],
           },
       },
   )
   res.raise_for_status()  # 202 Accepted
   print(res.json()["links"])
   ```

   ```go tab="Go"
   package main

   import (
       "bytes"
       "encoding/json"
       "fmt"
       "net/http"
       "os"
   )

   func main() {
       token := os.Getenv("GODADDY_PAT")
       storeId := os.Getenv("STORE_ID")

       payload := map[string]any{
           "source": "COMMERCE",
           "customer": map[string]any{
               "firstName": "<CUSTOMER_FIRST_NAME>",
               "lastName":  "<CUSTOMER_LAST_NAME>",
               "emails":    []map[string]any{{"email": "<CUSTOMER_EMAIL_ADDRESS>", "default": true}},
               "phones":    []map[string]any{{"phone": "<CUSTOMER_PHONE_NUMBER>", "default": true}},
           },
       }
       body, _ := json.Marshal(payload)

       url := fmt.Sprintf("https://api.godaddy.com/v1/commerce/stores/%s/customers", storeId)
       req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
       req.Header.Set("Authorization", "Bearer "+token)
       req.Header.Set("Content-Type", "application/json")

       res, err := http.DefaultClient.Do(req)
       if err != nil { panic(err) }
       defer res.Body.Close()

       var result map[string]any
       json.NewDecoder(res.Body).Decode(&result)
       fmt.Println(result["links"]) // 202 Accepted
   }
   ```

2. The call returns `202 Accepted` with a `links` array — not the customer record. Read the `customerId` from the `href` of the link whose `rel` is `"customer"`:

   ```json
   {
     "links": [
       {
         "rel": "customer",
         "href": "https://api.godaddy.com/v1/commerce/stores/<STORE_ID>/customers/<CUSTOMER_UUID>",
         "method": "GET"
       },
       {
         "rel": "request",
         "href": "https://api.godaddy.com/v1/commerce/stores/<STORE_ID>/customer-requests/<REQUEST_UUID>",
         "method": "GET"
       }
     ]
   }
   ```

   The `customerId` is the last path segment of the `rel: "customer"` href. GET that href to confirm the customer (and its contact details) persisted. The `rel: "request"` link tracks the async action and may not be accessible to PAT callers.

## Get a customer

`GET /v1/commerce/stores/{storeId}/customers/{customerId}` retrieves a single customer by ID.

The following procedure reads a single customer by ID.

1. Retrieve a single customer by its `customerId`:

   ```bash tab="curl"
   CUSTOMER_ID="<CUSTOMER_UUID>"

   curl -s "https://api.godaddy.com/v1/commerce/stores/${STORE_ID}/customers/${CUSTOMER_ID}" \
     -H "Authorization: Bearer $GODADDY_PAT"
   ```

   ```js tab="Node"
   const token = process.env.GODADDY_PAT;
   const storeId = process.env.STORE_ID;
   const customerId = "<CUSTOMER_UUID>";

   const res = await fetch(
     `https://api.godaddy.com/v1/commerce/stores/${storeId}/customers/${customerId}`,
     { headers: { Authorization: `Bearer ${token}` } }
   );
   const customer = await res.json();
   console.log(customer);
   ```

   ```python tab="Python"
   import os, requests

   token = os.environ["GODADDY_PAT"]
   store_id = os.environ["STORE_ID"]
   customer_id = "<CUSTOMER_UUID>"

   res = requests.get(
       f"https://api.godaddy.com/v1/commerce/stores/{store_id}/customers/{customer_id}",
       headers={"Authorization": f"Bearer {token}"},
   )
   res.raise_for_status()
   print(res.json())
   ```

   ```go tab="Go"
   package main

   import (
       "encoding/json"
       "fmt"
       "net/http"
       "os"
   )

   func main() {
       token := os.Getenv("GODADDY_PAT")
       storeId := os.Getenv("STORE_ID")
       customerId := "<CUSTOMER_UUID>"

       url := fmt.Sprintf("https://api.godaddy.com/v1/commerce/stores/%s/customers/%s", storeId, customerId)
       req, _ := http.NewRequest("GET", url, nil)
       req.Header.Set("Authorization", "Bearer "+token)

       res, err := http.DefaultClient.Do(req)
       if err != nil { panic(err) }
       defer res.Body.Close()

       var customer map[string]any
       json.NewDecoder(res.Body).Decode(&customer)
       fmt.Println(customer)
   }
   ```

2. Review the response. The customer is returned wrapped in a `customer` object, alongside `links`. Use this GET to confirm that the `emails`/`phones` arrays and the latest `updatedAt` persisted after a create or update:

   ```json
   {
     "customer": {
       "customerId": "<CUSTOMER_UUID>",
       "firstName": "<CUSTOMER_FIRST_NAME>",
       "lastName": "<CUSTOMER_LAST_NAME>",
       "emails": [{ "email": "<CUSTOMER_EMAIL_ADDRESS>", "default": true }],
       "phones": [{ "phone": "<CUSTOMER_PHONE_NUMBER>", "default": true }],
       "createdAt": "<ISO8601_CREATED_TIMESTAMP>",
       "updatedAt": "<ISO8601_UPDATED_TIMESTAMP>"
     },
     "links": []
   }
   ```

## Update a customer

`PATCH /v1/commerce/stores/{storeId}/customers/{customerId}` updates a customer. The body is a nested `customer` object containing the fields you want to change plus the current `updatedAt` value for optimistic concurrency. Like create, the update is **asynchronous** and returns `202 Accepted`.

Include the `updatedAt` you received from your most recent GET. If it is missing the API returns `422`; if it is **stale** the API returns `202` but silently drops the update. After patching, re-GET the customer to confirm the change landed, and retry with the fresh `updatedAt` if it did not.

The following procedure updates the email address for a customer.

1. GET the customer first (see [Get a customer](#get-a-customer)) and note its current `updatedAt`.

2. Send the update as a nested `customer` object:

   ```bash tab="curl"
   CUSTOMER_ID="<CUSTOMER_UUID>"

   curl -s -X PATCH "https://api.godaddy.com/v1/commerce/stores/${STORE_ID}/customers/${CUSTOMER_ID}" \
     -H "Authorization: Bearer $GODADDY_PAT" \
     -H "Content-Type: application/json" \
     -d '{
       "customer": {
         "emails": [{ "email": "<NEW_EMAIL_ADDRESS>", "default": true }],
         "updatedAt": "<ISO8601_UPDATED_TIMESTAMP_FROM_GET>"
       }
     }'
   ```

   ```js tab="Node"
   const token = process.env.GODADDY_PAT;
   const storeId = process.env.STORE_ID;
   const customerId = "<CUSTOMER_UUID>";

   const res = await fetch(
     `https://api.godaddy.com/v1/commerce/stores/${storeId}/customers/${customerId}`,
     {
       method: "PATCH",
       headers: {
         Authorization: `Bearer ${token}`,
         "Content-Type": "application/json",
       },
       body: JSON.stringify({
         customer: {
           emails: [{ email: "<NEW_EMAIL_ADDRESS>", default: true }],
           updatedAt: "<ISO8601_UPDATED_TIMESTAMP_FROM_GET>",
         },
       }),
     }
   );
   console.log(res.status); // 202 Accepted
   ```

   ```python tab="Python"
   import os, requests

   token = os.environ["GODADDY_PAT"]
   store_id = os.environ["STORE_ID"]
   customer_id = "<CUSTOMER_UUID>"

   res = requests.patch(
       f"https://api.godaddy.com/v1/commerce/stores/{store_id}/customers/{customer_id}",
       headers={
           "Authorization": f"Bearer {token}",
           "Content-Type": "application/json",
       },
       json={
           "customer": {
               "emails": [{"email": "<NEW_EMAIL_ADDRESS>", "default": True}],
               "updatedAt": "<ISO8601_UPDATED_TIMESTAMP_FROM_GET>",
           }
       },
   )
   res.raise_for_status()  # 202 Accepted
   ```

   ```go tab="Go"
   package main

   import (
       "bytes"
       "encoding/json"
       "fmt"
       "net/http"
       "os"
   )

   func main() {
       token := os.Getenv("GODADDY_PAT")
       storeId := os.Getenv("STORE_ID")
       customerId := "<CUSTOMER_UUID>"

       payload := map[string]any{
           "customer": map[string]any{
               "emails":    []map[string]any{{"email": "<NEW_EMAIL_ADDRESS>", "default": true}},
               "updatedAt": "<ISO8601_UPDATED_TIMESTAMP_FROM_GET>",
           },
       }
       body, _ := json.Marshal(payload)

       url := fmt.Sprintf("https://api.godaddy.com/v1/commerce/stores/%s/customers/%s", storeId, customerId)
       req, _ := http.NewRequest("PATCH", url, bytes.NewReader(body))
       req.Header.Set("Authorization", "Bearer "+token)
       req.Header.Set("Content-Type", "application/json")

       res, err := http.DefaultClient.Do(req)
       if err != nil { panic(err) }
       defer res.Body.Close()

       fmt.Println(res.Status) // 202 Accepted
   }
   ```

## Common errors

| Status | Most likely cause                                                                                        | Note                                                                             |
| ------ | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `400`  | Malformed JSON in the request body.                                                                      |                                                                                  |
| `401`  | PAT is missing or expired.                                                                               | Go to [Authentication](https://developer.godaddy.com/docs/api-users/auth) to generate a new token.            |
| `403`  | Token doesn't include the required scope (`commerce.customer:read`, `:create`, or `:update`).            |                                                                                  |
| `404`  | Store ID or customer ID doesn't exist or isn't accessible with the provided token.                       |                                                                                  |
| `422`  | Body failed schema validation — e.g. a missing `updatedAt` on update, or a wrong contact sub-field name. | Response is a `ValidationError` with a `fields` array pinpointing each bad path. |
| `429`  | Rate limit exceeded.                                                                                     | Honor the `Retry-After` header before retrying.                                  |

## Associate a customer with an order

There is no dedicated "associate customer" endpoint. You link a customer to an order by setting both `customerId` and `billing` on the order via the Order subgraph GraphQL API (`addDraftOrder` or `updateOrder` mutations).

### What is billing

The `billing` field is a nested object on the order that holds the buyer's contact information. It is not a separate API — you set it as part of the `addDraftOrder` or `updateOrder` mutation input. The fields are:

| Field         | Type   | Required | Description         |
| ------------- | ------ | -------- | ------------------- |
| `firstName`   | String | Yes      | Buyer first name    |
| `lastName`    | String | Yes      | Buyer last name     |
| `email`       | String | Yes      | Buyer email address |
| `phone`       | String | No       | Buyer phone number  |
| `companyName` | String | No       | Company name        |
| `address`     | Object | No       | Billing address     |

The `billing` object drives the customer-service integration. When an order with `billing` transitions to `OPEN`, the system uses that data to increment the customer's `orderCount` and establish the order–customer relationship internally.

Setting only `customerId` creates a reference on the order but does not trigger the customer-service integration. The `billing` object (with at least `firstName`, `lastName`, and `email`) is what drives `orderCount` updates. Always provide both fields together.

### Behavior by combination

| What you provide         | `customerId` on order | `orderCount` increments     |
| ------------------------ | --------------------- | --------------------------- |
| `customerId` only        | Set                   | No                          |
| `customerId` + `billing` | Set                   | Yes                         |
| `billing` only           | Not set               | Yes (customer auto-created) |

When you provide only `customerId`, the order stores the reference but the customer service does not learn about the order. The customer's `orderCount` stays at 0.

When you provide both `customerId` and `billing`, the customer's `orderCount` increments when the order transitions to `OPEN`.

When you provide only `billing`, the system auto-creates a customer record from the billing info with `orderCount` set to 1. However, the order's `customerId` field is not backfilled — the auto-created customer's `orderCount` reflects the relationship, but querying the order returns `customerId: null`. There is no way to discover the link from the order side.

### Recommended flow

The following procedure creates a customer and associates it with a new order.

1. Create the customer via REST ([see above](#create-a-customer)) and note the `customerId`.

2. Create a draft order with both `customerId` and `billing`:

   ```bash tab="curl"
   curl -s "https://api.godaddy.com/v1/commerce/order-subgraph" \
     -H "Authorization: Bearer $GODADDY_PAT" \
     -H "x-store-id: $STORE_ID" \
     -H "Content-Type: application/json" \
     -d '{
       "query": "mutation($input: AddDraftOrderInput!) { addDraftOrder(input: $input) { id customerId billing { firstName lastName email } } }",
       "variables": {
         "input": {
           "context": {
             "storeId": "<STORE_ID>",
             "channelId": "<CHANNEL_ID>"
           },
           "customerId": "<CUSTOMER_UUID>",
           "billing": {
             "firstName": "<FIRST_NAME>",
             "lastName": "<LAST_NAME>",
             "email": "<EMAIL>",
             "phone": "<PHONE>"
           }
         }
       }
     }'
   ```

3. Add line items with the `addLineItemBySkuId` mutation, then transition the order with `openOrder`. The customer's `orderCount` increments when the order reaches `OPEN`.

To attach a customer to an existing order that has not yet been opened, use `updateOrder` with the same two fields while the order is still in `DRAFT`. The `orderCount` increment only fires during the `DRAFT` → `OPEN` transition — adding billing to an already-open order does not retroactively trigger it.

```bash tab="curl"
curl -s "https://api.godaddy.com/v1/commerce/order-subgraph" \
  -H "Authorization: Bearer $GODADDY_PAT" \
  -H "x-store-id: $STORE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation($input: UpdateOrderInput!) { updateOrder(input: $input) { id customerId billing { firstName lastName email } } }",
    "variables": {
      "input": {
        "id": "<ORDER_ID>",
        "customerId": "<CUSTOMER_UUID>",
        "billing": {
          "firstName": "<FIRST_NAME>",
          "lastName": "<LAST_NAME>",
          "email": "<EMAIL>",
          "phone": "<PHONE>"
        },
        "context": {
          "storeId": "<STORE_ID>",
          "channelId": "<CHANNEL_ID>"
        }
      }
    }
  }'
```

| Field           | Where to find it                                             |
| --------------- | ------------------------------------------------------------ |
| `ORDER_ID`      | From `addDraftOrder` or `orders` query (e.g. `Order_abc123`) |
| `CUSTOMER_UUID` | From the `rel: "customer"` link in the create response       |
| `CHANNEL_ID`    | From `order.context.channelId` on any existing order         |

The `context` object with both `storeId` and `channelId` is mandatory for `addDraftOrder` and `updateOrder`. Retrieve the `channelId` from the Store REST endpoint:

```bash
curl -s "https://api.godaddy.com/v1/commerce/stores/${STORE_ID}" \
  -H "Authorization: Bearer $GODADDY_PAT" | jq '.defaultChannelId'
```

The `defaultChannelId` field is available on every store, including new stores with no orders. The value is stable — use it for all orders in the store.
