Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Analytics Dashboard API Reference

Complete API documentation for the analytics dashboard endpoints.


Endpoints

Get Chart Data

Retrieve analytics chart data for revenue, bookings, or capacity metrics.

URL: GET /api/partner/v1/analytics/chart_data

Authentication: Required (Pundit authorization)

Query Parameters:

ParameterTypeRequiredDescriptionExample
chart_typeStringYesType of chart to retrieverevenue, bookings, capacity
date_filterStringYesDate range filtertoday, last_7_day, last_30_day, this_month, last_month, last_3_month

Request Examples

Revenue Chart - Last 7 Days

GET /api/partner/v1/analytics/chart_data?chart_type=revenue&date_filter=last_7_day
Authorization: Bearer <token>

Bookings Chart - This Month

GET /api/partner/v1/analytics/chart_data?chart_type=bookings&date_filter=this_month
Authorization: Bearer <token>

Capacity Chart - Today

GET /api/partner/v1/analytics/chart_data?chart_type=capacity&date_filter=today
Authorization: Bearer <token>

Response Format

Success Response (HTTP 200)

All chart endpoints return data wrapped in a success response:

{
  "success": true,
  "message": "Success",
  "data": {
    "labels": ["01", "02", "03", "04", "05", "06", "07"],
    "current_period": {
      "label": "Last 7 days",
      "data": [5000, 5200, 4800, 6100, 5900, 6200, 5800]
    },
    "previous_period": {
      "label": "Previous 7 days",
      "data": [4800, 5000, 5100, 5300, 5400, 5200, 5100]
    },
    "raw_data": [
      {
        "date": "01",
        "current_period": {
          "label": "Last 7 days",
          "revenue": 5000
        },
        "previous_period": {
          "label": "Previous 7 days",
          "revenue": 4800
        }
      },
      ...
    ],
    "metadata": {
      "start_date": "2025-12-31",
      "end_date": "2026-01-07",
      "date_filter": "last_7_day",
      "restaurant_ids": [1597],
      "current_period_label": "Last 7 days",
      "previous_period_label": "Previous 7 days"
    }
  }
}

Revenue Chart Response

Revenue data includes financial metrics with comparison periods.

Response Structure:

{
  "success": true,
  "message": "Success",
  "data": {
    "labels": ["01", "02", "03", ...],
    "current_period": {
      "label": "Last 7 days",
      "data": [5000.00, 5200.00, 4800.00, ...]
    },
    "previous_period": {
      "label": "Previous 7 days",
      "data": [4800.00, 5000.00, 5100.00, ...]
    },
    "raw_data": [...],
    "metadata": {...}
  }
}

Data Type: Number (Float - currency amounts)

Value Range: 0 - unlimited (THB currency)

Bookings Chart Response

Bookings data includes two metrics: bookings count and confirmed covers (current period only).

Response Structure:

{
  "success": true,
  "message": "Success",
  "data": {
    "labels": ["01", "02", "03", ...],
    "data": {
      "bookings": [45, 52, 48, ...],
      "covers": [250, 285, 220, ...]
    },
    "raw_data": [
      {
        "date": "01",
        "bookings": 45,
        "covers": 250
      },
      ...
    ],
    "metadata": {
      "metrics": ["bookings", "covers"],
      "start_date": "2026-01-01",
      "end_date": "2026-01-07",
      "date_filter": "last_7_day",
      "restaurant_ids": [1597]
    }
  }
}

Data Type: Integer (count of bookings and covers)

Value Range: 0 - unlimited

Note: Bookings data represents current period only (no historical comparison)

Capacity Chart Response

Capacity data includes utilization percentages (current period only).

Response Structure:

{
  "success": true,
  "message": "Success",
  "data": {
    "labels": ["01", "02", "03", ...],
    "data": [75.5, 82.3, 68.9, ...],
    "raw_data": [
      {
        "date": "01",
        "utilization": 75.5
      },
      ...
    ],
    "metadata": {
      "metric": "capacity_utilization_percentage",
      "start_date": "2026-01-01",
      "end_date": "2026-01-07",
      "date_filter": "last_7_day",
      "restaurant_ids": [1597]
    }
  }
}

Data Type: Float (percentage values)

Value Range: 0 - 100 (percentage)

Note: Capacity data represents current period only (no historical comparison). Single metric, simple array format.

Data Type: Number (Float - percentage)

Value Range: 0.0 - 100.0

Note: Capacity data represents current period only (single metric)


Date Filters

Chart data varies by date filter selection:

FilterRevenue LabelBookings/Capacity LabelFormatPeriods
todayLast 24 hours (hourly)Today (hourly)HH:00 (e.g., “10:00”, “11:00”)Revenue: current vs yesterday; Bookings/Capacity: current only
last_7_dayLast 7 days (daily)Last 7 days (daily)DD (e.g., “01”, “02”)Revenue: current vs previous; Bookings/Capacity: current only
last_30_dayLast 30 days (daily)Last 30 days (daily)DD (e.g., “01”, “02”)Revenue: current vs previous; Bookings/Capacity: current only
this_monthCurrent month (daily)Current month (daily)DD (e.g., “01”, “02”)Revenue: current vs previous; Bookings/Capacity: current only
last_monthLast month (daily)Last month (daily)DD (e.g., “01”, “02”)Revenue: current vs previous; Bookings/Capacity: current only
last_3_monthLast 3 months (monthly)Last 3 months (monthly)MMM YYYY (e.g., “Jan 2026”)Revenue: current vs previous; Bookings/Capacity: current only

Error Responses

Bad Request (HTTP 400)

Returned when required parameters are missing or invalid.

{
  "success": false,
  "message": "chart_type is required. Allowed values: revenue, bookings, capacity"
}

Common Causes:

  • Missing chart_type parameter
  • Missing date_filter parameter
  • Invalid chart_type value (not in: revenue, bookings, capacity)
  • Invalid date_filter value (not in: today, last_7_day, last_30_day, this_month, last_month, last_3_month)

Forbidden (HTTP 403)

Returned when user lacks authorization to access restaurant data.

{
  "success": false,
  "message": "Not authorized to access this restaurant"
}

Reason: User is not authorized to view analytics for the requested restaurant (Pundit policy violation)

Fix: User must be assigned to the restaurant with appropriate permissions

Internal Server Error (HTTP 500)

Returned when ClickHouse query fails or response validation fails.

{
  "success": false,
  "message": "Internal server error"
}

Common Causes:

  • ClickHouse connection failure
  • Invalid response structure from ClickHouse
  • Missing required fields in ClickHouse response
  • Data array length mismatch with labels

Debug Information: Check server logs for APM errors via BUSINESS_LOGGER


HTTP Status Codes

StatusMeaningRetry?
200SuccessN/A
400Bad request (invalid parameters)No - fix parameters
403Forbidden (authorization denied)No - user not authorized
500Server error (ClickHouse/validation)Yes - may be transient

Response Validation

All responses are validated before caching to ensure data integrity.

Validation Rules (by chart type):

Revenue Chart:

  1. Response must be a Hash
  2. Must contain keys: labels, current_period, previous_period
  3. labels must be an Array
  4. current_period and previous_period must be Hashes
  5. current_period.data and previous_period.data must be Arrays
  6. Data array lengths must match labels array length

Bookings Chart:

  1. Response must be a Hash
  2. Must contain keys: labels, data
  3. labels must be an Array
  4. data must be a Hash with keys bookings and covers
  5. data.bookings and data.covers must be Arrays
  6. Data array lengths must match labels array length

Capacity Chart:

  1. Response must be a Hash
  2. Must contain keys: labels, current_period
  3. labels must be an Array
  4. current_period must be a Hash
  5. current_period.data must be an Array
  6. Data array length must match labels array length

Example Validation Error:

If data length mismatches labels:

Response Validation Error: current_period data length (6) must match labels length (7)

Caching

Cache Configuration

  • TTL (Time To Live): 1 hour (3600 seconds)
  • Key Format: revenue_chart_service:restaurant_ids_filter:timestamp
  • Cache Store: Rails.cache (Redis or Memcached, based on config)

Cache Key Generation

Cache keys are generated from:

  1. Service name (e.g., “revenue_chart_service”)
  2. Restaurant IDs (sorted, comma-separated)
  3. Date filter (e.g., “last_7_day”)

Example Cache Key:

revenue_chart_service:1597_last_7_day:1704801600

Cache Hit Verification

To verify cache is working:

  1. Make two identical requests within 1 hour
  2. Second request should return same data
  3. Check response headers for cache indicators

Performance Considerations

Query Timeout

  • Timeout: 30 seconds (read operation)
  • Retry Logic: 3 attempts with exponential backoff (1s, 2s, 4s)
  • Max Retry Duration: ~7 seconds total

Rate Limiting

No explicit rate limiting is configured. Standard API rate limits apply via Rack::Attack if configured.

Response Size

Typical response sizes (with metadata):

  • Today filter (hourly): ~2 KB
  • 7-day filter (daily): ~1.5 KB
  • 30-day filter (daily): ~2.5 KB
  • 3-month filter (monthly): ~1 KB

Authentication & Authorization

Authentication

All endpoints require Bearer token authentication:

Authorization: Bearer <JWT_TOKEN>

Authorization

Pundit gem is used for authorization. User must have show? permission on all requested restaurants.

Required Permission: RestaurantPolicy#show?

Example Pundit Policy:

class RestaurantPolicy < ApplicationPolicy
  def show?
    user.present? && (record.public? || user_can_access_restaurant?)
  end

  private

  def user_can_access_restaurant?
    user.staff_restaurants.include?(record) ||
    user.manager? ||
    user.admin?
  end
end

Logging

Business Logging

All chart requests are logged with business context:

BUSINESS_LOGGER.info('ClickHouse query starting', {
  service: 'RevenueChartService',
  restaurant_ids: [1597],
  date_filter: 'last_7_day'
})

APM Tracking

All methods are instrumented with ElasticAPM SpanHelpers for production monitoring.


Rate Limits & Quotas

No explicit rate limits per user/API key. Consider implementing:

  • Maximum requests per minute: 60
  • Maximum requests per hour: 1000
  • Burst limit: 10 requests per second

Versioning

Current API Version: v1 (path: /api/partner/v1/analytics/)

Planned Future Versions:

  • v2: GraphQL endpoint for flexible data queries
  • v3: Real-time WebSocket subscriptions

Examples

Complete Request/Response Cycle

Request:

curl -X GET "http://localhost:3000/api/partner/v1/analytics/chart_data?chart_type=revenue&date_filter=last_7_day" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json"

Response (HTTP 200):

{
  "success": true,
  "message": "Success",
  "data": {
    "labels": ["01", "02", "03", "04", "05", "06", "07"],
    "current_period": {
      "label": "Last 7 days",
      "data": [5200.50, 5850.75, 5100.00, 6200.25, 5950.00, 6300.50, 5800.75]
    },
    "previous_period": {
      "label": "Previous 7 days",
      "data": [4950.00, 5200.50, 5100.00, 5400.25, 5600.00, 5300.00, 5100.50]
    },
    "raw_data": [
      {
        "date": "01",
        "revenue": 5200.50,
        "previous_revenue": 4950.00
      },
      ...
    ],
    "metadata": {
      "start_date": "2026-01-01",
      "end_date": "2026-01-07",
      "date_filter": "last_7_day",
      "restaurant_ids": [1597],
      "current_period_label": "Last 7 days",
      "previous_period_label": "Previous 7 days"
    }
  }
}

Troubleshooting

Empty Data Response

Symptom: data arrays are all zeros or empty

Causes:

  1. No data exists in ClickHouse for requested date range
  2. Restaurant ID has no bookings/transactions in period
  3. ClickHouse MVs not populated yet (check Kafka consumer)

Fix:

  • Verify data exists in ClickHouse: SELECT * FROM analytics_revenue WHERE restaurant_id = 1597
  • Check Kafka consumer logs for errors
  • Verify MV refresh status

Authorization Denied

Symptom: HTTP 403 Forbidden response

Causes:

  1. User not assigned to restaurant
  2. User doesn’t have required permissions
  3. Restaurant doesn’t exist

Fix:

  • Assign user to restaurant via admin panel
  • Verify restaurant ID is correct
  • Check staff_restaurants relationship in database

Timeout Errors

Symptom: HTTP 500 after 30 seconds

Causes:

  1. ClickHouse query too slow (large date range, many restaurants)
  2. ClickHouse unavailable/unreachable
  3. Network issues

Fix:

  • Monitor ClickHouse query performance
  • Check ClickHouse server status
  • Verify network connectivity
  • Check retry logs in BUSINESS_LOGGER

Last Updated: January 7, 2026
API Version: v1
Documentation Version: 1.0