Skip to content

Customer Management

Complete guide to managing customers, preferences, appointment history, and analytics in the Reserva platform.


Overview

The customer management system provides comprehensive tools for:

  • Customer CRUD Operations - Create, read, update, and delete customer profiles
  • Advanced Search - Full-text search across name, email, and phone
  • Duplicate Detection - Automatic validation to prevent duplicate entries
  • Appointment History - Track customer booking history and statistics
  • Preference Management - Service, staff, and communication preferences
  • Analytics & Reporting - Customer statistics and business insights
  • Walk-in Support - Customers without password (staff-created)
  • Self-Registration - Customers with password (portal access)

Key Concepts:

  • Walk-in Customer = Staff-created, no password, no portal access
  • Registered Customer = Self-created or with password, has portal access
  • Soft Delete = Customer marked inactive, data preserved for audit
  • Hard Delete = Permanent removal (SUPER_ADMIN only)
  • Tenant Isolation = All operations scoped to current tenant

List All Customers

Retrieve all customers with advanced search and filtering capabilities.

Endpoint

GET /api/v1/customers

Authentication: Required (JWT token) Access: All staff members

Query Parameters

Parameter Type Required Description Example
page integer No Page number (default: 1) 1
size integer No Items per page (default: 20, max: 100) 20
search string No Search in name, email, or phone "John"
tags string No Comma-separated tags to filter "vip,regular"
has_password boolean No Filter by password presence (True=registered, False=walk-in) true
email_verified boolean No Filter by email verification status true
created_from date No Filter customers created from this date "2025-01-01"
created_to date No Filter customers created until this date "2025-12-31"

Response

{
  "items": [
    {
      "id": "507f1f77bcf86cd799439011",
      "tenant_id": "507f1f77bcf86cd799439010",
      "email": "john.doe@example.com",
      "phone": "+628123456789",
      "first_name": "John",
      "last_name": "Doe",
      "full_name": "John Doe",
      "date_of_birth": "1990-05-15",
      "gender": "male",
      "address": "Jl. Sudirman No. 123, Jakarta",
      "avatar_url": "https://cdn.example.com/avatars/john.jpg",
      "tags": ["vip", "regular", "staff-created"],
      "email_verified": true,
      "is_active": true,
      "preferences": {
        "preferred_services": ["507f1f77bcf86cd799439020"],
        "preferred_staff": ["507f1f77bcf86cd799439021"],
        "preferred_outlets": ["507f1f77bcf86cd799439022"],
        "communication_channels": {
          "sms": true,
          "email": true,
          "push": false
        },
        "marketing_consent": true,
        "language": "id"
      },
      "loyalty_points": 1250,
      "total_spent": 5000000,
      "total_appointments": 28,
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2025-01-20T14:45:00Z"
    }
  ],
  "total": 145,
  "page": 1,
  "size": 20,
  "pages": 8
}

Search Behavior

Full-text search across:

  • First name and last name
  • Email address
  • Phone number

Tag filtering:

  • Supports multiple tags (comma-separated)
  • Returns customers matching ANY of the specified tags
  • Common tags: vip, regular, walk-in, staff-created

Subscription Limits

Customer list access is not limited by subscription plan. However, the number of active customers may be tracked for usage analytics.

Usage Tracking:

  • Total active customers count
  • Customer growth metrics
  • Available in Usage Dashboard for PRO and ENTERPRISE plans

See Subscription Management - Usage Tracking for details.


Create New Customer

Create a new customer with automatic duplicate detection.

Endpoint

POST /api/v1/customers

Authentication: Required (JWT token) Access: All staff members

Request Body

{
  "email": "jane.smith@example.com",
  "phone": "+628123456780",
  "first_name": "Jane",
  "last_name": "Smith",
  "date_of_birth": "1995-08-20",
  "gender": "female",
  "address": "Jl. Thamrin No. 45, Jakarta",
  "password": "SecurePass123!",
  "tags": ["new-customer"],
  "preferences": {
    "communication_channels": {
      "sms": true,
      "email": true,
      "push": true
    },
    "marketing_consent": true,
    "language": "en"
  }
}

Query Parameters

Parameter Type Required Description Default
check_duplicate boolean No Check for duplicate customers true

Request Fields

Field Type Required Description Validation
email string Yes* Customer email address Valid email format, unique per tenant
phone string Yes* Customer phone number Valid phone format, unique per tenant
first_name string Yes Customer first name 1-100 characters
last_name string No Customer last name 1-100 characters
date_of_birth date No Customer birth date Valid date, not in future
gender string No Customer gender male, female, other
address string No Customer address Max 500 characters
password string No Password for customer portal access Min 8 characters if provided
avatar_url string No Profile picture URL Valid URL
tags array No Customer tags for categorization Array of strings
preferences object No Customer preferences See CustomerPreferences schema

Note: Either email OR phone must be provided (at least one required).

Response

{
  "id": "507f1f77bcf86cd799439030",
  "tenant_id": "507f1f77bcf86cd799439010",
  "email": "jane.smith@example.com",
  "phone": "+628123456780",
  "first_name": "Jane",
  "last_name": "Smith",
  "full_name": "Jane Smith",
  "date_of_birth": "1995-08-20",
  "gender": "female",
  "address": "Jl. Thamrin No. 45, Jakarta",
  "avatar_url": null,
  "tags": ["new-customer", "staff-created"],
  "email_verified": false,
  "is_active": true,
  "preferences": {
    "preferred_services": [],
    "preferred_staff": [],
    "preferred_outlets": [],
    "communication_channels": {
      "sms": true,
      "email": true,
      "push": true
    },
    "marketing_consent": true,
    "language": "en"
  },
  "loyalty_points": 0,
  "total_spent": 0,
  "total_appointments": 0,
  "created_at": "2025-01-22T09:15:00Z",
  "updated_at": "2025-01-22T09:15:00Z"
}

Business Rules

  1. Duplicate Detection:

  2. Email must be unique within tenant

  3. Phone must be unique within tenant
  4. Set check_duplicate=false to bypass (not recommended)

  5. Password Handling:

  6. If password provided → Customer can access portal

  7. If no password → Walk-in customer, no portal access
  8. Password is hashed using bcrypt before storage

  9. Automatic Tagging:

  10. staff-created tag added automatically

  11. Additional tags can be specified in request

  12. Default Preferences:

  13. All communication channels enabled by default

  14. Marketing consent defaults to false
  15. Language defaults to tenant's default language

Error Responses

409 Conflict - Duplicate Email:

{
  "detail": "Customer with email jane.smith@example.com already exists"
}

409 Conflict - Duplicate Phone:

{
  "detail": "Customer with phone +628123456780 already exists"
}

400 Bad Request - Validation Error:

{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    }
  ]
}

Subscription Limits

Customer creation is NOT directly limited by subscription plans. However, subscription plans may limit related features:

FREE Plan:

  • Unlimited customer creation
  • Limited to 100 appointments/month (affects customer bookings)
  • Single outlet only

PRO Plan:

  • Unlimited customer creation
  • 2,000 appointments/month
  • Up to 10 outlets

ENTERPRISE Plan:

  • Unlimited everything

See Subscription Management - Available Plans for complete plan details.


Get Customer Details

Retrieve detailed information about a specific customer.

Endpoint

GET /api/v1/customers/{customer_id}

Authentication: Required (JWT token) Access: All staff members

Path Parameters

Parameter Type Required Description
customer_id string Yes Customer ID (MongoDB ObjectId)

Response

{
  "id": "507f1f77bcf86cd799439011",
  "tenant_id": "507f1f77bcf86cd799439010",
  "email": "john.doe@example.com",
  "phone": "+628123456789",
  "first_name": "John",
  "last_name": "Doe",
  "full_name": "John Doe",
  "date_of_birth": "1990-05-15",
  "gender": "male",
  "address": "Jl. Sudirman No. 123, Jakarta",
  "avatar_url": "https://cdn.example.com/avatars/john.jpg",
  "tags": ["vip", "regular"],
  "email_verified": true,
  "is_active": true,
  "preferences": {
    "preferred_services": ["507f1f77bcf86cd799439020"],
    "preferred_staff": ["507f1f77bcf86cd799439021"],
    "preferred_outlets": ["507f1f77bcf86cd799439022"],
    "communication_channels": {
      "sms": true,
      "email": true,
      "push": false
    },
    "marketing_consent": true,
    "language": "id"
  },
  "loyalty_points": 1250,
  "total_spent": 5000000,
  "total_appointments": 28,
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2025-01-20T14:45:00Z"
}

Error Responses

404 Not Found:

{
  "detail": "Customer not found"
}

403 Forbidden - Wrong Tenant:

{
  "detail": "Customer belongs to different tenant"
}


Update Customer

Update customer information with duplicate validation.

Endpoint

PUT /api/v1/customers/{customer_id}

Authentication: Required (JWT token) Access: TENANT_ADMIN, OUTLET_MANAGER, or SUPER_ADMIN

Path Parameters

Parameter Type Required Description
customer_id string Yes Customer ID (MongoDB ObjectId)

Request Body

All fields are optional. Only provided fields will be updated.

{
  "email": "john.newemail@example.com",
  "phone": "+628123456790",
  "first_name": "Jonathan",
  "last_name": "Doe",
  "date_of_birth": "1990-05-15",
  "gender": "male",
  "address": "Jl. Sudirman No. 456, Jakarta",
  "avatar_url": "https://cdn.example.com/avatars/john-new.jpg",
  "tags": ["vip", "premium"],
  "is_active": true
}

Response

Returns updated customer profile (same format as Get Customer Details).

Business Rules

  1. Duplicate Validation:

  2. Email uniqueness checked if email is changed

  3. Phone uniqueness checked if phone is changed
  4. Returns 409 Conflict if duplicate found

  5. Protected Fields:

  6. tenant_id cannot be changed

  7. created_at cannot be changed
  8. loyalty_points, total_spent, total_appointments managed by system

  9. Tag Management:

  10. Tags array completely replaces existing tags

  11. staff-created tag preserved automatically

Error Responses

409 Conflict - Duplicate Email:

{
  "detail": "Email john.newemail@example.com is already in use"
}

409 Conflict - Duplicate Phone:

{
  "detail": "Phone +628123456790 is already in use"
}


Delete Customer

Delete a customer with soft delete option for data preservation.

Endpoint

DELETE /api/v1/customers/{customer_id}

Authentication: Required (JWT token) Access: TENANT_ADMIN or SUPER_ADMIN

Path Parameters

Parameter Type Required Description
customer_id string Yes Customer ID (MongoDB ObjectId)

Query Parameters

Parameter Type Required Description Default
permanent boolean No Permanently delete (True) or soft delete (False) false

Response

{
  "message": "Customer has been deleted successfully"
}

Delete Types

Soft Delete (Default):

  • Sets is_deleted=True and is_active=False
  • Customer data preserved in database
  • Excluded from normal queries
  • Can be restored by admin
  • Preserves appointment history

Hard Delete (permanent=true):

  • Permanently removes customer from database
  • Requires SUPER_ADMIN role
  • Cannot be undone
  • Use with extreme caution

Business Rules

  1. Data Preservation:

  2. Soft delete recommended for audit trail

  3. Hard delete only for GDPR/data removal requests

  4. Related Data:

  5. Appointment history preserved in both delete types

  6. Customer references in appointments remain valid

  7. Restoration:

  8. Soft deleted customers can be restored by setting is_deleted=False

  9. Restoration requires direct database access or admin endpoint

Error Responses

403 Forbidden - Insufficient Permissions:

{
  "detail": "Only SUPER_ADMIN can permanently delete customers"
}


Get Customer Appointments

Retrieve appointment history and statistics for a specific customer.

Endpoint

GET /api/v1/customers/{customer_id}/appointments

Authentication: Required (JWT token) Access: All staff members

Path Parameters

Parameter Type Required Description
customer_id string Yes Customer ID (MongoDB ObjectId)

Query Parameters

Parameter Type Required Description Example
page integer No Page number (default: 1) 1
size integer No Items per page (default: 20) 20
status string No Filter by appointment status "confirmed"
from_date date No Appointments from this date "2025-01-01"
to_date date No Appointments until this date "2025-12-31"

Appointment Statuses

  • pending - Awaiting confirmation
  • confirmed - Confirmed by customer/staff
  • checked_in - Customer arrived
  • in_progress - Service in progress
  • completed - Service completed
  • cancelled - Cancelled by customer/staff
  • no_show - Customer didn't show up

Response

{
  "appointments": [
    {
      "id": "507f1f77bcf86cd799439040",
      "tenant_id": "507f1f77bcf86cd799439010",
      "outlet_id": "507f1f77bcf86cd799439022",
      "customer_id": "507f1f77bcf86cd799439011",
      "staff_id": "507f1f77bcf86cd799439021",
      "service_id": "507f1f77bcf86cd799439020",
      "appointment_date": "2025-01-25",
      "start_time": "14:00:00",
      "end_time": "15:30:00",
      "status": "confirmed",
      "total_price": 350000,
      "notes": "Customer prefers window seat",
      "created_at": "2025-01-20T10:00:00Z",
      "updated_at": "2025-01-20T10:00:00Z"
    }
  ],
  "statistics": {
    "total_appointments": 28,
    "completed": 24,
    "cancelled": 2,
    "no_show": 1,
    "upcoming": 3
  },
  "pagination": {
    "total": 28,
    "page": 1,
    "size": 20,
    "pages": 2
  }
}

Statistics Calculation

Computed in real-time:

  • total_appointments - Total count matching filters
  • completed - Count of completed appointments
  • cancelled - Count of cancelled appointments
  • no_show - Count of no-show appointments
  • upcoming - Count of future appointments (date >= today)

Note: For better performance with large datasets, consider using the Customer Analytics endpoint instead.


Update Customer Preferences

Update customer preferences for bookings and communications.

Endpoint

PUT /api/v1/customers/{customer_id}/preferences

Authentication: Required (JWT token) Access: All staff members

Path Parameters

Parameter Type Required Description
customer_id string Yes Customer ID (MongoDB ObjectId)

Request Body

{
  "preferred_services": [
    "507f1f77bcf86cd799439020",
    "507f1f77bcf86cd799439021"
  ],
  "preferred_staff": [
    "507f1f77bcf86cd799439030"
  ],
  "preferred_outlets": [
    "507f1f77bcf86cd799439022"
  ],
  "communication_channels": {
    "sms": true,
    "email": true,
    "push": false
  },
  "marketing_consent": true,
  "language": "id",
  "accessibility_needs": "Wheelchair accessible entrance required"
}

Preference Fields

Field Type Description Default
preferred_services array List of preferred service IDs []
preferred_staff array List of preferred staff IDs []
preferred_outlets array List of preferred outlet IDs []
communication_channels object Channel preferences (sms, email, push) All true
marketing_consent boolean Consent to marketing communications false
language string Preferred language (id, en) "id"
accessibility_needs string Special accessibility requirements null

Response

Returns complete customer profile with updated preferences (same format as Get Customer Details).

How Preferences Are Used

Booking Recommendations:

  • Preferred services shown first in booking flow
  • Preferred staff highlighted in staff selection
  • Preferred outlets used for default location

Communication Delivery:

  • SMS notifications sent only if sms: true
  • Email notifications sent only if email: true
  • Push notifications sent only if push: true
  • Marketing messages require marketing_consent: true

Accessibility:

  • Staff can view accessibility needs before appointment
  • Outlet selection shows accessibility features
  • Special accommodations can be prepared

Business Rules

  1. Privacy Compliance:

  2. Marketing consent must be explicit opt-in

  3. Customers can withdraw consent anytime
  4. Transactional messages (appointment confirmations) sent regardless of marketing consent

  5. Preference Validation:

  6. Service IDs must exist and belong to tenant

  7. Staff IDs must exist and belong to tenant
  8. Outlet IDs must exist and belong to tenant

  9. Default Behavior:

  10. If no preferred staff → Show all available staff

  11. If no preferred outlets → Show all outlets
  12. If no preferred services → Show all services

Get Customer Analytics

Retrieve comprehensive customer analytics and statistics.

Endpoint

GET /api/v1/customers/statistics/summary

Authentication: Required (JWT token) Access: TENANT_ADMIN or SUPER_ADMIN

Query Parameters

Parameter Type Required Description Example
from_date date No Statistics from this date "2025-01-01"
to_date date No Statistics until this date "2025-12-31"

Response

{
  "statistics": {
    "total_customers": 145,
    "active_customers": 138,
    "inactive_customers": 7,
    "verified_emails": 92,
    "customers_with_password": 115,
    "walk_in_customers": 30,
    "new_customers_in_period": 12,
    "total_appointments": 3456,
    "total_revenue": 125000000,
    "total_loyalty_points": 45000,
    "avg_appointments_per_customer": 23.8,
    "avg_spent_per_customer": 862068,
    "retention_rate": 78.5,
    "top_customers_by_appointments": [
      {
        "customer_id": "507f1f77bcf86cd799439011",
        "full_name": "John Doe",
        "email": "john.doe@example.com",
        "total_appointments": 52,
        "total_spent": 4500000
      }
    ],
    "top_customers_by_revenue": [
      {
        "customer_id": "507f1f77bcf86cd799439012",
        "full_name": "Jane Smith",
        "email": "jane.smith@example.com",
        "total_appointments": 38,
        "total_spent": 6200000
      }
    ],
    "customer_segments": {
      "vip": 25,
      "regular": 90,
      "new": 12,
      "at_risk": 18
    }
  },
  "period": {
    "from": "2025-01-01",
    "to": "2025-12-31"
  },
  "generated_at": "2025-10-08T10:00:00Z"
}

Metric Definitions

Customer Counts:

  • total_customers - All customers (excluding soft-deleted)
  • active_customers - Customers with is_active=True
  • inactive_customers - Customers with is_active=False
  • verified_emails - Customers with email_verified=True
  • customers_with_password - Registered customers (portal access)
  • walk_in_customers - Staff-created without password
  • new_customers_in_period - Customers created within date range

Financial Metrics:

  • total_appointments - Sum of all appointments
  • total_revenue - Sum of completed appointment payments
  • total_loyalty_points - Sum of all customer loyalty points
  • avg_appointments_per_customer - Total appointments ÷ total customers
  • avg_spent_per_customer - Total revenue ÷ total customers

Retention Metrics:

  • retention_rate - % of customers with appointment in last 90 days

Customer Segments:

  • vip - Customers with "vip" tag
  • regular - Customers with "regular" tag
  • new - Customers created in last 30 days
  • at_risk - No appointment in last 60 days

Performance Notes

Large Dataset Handling:

  • Analytics calculations use MongoDB aggregation pipelines
  • May take several seconds for tenants with 10,000+ customers
  • Consider caching results for dashboard views
  • Use date range filters to improve performance

Customer Preferences Schema

Complete reference for the CustomerPreferences object.

Schema Definition

class CustomerPreferences(BaseModel):
    preferred_services: List[str] = []
    preferred_staff: List[str] = []
    preferred_outlets: List[str] = []
    communication_channels: CommunicationChannels = CommunicationChannels()
    marketing_consent: bool = False
    language: str = "id"
    accessibility_needs: Optional[str] = None

class CommunicationChannels(BaseModel):
    sms: bool = True
    email: bool = True
    push: bool = True

Field Descriptions

Field Type Default Description
preferred_services array [] List of service IDs customer prefers
preferred_staff array [] List of staff IDs customer prefers
preferred_outlets array [] List of outlet IDs customer prefers
communication_channels.sms boolean true Receive SMS notifications
communication_channels.email boolean true Receive email notifications
communication_channels.push boolean true Receive push notifications
marketing_consent boolean false Consent to marketing communications
language string "id" Preferred language (id or en)
accessibility_needs string null Special accessibility requirements

Best Practices

For Customer Creation

DO:

  • Always check for duplicates before creating (default behavior)
  • Provide password for customers who need portal access
  • Use meaningful tags for customer segmentation
  • Initialize preferences with sensible defaults
  • Validate email and phone formats client-side

DON'T:

  • Skip duplicate checks (prevents data quality issues)
  • Store plain-text passwords (always use password field)
  • Create customers without email OR phone (at least one required)
  • Assign customers to wrong tenant

For Customer Updates

DO:

  • Validate uniqueness when changing email/phone
  • Preserve important tags like vip, staff-created
  • Update preferences separately using preferences endpoint
  • Use soft delete for data preservation

DON'T:

  • Change tenant_id (violates tenant isolation)
  • Modify system-calculated fields (loyalty_points, total_spent)
  • Hard delete without GDPR/legal requirement
  • Update email/phone without duplicate check

DO:

  • Use pagination for large result sets
  • Combine multiple filters for precise results
  • Use tags for customer segmentation
  • Cache frequently-used search results

DON'T:

  • Fetch all customers without pagination (performance issue)
  • Use wildcard searches on large datasets
  • Query customers across multiple tenants

For Privacy Compliance

DO:

  • Respect marketing consent settings
  • Provide easy preference update mechanism
  • Honor communication channel preferences
  • Implement data export on request
  • Support soft delete for data retention

DON'T:

  • Send marketing messages without consent
  • Share customer data across tenants
  • Ignore opt-out requests
  • Keep data indefinitely without retention policy

Error Handling

Common Error Codes

Status Code Error Type Description Resolution
400 Validation Error Invalid input data Check request body format and field values
401 Not Authenticated Missing or invalid JWT token Provide valid authentication token
403 Forbidden Insufficient permissions Check user role and permissions
404 Not Found Customer not found Verify customer ID exists
409 Conflict Duplicate email or phone Use different email/phone or update existing

Example Error Responses

Validation Error:

{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    },
    {
      "loc": ["body", "phone"],
      "msg": "phone number must start with +",
      "type": "value_error"
    }
  ]
}

Authorization Error:

{
  "detail": "Only TENANT_ADMIN can permanently delete customers"
}

Tenant Isolation Error:

{
  "detail": "Customer belongs to different tenant"
}


Integration Examples

Create Walk-in Customer (No Portal Access)

curl -X POST https://api.example.com/api/v1/customers \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+628123456789",
    "first_name": "Walk-in",
    "last_name": "Customer",
    "tags": ["walk-in"]
  }'

Create Registered Customer (With Portal Access)

curl -X POST https://api.example.com/api/v1/customers \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com",
    "phone": "+628123456789",
    "first_name": "Registered",
    "last_name": "Customer",
    "password": "SecurePass123!",
    "preferences": {
      "communication_channels": {
        "sms": true,
        "email": true,
        "push": true
      },
      "marketing_consent": true
    }
  }'

Search VIP Customers

curl -X GET "https://api.example.com/api/v1/customers?tags=vip&page=1&size=50" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Update Customer Preferences

curl -X PUT https://api.example.com/api/v1/customers/507f1f77bcf86cd799439011/preferences \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "preferred_services": ["507f1f77bcf86cd799439020"],
    "communication_channels": {
      "sms": true,
      "email": true,
      "push": false
    },
    "marketing_consent": false
  }'

Get Customer Analytics

curl -X GET "https://api.example.com/api/v1/customers/statistics/summary?from_date=2025-01-01&to_date=2025-12-31" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

API Reference Summary

Endpoint Method Purpose Access Level
/customers GET List all customers with search/filters All staff
/customers POST Create new customer All staff
/customers/{customer_id} GET Get customer details All staff
/customers/{customer_id} PUT Update customer TENANT_ADMIN+
/customers/{customer_id} DELETE Delete customer (soft/hard) TENANT_ADMIN+
/customers/{customer_id}/appointments GET Get appointment history All staff
/customers/{customer_id}/preferences PUT Update preferences All staff
/customers/statistics/summary GET Get analytics TENANT_ADMIN+


Next Steps:

  1. Review customer schema and validation rules
  2. Test customer creation with duplicate detection
  3. Implement customer search in your application
  4. Set up preference management UI
  5. Configure analytics dashboard with customer metrics

For webhook integration and real-time updates, refer to the Webhook Architecture section.