Skip to main content

Customers

Explore the API
Launch the OpenAPI Explorer to browse the live REST specs, inspect request and response schemas, and execute calls against your environment with an API key.

All examples assume the environment variables defined in REST API Overview:

export BASE_URL="http://localhost:3000/api"
export API_KEY="<paste your API key secret here>"
export ORG_ID="<organization uuid in scope>"
export TENANT_ID="<tenant uuid in scope>"

Shared conventions

  • Pass X-Api-Key: $API_KEY on every request. JSON responses are returned unless stated otherwise.
  • Mutation payloads must include organizationId and tenantId. With an API key, you must supply both values explicitly.
  • Features map to RBAC feature flags declared in packages/core/src/modules/customers/acl.ts. Calls fail with 403 when the caller lacks the feature.
  • Custom fields can be written by adding either cf_<slug> top-level keys or a customFields object (e.g. { "customFields": { "language": "en" } }). Both map to the same runtime handler.
  • Every mutation automatically schedules a reindex with the Query Index module so search surfaces stay consistent.

People

List people — GET /customers/people

Feature: customers.people.view
Query parameters (selected):

  • page, pageSize — pagination (pageSize max 100).
  • search — case-insensitive match on display_name.
  • email, emailStartsWith, emailContains — mutually exclusive email filters.
  • status, lifecycleStage, source — dictionary-backed filters.
  • hasEmail, hasPhone, hasNextInteractiontrue/false.
  • createdFrom, createdTo — ISO timestamps (filters created_at).
  • tagIds — comma separated UUIDs to require assigned tags. tagIdsEmpty=true matches people without tags.
  • Custom fields — prefix with cf_ to filter against custom field values.
curl -X GET "$BASE_URL/customers/people?page=1&pageSize=20&search=Acme" \
-H "X-Api-Key: $API_KEY" \
-H "Accept: application/json"

Response includes the matching records plus pagination metadata.

Retrieve person — GET /customers/people/{id}

Feature: customers.people.view
By default the payload contains the core record, person profile, addresses, tags, custom field values, organization scope, and viewer metadata. Add include parameters to hydrate related collections on demand:

  • include=comments or include=notes
  • include=activities
  • include=deals
  • include=todos or include=tasks
  • include=addresses
curl -X GET "$BASE_URL/customers/people/$PERSON_ID?include=deals&include=activities" \
-H "X-Api-Key: $API_KEY" \
-H "Accept: application/json"

Check phone duplicates — GET /customers/people/check-phone?digits=<4+ digits>

Feature: customers.people.view
Normalises a primary phone to digits and returns { match: { id, displayName } | null }. Use it before creation to prevent duplicates.

Create person — POST /customers/people

Feature: customers.people.manage
Body must satisfy personCreateSchema:

  • Required: organizationId, tenantId, firstName, lastName.
  • Optional entity fields: displayName, description, ownerUserId, primaryEmail, primaryPhone, status, lifecycleStage, source, nextInteraction.
  • Optional profile fields: preferredName, jobTitle, department, seniority, timezone, linkedInUrl, twitterUrl, companyEntityId.
  • Optional tags (array of tag UUIDs) and custom field values.

Response: { "entityId": "<uuid>", "personId": "<uuid>" }.

Update person — PUT /customers/people

Feature: customers.people.manage
Send id, organizationId, tenantId, and any mutable fields from the create schema. Only supplied keys are updated.

Delete person — DELETE /customers/people?id=<uuid>

Feature: customers.people.manage
Provide the id in the query string or body. Soft deletes the entity and reindexes downstream search views.

Companies

List companies — GET /customers/companies

Feature: customers.companies.view
Supports the same pagination, search, tag, status, lifecycle stage, source, email, and custom field filters as /customers/people.

Retrieve company — GET /customers/companies/{id}

Feature: customers.companies.view
Detail payload includes core company data, profile, tags, custom fields, and viewer info. Optional include values hydrate:

  • include=addresses
  • include=comments / include=notes
  • include=activities
  • include=deals
  • include=todos / include=tasks
  • include=people (company members)

Create company — POST /customers/companies

Feature: customers.companies.manage
Body follows companyCreateSchema:

  • Required: organizationId, tenantId, displayName.
  • Optional entity fields: description, ownerUserId, primaryEmail, primaryPhone, status, lifecycleStage, source, nextInteraction, tags.
  • Optional profile fields: legalName, brandName, domain, websiteUrl, industry, sizeBucket, annualRevenue.
  • Custom fields allowed via cf_ keys or customFields object.

Response: { "entityId": "<uuid>", "companyId": "<uuid>" }.

Update company — PUT /customers/companies

Feature: customers.companies.manage
Provide id, organizationId, tenantId, and the fields you need to modify.

Delete company — DELETE /customers/companies?id=<uuid>

Feature: customers.companies.manage
Accepts id in the query string or JSON body. Performs a soft delete.

Deals

List deals — GET /customers/deals

Feature: customers.deals.view
Query parameters: page, pageSize, search (title substring), status, pipelineStage, sortField (createdAt, updatedAt, title, value), sortDir, personEntityId, companyEntityId, and legacy aliases personId, companyId.

Retrieve deal — GET /customers/deals/{id}

Feature: customers.deals.view
Returns core deal data plus:

  • Associated people and companies with labels/subtitles.
  • Custom field values.
  • Viewer metadata (viewerName, viewerEmail).

The route enforces tenant and organization scoping just like the list endpoint.

Create deal — POST /customers/deals

Feature: customers.deals.manage
Required body fields: organizationId, tenantId, title. Optional: description, status, pipelineStage, valueAmount, valueCurrency, probability, expectedCloseAt, ownerUserId, source, companyIds, personIds, custom fields.

Update deal — PUT /customers/deals

Feature: customers.deals.manage
Send id, organizationId, tenantId, and the fields to change.

Delete deal — DELETE /customers/deals?id=<uuid>

Feature: customers.deals.manage
Accepts the id via query string or JSON body.

Activities

List activities — GET /customers/activities

Feature: customers.activities.view
Query parameters: page, pageSize, entityId, dealId, activityType, sortField (occurredAt, createdAt), sortDir.

The response normalises timestamps to ISO strings, carries author information (including enriched name/email when present), and includes any custom field values.

Create activity — POST /customers/activities

Feature: customers.activities.manage
Body (plus scope identifiers): entityId, activityType, optional subject, body, occurredAt, dealId, authorUserId, appearanceIcon, appearanceColor, custom fields.

Update activity — PUT /customers/activities

Feature: customers.activities.manage
Send id plus any fields from the create schema to mutate.

Delete activity — DELETE /customers/activities?id=<uuid>

Feature: customers.activities.manage
Removes the record via soft delete and reindexes.

Notes & comments

List comments — GET /customers/comments

Feature: customers.activities.view
Query parameters: page, pageSize, entityId, dealId, sortField, sortDir.

Every comment in the response is enriched with dealTitle when linked to a deal.

Create comment — POST /customers/comments

Feature: customers.activities.manage
Body: organizationId, tenantId, entityId, body, optional dealId, authorUserId, appearanceIcon, appearanceColor.

Response: { "id": "<uuid>", "authorUserId": "<uuid|null>" }.

Update comment — PUT /customers/comments

Feature: customers.activities.manage
Provide id alongside scoped fields to update body/appearance attributes.

Delete comment — DELETE /customers/comments?id=<uuid>

Feature: customers.activities.manage
id may be supplied via query string or JSON payload.

Addresses

List addresses — GET /customers/addresses

Feature: customers.activities.view
Query parameters: page, pageSize, entityId, sortField, sortDir.

Create address — POST /customers/addresses

Feature: customers.activities.manage
Body: organizationId, tenantId, entityId, addressLine1, optional name, purpose, addressLine2, buildingNumber, flatNumber, city, region, postalCode, country, latitude, longitude, isPrimary.

Update address — PUT /customers/addresses

Feature: customers.activities.manage
Include id, scope identifiers, and the fields to change.

Delete address — DELETE /customers/addresses?id=<uuid>

Feature: customers.activities.manage

Tags

List tags — GET /customers/tags

Feature: customers.activities.view
Optional query parameters: page, pageSize, search, sortField, sortDir.

Create tag — POST /customers/tags

Feature: customers.activities.manage
Body: organizationId, tenantId, slug, label, optional color, description. Returns { id: "<uuid>" }.

Update tag — PUT /customers/tags

Feature: customers.activities.manage
Send id, scope fields, and any updatable attributes from the create schema.

Delete tag — DELETE /customers/tags?id=<uuid>

Feature: customers.activities.manage
Soft deletes the tag. id can be in the query string or payload.

Assign tag — POST /customers/tags/assign

Feature: customers.activities.manage
Body: organizationId, tenantId, entityId, tagId. Creates a tag assignment and returns { id: "<uuid>" }. The response includes an x-om-operation header for undo metadata.

Unassign tag — POST /customers/tags/unassign

Feature: customers.activities.manage
Accepts the same body as assign. Removes the tag link and emits undo metadata via x-om-operation.

Todos

Tasks are linked to customers via CustomerTodoLink records. The module can both create a todo (via a registered provider) and attach an existing item.

Feature: customers.activities.view
Query parameters: page, pageSize, search, isDone (true/false), organizationId, entityId.

The response includes todo metadata resolved from the remote source (todoSource) and the linked customer summary.

Create and link a todo — POST /customers/todos

Feature: customers.activities.manage
Body:

  • organizationId, tenantId, entityId, title (required).
  • Optional: isDone/is_done, todoSource (defaults to example:todo), createdByUserId.
  • todoCustom and custom objects are forwarded to the provider to seed fields.

Response: { "todoId": "<uuid|null>", "linkId": "<uuid>" } plus the x-om-operation header.

Feature: customers.activities.manage
Body: organizationId, tenantId, entityId, todoId, optional todoSource, createdByUserId. Response mirrors the create endpoint.

Feature: customers.activities.manage
Provide the todo link id in the query string or body to remove the association.

Dictionaries

List dictionary values — GET /customers/dictionaries/{kind}

Feature: customers.people.view
{kind} accepts: statuses, sources, lifecycle-stages, address-types, activity-types, deal-statuses, pipeline-stages, job-titles, industries.

The response merges organisation-level entries with inherited ancestors and marks inherited values with isInherited: true.

Upsert dictionary entry — POST /customers/dictionaries/{kind}

Feature: customers.settings.manage
Body: value (required), optional label, color (hex), icon. Values are normalised per organization; sending the same value updates the label.

Load currency dictionary — GET /customers/dictionaries/currency

Feature: customers.people.view
Returns the first active currency/currencies dictionary visible to the caller, or 404 when none exist.

Settings

Address format — GET /customers/settings/address-format

Feature: customers.settings.manage
Returns { "addressFormat": "line_first" | "street_first" } scoped to the selected organization.

Update address format — PUT /customers/settings/address-format

Feature: customers.settings.manage
Body: organizationId, tenantId, addressFormat (line_first or street_first). Response echoes the persisted value.