Skip to content
Docs
About By Dawn Contact supportOpen By Dawn
  1. Work with customers
  2. Customers API

Customers API

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.

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.

Customers are off until an organization admin turns them on, and the organization’s plan has to include them (see Customers settings and privacy). 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.

In the app, an owner or admin makes the key in Settings › Organization › Security and picks its Customers access (see API keys):

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 for the rest of what each scope allows.

Create and update customers by your own IDs

Section titled “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} creates the customer the first time and updates it after that:

PUT /customers/external/hubspot/18842
{ "name": "Acme Lending", "domains": ["acme-lending.com"], "revenue": 120000 }

The customer comes back with your ID recorded on it:

{
"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. To change one by By Dawn’s ID, use PATCH /customers/{customerId}. In both, a field you leave out keeps its value and null clears an optional one, and domains replaces the whole set.

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.

statusId and tierId are IDs from your organization’s own lists, which admins edit in the app. Read them with GET /customer-statuses and GET /customer-tiers, 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 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.

When a customer asks for something, record it on the ticket with POST /tickets/{ticketId}/customer-requests:

{
"customerId": "customer-3f1c…",
"important": true,
"quote": "We need bulk export before our audit in November.",
"requesterName": "Dana Okafor",
"requesterRef": "[email protected]",
"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).

Change a request with PATCH /customer-requests/{requestId}, for example to mark it important or move it to another customer. Remove it with DELETE /customer-requests/{requestId}. The ticket and the customer stay.

GET /customer-requests 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 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.

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 lists the others.

  • 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).