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:
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
chart_type | String | Yes | Type of chart to retrieve | revenue, bookings, capacity |
date_filter | String | Yes | Date range filter | today, 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:
| Filter | Revenue Label | Bookings/Capacity Label | Format | Periods |
|---|---|---|---|---|
today | Last 24 hours (hourly) | Today (hourly) | HH:00 (e.g., “10:00”, “11:00”) | Revenue: current vs yesterday; Bookings/Capacity: current only |
last_7_day | Last 7 days (daily) | Last 7 days (daily) | DD (e.g., “01”, “02”) | Revenue: current vs previous; Bookings/Capacity: current only |
last_30_day | Last 30 days (daily) | Last 30 days (daily) | DD (e.g., “01”, “02”) | Revenue: current vs previous; Bookings/Capacity: current only |
this_month | Current month (daily) | Current month (daily) | DD (e.g., “01”, “02”) | Revenue: current vs previous; Bookings/Capacity: current only |
last_month | Last month (daily) | Last month (daily) | DD (e.g., “01”, “02”) | Revenue: current vs previous; Bookings/Capacity: current only |
last_3_month | Last 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_typeparameter - Missing
date_filterparameter - Invalid
chart_typevalue (not in: revenue, bookings, capacity) - Invalid
date_filtervalue (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
| Status | Meaning | Retry? |
|---|---|---|
| 200 | Success | N/A |
| 400 | Bad request (invalid parameters) | No - fix parameters |
| 403 | Forbidden (authorization denied) | No - user not authorized |
| 500 | Server 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:
- Response must be a Hash
- Must contain keys:
labels,current_period,previous_period labelsmust be an Arraycurrent_periodandprevious_periodmust be Hashescurrent_period.dataandprevious_period.datamust be Arrays- Data array lengths must match labels array length
Bookings Chart:
- Response must be a Hash
- Must contain keys:
labels,data labelsmust be an Arraydatamust be a Hash with keysbookingsandcoversdata.bookingsanddata.coversmust be Arrays- Data array lengths must match labels array length
Capacity Chart:
- Response must be a Hash
- Must contain keys:
labels,current_period labelsmust be an Arraycurrent_periodmust be a Hashcurrent_period.datamust be an Array- 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:
- Service name (e.g., “revenue_chart_service”)
- Restaurant IDs (sorted, comma-separated)
- 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:
- Make two identical requests within 1 hour
- Second request should return same data
- 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:
- No data exists in ClickHouse for requested date range
- Restaurant ID has no bookings/transactions in period
- 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:
- User not assigned to restaurant
- User doesn’t have required permissions
- 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:
- ClickHouse query too slow (large date range, many restaurants)
- ClickHouse unavailable/unreachable
- 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