Skip to content

Public API

Public endpoints for accessing business information without authentication. These endpoints enable customers to discover businesses, browse outlets, and register new businesses on the platform.


Overview

The public API provides unauthenticated access to:

  • Business Discovery - Find and view business information by slug
  • Outlet Browsing - Search and filter business locations
  • Service Catalog - Browse available services with pricing
  • Package Browsing - View available packages with discounts and pricing
  • Self-Service Registration - Create new business accounts instantly

Key Concepts:

  • No Authentication Required - All endpoints are publicly accessible
  • Rate Limited - 30% of authenticated limits to prevent abuse
  • Read-Only Access - Except for registration endpoint
  • Cached Responses - 5-minute cache TTL for performance
  • Active Records Only - Only active businesses and outlets are returned

Get Business Information

Retrieve public business information by slug identifier.

Endpoint

GET /api/v1/public/tenants/{slug}

Authentication: None required (rate limited)

Path Parameters

  • slug (required) - Business slug identifier (e.g., beauty-salon-downtown)

Response

{
  "name": "Bella Vista Spa & Salon",
  "slug": "bella-vista-spa",
  "description": "Luxury spa and salon offering premium beauty services",
  "website": "https://bellavista.com",
  "business_type": "spa",
  "outlets_count": 3,
  "support_email": "contact@bellavista.com",
  "support_phone": "+1-555-123-4567"
}

Response Fields:

  • name - Business name
  • slug - URL-friendly identifier
  • description - Business description
  • website - Business website URL
  • business_type - Type of business (e.g., spa, salon, barbershop)
  • outlets_count - Total number of active outlets
  • support_email - Contact email
  • support_phone - Contact phone number

Business Rules

  • Only active tenants are returned
  • Sensitive data (subscription details, settings) excluded
  • Response cached for 5 minutes
  • Returns 404 if business not found or inactive

Example Request

curl -X GET "https://api.myreserva.id/api/v1/public/tenants/bella-vista-spa"

Error Responses

404 Not Found:

{
  "detail": "Business 'bella-vista-spa' not found"
}

429 Rate Limit Exceeded:

{
  "detail": "Rate limit exceeded. Please try again later."
}


List Public Outlets

Browse and search business outlet locations with filtering options.

Endpoint

GET /api/v1/public/outlets

Authentication: None required (rate limited)

Query Parameters

  • tenant_slug (optional) - Filter by business slug (e.g., beauty-salon-downtown)
  • city (optional) - Filter by city (case-insensitive, e.g., New York)
  • is_active (optional) - Filter active outlets only (default: true)
  • page (optional) - Page number, 1-based (default: 1, min: 1)
  • size (optional) - Items per page (default: 10, min: 1, max: 50)

Response

{
  "items": [
    {
      "name": "Bella Vista - Downtown",
      "slug": "bella-vista-downtown",
      "address": {
        "street": "123 Main Street",
        "city": "New York",
        "state": "NY",
        "postal_code": "10001",
        "country": "USA"
      },
      "phone": "+1-555-987-6543",
      "email": "downtown@bellavista.com",
      "business_hours": [
        {
          "day": 0,
          "is_open": true,
          "open_time": "09:00",
          "close_time": "18:00",
          "break_start": null,
          "break_end": null
        },
        {
          "day": 1,
          "is_open": true,
          "open_time": "09:00",
          "close_time": "18:00",
          "break_start": null,
          "break_end": null
        }
      ],
      "status": "active",
      "services_count": 15,
      "accepts_online_booking": true,
      "images": [
        "https://cdn.myreserva.id/outlets/bella-vista-downtown-1.jpg"
      ],
      "description": "Our flagship downtown location featuring premium spa services",
      "amenities": [
        "WiFi",
        "Parking Available",
        "Wheelchair Accessible",
        "Air Conditioning"
      ]
    }
  ],
  "total": 3,
  "page": 1,
  "size": 10,
  "pages": 1
}

Business Hours Day Mapping:

  • 0 = Monday
  • 1 = Tuesday
  • 2 = Wednesday
  • 3 = Thursday
  • 4 = Friday
  • 5 = Saturday
  • 6 = Sunday

Business Rules

  • Only active outlets are shown (unless is_active=false specified)
  • City filter is case-insensitive
  • Results include business hours for each outlet
  • Maximum 50 items per page
  • Empty results if tenant not found

Example Requests

List all active outlets:

curl -X GET "https://api.myreserva.id/api/v1/public/outlets?page=1&size=10"

Filter by business:

curl -X GET "https://api.myreserva.id/api/v1/public/outlets?tenant_slug=bella-vista-spa&page=1&size=10"

Filter by city:

curl -X GET "https://api.myreserva.id/api/v1/public/outlets?city=New+York&page=1&size=10"

Combine filters:

curl -X GET "https://api.myreserva.id/api/v1/public/outlets?tenant_slug=bella-vista-spa&city=New+York&page=1&size=10"

Error Responses

400 Bad Request:

{
  "detail": "Invalid page or size parameter"
}

429 Rate Limit Exceeded:

{
  "detail": "Rate limit exceeded. Please try again later."
}


Get Outlet Details

Get detailed public information for a specific outlet by slug.

Endpoint

GET /api/v1/public/outlets/{outlet_slug}

Authentication: None required (rate limited)

Path Parameters

  • outlet_slug (required) - Outlet slug identifier (e.g., downtown-spa)

Response

{
  "name": "Bella Vista - Downtown",
  "slug": "bella-vista-downtown",
  "address": {
    "street": "123 Main Street",
    "city": "New York",
    "state": "NY",
    "postal_code": "10001",
    "country": "USA"
  },
  "phone": "+1-555-987-6543",
  "email": "downtown@bellavista.com",
  "business_hours": [
    {
      "day": 0,
      "is_open": true,
      "open_time": "09:00",
      "close_time": "18:00",
      "break_start": null,
      "break_end": null
    },
    {
      "day": 1,
      "is_open": true,
      "open_time": "09:00",
      "close_time": "18:00",
      "break_start": null,
      "break_end": null
    }
  ],
  "status": "active",
  "services_count": 15,
  "accepts_online_booking": true,
  "images": [
    "https://cdn.myreserva.id/outlets/bella-vista-downtown-1.jpg",
    "https://cdn.myreserva.id/outlets/bella-vista-downtown-2.jpg"
  ],
  "description": "Our flagship downtown location featuring premium spa services",
  "amenities": [
    "WiFi",
    "Parking Available",
    "Wheelchair Accessible",
    "Air Conditioning"
  ]
}

Response Fields:

  • name - Outlet name
  • slug - URL-friendly identifier
  • address - Complete address details
  • phone - Contact phone number
  • email - Contact email
  • business_hours - Operating hours for each day (0=Monday, 6=Sunday)
  • status - Outlet status (e.g., active)
  • services_count - Number of services available
  • accepts_online_booking - Whether online booking is enabled
  • images - Array of image URLs
  • description - Outlet description
  • amenities - List of available amenities

Business Rules

  • Only active outlets are accessible
  • Returns amenities and parking information
  • Images and descriptions included
  • Uses slug for SEO-friendly URLs

Example Request

curl -X GET "https://api.myreserva.id/api/v1/public/outlets/bella-vista-downtown"

Error Responses

404 Not Found:

{
  "detail": "Outlet not found"
}

429 Rate Limit Exceeded:

{
  "detail": "Rate limit exceeded. Please try again later."
}


Browse Service Catalog

Browse public service catalog with comprehensive filtering and enhanced pricing.

Endpoint

GET /api/v1/public/services

Authentication: None required (rate limited)

Query Parameters

  • tenant_slug (optional) - Filter by business identifier (e.g., beauty-salon-downtown)
  • outlet_id (optional) - Filter by outlet ID for outlet-specific pricing (e.g., 507f1f77bcf86cd799439011)
  • category (optional) - Filter by service category (e.g., facial, nails, massage)
  • min_price (optional) - Minimum display_price filter after discounts (e.g., 25.00, min: 0)
  • max_price (optional) - Maximum display_price filter after discounts (e.g., 150.00, min: 0)
  • page (optional) - Page number, 1-based (default: 1, min: 1)
  • size (optional) - Items per page (default: 20, min: 1, max: 100)

Response

{
  "items": [
    {
      "name": "Premium Facial Treatment",
      "description": "Deep cleansing facial with hydration",
      "category": "facial",
      "duration_minutes": 60,
      "pricing": {
        "base_price": "350000.00",
        "display_price": "280000.00",
        "strikethrough_price": "350000.00",
        "discount_type": "promotional",
        "promotional_valid_until": "2025-12-31T23:59:59Z",
        "price_varies_by_outlet": true
      },
      "currency": "IDR",
      "image_url": "https://cdn.myreserva.id/services/premium-facial.jpg",
      "popular": true,
      "available": true,
      "tags": ["facial", "hydration", "skincare"]
    },
    {
      "name": "Classic Manicure",
      "description": "Traditional nail care and polish",
      "category": "nails",
      "duration_minutes": 45,
      "pricing": {
        "base_price": "150000.00",
        "display_price": "150000.00",
        "strikethrough_price": null,
        "discount_type": null,
        "promotional_valid_until": null,
        "price_varies_by_outlet": false
      },
      "currency": "IDR",
      "image_url": "https://cdn.myreserva.id/services/manicure.jpg",
      "popular": false,
      "available": true,
      "tags": ["nails", "manicure"]
    }
  ],
  "total": 45,
  "page": 1,
  "size": 20,
  "pages": 3
}

Pricing Object Fields:

Field Type Description
base_price Decimal Always the service's base price (for reference)
display_price Decimal The actual price to charge (calculated using pricing hierarchy)
strikethrough_price Decimal or null The "was" price - only set when display_price < regular price
discount_type string or null Type of discount applied: "promotional" or null
promotional_valid_until datetime or null Promo expiry date for urgency UI (e.g., "Ends in 3 days!")
price_varies_by_outlet boolean Hint that price may change after outlet selection

Pricing Hierarchy:

Prices are calculated using the following priority (highest to lowest):

  1. Promotional Price - Global promotional pricing (applies to all outlets)
  2. Outlet-Specific Price - Location-specific pricing when outlet_id is provided
  3. Base Price - Default fallback price

Frontend Display Examples:

┌─────────────────────────────────────────────┐
│  Premium Facial Treatment                   │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│  IDR 350,000  →  IDR 280,000               │  ← strikethrough_price + display_price
│  🏷️ PROMO - Ends Dec 31!                   │  ← discount_type + promotional_valid_until
│  ⚠️ Price may vary by outlet               │  ← price_varies_by_outlet
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│  Classic Manicure                           │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│  IDR 150,000                               │  ← display_price only (no strikethrough)
└─────────────────────────────────────────────┘

Service Categories (Common):

  • hair - Haircuts, styling, coloring
  • nails - Manicure, pedicure, nail art
  • spa - Massage, facials, body treatments
  • facial - Facial treatments and skincare
  • makeup - Makeup application services
  • waxing - Hair removal services
  • therapy - Wellness treatments, therapy sessions

Business Rules

  • Only active services are shown
  • Services must have ACTIVE status
  • Price filtering based on display_price (after promotional discounts)
  • Results sorted by popularity and name
  • Maximum 100 items per page
  • When outlet_id is provided, pricing considers outlet-specific prices

Example Requests

List all services:

curl -X GET "https://api.myreserva.id/api/v1/public/services?page=1&size=20"

Filter by business:

curl -X GET "https://api.myreserva.id/api/v1/public/services?tenant_slug=bella-vista-spa&page=1&size=20"

Filter by category:

curl -X GET "https://api.myreserva.id/api/v1/public/services?category=facial&page=1&size=20"

Filter by price range (filters by display_price after discounts):

curl -X GET "https://api.myreserva.id/api/v1/public/services?min_price=50000&max_price=200000&page=1&size=20"

Get outlet-specific pricing:

curl -X GET "https://api.myreserva.id/api/v1/public/services?outlet_id=507f1f77bcf86cd799439011&page=1&size=20"

Combine filters:

curl -X GET "https://api.myreserva.id/api/v1/public/services?tenant_slug=bella-vista-spa&category=facial&min_price=50000&max_price=200000&page=1&size=20"

Error Responses

400 Bad Request:

{
  "detail": "Invalid parameters"
}

429 Rate Limit Exceeded:

{
  "detail": "Rate limit exceeded. Please try again later."
}


Browse Available Packages

Browse all active packages available for purchase at a business with pricing and discount information.

Endpoint

GET /api/v1/public/packages

Authentication: None required (rate limited)

Query Parameters

  • tenant_slug (required) - Business slug identifier (e.g., beauty-salon-downtown)
  • outlet_id (optional) - Filter packages available at specific outlet (e.g., 507f1f77bcf86cd799439012)
  • page (optional) - Page number, 1-based (default: 1, min: 1)
  • size (optional) - Items per page (default: 10, min: 1, max: 50)

Response

{
  "items": [
    {
      "name": "Luxury Spa Package",
      "description": "Premium spa treatments bundle",
      "package_items": [
        {
          "service_name": "Full Body Massage",
          "quantity": 3,
          "unit_price": "150000.0"
        },
        {
          "service_name": "Facial Treatment",
          "quantity": 2,
          "unit_price": "150000.0"
        }
      ],
      "package_price": "500000.0",
      "currency": "IDR",
      "total_individual_price": "750000.0",
      "discount_percentage": 33.33,
      "validity_days": 90
    }
  ],
  "total": 5,
  "page": 1,
  "size": 10,
  "pages": 1
}

Response Fields:

Field Type Description
name string Package display name
description string or null Package description and terms
package_items array Services included in the package
package_items[].service_name string Service display name
package_items[].quantity integer Number of sessions/credits for this service
package_items[].unit_price Decimal or null Individual service price (for value comparison)
package_price Decimal Total package price (final price to pay)
currency string Currency code (e.g., IDR, USD)
total_individual_price Decimal or null Sum of individual service prices (strikethrough "was" price)
discount_percentage float or null Discount percentage (e.g., 33.33 for "Save 33%!")
validity_days integer or null Days until credits expire after purchase (null = never expires)

Intentionally Hidden (requires authentication):

  • id - Package internal identifier
  • service_id - Service internal identifiers within package_items

Frontend Display Example:

┌─────────────────────────────────────────────┐
│  🏆 Luxury Spa Package                      │
│  Premium spa treatments bundle              │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│  ~~IDR 750,000~~  →  IDR 500,000           │  ← strikethrough + package_price
│                      Save 33%!              │  ← discount_percentage
│                                             │
│  What's included:                           │
│  • 3x Full Body Massage (@ 150,000)         │  ← package_items
│  • 2x Facial Treatment (@ 150,000)          │
│                                             │
│  Valid for 90 days                          │  ← validity_days
│  [Login to Purchase]                        │
└─────────────────────────────────────────────┘

Business Rules

  • Only active packages are shown (is_active=true, status=active)
  • Packages with empty outlet_ids are available at all outlets
  • Discount percentage calculated from individual service prices
  • tenant_slug is required to filter by business
  • Internal IDs (package_id, service_id) are hidden from public response
  • Results filtered by tenant automatically

Example Requests

List all packages for a business:

curl -X GET "https://api.myreserva.id/api/v1/public/packages?tenant_slug=bella-vista-spa&page=1&size=10"

Filter by outlet:

curl -X GET "https://api.myreserva.id/api/v1/public/packages?tenant_slug=bella-vista-spa&outlet_id=507f1f77bcf86cd799439012&page=1&size=10"

Error Responses

400 Bad Request:

{
  "detail": "Invalid outlet_id: invalid-id"
}

404 Not Found:

{
  "detail": "Business 'unknown-business' not found"
}

429 Rate Limit Exceeded:

{
  "detail": "Rate limit exceeded. Please try again later."
}

Use Cases

  1. Public Website Package Browsing - Display available packages on marketing pages
  2. Embedded Booking Widgets - Show packages in third-party booking integrations
  3. Package Discovery - Allow customers to explore package options before signing up
  4. Price Comparison - Display strikethrough pricing to show package value/savings

Next Steps After Browsing

To purchase a package, customers must:

  1. Register/Login - Authenticate via customer portal
  2. Purchase Package - Use POST /api/v1/customer/packages/purchase
  3. Complete Payment - Via Paper.id, bank transfer, or pay on visit

See Customer Package Management for purchase flow details.


Get Business Hours

Get complete business hours schedule for an outlet by slug.

Endpoint

GET /api/v1/public/business-hours/{outlet_slug}

Authentication: None required (rate limited)

Path Parameters

  • outlet_slug (required) - Outlet slug identifier (e.g., downtown-spa)

Response

{
  "outlet_slug": "bella-vista-downtown",
  "outlet_name": "Bella Vista - Downtown",
  "timezone": "America/New_York",
  "regular_hours": [
    {
      "day": "Monday",
      "day_number": 0,
      "is_open": true,
      "open_time": "09:00",
      "close_time": "18:00",
      "break_start": null,
      "break_end": null
    },
    {
      "day": "Tuesday",
      "day_number": 1,
      "is_open": true,
      "open_time": "09:00",
      "close_time": "18:00",
      "break_start": null,
      "break_end": null
    },
    {
      "day": "Wednesday",
      "day_number": 2,
      "is_open": true,
      "open_time": "09:00",
      "close_time": "18:00",
      "break_start": null,
      "break_end": null
    },
    {
      "day": "Thursday",
      "day_number": 3,
      "is_open": true,
      "open_time": "09:00",
      "close_time": "18:00",
      "break_start": "13:00",
      "break_end": "14:00"
    },
    {
      "day": "Friday",
      "day_number": 4,
      "is_open": true,
      "open_time": "09:00",
      "close_time": "18:00",
      "break_start": null,
      "break_end": null
    },
    {
      "day": "Saturday",
      "day_number": 5,
      "is_open": true,
      "open_time": "10:00",
      "close_time": "16:00",
      "break_start": null,
      "break_end": null
    },
    {
      "day": "Sunday",
      "day_number": 6,
      "is_open": false,
      "open_time": null,
      "close_time": null,
      "break_start": null,
      "break_end": null
    }
  ],
  "special_dates": [
    {
      "date": "2025-12-25",
      "is_open": false,
      "reason": "Christmas Day"
    },
    {
      "date": "2025-01-01",
      "is_open": false,
      "reason": "New Year's Day"
    }
  ],
  "accepts_online_booking": true
}

Response Fields:

  • outlet_slug - Outlet slug identifier
  • outlet_name - Outlet display name
  • timezone - Timezone identifier (e.g., America/New_York, UTC)
  • regular_hours - Array of business hours for each day of week
  • day - Day name (e.g., Monday, Tuesday)
  • day_number - Day number (0=Monday, 6=Sunday)
  • is_open - Whether outlet is open on this day
  • open_time - Opening time in HH:MM format (null if closed)
  • close_time - Closing time in HH:MM format (null if closed)
  • break_start - Break start time in HH:MM format (null if no break)
  • break_end - Break end time in HH:MM format (null if no break)
  • special_dates - Array of special dates (holidays, closures)
  • date - Date in YYYY-MM-DD format
  • is_open - Whether outlet is open on this date
  • reason - Reason for special hours (e.g., Christmas Day)
  • accepts_online_booking - Whether online booking is enabled

Business Rules

  • Only active outlets return business hours
  • Day numbering: 0=Monday, 6=Sunday
  • Times returned in HH:MM format
  • Closed days show is_open=false
  • Uses slug for SEO-friendly URLs
  • Break times are optional and only included when configured

Example Request

curl -X GET "https://api.myreserva.id/api/v1/public/business-hours/bella-vista-downtown"

Error Responses

404 Not Found:

{
  "detail": "Outlet not found or inactive"
}

429 Rate Limit Exceeded:

{
  "detail": "Rate limit exceeded. Please try again later."
}


Register New Business (Self-Service)

Self-service tenant registration for new businesses. Creates a fully-functional account with FREE subscription plan.

Endpoint

POST /api/v1/public/register

Authentication: None required (rate limited)

Request Body

{
  "business_name": "Bella Vista Spa",
  "business_email": "contact@bellavista.com",
  "business_phone": "+1-555-987-6543",
  "business_type": "spa",
  "description": "Luxury spa and wellness center",
  "website": "https://bellavista.com",
  "admin_first_name": "Maria",
  "admin_last_name": "Rodriguez",
  "admin_email": "maria@bellavista.com",
  "admin_password": "SecurePass123!",
  "preferred_slug": "bella-vista-spa",
  "terms_accepted": true,
  "privacy_accepted": true
}

Required Fields:

  • business_name - Business name (must be unique, case-insensitive)
  • business_email - Business contact email (must be unique)
  • business_phone - Business phone number (must be unique)
  • business_type - Type of business (e.g., spa, salon, barbershop, clinic)
  • admin_first_name - Administrator first name
  • admin_last_name - Administrator last name
  • admin_email - Administrator email (must be unique, different from business_email)
  • admin_password - Administrator password (min 8 chars, must include uppercase, lowercase, number)
  • terms_accepted - Must be true
  • privacy_accepted - Must be true

Optional Fields:

  • description - Business description
  • website - Business website URL (auto-prepends https:// if missing)
  • preferred_slug - Desired URL slug (auto-generated from business_name if omitted)

Response

{
  "registration_id": "67890fedcba09876543210ab",
  "tenant_id": "12345abcdef67890abcdef12",
  "slug": "bella-vista-spa",
  "client_partner_id": "CP-20250116-ABC123",
  "client_partner_number": "BVS-001",
  "status": "active",
  "message": "Registration successful! Your account is now active and ready to use.",
  "next_steps": [
    "1. Log in with your admin credentials",
    "2. Complete your business profile and upload a logo",
    "3. Add your first outlet location with address and business hours",
    "4. Configure your services, pricing, and duration",
    "5. Add staff members and assign them to services",
    "6. Start accepting customer bookings!"
  ]
}

Response Fields:

  • registration_id - Registration tracking ID
  • tenant_id - New tenant identifier
  • slug - Generated or preferred slug
  • client_partner_id - Paper.id payment integration ID (may be null if integration fails non-blocking)
  • client_partner_number - Business partner number
  • status - Account status (active - ready to use)
  • message - Success message
  • next_steps - Onboarding checklist

Registration Flow

The registration process follows these steps:

  1. Duplicate Detection - Check for existing business name, email, phone, and admin email
  2. Slug Generation - Auto-generate slug from business name or use preferred_slug
  3. Slug Uniqueness - Auto-increment slug if duplicate exists (e.g., salonsalon-1)
  4. Tenant Creation - Create tenant with is_active=True
  5. FREE Subscription - Create subscription with FREE plan and ACTIVE status
  6. Admin User - Create admin user with TENANT_ADMIN role
  7. Paper.id Integration - Create payment partner (non-blocking, optional)
  8. Response - Return registration details for immediate login

Subscription Plan Assignment

Automatic FREE Plan Assignment:

All new registrations receive the FREE plan with the following limits:

Resource FREE Plan Limit
Max Outlets 1
Max Staff per Outlet 5
Max Appointments/Month 100
Max Services 20
Price IDR 0 (Free)

Upgrading After Registration:

To upgrade to PRO or ENTERPRISE plans, see Subscription Management.

Business Rules

Duplicate Detection (409 Conflict)

The following fields must be unique:

  • business_name (case-insensitive)
  • business_email
  • business_phone
  • admin_email (must differ from business_email)

Password Requirements

  • Minimum 8 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one number
  • Special characters allowed but not required

Slug Generation

  • Auto-generated from business_name if preferred_slug not provided
  • Converted to lowercase, spaces replaced with hyphens
  • Special characters removed
  • Duplicate slugs auto-incremented (e.g., salon, salon-1, salon-2)

Website URL Formatting

  • Auto-prepends https:// if protocol missing
  • Validates URL format
  • Stores normalized URL

Immediate Activation

  • Account activated immediately (is_active=True)
  • No email verification required (deferred to GTM phase)
  • No admin approval required
  • Can login immediately after registration

Example Request

curl -X POST "https://api.myreserva.id/api/v1/public/register" \
  -H "Content-Type: application/json" \
  -d '{
    "business_name": "Bella Vista Spa",
    "business_email": "contact@bellavista.com",
    "business_phone": "+1-555-987-6543",
    "business_type": "spa",
    "description": "Luxury spa and wellness center",
    "website": "https://bellavista.com",
    "admin_first_name": "Maria",
    "admin_last_name": "Rodriguez",
    "admin_email": "maria@bellavista.com",
    "admin_password": "SecurePass123!",
    "preferred_slug": "bella-vista-spa",
    "terms_accepted": true,
    "privacy_accepted": true
  }'

Error Responses

400 Bad Request - Validation Error:

{
  "detail": "Password must be at least 8 characters and contain uppercase, lowercase, and number"
}

409 Conflict - Duplicate Business Name:

{
  "detail": "Business name 'Bella Vista Spa' already exists"
}

409 Conflict - Duplicate Email:

{
  "detail": "Business email 'contact@bellavista.com' already registered"
}

409 Conflict - Duplicate Phone:

{
  "detail": "Phone number '+1-555-987-6543' already registered"
}

409 Conflict - Admin Email Already Used:

{
  "detail": "Admin email 'maria@bellavista.com' already registered as a user"
}

422 Unprocessable Entity - Terms Not Accepted:

{
  "detail": "You must accept the terms of service and privacy policy"
}

429 Rate Limit Exceeded:

{
  "detail": "Rate limit exceeded. Please try again later."
}

500 Internal Server Error:

{
  "detail": "Registration failed: [error details]"
}

Post-Registration Steps

After successful registration, the admin should:

  1. Login - Use admin credentials at /api/v1/auth/login
  2. Complete Profile - Add logo, update business description
  3. Create First Outlet - Add address, business hours, contact info
  4. Add Services - Configure service catalog with pricing and duration
  5. Add Staff - Create staff profiles and assign to outlets
  6. Configure Availability - Set staff working hours and availability
  7. Test Booking - Create a test appointment to verify setup
  8. Go Live - Share booking link with customers

Next Steps Reference

For detailed guides on post-registration setup:


Rate Limiting

All public endpoints are rate limited to prevent abuse.

Rate Limit Configuration

Endpoint Type Rate Limit
Public Endpoints 30% of authenticated limits
Registration Endpoint Stricter limits (10 requests/hour per IP)
Business Information Cached (5-minute TTL)

Rate Limit Headers

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642345678

Rate Limit Exceeded Response

{
  "detail": "Rate limit exceeded. Please try again later."
}

Status Code: 429 Too Many Requests


Caching Strategy

Cached Endpoints

  • GET /api/v1/public/tenants/{slug} - 5-minute cache TTL

Cache Headers

Cache-Control: public, max-age=300
ETag: "abc123def456"

Cache Invalidation

  • Cache automatically expires after 5 minutes
  • Manual invalidation on tenant update (staff portal)
  • ETag-based conditional requests supported

Best Practices

For Business Discovery

DO:

  • Use slugs for SEO-friendly URLs
  • Cache business information client-side
  • Handle 404 gracefully (business not found or inactive)
  • Display outlet count for multi-location businesses

DON'T:

  • Expose tenant IDs in public URLs (use slugs instead)
  • Make excessive requests (respect rate limits)
  • Cache for longer than 5 minutes (data may become stale)

For Outlet Browsing

DO:

  • Use city filter for location-based search
  • Implement pagination for better UX
  • Display business hours prominently
  • Show amenities and images
  • Filter by is_active=true (default behavior)

DON'T:

  • Request all outlets at once (use pagination)
  • Ignore accepts_online_booking flag
  • Display inactive outlets to customers

For Business Registration

DO:

  • Validate password strength client-side before submission
  • Check for duplicate business names before submitting
  • Display clear password requirements
  • Show terms of service and privacy policy links
  • Guide users through next steps after registration
  • Use preferred_slug for SEO-friendly URLs

DON'T:

  • Submit weak passwords (will be rejected)
  • Skip terms acceptance validation
  • Use admin_email same as business_email
  • Cache registration responses
  • Auto-fill passwords in forms

Security Considerations

Public Access

  • No authentication required
  • Rate limiting enforced
  • IP-based rate limiting for registration
  • No sensitive data exposed (subscription details, settings excluded)

Registration Security

  • Password hashed with bcrypt (12 rounds)
  • Email format validated
  • Phone format validated
  • Duplicate detection prevents data pollution
  • Terms acceptance required for legal compliance

Data Privacy

  • Only active businesses/outlets returned
  • Sensitive fields excluded from public responses:
  • Subscription details
  • Payment information
  • Internal settings
  • Staff salaries
  • Customer PII

API Reference Summary

Endpoint Method Purpose Authentication Rate Limited
/public/tenants/{slug} GET Get business info by slug None Yes (cached)
/public/outlets GET List and search outlets None Yes
/public/outlets/{outlet_slug} GET Get outlet details by slug None Yes
/public/services GET Browse service catalog None Yes
/public/packages GET Browse available packages None Yes
/public/business-hours/{outlet_slug} GET Get outlet business hours None Yes
/public/register POST Register new business None Yes (strict)


Troubleshooting

Business Not Found (404)

Symptoms: GET /public/tenants/{slug} returns 404

Possible Causes:

  1. Incorrect slug (typo in URL)
  2. Business is inactive (is_active=false)
  3. Business deleted

Solution:

  • Verify slug spelling
  • Check business status in admin panel
  • Contact support if business should be active

Registration Failed (409 Conflict)

Symptoms: Registration returns 409 with duplicate error

Possible Causes:

  1. Business name already exists (case-insensitive match)
  2. Business email already registered
  3. Business phone already registered
  4. Admin email already in use

Solution:

  • Use different business name
  • Use different contact email
  • Use different phone number
  • Use different admin email

Registration Failed (400 Bad Request)

Symptoms: Registration returns 400 with validation error

Possible Causes:

  1. Weak password (doesn't meet requirements)
  2. Invalid email format
  3. Invalid phone format
  4. Missing required fields
  5. Terms not accepted

Solution:

  • Ensure password has 8+ characters with uppercase, lowercase, and number
  • Validate email format client-side
  • Use international phone format (+1-555-123-4567)
  • Include all required fields
  • Set terms_accepted and privacy_accepted to true

Rate Limit Exceeded (429)

Symptoms: Requests return 429 Too Many Requests

Possible Causes:

  1. Too many requests in short period
  2. Shared IP with high traffic
  3. Automated scraping detected

Solution:

  • Wait for rate limit reset (check X-RateLimit-Reset header)
  • Implement client-side caching
  • Use pagination instead of bulk requests
  • Contact support if legitimate use case requires higher limits

Next Steps:

  1. Browse public business information: GET /public/tenants/{slug}
  2. Search outlets by location: GET /public/outlets?city=New+York
  3. Register new business: POST /public/register
  4. Login with admin credentials: Staff Authentication
  5. Complete business setup: Tenant Management

For customer-facing booking endpoints, see Customer Booking & Appointments.