# Create customer

> POST /v1/customers · Core resources API

Source: https://developer.kustomer.com/api/core-resources/create-customer

```http
POST https://{orgname}.api.kustomerapp.com/v1/customers
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.customer.create`; or legacy `org.user.customer.write`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

Creates a new customer record.

### Custom Attributes
[Custom attributes](https://help.kustomer.com/define-attributes-Skr924HI) are optional and allow you to specify custom key-value pairs.

Kustomer uses a naming convention for custom attributes to specify the field data type with a name suffix:

#### Naming Suffix

* `Num` *number* or *null*
* `At` *date-time* or *null*
* `Str` *string* or *null*, length: [ 0 .. 1024 ] characters
* `Txt` *string* or *null*, length: [ 0 .. 1024 ] characters
* `Bool` *boolean* or *null*
* `Url` *string* or *null*, format: *uri*

## About Customers

A customer is one person your team supports. Every conversation, message, note and custom object about that person hangs off their customer record, and agents see it all on a single timeline.

Kustomer is organized around the customer rather than the ticket. When someone emails, chats or calls for the first time, Kustomer creates a customer for them and matches later contact on the same email address, phone number or social handle back to that record. In the agent desktop the customer is the profile and the timeline of everything that has happened with them: [conversations](https://developer.kustomer.com/api/core-resources#conversations), orders and other custom objects, and tracked events. You can also create customers yourself, usually to sync people from your own database or CRM before they ever contact support.

#### Identifiers and matching

A customer carries several kinds of contact identifiers, each a list of objects with a `type` and an optional `verified` flag: `emails`, `phones`, `whatsapps`, `socials` and `externalIds`. Values in these lists must be unique across your org, because Kustomer uses them to route new inbound messages to the right person; a write that would give a second customer the same email, phone or external ID is rejected. When people genuinely share a contact point, such as a family email address, use `sharedEmails`, `sharedPhones`, `sharedSocials` or `sharedExternalIds`, which have no uniqueness rule.

`externalId` is a shortcut for your own ID for the person. Set it on create and you can look the customer up later by it, which makes syncs idempotent. You can also look customers up by email or phone.

#### What the record holds

Besides `name`, contact identifiers, `locations`, `locale`, `timeZone` and `tags`, a customer has a `company` (the [company](https://developer.kustomer.com/api/core-resources#companies) they belong to) and [custom attributes](https://developer.kustomer.com/docs/concepts/custom-attributes) under `custom`. Kustomer maintains several read-only summaries for you: `conversationCounts` (open, snoozed, done and all), `lastConversation`, `lastMessageIn`, `lastActivityAt` and `preview`, a snippet of the latest message.

#### Merging duplicates

Duplicates happen, for example when the same person writes from a work and a personal email. A merge combines a `source` customer into a `target`. Conversations, messages, notes and custom objects move to the target; contact identifiers, tags and custom attributes are combined (the target wins when both have the same custom attribute); and the source is deleted. A merge reports `status` as `pending`, `running`, `success` or `error`. Merging a customer into itself, or starting a merge while another one for the same pair is in progress, fails with `409 Conflict`. There is no unmerge endpoint.

#### Which endpoint to use

| To | Call |
| --- | --- |
| Create, read, update or delete one customer | [`POST /v1/customers`](https://developer.kustomer.com/api/core-resources/create-customer), [`GET`](https://developer.kustomer.com/api/core-resources/get-customer-by-id), [`PUT`](https://developer.kustomer.com/api/core-resources/update-customer), [`PATCH`](https://developer.kustomer.com/api/core-resources/update-customer-attributes) or [`DELETE /v1/customers/{id}`](https://developer.kustomer.com/api/core-resources/delete-customer) |
| Find a customer by your own ID | [`GET /v1/customers/externalId={externalId}`](https://developer.kustomer.com/api/core-resources/get-customer-by-external-id) |
| Find a customer by contact details | [`GET /v1/customers/email={email}`](https://developer.kustomer.com/api/core-resources/get-customer-by-email) or [`GET /v1/customers/phone={phone}`](https://developer.kustomer.com/api/core-resources/get-customer-by-phone-number) |
| Page through customers by created or updated date | [`GET /v1/customers`](https://developer.kustomer.com/api/core-resources/get-customers) |
| Import, update or delete up to 100 customers per request | [`POST`](https://developer.kustomer.com/api/core-resources/bulk-create-customers), [`PUT`](https://developer.kustomer.com/api/core-resources/bulk-batch-update-customers) or [`DELETE /v1/customers/bulk`](https://developer.kustomer.com/api/core-resources/bulk-delete-customers) |
| Merge a duplicate into another customer | [`POST /v1/customers/merges`](https://developer.kustomer.com/api/core-resources/create-customer-merge) |
| Check a merge, or list a customer's merges | [`GET /v1/customers/merges/{id}`](https://developer.kustomer.com/api/core-resources/get-customer-merge) or [`GET /v1/customers/{id}/merges`](https://developer.kustomer.com/api/core-resources/get-customer-merges-by-customer-id) |

`PUT` merges the attributes you send into the record unless you add `replace=true`, but leaving out `company` unlinks the customer from their company, so always resend it. `PATCH` also accepts a JSON Patch body (`Content-Type: application/json-patch+json`), which is handy for appending one email or phone without resending the list; pass `rev` to make the write depend on the revision you last read. Customer updates are subject to [object limits](https://developer.kustomer.com/docs/concepts/rate-limits#object-limits). `GET /v1/customers` only filters on `createdAt` and `updatedAt`; use [search](https://developer.kustomer.com/api/core-resources#search) for anything richer. Bulk requests run asynchronously, as described in [Bulk operations](https://developer.kustomer.com/docs/guides/bulk-operations).

Related: [Data model overview](https://developer.kustomer.com/docs/concepts/data-model), [Conversations](https://developer.kustomer.com/api/core-resources#conversations), [Companies](https://developer.kustomer.com/api/core-resources#companies), [Sync customers from a CRM](https://developer.kustomer.com/docs/guides/sync-customers-from-crm)

### Recipes

#### 1. Find or create a customer by email, then open a conversation

Make sure a person exists in Kustomer before you log something about them, without creating a duplicate of someone who has already contacted support.

**Step 1.** [`GET /v1/customers/email={email}`](https://developer.kustomer.com/api/core-resources/get-customer-by-email.md). Look the person up first. Kustomer creates customers automatically for anyone who emails or chats, so they may already exist. A `200` returns the customer; keep `data.id`. A `404` means no customer has that address. URL-encode the address in the path, so `+` becomes `%2B`.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/customers/email=$EMAIL" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
# exits with code 4 when no customer has the address
kusty api GET "/v1/customers/email=$EMAIL" --fields id | jq -r .data.id
```

**Step 2.** [`POST /v1/customers`](https://developer.kustomer.com/api/core-resources/create-customer.md). On a `404`, create the customer with the address in `emails`. Emails must be unique across the org, so if another process created the same person between your lookup and this call, the create is rejected; look them up again and use that record. If you have your own ID for the person, set `externalId` now so later lookups don't depend on the email.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"name\": \"$NAME\", \"emails\": [{\"type\": \"work\", \"email\": \"$EMAIL\"}]}"
```

```bash title="kusty"
kusty api POST /v1/customers \
  --field name="$NAME" --field emails.0.type=work --field emails.0.email="$EMAIL" \
  | jq -r .data.id
```

**Step 3.** [`POST /v1/customers/{id}/conversations`](https://developer.kustomer.com/api/core-resources/create-conversation-for-customer.md). Open a conversation under the customer. Give it a `name`, which is the subject agents see, and optionally a `priority` from 1 to 5. Don't send a channel: the conversation's `channels` fill in from the messages you add. Creating the conversation sends nothing to the customer. The response's `data.id` is the conversation ID.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID/conversations" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"name": "Return request for order 4417", "priority": 3}'
```

```bash title="kusty"
kusty api POST /v1/customers/$CUSTOMER_ID/conversations \
  --field name="Return request for order 4417" --field priority:=3 \
  | jq -r .data.id
```

> Next, record what was said with a [message](https://developer.kustomer.com/api/core-resources#messages), reply to the customer with a [draft](https://developer.kustomer.com/api/core-resources#drafts), or leave context for agents in a [note](https://developer.kustomer.com/api/core-resources#notes). Phone numbers work the same way through [`GET /v1/customers/phone={phone}`](https://developer.kustomer.com/api/core-resources/get-customer-by-phone-number).

#### 2. Upsert a contact from your CRM by externalId

Keep a Kustomer customer in step with a contact in your CRM or database, creating it the first time and updating it after, so the sync is safe to retry.

**Step 1.** [`GET /v1/customers/externalId={externalId}`](https://developer.kustomer.com/api/core-resources/get-customer-by-external-id.md). Look the customer up by your contact ID, which you store in `externalId`. Unlike an email address, it never changes. A `200` means the customer exists: keep `data.id` and the attributes you sync, to compare in the last step. A `404` means you create the customer.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/customers/externalId=$CRM_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET "/v1/customers/externalId=$CRM_ID" \
  --fields id,attributes.name,attributes.emails,attributes.custom
```

**Step 2.** [`GET /v1/companies`](https://developer.kustomer.com/api/core-resources/get-companies.md). If the contact belongs to an account, find its Kustomer company by the account's ID. The response is a list; the company is the first item in `data`. If the list is empty, [create the company](https://developer.kustomer.com/api/core-resources/create-company) first. Don't send `filter` in the same request, because sending both parameters makes the API ignore both.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/companies?externalId=$ACCOUNT_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/companies --query externalId=$ACCOUNT_ID | jq -r '.data[0].id // empty'
```

**Step 3.** [`POST /v1/customers`](https://developer.kustomer.com/api/core-resources/create-customer.md). For a new contact, create the customer with `externalId`, the fields you sync, and `company` set to the company ID. Custom attributes go under `custom`, with names ending in a type suffix such as `Str` or `Num`.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"externalId\": \"$CRM_ID\", \"name\": \"$NAME\", \"emails\": [{\"type\": \"work\", \"email\": \"$EMAIL\"}], \"company\": \"$COMPANY_ID\", \"custom\": {\"lifecycleStageStr\": \"customer\"}}"
```

```bash title="kusty"
kusty api POST /v1/customers \
  --field externalId=$CRM_ID --field name="$NAME" \
  --field emails.0.type=work --field emails.0.email="$EMAIL" \
  --field company=$COMPANY_ID --field custom.lifecycleStageStr=customer
```

**Step 4.** [`PUT /v1/customers/{id}`](https://developer.kustomer.com/api/core-resources/update-customer.md). For an existing contact, compare what you'd write with what you read, and skip the call if nothing changed. That saves rate limit and stops two-way syncs from looping. Otherwise `PUT` the fields you own. Attributes you leave out are kept, but lists you send, such as `emails`, replace the stored list. Always include `company`, because leaving it out unlinks the customer from their company.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"name\": \"$NAME\", \"company\": \"$COMPANY_ID\", \"custom\": {\"lifecycleStageStr\": \"customer\"}}"
```

```bash title="kusty"
kusty api PUT /v1/customers/$CUSTOMER_ID \
  --field name="$NAME" --field company=$COMPANY_ID --field custom.lifecycleStageStr=customer
```

> For the first load, send contacts in batches of 100 with [`POST /v1/customers/bulk`](https://developer.kustomer.com/api/core-resources/bulk-create-customers), as described in [Bulk operations](https://developer.kustomer.com/docs/guides/bulk-operations). [Sync customers from a CRM](https://developer.kustomer.com/docs/guides/sync-customers-from-crm) covers webhooks back to the CRM and loop prevention.

#### 3. Add an email address without resending the list

Append one email, phone or external ID to a customer without overwriting the ones they already have, or a change someone else just made.

**Step 1.** [`GET /v1/customers/{id}`](https://developer.kustomer.com/api/core-resources/get-customer-by-id.md). Read the customer's `emails` and `rev`. If the address is already in the list, stop. If another customer has it, the next step is rejected, because addresses are unique across the org; that usually means the two records are the same person and should be [merged](https://developer.kustomer.com/api/core-resources/create-customer-merge).

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/customers/$CUSTOMER_ID --fields attributes.emails,attributes.rev
```

**Step 2.** [`PATCH /v1/customers/{id}`](https://developer.kustomer.com/api/core-resources/update-customer-attributes.md). Send a JSON Patch with `Content-Type: application/json-patch+json`. Paths are attribute names without `attributes`, and `/emails/-` appends to the end of the list. Pass the `rev` you read as a query parameter; if the customer changed in between, the write is rejected, so read it again and retry.

```bash title="cURL"
curl --request PATCH "https://api.kustomerapp.com/v1/customers/$CUSTOMER_ID?rev=$REV" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json-patch+json" \
  --data "[{\"op\": \"add\", \"path\": \"/emails/-\", \"value\": {\"type\": \"work\", \"email\": \"$EMAIL\"}}]"
```

```bash title="kusty"
kusty api PATCH /v1/customers/$CUSTOMER_ID --query rev=$REV \
  --header 'Content-Type: application/json-patch+json' \
  --data "[{\"op\":\"add\",\"path\":\"/emails/-\",\"value\":{\"type\":\"work\",\"email\":\"$EMAIL\"}}]"
```

> Use `/phones/-` with `{ "type": "mobile", "phone": "+15555550123" }` for a phone number, or `/externalIds/-` with `{ "externalId": "..." }` for another system's ID.

#### 4. Merge a duplicate customer into the one you keep

Combine two records for the same person, such as one created from a work email and one from a personal email, so agents see one timeline.

**Step 1.** [`GET /v1/customers/email={email}`](https://developer.kustomer.com/api/core-resources/get-customer-by-email.md). Find both records. Choose the one to keep as the target, for example the one with your `externalId` or the larger `conversationCounts.all`. The other is the source, which Kustomer deletes once the merge succeeds.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/customers/email=$OTHER_EMAIL" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET "/v1/customers/email=$OTHER_EMAIL" \
  --fields id,attributes.externalId,attributes.conversationCounts
```

**Step 2.** [`POST /v1/customers/merges`](https://developer.kustomer.com/api/core-resources/create-customer-merge.md). Start the merge with the two customer IDs. Conversations, messages, notes and custom objects move to the target; contact identifiers, tags and custom attributes are combined, and the target's value wins when both have the same custom attribute. A `409 Conflict` means the IDs are the same or a merge of this pair is already in progress. Keep the merge's `data.id`.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/customers/merges" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{\"source\": \"$SOURCE_ID\", \"target\": \"$TARGET_ID\"}"
```

```bash title="kusty"
kusty api POST /v1/customers/merges \
  --field source=$SOURCE_ID --field target=$TARGET_ID | jq -r .data.id
```

**Step 3.** [`GET /v1/customers/merges/{id}`](https://developer.kustomer.com/api/core-resources/get-customer-merge.md). Merges can take a moment. Poll until `status` is `success` or `error`, rather than `pending` or `running`.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/customers/merges/$MERGE_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/customers/merges/$MERGE_ID --fields attributes.status
```

> There is no unmerge, so check the pair before you start. If your system stores Kustomer customer IDs, replace the source's ID with the target's.

### Example object

A real record from [`GET /v1/customers/{id}`](https://developer.kustomer.com/api/core-resources/get-customer-by-id.md):

```json
{
  "type": "customer",
  "id": "58210c3db0f09110006b7953",
  "attributes": {
    "name": "John Smith Jr.",
    "displayName": "John Smith Jr.",
    "displayColor": "yellow",
    "displayIcon": "flower",
    "externalId": "my-customer-database-id",
    "externalIds": [
      {
        "externalId": "my-customer-database-id",
        "verified": true,
        "id": null
      }
    ],
    "sharedExternalIds": [],
    "signedUpAt": null,
    "avatarUrl": null,
    "username": null,
    "emails": [
      {
        "email": "abu@kustomer.com",
        "verified": true,
        "type": "home",
        "id": null
      }
    ],
    "sharedEmails": [
      {
        "email": "abu@kustomer.com",
        "verified": false,
        "type": "home",
        "id": null
      }
    ],
    "phones": [],
    "sharedPhones": [],
    "socials": [],
    "sharedSocials": [],
    "urls": [],
    "locations": [],
    "activeUsers": [],
    "watchers": [],
    "recentLocation": {
      "updatedAt": "2016-11-07T23:22:01.746Z"
    },
    "locale": null,
    "timeZone": null,
    "birthdayAt": null,
    "gender": null,
    "presence": "offline",
    "createdAt": "2016-11-07T23:20:29.080Z",
    "updatedAt": "2016-11-09T04:47:07.036Z",
    "modifiedAt": "2016-11-09T04:47:07.036Z",
    "lastSeenAt": "2016-11-07T23:23:51.582Z",
    "lastActivityAt": "2016-11-09T04:47:07.036Z",
    "lastCustomerActivityAt": "2016-11-07T23:23:51.582Z",
    "lastMessageIn": {
      "sentAt": "2016-11-07T23:22:02.281Z",
      "sentiment": null
    },
    "lastConversation": {
      "id": "58210c99b0f09110006b7969",
      "sentiment": {
        "confidence": 0.649023,
        "polarity": 1
      },
      "tags": []
    },
    "conversationCounts": {
      "all": 3,
      "snoozed": 0,
      "open": 2,
      "done": 1
    },
    "preview": {
      "previewAt": "2016-11-07T23:23:26.039Z",
      "type": "message_out",
      "text": "dfsd fsdsfdsfdsf",
      "subject": "Hi, do you guys have an XXL hoodie"
    },
    "tags": [],
    "sentiment": {
      "polarity": 1,
      "confidence": 0.649023
    },
    "progressiveStatus": "open",
    "verified": true,
    "rev": 37
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/57f29863a1dbf61100e6aa92"
      },
      "data": {
        "type": "org",
        "id": "57f29863a1dbf61100e6aa92"
      }
    },
    "messages": {
      "links": {
        "self": "/v1/customers/58210c3db0f09110006b7953/messages"
      }
    },
    "modifiedBy": {
      "links": {
        "self": "/v1/users/58190b991f2932100010d683"
      },
      "data": {
        "type": "user",
        "id": "58190b991f2932100010d683"
      }
    }
  },
  "links": {
    "self": "/v1/customers/58210c3db0f09110006b7953"
  }
}
```

Fields:

- `attributes.name` (string): The person's full name, up to 256 characters. Optional and writable.
- `attributes.displayName` (string): The name agents see in the agent desktop. Response-only; change it by setting `name`.
- `attributes.displayColor` (string): Set by Kustomer, together with `displayIcon`, to style the customer in the agent desktop. Neither can be written through the API.
- `attributes.externalId` (string): Your own ID for the person, 1 to 256 characters. Unique across the org, and the key for `GET /v1/customers/externalId={externalId}`, which makes syncs idempotent.
- `attributes.externalIds` (array): Up to 10 more IDs from other systems, each `{ externalId, verified }`. Like `emails`, values must be unique across the org, and sending the list on an update replaces it.
- `attributes.sharedExternalIds` (array): External IDs that several customers may share. No uniqueness rule, and not used to match inbound contact.
- `attributes.signedUpAt` (null): When the person signed up in your product. Writable; Kustomer doesn't set it.
- `attributes.avatarUrl` (null): URL of the customer's picture. Writable.
- `attributes.emails` (array): Up to 10 email addresses. Each must be unique across the org, because Kustomer matches inbound email to customers by address. Sending `emails` replaces the list; append one with a JSON Patch `PATCH`.
- `attributes.emails[].verified` (boolean): Whether the address is known to belong to this person. Writable.
- `attributes.emails[].type` (string): `home`, `work` or `other`.
- `attributes.sharedEmails` (array): Addresses several customers may share, such as a family inbox. They have no uniqueness rule and inbound email isn't matched on them.
- `attributes.phones` (array): Up to 10 phone numbers, each `{ phone, type, verified }` with `type` one of `mobile`, `home`, `work`, `fax` or `other`. Unique across the org, like `emails`. Use E.164 format, such as `+15555550123`.
- `attributes.socials` (array): Social profiles, each `{ type, username, userid, url, verified }` with `type` one of `twitter`, `facebook`, `instagram`, `linkedin` or `pinterest`.
- `attributes.urls` (array): Websites, each `{ url, type }` with `type` one of `website`, `blog` or `other`.
- `attributes.locations` (array): Addresses, each with `type` (`home`, `work` or `other`) and fields such as `address`, `cityName`, `regionCode`, `countryCode`, `zipCode`, `latitude` and `longitude`.
- `attributes.locale` (null): The customer's locale, up to 16 characters. Writable.
- `attributes.timeZone` (null): The customer's time zone, up to 64 characters. Writable.
- `attributes.birthdayAt` (null): The customer's birthday, as a date-time. Writable.
- `attributes.gender` (null): `m`, `f` or `null`.
- `attributes.lastActivityAt` (string (date-time)): When anything last happened on the customer's timeline. Kustomer keeps it up to date, and you can also set it on create or update.
- `attributes.lastCustomerActivityAt` (string (date-time)): When the customer themselves last did something, such as sending a message.
- `attributes.lastMessageIn` (object): Read-only summary of the latest inbound message, with its `sentAt` and `sentiment`.
- `attributes.lastConversation` (object): Read-only summary of the customer's most recent conversation, with its `id`, `sentiment` and `tags`.
- `attributes.conversationCounts` (object): Read-only counts of the customer's conversations by status, kept up to date as conversations open, snooze and close.
- `attributes.preview` (object): Read-only snippet of the latest message on the timeline. `type` says which kind, such as `message_out` for an outbound message; `text` and `subject` come from it.
- `attributes.tags` (array): Tag IDs, up to 20. Sending `tags` replaces the whole list.
- `attributes.sentiment` (object): The customer's overall sentiment. `polarity` is `-1` (negative), `0` (neutral) or `1` (positive); `confidence` runs from `-1` to `1`.
- `attributes.rev` (number): The revision, which goes up with every change. Pass it as `rev` on a JSON Patch `PATCH` so the write is rejected if someone changed the customer after you read it.
- `relationships.messages` (object): Link to every message with this customer, across all of their conversations.

### Relationships

- Points to **Messages** via `relationships.messages` (list): Every message exchanged with this customer, across all of their conversations. Each message also points back to its customer.
- Points to **Companies** via `relationships.company`: The company this customer belongs to. A customer has at most one; set it by sending the company ID as `company`, and resend it on every `PUT`, because leaving it out unlinks the customer.
- Referenced by **Audit logs** via `relationships.customer`: The customer an audit log entry involves, such as the customer whose conversation changed. For a customer's own history, filter with `filter[objectType]=customer` and `filter[objectId]`, and add `filter[include]=conversation` to include their conversations.
- Referenced by **Chat conversations** via `relationships.customer`: The customer a chat session is with. A customer can have many chat sessions; list them with `GET /v2.1/chat/sessions?customerId=…`.
- Referenced by **Comments** via `relationships.customer`: On a comment, the customer that the commented KObject belongs to. It's copied from the KObject and is absent when the KObject has no customer.
- Referenced by **Conversation time** via `relationships.customer`: The customer whose conversation this conversation time record measures.
- Referenced by **Conversations** via `relationships.customer`: The customer a conversation is with. Every conversation has exactly one customer, and a customer can have many conversations over time.
- Referenced by **Drafts** via `relationships.customer`: The customer a draft will be sent to. Every draft is created for one customer, who can have several unsent drafts.
- Referenced by **Conversation forwards** via `relationships.customer`: The customer whose conversation a forward copies, taken from the conversation.
- Referenced by **KObjects (custom objects)** via `relationships.customer`: The customer whose timeline shows this KObject. List a customer's KObjects of one Klass with `GET /v1/customers/{id}/klasses/{name}`.
- Referenced by **Messages** via `relationships.customer`: The customer a message was exchanged with. It's always the customer of the message's conversation, and a customer's messages can be listed across all their conversations.
- Referenced by **Notes** via `relationships.customer`: The customer a note is about, taken from its conversation. List every note about a customer with `GET /v1/customers/{id}/notes`.
- Referenced by **Satisfaction surveys** via `relationships.customer`: The customer a satisfaction response was sent to. Each response, read with `GET /v1/satisfaction-responses/{id}`, links to one customer, one conversation and the survey it came from.
- Referenced by **Spam senders** via `relationships.customer`: The customer whose email address matches the spam sender entry, linked when the entry is created. Filter `GET /v1/spam/senders` by `customer` to see whether a customer's mail is being allowed or blocked.
- Referenced by **Tracking events** via `relationships.customer`: The customer whose event history holds this event. A visitor's tracking events are attached to the customer once an identity call ties their `trackingId` to that customer.
- Referenced by **Unmasking windows** via `relationships.customer`: An unmasking window belongs to one customer and is stored on the customer as `unmaskingWindow`. A customer has at most one window at a time.

### Permissions

Fewest roles for a read-only integration: `org.permission.customer.read`, `org.permission.customer_merge.create`, `org.permission.customer_merge.read`. For every endpoint: `org.permission.customer.read`, `org.permission.customer.update`, `org.permission.customer_merge.create`, `org.permission.customer.create`, `org.permission.customer.delete`, `org.permission.customer_merge.read`.

### Events

Webhook events:

- `kustomer.customer.create`: A customer is created by an agent, the API, chat, an import or a channel
- `kustomer.customer.update`: Any customer attribute changes, including activity timestamps

Workflow triggers:

- `kustomer.customer.create` (Customer Created): A customer is created
- `kustomer.customer.update` (Customer Updated): A customer changes

Workflow actions:

- `kustomer.customer.create` (Customer: Create): required inputs none
- `kustomer.customer.update` (Customer: Update): required inputs `id`
- `kustomer.customer.find` (Customer: Lookup (By ID)): required inputs `identifier`
- `kustomer.customer.find-by-email` (Customer: Lookup (By Email)): required inputs `email`
- `kustomer.customer.find-by-phone` (Customer: Lookup (By Phone)): required inputs `phone`
- `kustomer.customer.find-by-external-id` (Customer: Lookup (By External ID)): required inputs `externalId`
- `kustomer.customer.find-by-whatsapp` (Customer: Lookup (By WhatsApp Phone)): required inputs `whatsapp`
- `kustomer.customer.find-by-bsuid` (Customer: Lookup (By WhatsApp BSUID)): required inputs `bsuid`, `bsuidScopeId`
- `kustomer.customer.find-by-facebook-id` (Customer: Lookup (By Facebook Messenger User ID)): required inputs `pageId`, `userId`
- `kustomer.customer.find-by-instagram-id` (Customer: Lookup (By Instagram Thread ID)): required inputs `threadId`
- `kustomer.customer.append-email` (Customer: Append Email): required inputs `id`, `email`
- `kustomer.customer.append-phone` (Customer: Append Phone): required inputs `id`, `phone`
- `kustomer.customer.append-external-id` (Customer: Append External ID): required inputs `id`, `externalId`
- `kustomer.customer.merge` (Merge Customers): required inputs `source`, `target`

## Request body

Content type: `application/json` (required)

- `name` (string, nullable): (max length 256)
- `company` (string, nullable)
- `externalId` (string, nullable): (min length 1; max length 256)
- `username` (string, nullable): (max length 256)
- `signedUpAt` (string<date-time>, nullable)
- `lastActivityAt` (string<date-time>, nullable)
- `lastCustomerActivityAt` (string<date-time>, nullable)
- `lastSeenAt` (string<date-time>, nullable)
- `avatarUrl` (string<uri>, nullable)
- `externalIds` (array<external_id>): (max items 10; unique items)
  - `externalId` (string, required): (min length 1; max length 255)
  - `verified` (boolean)
- `sharedExternalIds` (array<external_id>): (max items 10; unique items)
  - `externalId` (string, required): (min length 1; max length 255)
  - `verified` (boolean)
- `emails` (array<email>): (max items 10; unique items)
  - `type` (string): One of: `home`, `work`, `other`.
  - `email` (string<email-relaxed>, required)
  - `verified` (boolean)
- `sharedEmails` (array<email>): (max items 10; unique items)
  - `type` (string): One of: `home`, `work`, `other`.
  - `email` (string<email-relaxed>, required)
  - `verified` (boolean)
- `phones` (array<phone>): (max items 10; unique items)
  - `type` (string): One of: `mobile`, `home`, `work`, `fax`, `other`.
  - `phone` (string<phone-number>, required)
  - `verified` (boolean)
- `sharedPhones` (array<phone>): (max items 10; unique items)
  - `type` (string): One of: `mobile`, `home`, `work`, `fax`, `other`.
  - `phone` (string<phone-number>, required)
  - `verified` (boolean)
- `whatsapps` (array<whatsapp>): (max items 10; unique items)
  - `type` (string): One of: `mobile`.
  - `phone` (string<whatsapp-number>, required)
  - `verified` (boolean)
- `facebookIds` (array<facebook_id>): (max items 10; unique items)
  - `pageId` (string, required): (max length 255)
  - `userId` (string, required): (max length 255)
  - `name` (string): (max length 255)
- `instagramIds` (array<instagram_id>): (max items 10; unique items)
  - `pageId` (string, required): (max length 255)
  - `threadId` (string, required): (max length 255)
  - `username` (string, required): (max length 255)
  - `instagramId` (string): (max length 255)
- `socials` (array<social>): (max items 10; unique items)
  - `type` (string, required): One of: `twitter`, `facebook`, `instagram`, `linkedin`, `pinterest`.
  - `userid` (string): (max length 256)
  - `username` (string, required): (min length 1; max length 256)
  - `url` (string<uri>)
  - `verified` (boolean)
- `sharedSocials` (array<social>): (max items 10; unique items)
  - `type` (string, required): One of: `twitter`, `facebook`, `instagram`, `linkedin`, `pinterest`.
  - `userid` (string): (max length 256)
  - `username` (string, required): (min length 1; max length 256)
  - `url` (string<uri>)
  - `verified` (boolean)
- `urls` (array<url>): (max items 10; unique items)
  - `type` (string): One of: `website`, `blog`, `other`.
  - `url` (string<url>, required)
- `locations` (array<location>): (max items 10; unique items)
  - `type` (string, nullable): One of: `home`, `work`, `other`.
  - `name` (string, nullable)
  - `address` (string, nullable): (max length 256)
  - `address2` (string, nullable): (max length 256)
  - `address3` (string, nullable): (max length 256)
  - `latitude` (number, nullable)
  - `longitude` (number, nullable)
  - `countryCode` (string, nullable): (min length 2; max length 2)
  - `countryName` (string, nullable): (max length 64)
  - `regionCode` (string, nullable): (max length 2)
  - `regionName` (string, nullable): (max length 128)
  - `cityName` (string, nullable): (max length 128)
  - `zipCode` (string, nullable): (max length 30)
  - `areaCode` (string, nullable): (max length 30)
- `locale` (string, nullable): (max length 16)
- `timeZone` (string, nullable): (max length 64)
- `tags` (array<tag_name>): (max items 20; unique items)
- `sentiment` (object)
  - `polarity` (number, required): One of: `-1`, `0`, `1`.
  - `confidence` (number, required): (≥ -1; ≤ 1)
- `custom` (object)
- `birthdayAt` (string<date-time>, nullable)
- `gender` (string, nullable): One of: `m`, `f`.
- `createdAt` (string<date-time>)
- `importedAt` (string<date-time>)
- `rev` (number)
- `defaultLang` (string, nullable): (max length 100)

Example:

```json
{
  "name": "Jane Doe",
  "externalId": "crm-10293",
  "emails": [
    {
      "type": "home",
      "email": "jane.doe@example.com",
      "verified": true
    }
  ],
  "phones": [
    {
      "type": "mobile",
      "phone": "+15555550123",
      "verified": true
    }
  ],
  "tags": [
    "vip"
  ],
  "custom": {}
}
```

## Responses

### 201

Created

- `data` (object, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `name` (string, required)
    - `displayName` (string, required)
    - `displayColor` (string, required)
    - `displayIcon` (string, required)
    - `externalIds` (array<any>, required)
    - `sharedExternalIds` (array<any>, required)
    - `emails` (array<object>, required)
    - `sharedEmails` (array<any>, required)
    - `phones` (array<object>, required)
    - `sharedPhones` (array<any>, required)
    - `socials` (array<object>, required)
    - `sharedSocials` (array<any>, required)
    - `urls` (array<object>, required)
    - `locations` (array<object>, required)
    - `activeUsers` (array<any>, required)
    - `watchers` (array<any>, required)
    - `recentLocation` (object, required)
    - `locale` (string, required)
    - `defaultLang` (string, required)
    - `gender` (string, required)
    - `createdAt` (string, required)
    - `updatedAt` (string, required)
    - `modifiedAt` (string, required)
    - `lastActivityAt` (string, required)
    - `lastConversation` (object, required)
    - `conversationCounts` (object, required)
    - `tags` (array<string>, required)
    - `progressiveStatus` (string, required, nullable)
    - `verified` (boolean, required)
    - `rev` (integer, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `messages` (object, required)
    - `createdBy` (object, required)
    - `modifiedBy` (object, required)
  - `links` (object, required)
    - `self` (string, required)

```json
{
  "data": {
    "type": "customer",
    "id": "5822a4a5aa5f92110005d69a",
    "attributes": {
      "name": "John Smith",
      "displayName": "John Smith",
      "displayColor": "green",
      "displayIcon": "compass",
      "externalIds": [],
      "sharedExternalIds": [],
      "emails": [
        {
          "email": "johnsmith@example.com",
          "verified": false,
          "type": "home",
          "id": null
        }
      ],
      "sharedEmails": [],
      "phones": [
        {
          "phone": "+18005551212",
          "verified": false,
          "type": "work",
          "id": null
        }
      ],
      "sharedPhones": [],
      "socials": [
        {
          "type": "twitter",
          "userid": "1234",
          "username": "@johnsmith",
          "url": "http://twitter.com/johnsmith",
          "verified": false,
          "id": null
        }
      ],
      "sharedSocials": [],
      "urls": [
        {
          "url": "https://kustomer.com",
          "type": "website",
          "id": null
        }
      ],
      "locations": [
        {
          "type": "work",
          "address": "530 7th Ave, New York, NY 10018",
          "id": null
        }
      ],
      "activeUsers": [],
      "watchers": [],
      "recentLocation": {
        "updatedAt": "2016-11-09T04:23:01.927Z"
      },
      "locale": "en_US",
      "defaultLang": "pt",
      "gender": "m",
      "createdAt": "2016-11-09T04:23:01.925Z",
      "updatedAt": "2016-11-09T04:23:01.925Z",
      "modifiedAt": "2016-11-09T04:23:01.925Z",
      "lastActivityAt": "2016-11-09T04:23:01.925Z",
      "lastConversation": {
        "tags": []
      },
      "conversationCounts": {
        "all": 0,
        "snoozed": 0,
        "open": 0,
        "done": 0
      },
      "tags": [
        "5822a4a5aa5f92110005d696",
        "5822a4a5aa5f92110005d698"
      ],
      "progressiveStatus": null,
      "verified": true,
      "rev": 1
    },
    "relationships": {
      "org": {
        "links": {
          "self": "/v1/orgs/57f29863a1dbf61100e6aa92"
        },
        "data": {
          "type": "org",
          "id": "57f29863a1dbf61100e6aa92"
        }
      },
      "messages": {
        "links": {
          "self": "/v1/customers/5822a4a5aa5f92110005d69a/messages"
        }
      },
      "createdBy": {
        "links": {
          "self": "/v1/users/58190b991f2932100010d683"
        },
        "data": {
          "type": "user",
          "id": "58190b991f2932100010d683"
        }
      },
      "modifiedBy": {
        "links": {
          "self": "/v1/users/58190b991f2932100010d683"
        },
        "data": {
          "type": "user",
          "id": "58190b991f2932100010d683"
        }
      }
    },
    "links": {
      "self": "/v1/customers/5822a4a5aa5f92110005d69a"
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

## Example request

```bash
curl --request POST \
  --url 'https://api.kustomerapp.com/v1/customers' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "Jane Doe",
  "externalId": "crm-10293",
  "emails": [
    {
      "type": "home",
      "email": "jane.doe@example.com",
      "verified": true
    }
  ],
  "phones": [
    {
      "type": "mobile",
      "phone": "+15555550123",
      "verified": true
    }
  ],
  "tags": [
    "vip"
  ],
  "custom": {}
}'
```
