Back to Arky

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.

Email

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)

POST /v1/stores/{storeId}/profiles
SDK: 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)

POST /v1/stores/{storeId}/profiles/initialize
SDK: 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)

POST /v1/stores/{storeId}/profiles/connect
SDK: 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)

POST /v1/stores/{storeId}/profiles/auth/code
SDK: sdk.crm.requestCode()

Sends a verification code to the given email address. Use this to start a profile login flow.

Verify Auth Code (Public)

POST /v1/stores/{storeId}/profiles/auth/verify
SDK: sdk.crm.verify()

Exchanges a verification code for an auth token, marking the profile as verified.

Refresh Token (Public)

POST /v1/stores/{storeId}/profiles/auth/refresh
SDK: sdk.crm.refreshToken()

Exchanges a refresh token for a new access token.

Get Current Profile

GET /v1/stores/{storeId}/profiles/me
SDK: sdk.crm.getMe()

Returns the currently authenticated profile based on the bearer token.

Get Profile

GET /v1/stores/{storeId}/profiles/{id}
SDK: sdk.crm.get()

Find Profiles

GET /v1/stores/{storeId}/profiles
SDK: 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

PUT /v1/stores/{storeId}/profiles/{id}
SDK: 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

POST /v1/stores/{storeId}/profiles/{id}/merge
SDK: 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

DELETE /v1/stores/{storeId}/profiles/{id}/sessions/{tokenId}
SDK: sdk.crm.revokeToken()

Revokes a single active session for a profile.

Revoke All Session Tokens

DELETE /v1/stores/{storeId}/profiles/{id}/sessions
SDK: 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), or paid (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.

Note

Profile lists collect and organize subscribers. Campaigns send newsletters, broadcasts, and outreach email to profile-list members.

Create Profile List

POST /v1/stores/{storeId}/profile-lists
SDK: 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

GET /v1/stores/{storeId}/profile-lists/{id}
SDK: sdk.crm.profileList.get()

Look up a profile list by ID.

Find Profile Lists

GET /v1/stores/{storeId}/profile-lists
SDK: 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

PUT /v1/stores/{storeId}/profile-lists/{id}
SDK: 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

POST /v1/storefront/{storeId}/profile-lists/{id}/subscribe
SDK: 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 pending until 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

GET /v1/storefront/{storeId}/profile-lists/{id}/access
SDK: sdk.crm.profileList.checkAccess()

Check if the current profile has access to a profile list.

Get Profiles

GET /v1/stores/{storeId}/profile-lists/{id}/profiles
SDK: sdk.crm.profileList.profiles.find()

List Profiles in a Profile List (admin only).

Add Profile

POST /v1/stores/{storeId}/profile-lists/{id}/profiles/{profileId}
SDK: sdk.crm.profileList.profiles.add()

Add a Profile to a Profile List by profile ID (admin only). Skips if already present.

Remove Profile

DELETE /v1/stores/{storeId}/profile-lists/{id}/profiles/{profileId}
SDK: sdk.crm.profileList.profiles.remove()

Import Profiles Into Profile List

POST /v1/stores/{storeId}/profile-lists/{id}/profiles/import
SDK: 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

POST /v1/stores/{storeId}/campaigns
SDK: sdk.crm.campaign.create()

Import Campaign Recipients

POST /v1/stores/{storeId}/campaigns/{id}/recipients/import
SDK: sdk.crm.campaign.importRecipients()

Import from one profile list, multiple profile lists, individual profiles, or manual emails.

Launch Campaign

POST /v1/stores/{storeId}/campaigns/{id}/launch
SDK: 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

POST /v1/stores/{storeId}/campaigns/{id}/duplicate
SDK: 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
Tip

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.