# Customers API

> Keep By Dawn's customers in step with your CRM or support tool, and record which customer asked for which ticket.

Use this when your integration knows who your customers are and what they ask for: a CRM that holds your accounts,
or a support tool where requests arrive. By the end you'll know which key to make, how to create and update customers
by your own IDs, and how to add a customer's request to a ticket so it counts on the board.

## How customers fit with tickets

A **customer** is a company your organization works for: its name, the email domains its people write from, and
optional details such as revenue, size, tier and status. A **customer request** says that a customer asked for a
ticket. It belongs to one ticket and one customer, and can be marked important and carry a quote of what they said.

A ticket can have requests from several customers, and several requests from the same one. The board counts each
customer once per ticket, so people can filter and sort by how many customers asked and how much revenue they bring.
What people see in the app is on [Customers](https://docs.bydawn.com/customers/).

Customers are off until an organization admin turns them on, and the organization's plan has to include them (see
[Customers settings and privacy](https://docs.bydawn.com/customers-settings/)). While they're off, every call on this page answers
`409 customers_disabled`, except reading the statuses and tiers (`GET /customer-statuses` and `GET /customer-tiers`),
which still answer. So check that Customers is on with a call such as `GET /customers`, not with those two.

## Make a key

In the app, an owner or admin makes the key in **Settings › Organization › Security** and picks its **Customers**
access (see [API keys](https://docs.bydawn.com/api-keys/#create-a-key)):

| You want to | Customers | Access | Scopes |
|---|---|---|---|
| Read customers and their requests | Read | any, or **Customers only** | `customers:read` |
| Create and update customers from your CRM | Read & write | **Customers only** | `customers:read`, `customers:write` |
| Also add a customer's request to a ticket | Read & write | **Board — read only** or higher | `customers:read`, `customers:write`, `tickets:read` |

A request is part of a ticket, so changing a ticket's requests also needs `tickets:read` on the ticket's project, and
the project must be one the key can read. A key never sees restricted projects, so it can't add a request to their
tickets, and the counts it reads leave their tickets out. See [Scopes](https://docs.bydawn.com/developers/authentication/#scopes) for the
rest of what each scope allows.

## Create and update customers by your own IDs

Keep a customer in step with your system by addressing it with your own ID, so you never have to store By Dawn's.
[`PUT /customers/external/{namespace}/{externalId}`](https://docs.bydawn.com/reference/#api/tag/customers/upsertCustomerByExternalId) creates
the customer the first time and updates it after that:

```text
PUT /customers/external/hubspot/18842
```

```json
{ "name": "Acme Lending", "domains": ["acme-lending.com"], "revenue": 120000 }
```

The customer comes back with your ID recorded on it:

```json
{
  "ok": true,
  "data": {
    "id": "customer-3f1c…",
    "name": "Acme Lending",
    "domains": ["acme-lending.com"],
    "externalIds": [{ "source": "api", "tenant": "hubspot", "externalId": "18842" }],
    "statusId": "customerstatus-6a2d…",
    "tierId": null,
    "revenue": 120000,
    "size": null,
    "ownerId": null,
    "logoUrl": null,
    "openRequests": 0,
    "shippedRequests": 0,
    "createdAt": "2026-10-17T09:12:44Z",
    "updatedAt": "2026-10-17T09:12:44Z"
  }
}
```

- **The namespace** is your name for the system the ID comes from, such as `hubspot`: lower-case letters, digits,
  `.`, `_` and `-`. IDs in different namespaces never collide, so two integrations can't overwrite each other's
  mapping.
- **The first call needs a `name`**, because it creates the customer. Later calls send only the fields that changed.
- **Two first calls at once** can answer `409 conflict` to one of them. Retry it, and it updates the customer the
  other one created.

To create a customer you don't know by an ID of your own, use
[`POST /customers`](https://docs.bydawn.com/reference/#api/tag/customers/createCustomer). To change one by By Dawn's ID, use
[`PATCH /customers/{customerId}`](https://docs.bydawn.com/reference/#api/tag/customers/updateCustomer). In both, a field you leave out keeps
its value and `null` clears an optional one, and `domains` replaces the whole set.

### Names and domains

A customer's name is unique in your organization, ignoring case, so a second "acme lending" is
`409 customer_name_taken`. Look the customer up and update it instead.

By Dawn matches incoming requests to customers by email domain, so a domain belongs to one customer only:

- **By Dawn tidies a domain before storing it.** It's lower-cased, and a trailing dot and a leading `www.` are removed.
- **A domain another customer has** is `409 customer_domain_taken`, and the message names that customer.
- **A domain that can't belong to a customer** is `422 customer_domain_excluded`. That's a personal mail provider such
  as `gmail.com`, a domain your admins excluded, or one of your organization's own domains.

### Status and tier

`statusId` and `tierId` are IDs from your organization's own lists, which admins edit in the app. Read them with
[`GET /customer-statuses`](https://docs.bydawn.com/reference/#api/tag/customers/listCustomerStatuses) and
[`GET /customer-tiers`](https://docs.bydawn.com/reference/#api/tag/customers/listCustomerTiers), and map them by name once when your
integration starts. A new customer without a `statusId` takes the first active status. A status that isn't `active`,
such as **Churned**, marks a customer you no longer serve. A new organization has no tiers.

### Revenue

`revenue` is a whole number in the unit your organization chose, such as US dollars per year. An admin can limit
revenue to admins. Then a key never receives `revenue`: it's left out of every customer, not `null`. Sending it, or
sorting the list by it, is `403 forbidden`. Check for the field rather than assuming it's there.

When an organization fills revenue, size or tier from Intercom, the customer lists those fields in `syncedFields`, and
changing one is `422 validation_error`, because the sync would overwrite it.

## Add a customer's request to a ticket

When a customer asks for something, record it on the ticket with
[`POST /tickets/{ticketId}/customer-requests`](https://docs.bydawn.com/reference/#api/tag/customers/createCustomerRequest):

```json
{
  "customerId": "customer-3f1c…",
  "important": true,
  "quote": "We need bulk export before our audit in November.",
  "requesterName": "Dana Okafor",
  "requesterRef": "dana.okafor@acme-lending.com",
  "sourceUrl": "https://app.hubspot.com/contacts/1234/ticket/55120",
  "sourceKey": "hubspot-ticket-55120"
}
```

The request comes back with `"source": "api"`, which the app shows as where it came from.

- **Send a `sourceKey`** to make the call safe to retry. If the ticket already has a request from this customer with
  the same key, you get that request back instead of a second one. Use your own ID for the conversation or the
  support ticket.
- **`quote`** is plain text, up to 4,000 characters. By Dawn never renders it as HTML.
- **`requesterName` and `requesterRef`** say who asked: a name, and your system's reference for them. Keep them to
  what people need to see, because they're personal data, and an admin can clear them (see
  [Customers settings and privacy](https://docs.bydawn.com/customers-settings/)).

Change a request with [`PATCH /customer-requests/{requestId}`](https://docs.bydawn.com/reference/#api/tag/customers/updateCustomerRequest),
for example to mark it important or move it to another customer. Remove it with
[`DELETE /customer-requests/{requestId}`](https://docs.bydawn.com/reference/#api/tag/customers/deleteCustomerRequest). The ticket and the
customer stay.

## Read a customer's requests

[`GET /customer-requests`](https://docs.bydawn.com/reference/#api/tag/customers/listCustomerRequests) lists one customer's requests or one
ticket's. Name exactly one of `customerId` and `ticketId`:

- **`?customerId=…`** lists the customer's requests newest first, each with its `ticket`. Add `state=open`,
  `shipped` or `canceled` to see one group. Shipped means the ticket reached a completed column. Canceled means it was
  archived, or closed as a duplicate or won't fix, without being completed.
- **`?ticketId=…`** lists the ticket's requests oldest first, each with its `customer`.

[`GET /customers`](https://docs.bydawn.com/reference/#api/tag/customers/listCustomers) lists customers by name. `q` finds one by the start of
a word in its name or of a domain, and each customer carries `openRequests` and `shippedRequests`: its requests on
open tickets, and on tickets completed in the last 90 days.

## Merged customers

An organization admin can merge two customers in the app. The one merged away keeps its ID, and reading it answers
the customer it was merged into, so the `id` you get back can differ from the one you asked for. The same goes for your
own IDs: the external ID moves to the customer it was merged into. When the IDs differ, store the new one.

A key can't merge customers itself. Merging is one of the things that stay with people in the app, and
[What a key can't do](https://docs.bydawn.com/developers/authentication/#what-a-key-cant-do) lists the others.

## Do and don't

- **Do** upsert by your own ID, so a retried sync never makes a second customer.
- **Do** send a `sourceKey` with every request you add, for the same reason.
- **Don't** add a customer for every email domain you see. Add the companies you work for, and let By Dawn match their
  people's emails by domain.
- **Don't** branch on an error's message. Branch on its slug (see
  [Responses and errors](https://docs.bydawn.com/developers/responses-and-errors/)).

## Where to go next

- [Customers](https://docs.bydawn.com/customers/): what people see in the app, and where requests come from.
- [Customers settings and privacy](https://docs.bydawn.com/customers-settings/): the admin's settings, and how requester data is cleared.
- The [API reference](https://docs.bydawn.com/reference/) lists every customer route under **Customers**.

Source: https://docs.bydawn.com/developers/customers/
