CRM
Profiles, profile authentication, and profiles in profile lists
The CRM module manages profile records and profile lists for your store. Profiles are store-scoped. A profile can be linked to orders and profile lists, and can authenticate via email magic codes.
Key Concepts
Profile vs Account
- Account — platform-level auth identity. One person, one Account across all stores.
- Profile — store-level CRM record with one email, verification state, status, and taxonomies. Each store has its own view of a profile.
Taxonomies
Profile classification and searchable custom fields are stored as taxonomies — the same content model used across the platform. There is no direct profile tags field; tags and segments are taxonomy entries.
Profiles have a single email. The initialize flow creates an anonymous guest profile; the email is set once the profile authenticates with requestCode / verify, calls connect, or is updated by an admin.
Profile Endpoints
Create Profile (Admin)
/v1/stores/{storeId}/profiles sdk.crm.create() Creates a new profile. If a profile with the same email already exists, returns the existing one (idempotent).
Parameters
| Name | Type | Description |
|---|---|---|
email required | string | Primary email for the profile |
taxonomies optional | TaxonomyEntry[] | Taxonomy entries for segments, lifecycle stage, or other custom classification |
Initialize Profile (Public)
/v1/stores/{storeId}/profiles/initialize sdk.crm.initialize() Public endpoint for end users. Creates an anonymous guest profile and returns an auth token. No email required — use this to get a profile session before any identity is known. The returned token can later be upgraded by calling connect or requestCode + verify.
Connect Profile (Public)
/v1/stores/{storeId}/profiles/connect sdk.crm.connect() Attaches an email to the current profile session (or returns/creates a profile matching the email). Returns a fresh auth token.
Request Auth Code (Public)
/v1/stores/{storeId}/profiles/auth/code sdk.crm.requestCode() Sends a verification code to the given email address. Use this to start a profile login flow.
Verify Auth Code (Public)
/v1/stores/{storeId}/profiles/auth/verify sdk.crm.verify() Exchanges a verification code for an auth token, marking the profile as verified.
Refresh Token (Public)
/v1/stores/{storeId}/profiles/auth/refresh sdk.crm.refreshToken() Exchanges a refresh token for a new access token.
Get Current Profile
/v1/stores/{storeId}/profiles/me sdk.crm.getMe() Returns the currently authenticated profile based on the bearer token.
Get Profile
/v1/stores/{storeId}/profiles/{id} sdk.crm.get() Find Profiles
/v1/stores/{storeId}/profiles sdk.crm.find() Search and list profiles.
Parameters
| Name | Type | Description |
|---|---|---|
query optional | string | Search across profile email, ID, and indexed taxonomy values |
taxonomy_query optional | TaxonomyQuery[] | Filter by taxonomy entries |
status optional | 'active' | 'archived' | Filter by profile status |
limit optional | number | Items per page |
cursor optional | string | Pagination cursor |
sort_field optional | string | Field to sort by (e.g. createdAt) |
sort_direction optional | string | asc or desc |
Update Profile
/v1/stores/{storeId}/profiles/{id} sdk.crm.update() Parameters
| Name | Type | Description |
|---|---|---|
id required | string | Profile ID |
email optional | string | Replace the profile's email |
taxonomies optional | TaxonomyEntry[] | Replace taxonomy entries |
status optional | 'active' | 'archived' | Profile status |
Merge Profiles
/v1/stores/{storeId}/profiles/{id}/merge sdk.crm.merge() Merges a source profile into the target. The source is permanently deleted (GDPR-compliant). Orders and profile list entries are moved onto the target profile.
Revoke Session Token
/v1/stores/{storeId}/profiles/{id}/sessions/{tokenId} sdk.crm.revokeToken() Revokes a single active session for a profile.
Revoke All Session Tokens
/v1/stores/{storeId}/profiles/{id}/sessions sdk.crm.revokeAllTokens() Revokes every active session for a profile (logout everywhere).
Profile Object
{
"id": "uuid",
"store_id": "uuid",
"email": "john@example.com",
"verified": true,
"status": "active",
"taxonomies": [],
"promo_usage": [],
"auth_tokens": [],
"verification_codes": [],
"created_at": 1234567890,
"updated_at": 1234567890
}
Profile Lists
Profile Lists are profile groups used for newsletter subscriptions, content gating, paid access, and outreach audience sourcing. They live under the CRM module as sdk.crm.profileList.*.
Key Concepts
A profile list is a subscriber group with two dimensions:
- Type:
standard(free),confirmation(double opt-in), orpaid(Stripe checkout) - Confirmation: Optional double opt-in via an email template (
type.confirm_template_id)
Use cases: newsletter subscriptions, premium memberships, course access, tiered content, and reusable audience sources for campaigns.
Profile lists collect and organize subscribers. Campaigns send newsletters, broadcasts, and outreach email to profile-list members.
Create Profile List
/v1/stores/{storeId}/profile-lists sdk.crm.profileList.create() Parameters
| Name | Type | Description |
|---|---|---|
key required | string | Unique profile list identifier (alphanumeric, hyphens, underscores) |
name optional | string | Display name |
description optional | string | Internal description |
type optional | ProfileListType | standard (default), confirmation, or paid with prices |
type.confirm_template_id optional | string | Email template ID for double opt-in confirmation lists |
Get Profile List
/v1/stores/{storeId}/profile-lists/{id} sdk.crm.profileList.get() Look up a profile list by ID.
Find Profile Lists
/v1/stores/{storeId}/profile-lists sdk.crm.profileList.find() Parameters
| Name | Type | Description |
|---|---|---|
ids optional | string[] | Filter by specific profile list IDs |
status optional | string | Filter by status (active, archived) |
query optional | string | Search in profile list keys and names |
limit optional | number | Items per page (default 50) |
cursor optional | string | Pagination cursor |
Update Profile List
/v1/stores/{storeId}/profile-lists/{id} sdk.crm.profileList.update() The type cannot be changed after creation, but you can update the key, name, description, and status.
Subscribe to Profile List
/v1/storefront/{storeId}/profile-lists/{id}/subscribe sdk.crm.profileList.subscribe() Subscribe a profile to a profile list. Behavior depends on the profile list configuration:
- Standard, no confirmation: Subscription is immediately active
- Standard, with confirmation: Subscription is
pendinguntil the user clicks the confirmation link - Paid: Returns a Stripe checkout URL
Parameters
| Name | Type | Description |
|---|---|---|
id required | string | Profile List ID |
profile_id required | string | Profile ID (obtain via sdk.crm.initialize or sdk.crm.connect) |
price_id optional | string | Stripe price ID (required for paid profile lists) |
success_url optional | string | Redirect URL after successful payment |
cancel_url optional | string | Redirect URL if user cancels payment |
confirm_url optional | string | Base URL for the confirmation link |
Check Access
/v1/storefront/{storeId}/profile-lists/{id}/access sdk.crm.profileList.checkAccess() Check if the current profile has access to a profile list.
Get Profiles
/v1/stores/{storeId}/profile-lists/{id}/profiles sdk.crm.profileList.profiles.find() List Profiles in a Profile List (admin only).
Add Profile
/v1/stores/{storeId}/profile-lists/{id}/profiles/{profileId} sdk.crm.profileList.profiles.add() Add a Profile to a Profile List by profile ID (admin only). Skips if already present.
Remove Profile
/v1/stores/{storeId}/profile-lists/{id}/profiles/{profileId} sdk.crm.profileList.profiles.remove() Import Profiles Into Profile List
/v1/stores/{storeId}/profile-lists/{id}/profiles/import sdk.crm.profileList.importProfiles() Upserts profiles and adds them to a profile list in one transient operation. The API returns row counts and errors; it does not create a stored import record.
Campaigns
Campaigns are one-off audience email sends or sequences. Create a campaign, choose mailbox sender(s), choose email templates for each step, import recipients from profile lists, profiles, or manual emails, then launch it.
Campaign recipients are fixed for that campaign once imported. Replies and sent messages stay attached to that campaign conversation.
Create Campaign
/v1/stores/{storeId}/campaigns sdk.crm.campaign.create() Import Campaign Recipients
/v1/stores/{storeId}/campaigns/{id}/recipients/import sdk.crm.campaign.importRecipients() Import from one profile list, multiple profile lists, individual profiles, or manual emails.
Launch Campaign
/v1/stores/{storeId}/campaigns/{id}/launch sdk.crm.campaign.launch() await sdk.crm.campaign.launch({ id: campaign.id });
Campaign messages keep the template ID, template variables, rendered subject, rendered HTML, rendered text, attachments, and mailbox ID. Editing the template later does not mutate sent campaign messages.
Duplicate Campaign
/v1/stores/{storeId}/campaigns/{id}/duplicate sdk.crm.campaign.duplicate() Duplicate a campaign when you want to reuse the setup for a new one-off send.
const draft = await sdk.crm.campaign.duplicate({
id: campaign.id,
name: 'Weekly Newsletter - July',
copy_recipients: true
});
Duplicating copies campaign setup and the current recipient set into a new draft. It does not copy sent messages, replies, or conversation history.
Typical Flows
E-commerce Checkout
// 1. Initialize a guest profile session
const token = await sdk.crm.initialize();
// 2. Attach the shopper's email
await sdk.crm.connect({ email: shippingAddress.email });
// 3. Checkout a cart — the authenticated profile is resolved from the token
const cart = await sdk.eshop.cart.current();
await sdk.eshop.cart.update({
id: cart.id,
items: [...],
shipping_address: {...}
});
const order = await sdk.eshop.cart.checkout({ id: cart.id });
Newsletter Subscribe with Double Opt-in
// 1. Initialize + connect to get a profile
await sdk.crm.initialize();
const { id: profileId } = await sdk.crm.connect({ email: "user@example.com" });
// 2. Subscribe to the profile list (sends confirmation email)
await sdk.crm.profileList.subscribe({
id: profileListId,
profile_id: profileId,
confirm_url: 'https://yoursite.com/confirm'
});
Profile Login (Magic Code)
// 1. Ask for a code
await sdk.crm.requestCode({ email: "user@example.com" });
// 2. User enters the code from their email
const token = await sdk.crm.verify({
email: "user@example.com",
code: "123456"
});
// 3. Persist token.refresh_token and use token.access_token for subsequent calls
If you delete a confirmation email template, any profile list referencing it will automatically lose its double opt-in and fall back to single opt-in.