> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tybritelabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Customers

> Reference for the CustomersService class in the Tybrite SDK.

The `CustomersService` class (accessed via `client.customers`) exposes the customer record as a first-class API resource — meant for integrations that manage authentication outside Galactic Core.

<Note>
  **Two integration paths.** If your storefront uses Galactic Core's built-in auth (`/v1/auth/register`, `/v1/auth/login`, etc.), prefer the [`AuthenticationService`](./AuthenticationService) — it creates and links the customer record for you. Use `CustomersService` when you're bringing your own identity provider (Auth0, Clerk, Cognito, Firebase, NextAuth, SSO, etc.) and want to provision the Galactic Core customer record directly from your backend. See [Customers and Auth](../../customers-and-auth) for the full decision matrix.
</Note>

<Note>
  **Sandbox isolation:** Customer records created with a `tybrite_sk_test_*` key exist only in the sandbox environment and are never visible to production keys. Every response includes a `Tybrite-Environment: sandbox | production` header confirming which environment resolved.
</Note>

## Profile Management

### `createCustomer`

Provision a Galactic Core customer record. The customer exists immediately and can place orders, hold a cart, leave reviews, etc. — but no Galactic Core login credentials are created. Authentication remains the responsibility of your external identity provider.

Pass `external_id` to store your upstream user identifier (Auth0 `sub`, Clerk `user.id`, etc.). It's unique per store + environment, so subsequent reads can look up the Galactic Core customer by that identifier without keeping a side-table.

Pass `marketing_consent` to record whether the customer opted in to marketing communications. It defaults to `false` (opt-in) — only set it to `true` when the customer affirmatively checks a marketing opt-in box on your storefront. When `true`, the customer becomes eligible to be subscribed to the store's connected marketing tools.

<Note>
  **Secret Key only.** Call this from a secure server-side environment — typically a webhook or post-signup hook in your identity provider.
</Note>

```typescript theme={null}
const result = await client.customers.createCustomer({
  requestBody: {
    email: 'jane.doe@example.com',
    name: 'Jane Doe',
    phone: '+12125550123',
    address: '350 Fifth Avenue, New York, NY 10118',
    external_id: 'auth0|66a3f8c2b1d9c204a1f7e3d1', // your upstream user id
    marketing_consent: true, // they ticked the marketing opt-in box
  }
});
const customer = result.customer;
// Store customer.id alongside your upstream user record for future calls.
```

**Response `201`:**

```json theme={null}
{
  "customer": {
    "id": "0adffe56-ccc7-4868-bb4c-0f0ca5db117f",
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "phone": "+12125550123",
    "address": "350 Fifth Avenue, New York, NY 10118",
    "status": "active",
    "tier": "bronze",
    "join_date": "2026-06-20",
    "external_id": "auth0|66a3f8c2b1d9c204a1f7e3d1",
    "marketing_consent": true,
    "total_purchases": 0,
    "last_purchase": null,
    "store_credit_balance": 0,
    "created_at": "2026-06-20T11:37:12.876998+00:00",
    "updated_at": "2026-06-20T11:37:12.876998+00:00",
    "store_metrics": null
  }
}
```

<Note>
  A newly created customer starts at the `bronze` `tier` with no purchase history, so `last_purchase` and `store_metrics` are `null` until their first order. The `tier` rises through `silver`, `gold`, and `platinum` as the customer's lifetime spend grows.
</Note>

<Note>
  The customer's marketing opt-in can be changed later via `updateCustomer` (e.g. when a
  signed-in shopper toggles email preferences in their account). Pass
  `requestBody: { marketing_consent: false }` to opt them out.
</Note>

<Note>
  `createCustomer` returns `{ customer }`. `getCustomer` returns the customer fields at the top level.
</Note>

***

### `getCustomer`

Retrieve a customer's profile, including store metrics and purchase history. This is the endpoint your "My Account" page calls. The response includes `store_credit_balance` — the customer's redeemable store-credit balance, which can be applied at checkout via `apply_store_credit` on `createOrder`.

Sign an `xExternalAuth` assertion in your backend (see [Customers and Auth](../../customers-and-auth) for the signing helper) and pass it through:

```typescript theme={null}
const assertion = signCustomerAssertion(upstreamUserId, process.env.GC_HMAC_SECRET);

const customer = await client.customers.getCustomer({
  id: gcCustomerId,
  xExternalAuth: assertion,
  fields: 'name,email,store_metrics.total_spent'
});
```

<Accordion title="Using Galactic Core auth instead?">
  If your storefront authenticates customers through Galactic Core (Path A), pass the customer's session JWT as `xAuthToken` in place of `xExternalAuth`:

  ```typescript theme={null}
  const customer = await client.customers.getCustomer({
    id: gcCustomerId,
    xAuthToken: customerSession.access_token,
  });
  ```

  Provide exactly one of `xAuthToken` or `xExternalAuth`. Both → `400`. Mismatch with the path `id` → `403`.
</Accordion>

**Response `200`** — the customer fields are returned at the top level (not wrapped in a `customer` key):

```json theme={null}
{
  "id": "15ecd58f-a8f8-49e4-9098-96f1fa8308e9",
  "name": "Jane Doe",
  "email": "jane.doe@example.com",
  "phone": "+12125550142",
  "address": null,
  "status": "active",
  "tier": "bronze",
  "join_date": "2026-06-20",
  "external_id": null,
  "marketing_consent": true,
  "total_purchases": 0,
  "last_purchase": null,
  "store_credit_balance": 0,
  "created_at": "2026-06-20T11:36:54.174303+00:00",
  "updated_at": "2026-06-20T11:36:54.174303+00:00",
  "store_metrics": null
}
```

<Note>
  `address`, `last_purchase`, and `store_metrics` are `null` until the customer has supplied an address and made a purchase. `tier` is one of `bronze` / `silver` / `gold` / `platinum`, derived from purchase history.
</Note>

***

### `updateCustomer`

Partially update a customer's profile. Only the fields you supply are modified. Typical caller: a "Save changes" button on the customer's account page.

```typescript theme={null}
const assertion = signCustomerAssertion(upstreamUserId, process.env.GC_HMAC_SECRET);

const result = await client.customers.updateCustomer({
  id: gcCustomerId,
  xExternalAuth: assertion,
  requestBody: {
    address: '350 Fifth Avenue, New York, NY 10118',
    name: 'Jane Doe'
  }
});
const updated = result.customer;
```

**Response `200`** — the updated profile, wrapped in a `customer` key:

```json theme={null}
{
  "customer": {
    "id": "15ecd58f-a8f8-49e4-9098-96f1fa8308e9",
    "name": "Jane Doe",
    "email": "jane.doe@example.com",
    "phone": "+12125550199",
    "address": "350 Fifth Avenue, New York, NY 10118",
    "status": "active",
    "join_date": "2026-06-20",
    "external_id": null,
    "marketing_consent": true,
    "total_purchases": 0,
    "last_purchase": null,
    "store_credit_balance": 0,
    "created_at": "2026-06-20T11:36:54.174303+00:00",
    "updated_at": "2026-06-20T11:36:54.174303+00:00",
    "store_metrics": null
  }
}
```

<Accordion title="Using Galactic Core auth instead?">
  ```typescript theme={null}
  const result = await client.customers.updateCustomer({
    id: gcCustomerId,
    xAuthToken: customerSession.access_token,
    requestBody: { address: '350 Fifth Avenue, New York, NY 10118' }
  });
  // → { customer: { ... } } — same response shape as above
  ```

  Provide exactly one of `xAuthToken` or `xExternalAuth`. Both → `400`. Mismatch with the path `id` → `403`.
</Accordion>

<Note>
  `updateCustomer` returns `{ customer }`. Unwrap the `customer` key to get the resource.
</Note>

## Saved addresses

Let a signed-in shopper keep a personal address book — multiple billing and shipping addresses they can reuse at checkout instead of re-typing them every time. Pre-fill your checkout form from `listAddresses`, or pre-select the shopper's default shipping/billing address.

These are **customer-self** operations: they work with a **publishable key** (safe to call from the browser), but require the shopper's session token in `xAuthToken`. The token must resolve to the customer in the `id` path parameter, otherwise the call returns `403`.

<Note>
  **Single default per kind.** Setting an address as the default for shipping (`is_default_shipping`) or billing (`is_default_billing`) automatically clears any previous default of the same kind, so there's always at most one default shipping address and one default billing address.
</Note>

### `listAddresses`

Return the shopper's saved addresses, defaults first. Use this to render their saved addresses at checkout.

```typescript theme={null}
const { addresses } = await client.customers.listAddresses({
  id: session.customer.id,
  xAuthToken: session.session.access_token,
});

const defaultShipping = addresses.find(a => a.is_default_shipping);
```

```json Response (200) theme={null}
{
  "addresses": [
    {
      "id": "2cef8df9-29d3-4a66-8b79-c9fab8609b63",
      "customer_id": "15ecd58f-a8f8-49e4-9098-96f1fa8308e9",
      "address_type": "both",
      "label": "Home",
      "full_name": "Jane Doe",
      "phone": "+12125550142",
      "line1": "350 Fifth Avenue",
      "line2": "Floor 21",
      "city": "New York",
      "state": "NY",
      "postal_code": "10118",
      "country": "US",
      "is_default_shipping": true,
      "is_default_billing": true,
      "created_at": "2026-06-20T11:37:14.253168+00:00",
      "updated_at": "2026-06-20T11:37:14.253168+00:00"
    }
  ]
}
```

### `createAddress`

Save a new address to the shopper's address book. `full_name`, `line1`, `city`, and `country` are required; everything else is optional. `address_type` is one of `shipping`, `billing`, or `both` (default `both`).

```typescript theme={null}
const { address } = await client.customers.createAddress({
  id: session.customer.id,
  xAuthToken: session.session.access_token,
  requestBody: {
    full_name: 'Jane Doe',
    line1: '123 Main St',
    line2: 'Apt 4B',
    city: 'New York',
    state: 'NY',
    postal_code: '10001',
    country: 'US',
    label: 'Home',
    address_type: 'both',
    is_default_shipping: true,
  },
});
// Save address.id to reference this address at checkout.
```

```json Response (201) theme={null}
{
  "address": {
    "id": "2cef8df9-29d3-4a66-8b79-c9fab8609b63",
    "customer_id": "15ecd58f-a8f8-49e4-9098-96f1fa8308e9",
    "address_type": "both",
    "label": "Home",
    "full_name": "Jane Doe",
    "phone": "+12125550142",
    "line1": "123 Main St",
    "line2": "Apt 4B",
    "city": "New York",
    "state": "NY",
    "postal_code": "10001",
    "country": "US",
    "is_default_shipping": true,
    "is_default_billing": false,
    "created_at": "2026-06-20T11:37:14.253168+00:00",
    "updated_at": "2026-06-20T11:37:14.253168+00:00"
  }
}
```

### `updateAddress`

Partially update a saved address — only the fields you supply are changed. Pass the address `id` as `addressId`.

```typescript theme={null}
const { address } = await client.customers.updateAddress({
  id: session.customer.id,
  addressId: existingAddressId,
  xAuthToken: session.session.access_token,
  requestBody: {
    line2: 'Suite 200',
    is_default_billing: true,
  },
});
// → { address: { id, customer_id, line2: 'Suite 200', is_default_billing: true, ... } }
//   (the full updated address, same shape as createAddress)
```

### `deleteAddress`

Remove an address from the shopper's address book.

```typescript theme={null}
await client.customers.deleteAddress({
  id: session.customer.id,
  addressId: existingAddressId,
  xAuthToken: session.session.access_token,
});
// → { success: true, message: 'Address deleted' }
```

## Customer Utilities

<Tip>
  **Retention Tip:** Use `store_metrics` to identify your top 10% of customers by lifetime value (LTV) and offer them personalized discount codes via the **Promotions Service**.
</Tip>

## Authentication Flow

The `getCustomer` and `updateCustomer` methods require a customer session JWT (`xAuthToken`) in addition to your API key. Obtain the token via the `AuthenticationService` — either through `login` or `verifyOtp`.

<Warning>
  Never expose **Secret Keys** in client-side code. For browser-based customer portals, use a **Publishable Key** in combination with the customer's `xAuthToken`.
</Warning>

```typescript theme={null}
// 1. Customer logs in via AuthenticationService
const session = await client.authentication.login({
  requestBody: { email: 'jane.doe@example.com', password: '...' }
});
// → { message: 'Login successful', user, customer, session }

// 2. Use the returned access_token for subsequent customer-scoped calls
const profile = await client.customers.getCustomer({
  id: session.customer.id,
  xAuthToken: session.session.access_token
});
// → { id, name, email, tier: 'bronze', address, store_credit_balance, store_metrics, ... }
//   (customer fields at the top level — not wrapped in a `customer` key)
```

<Note>
  `createCustomer` is intentionally **Secret Key only** — it is an admin/CRM operation (e.g. bulk imports) and does not involve customer authentication. No `xAuthToken` is required or accepted.
</Note>

### Response Codes

| Code  | Meaning                                                                     |
| ----- | --------------------------------------------------------------------------- |
| `201` | Customer created successfully (`createCustomer`)                            |
| `400` | Invalid request — malformed body or missing required fields                 |
| `401` | Invalid API key, or missing/invalid `xAuthToken`                            |
| `403` | Forbidden — the customer token does not match the `customer_id` in the path |
| `404` | Customer not found                                                          |
| `409` | Duplicate email at this store (on `createCustomer`)                         |
