API ReferenceEndpointsCustomersUpdate a customer

Update a customer

Supports Idempotency-Key header for safe retries.

PATCH
/v1/customers/{id}
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

idstring

Customer ID

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
email?string

Customer email. Changing email re-triggers verification (the new address must be verified before sensitive operations).

Formatemail
Lengthlength <= 320
phone?string

Phone number.

Lengthlength <= 50
firstName?string

First name.

Lengthlength <= 100
lastName?string

Last name.

Lengthlength <= 100
acceptsMarketing?boolean

Marketing consent flag.

birthMonth?integer | null

Birthday month (1-12) for the loyalty birthday gift. Month and day only, no year. Send null for BOTH fields to remove a stored birthday; sending neither leaves it untouched, so an edit form that simply omits an emptied field can correct a birthday but never clear one.

Range1 <= value <= 12
birthDay?integer | null

Birthday day of month (1-31). Send null for BOTH fields to remove a stored birthday; sending neither leaves it untouched, so an edit form that simply omits an emptied field can correct a birthday but never clear one.

Range1 <= value <= 31
tags?array<string>

Replace the customer's tag list. Pass [] to clear all tags.

Itemsitems <= 100
role?string

Free-form customer segment/role set by the merchant (e.g. "wholesale", "vip", "ambassador"). Pass an empty string to clear it. Not settable by the customer themselves: admin dashboard/API only.

Lengthlength <= 50
acquisitionSalesChannelId?string

Override the FIRST-TOUCH sales channel, the channel this customer came in through. Normally stamped automatically at registration / OAuth / checkout and then never changed; this is the merchant correction for customers created from the dashboard or imported from a file, where no channel was ever known. Accepts the internal SalesChannel id or the public vc_* connectionId. Pass an empty string to clear it back to "unknown". Does NOT change which channels the customer is active in: that is the publish/unpublish endpoints.

metadata?object

Replace the free-form metadata JSON (full replacement, not merge). Brainerce does not read it; it is for your own integration IDs and notes.

curl -X PATCH "https://api.brainerce.com/api/v1/customers/string" \  -H "Idempotency-Key: string" \  -H "Content-Type: application/json" \  -d '{}'
{
  "id": "cust_abc123",
  "email": "[email protected]",
  "firstName": "Jane",
  "lastName": "Doe",
  "phone": "+14155552671",
  "hasAccount": true,
  "emailVerified": true,
  "acceptsMarketing": true,
  "birthMonth": 4,
  "birthDay": 17,
  "tags": [
    "vip",
    "newsletter"
  ],
  "role": "wholesale",
  "totalOrders": 12,
  "totalSpent": "1284.50",
  "lastOrderAt": "2026-05-10T14:32:11.000Z",
  "rfm": {
    "r": 5,
    "f": 4,
    "m": 5,
    "score": "5-4-5",
    "segment": "champion"
  },
  "createdAt": "2025-08-01T09:00:00.000Z",
  "loyaltyPointsBalance": 240,
  "isLoyaltyMember": true,
  "acquisitionSalesChannel": {
    "id": "clsc_abc123",
    "name": "Main storefront",
    "connectionId": "vc_3n8Xk2p9QwErTyUiOpAsD"
  },
  "channelPublishes": [
    {
      "salesChannel": {
        "id": "clsc_abc123",
        "name": "Main storefront",
        "connectionId": "vc_3n8Xk2p9QwErTyUiOpAsD"
      },
      "firstSeenAt": "2026-03-04T08:12:00.000Z",
      "lastSeenAt": "2026-08-11T19:40:22.000Z"
    }
  ],
  "metadata": {
    "erpId": "CUST-00421"
  },
  "loyaltyMembershipId": "clmem_abc123",
  "platformConnections": [
    {
      "platformCode": "shopify",
      "externalId": "7654321098765"
    }
  ],
  "addresses": [
    {
      "id": "addr_abc123",
      "label": "Home",
      "firstName": "Jane",
      "lastName": "Doe",
      "company": "Acme Inc",
      "line1": "123 Main St",
      "line2": "Apt 4B",
      "city": "San Francisco",
      "region": "CA",
      "postalCode": "94103",
      "country": "US",
      "phone": "+14155552671",
      "isDefault": true,
      "createdAt": "2025-08-01T09:00:00.000Z",
      "updatedAt": "2026-05-12T11:24:08.000Z"
    }
  ],
  "updatedAt": "2026-05-12T11:24:08.000Z"
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/customers/{id}"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/customers/{id}"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/customers/{id}"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/customers/{id}"
}

{
  "statusCode": 409,
  "code": "CONFLICT",
  "message": "The request conflicts with the current state of the resource",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/customers/{id}"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}