Skip to main content

Customers API

Create and update canonical customer profiles per project, register push devices, and ingest customer-scoped events.

Endpoints

GET  /api/organizations/{organizationId}/projects/{projectId}/customers
POST /api/organizations/{organizationId}/projects/{projectId}/customers
POST /api/organizations/{organizationId}/projects/{projectId}/customers/import/csv
POST /api/organizations/{organizationId}/projects/{projectId}/customers/events
GET /api/organizations/{organizationId}/projects/{projectId}/customers/segments
POST /api/organizations/{organizationId}/projects/{projectId}/customers/segments
POST /api/organizations/{organizationId}/projects/{projectId}/customers/segments/preview
POST /api/organizations/{organizationId}/projects/{projectId}/customers/eligibility
GET /api/organizations/{organizationId}/projects/{projectId}/customers/{customerId}/devices
POST /api/organizations/{organizationId}/projects/{projectId}/customers/{customerId}/devices

Authentication and Scopes

These endpoints support dashboard session auth or project API keys.

EndpointScope
GET /customerscustomers:read
POST /customerscustomers:write
POST /customers/import/csvcustomers:write
POST /customers/eventscustomer_events:write
GET /customers/segmentscustomers:read
POST /customers/segmentscustomers:write
POST /customers/segments/previewcustomers:read
POST /customers/eligibilitycustomers:read
GET /customers/{customerId}/devicescustomers:read
POST /customers/{customerId}/devicescustomer_devices:write

project:admin can access all customer APIs.

Upsert Customer

POST /customers

Request Body

FieldTypeRequiredDescription
externalIdstringconditionalStable app/customer ID
emailstringconditionalCustomer email
namestringnoDisplay name
phonestringnoPhone number
statusstringnoactive, suppressed, deleted
metadataobjectnoArbitrary customer attributes
identitiesarraynoAlternate IDs (email, user_id, etc.)
preferencesobjectnoChannel opt-in settings

At least one of externalId, email, or identities is required.

Response

{
"customer": {
"id": "uuid",
"externalId": "user-123",
"email": "user@example.com"
},
"created": true
}

Example: SDK API Client

import { createSyntropyApiClient } from "@eclosion-tech/syntropy-node";

const api = createSyntropyApiClient({
baseUrl: "https://syntropy.chat/api",
apiKey: process.env.SYNTROPY_API_KEY,
});

const scope = { organizationId: "org_uuid", projectId: "project_uuid" };

await api.customers.upsert(scope, {
externalId: "user_123",
email: "user@example.com",
metadata: { plan: "pro" },
});

await api.customers.trackEvent(scope, {
name: "purchase_completed",
externalId: "user_123",
payload: { amount: 99.99 },
});

Backfill Existing Subscribers

To link existing email_subscribers rows into canonical customer profiles:

pnpm --filter @syntropy/web customers:backfill-subscriber-links \
--organizationId <org-uuid> \
--accountId <account-uuid>

If --accountId is omitted, the script only auto-links organizations with a single active account.

Identity Merge Policy

POST /customers evaluates all provided identifiers (externalId, email, identities) together.

  • If all matches resolve to the same existing customer, that customer is updated.
  • If there are no matches, a new customer is created.
  • If matches resolve to different customers, the request is rejected with 409 CUSTOMER_IDENTITY_CONFLICT and no automatic cross-customer merge is performed.

Import Customers from CSV

POST /customers/import/csv

Designed for Customer.io-style exports (header row with email, optional id / external_id, name, phone, and additional attribute columns).

Request Body

FieldTypeRequiredDescription
csvstringyesCSV file contents
hasHeaderbooleannoDefaults to true
subscribeToListIduuidnoIf provided, upserts subscriber rows into this list
sourcestringnoImport source label (default: customer_io_csv_import)
dryRunbooleannoValidate/parse only, do not write
maxRowsnumbernoMax rows to process (default: 2000, max 10000)

Response

{
"success": true,
"summary": {
"totalRows": 200,
"parsedRows": 198,
"skippedRows": 2,
"customerCreated": 120,
"customerUpdated": 78,
"conflicts": [],
"subscribersCreated": 40,
"subscribersUpdated": 30,
"subscribersLinked": 70
},
"skipped": [
{ "lineNumber": 18, "reason": "missing_email" }
]
}

Track Customer Event

POST /customers/events

Request Body

FieldTypeRequiredDescription
namestringyesEvent name
timestampISO datetimenoEvent time
customerIduuidconditionalCustomer id
externalIdstringconditionalExternal customer id
emailstringconditionalCustomer email
payloadobjectnoEvent payload

At least one of customerId, externalId, or email is required.

If more than one identifier is provided, they must resolve to the same customer. Otherwise the API returns 409 CUSTOMER_IDENTITY_CONFLICT.

Response

{
"success": true,
"eventId": "uuid",
"eventName": "customer.purchase_completed",
"customerId": "uuid"
}

Manage Segments

Create or Update Segment

POST /customers/segments

Creates a segment definition and re-materializes segment membership against current customer profiles.

FieldTypeRequiredDescription
segmentIduuidnoUpdate existing segment when provided
namestringyesSegment display name
slugstringnoOptional slug override
descriptionstringnoSegment description
statusstringnodraft, active, paused, archived
definition.logicstringnoand (default) or or
definition.conditionsarrayyesSegment condition rules

Condition operators: eq, neq, contains, in, not_in, gt, gte, lt, lte, exists.

List Segments

GET /customers/segments

Returns segments with materialized member counts and pagination.

Preview Segment

POST /customers/segments/preview

Dry-runs a segment definition against current customers and returns matched rows.

{
"definition": {
"logic": "and",
"conditions": [
{ "field": "status", "operator": "eq", "value": "active" },
{ "field": "metadata.plan", "operator": "eq", "value": "pro" }
]
},
"limit": 100,
"offset": 0
}

Customer Eligibility

POST /customers/eligibility

Evaluates whether a customer is currently eligible for a channel/topic send, optionally constrained by segment membership.

FieldTypeRequiredDescription
customerIduuidconditionalCustomer id
externalIdstringconditionalExternal customer id
emailstringconditionalCustomer email
channelstringyesemail, push, sms
topicstringnoOptional topic key
segmentIdsuuid[]noRequired segment ids
segmentMatchstringnoany (default) or all

Response includes eligible, reasons, and check breakdown (channelOptIn, topicAllowed, suppressionActive, segmentMatch).

Use this as a pre-send gate before calling the transactional email send API — if eligible is false, skip the send rather than letting it reach the deliverability guardrails. This gives you explicit control over suppression logic and lets you surface a reason to the user if needed.

const { eligible, reasons } = await api.customers.checkEligibility(scope, {
email: "user@example.com",
channel: "email",
});

if (!eligible) {
console.log("Skipping send:", reasons);
return;
}

// safe to send
await sendTransactionalEmail({ to: "user@example.com", ... });

Pass segmentIds to additionally gate sends on segment membership — useful for feature-flagged campaigns or plan-tier targeting.

Register Device

POST /customers/{customerId}/devices

Request Body

FieldTypeRequiredDescription
platformstringyesios, android, web
pushTokenstringyesPush token
deviceIdstringnoClient device identifier
appVersionstringnoApp version
osVersionstringnoOS version
localestringnoLocale
timezonestringnoTimezone
metadataobjectnoDevice attributes
activebooleannoDevice active flag

Response

{
"device": {
"id": "uuid",
"platform": "ios",
"active": true
}
}

Push Notification Delivery

Once devices are registered, Syntropy can deliver push notifications natively via:

  • Apple APNs -- for iOS devices with native APNs tokens
  • Firebase FCM -- for Android devices with FCM registration tokens
  • Expo Push -- for apps using Expo push tokens (ExponentPushToken[...])

Configure push credentials per project in the dashboard under Settings > Push. See the React Native SDK docs for setup instructions.

Syntropy automatically invalidates device tokens that are rejected by the push provider, keeping your device inventory clean.

  • Chat Triggers — capture leads from AI chat conversations and bridge them into customer records via webhook
  • Transactional Email — send email to customers; use the eligibility API as a pre-send gate
  • Chat Schemas — define structured fields extracted from chat leads
  • Project API Keyscustomers:read, customers:write, customer_events:write, customer_devices:write scopes