Customers
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_KEYon every request. JSON responses are returned unless stated otherwise. - Mutation payloads must include
organizationIdandtenantId. 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 with403when the caller lacks the feature. - Custom fields can be written by adding either
cf_<slug>top-level keys or acustomFieldsobject (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 (pageSizemax100).search— case-insensitive match ondisplay_name.email,emailStartsWith,emailContains— mutually exclusive email filters.status,lifecycleStage,source— dictionary-backed filters.hasEmail,hasPhone,hasNextInteraction—true/false.createdFrom,createdTo— ISO timestamps (filterscreated_at).tagIds— comma separated UUIDs to require assigned tags.tagIdsEmpty=truematches 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=commentsorinclude=notesinclude=activitiesinclude=dealsinclude=todosorinclude=tasksinclude=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=addressesinclude=comments/include=notesinclude=activitiesinclude=dealsinclude=todos/include=tasksinclude=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 orcustomFieldsobject.
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.
List todo links — GET /customers/todos
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 toexample:todo),createdByUserId. todoCustomandcustomobjects are forwarded to the provider to seed fields.
Response: { "todoId": "<uuid|null>", "linkId": "<uuid>" } plus the x-om-operation header.
Link an existing todo — PUT /customers/todos
Feature: customers.activities.manage
Body: organizationId, tenantId, entityId, todoId, optional todoSource, createdByUserId. Response mirrors the create endpoint.
Unlink a todo — DELETE /customers/todos?id=<uuid>
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.