This endpoint requires a valid JWT Bearer token. Accessible via the API gateway at /v1/commerce/*.
P&L Reporting API
Profit & Loss reporting across your enterprise -- consolidated, brand-level, regional, comparison, and custom reports backed by ClickHouse Cloud analytics (Epic #1475).
Overview
| Attribute | Value |
|---|---|
| Base Path | /api/v1/pnl |
| Authentication | Bearer Token (JWT) |
| Tenant Isolation | Automatic via tenant_id from JWT claims |
| Required Roles | manager, restaurant_manager, tenant_admin, platform_admin, super_admin |
| Data Source | ClickHouse Cloud (Epic #1475: BigQuery to ClickHouse Migration) |
The P&L Reporting API provides:
| Feature | Description |
|---|---|
| Consolidated P&L | Enterprise-wide profit and loss across all brands and locations |
| Brand P&L | Financial statement for a single brand |
| Regional P&L | Financial statement for a geographic region |
| Brand Comparison | Side-by-side comparison of 2+ brands |
| Custom Reports | Selectable sections, grouping, and filters |
| Trend Analysis | Metric trends over time with day/week/month granularity |
| Metrics Catalog | Discover available P&L metrics for report building |
| Sections Catalog | Discover available report sections for custom reports |
Data Models
PnLSummary
Core financial metrics returned in P&L responses:
| Field | Type | Description |
|---|---|---|
net_revenue | float | Total revenue after discounts and refunds |
gross_profit | float | Revenue minus cost of goods sold |
gross_margin | float | Gross profit as a percentage of revenue |
labor_cost | float | Total labor expenses |
labor_percentage | float | Labor cost as a percentage of revenue |
food_cost | float | Total food cost |
food_cost_percentage | float | Food cost as a percentage of revenue |
operating_income | float | Income from core operations |
operating_margin | float | Operating income as a percentage of revenue |
net_income | float | Final bottom-line income |
net_margin | float | Net income as a percentage of revenue |
Standard Response Envelope
All endpoints return a consistent envelope:
{
"success": true,
"data": { ... }
}
Consolidated P&L
Generate an enterprise-wide P&L statement aggregated across all brands and locations. Optionally include budget variance and prior period comparison.
Request
POST /api/v1/pnl/consolidated
Authorization: Bearer {access_token}
Content-Type: application/json
Request Body
{
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"brand_ids": ["brand-001", "brand-002"],
"include_budget": true,
"include_prior_period": true
}
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string (YYYY-MM-DD) | Yes | Report period start date |
end_date | string (YYYY-MM-DD) | Yes | Report period end date |
brand_ids | string[] | No | Filter to specific brands (omit for all) |
include_budget | boolean | No | Include budget comparison column (default: false) |
include_prior_period | boolean | No | Include prior period comparison column (default: false) |
Response
{
"success": true,
"data": {
"period": {
"start_date": "2026-01-01",
"end_date": "2026-01-31"
},
"summary": {
"net_revenue": 485000.00,
"gross_profit": 316250.00,
"gross_margin": 65.2,
"labor_cost": 145500.00,
"labor_percentage": 30.0,
"food_cost": 150350.00,
"food_cost_percentage": 31.0,
"operating_income": 72750.00,
"operating_margin": 15.0,
"net_income": 58200.00,
"net_margin": 12.0
},
"budget_comparison": {
"net_revenue": { "actual": 485000.00, "budget": 500000.00, "variance": -15000.00, "variance_pct": -3.0 },
"food_cost_percentage": { "actual": 31.0, "budget": 30.0, "variance": 1.0, "variance_pct": 3.3 }
},
"prior_period": {
"net_revenue": { "current": 485000.00, "prior": 470000.00, "change": 15000.00, "change_pct": 3.2 },
"gross_margin": { "current": 65.2, "prior": 64.0, "change": 1.2, "change_pct": 1.9 }
},
"brands_included": 2
}
}
Brand P&L
Generate a P&L statement for a single brand.
Request
POST /api/v1/pnl/brand/{brand_id}
Authorization: Bearer {access_token}
Content-Type: application/json
Path Parameters
| Parameter | Type | Description |
|---|---|---|
brand_id | string | Brand identifier |
Request Body
{
"start_date": "2026-01-01",
"end_date": "2026-01-31"
}
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string (YYYY-MM-DD) | Yes | Report period start date |
end_date | string (YYYY-MM-DD) | Yes | Report period end date |
Response
{
"success": true,
"data": {
"brand_id": "brand-001",
"brand_name": "Downtown Grill",
"period": {
"start_date": "2026-01-01",
"end_date": "2026-01-31"
},
"summary": {
"net_revenue": 245000.00,
"gross_profit": 161700.00,
"gross_margin": 66.0,
"labor_cost": 73500.00,
"labor_percentage": 30.0,
"food_cost": 73500.00,
"food_cost_percentage": 30.0,
"operating_income": 39200.00,
"operating_margin": 16.0,
"net_income": 31360.00,
"net_margin": 12.8
},
"revenue": {
"gross_revenue": 260000.00,
"discounts": -10000.00,
"refunds": -5000.00,
"net_revenue": 245000.00
},
"cost_of_goods_sold": {
"food_cost": 58800.00,
"beverage_cost": 14700.00,
"total_cogs": 73500.00
},
"operating_expenses": {
"labor_cost": 73500.00,
"occupancy_cost": 24500.00,
"utilities": 9800.00,
"marketing": 7350.00,
"other": 12250.00,
"total_operating_expenses": 127400.00
},
"locations_count": 3
}
}
Regional P&L
Generate a P&L statement for a specific geographic region.
Request
POST /api/v1/pnl/region/{region_id}
Authorization: Bearer {access_token}
Content-Type: application/json
Path Parameters
| Parameter | Type | Description |
|---|---|---|
region_id | string | Region identifier |
Request Body
{
"start_date": "2026-01-01",
"end_date": "2026-01-31"
}
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string (YYYY-MM-DD) | Yes | Report period start date |
end_date | string (YYYY-MM-DD) | Yes | Report period end date |
Response
{
"success": true,
"data": {
"region_id": "region-southeast",
"region_name": "Southeast",
"period": {
"start_date": "2026-01-01",
"end_date": "2026-01-31"
},
"summary": {
"net_revenue": 620000.00,
"gross_profit": 396800.00,
"gross_margin": 64.0,
"labor_cost": 192200.00,
"labor_percentage": 31.0,
"food_cost": 198400.00,
"food_cost_percentage": 32.0,
"operating_income": 86800.00,
"operating_margin": 14.0,
"net_income": 68200.00,
"net_margin": 11.0
},
"brands_in_region": 4,
"locations_in_region": 12
}
}
Compare Brands
Compare P&L metrics side-by-side across two or more brands. Requires at least 2 brand_ids.
Request
POST /api/v1/pnl/compare
Authorization: Bearer {access_token}
Content-Type: application/json
Request Body
{
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"brand_ids": ["brand-001", "brand-002", "brand-003"],
"metrics": ["net_revenue", "gross_margin", "labor_percentage", "net_margin"]
}
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string (YYYY-MM-DD) | Yes | Report period start date |
end_date | string (YYYY-MM-DD) | Yes | Report period end date |
brand_ids | string[] | Yes | Brands to compare (minimum 2) |
metrics | string[] | No | Specific metrics to compare (omit for all available metrics) |
Response
{
"success": true,
"data": {
"period": {
"start_date": "2026-01-01",
"end_date": "2026-01-31"
},
"brands": [
{
"brand_id": "brand-001",
"brand_name": "Downtown Grill",
"metrics": {
"net_revenue": 245000.00,
"gross_margin": 66.0,
"labor_percentage": 30.0,
"net_margin": 12.8
}
},
{
"brand_id": "brand-002",
"brand_name": "Harbor Bistro",
"metrics": {
"net_revenue": 180000.00,
"gross_margin": 62.5,
"labor_percentage": 32.0,
"net_margin": 10.5
}
},
{
"brand_id": "brand-003",
"brand_name": "Uptown Kitchen",
"metrics": {
"net_revenue": 60000.00,
"gross_margin": 68.0,
"labor_percentage": 28.0,
"net_margin": 14.2
}
}
],
"rankings": {
"net_revenue": ["brand-001", "brand-002", "brand-003"],
"gross_margin": ["brand-003", "brand-001", "brand-002"],
"labor_percentage": ["brand-003", "brand-001", "brand-002"],
"net_margin": ["brand-003", "brand-001", "brand-002"]
}
}
}
Custom Report
Build a custom P&L report by selecting specific sections, grouping dimensions, and filters.
Request
POST /api/v1/pnl/custom
Authorization: Bearer {access_token}
Content-Type: application/json
Request Body
{
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"sections": ["revenue", "cogs", "operating_expenses"],
"group_by": "brand",
"filters": {
"region_id": "region-southeast",
"min_revenue": 50000
}
}
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string (YYYY-MM-DD) | Yes | Report period start date |
end_date | string (YYYY-MM-DD) | Yes | Report period end date |
sections | string[] | Yes | Sections to include (see List Sections) |
group_by | string | No | Grouping dimension: brand, region, or location |
filters | object | No | Additional key-value filters |
Response
{
"success": true,
"data": {
"period": {
"start_date": "2026-01-01",
"end_date": "2026-01-31"
},
"group_by": "brand",
"sections_included": ["revenue", "cogs", "operating_expenses"],
"groups": [
{
"group_key": "brand-001",
"group_name": "Downtown Grill",
"revenue": {
"gross_revenue": 260000.00,
"discounts": -10000.00,
"refunds": -5000.00,
"net_revenue": 245000.00
},
"cogs": {
"food_cost": 58800.00,
"beverage_cost": 14700.00,
"total_cogs": 73500.00
},
"operating_expenses": {
"labor_cost": 73500.00,
"occupancy_cost": 24500.00,
"utilities": 9800.00,
"marketing": 7350.00,
"total_operating_expenses": 115150.00
}
},
{
"group_key": "brand-002",
"group_name": "Harbor Bistro",
"revenue": {
"gross_revenue": 195000.00,
"discounts": -8000.00,
"refunds": -7000.00,
"net_revenue": 180000.00
},
"cogs": {
"food_cost": 50400.00,
"beverage_cost": 16200.00,
"total_cogs": 66600.00
},
"operating_expenses": {
"labor_cost": 57600.00,
"occupancy_cost": 18000.00,
"utilities": 7200.00,
"marketing": 5400.00,
"total_operating_expenses": 88200.00
}
}
],
"totals": {
"revenue": { "net_revenue": 425000.00 },
"cogs": { "total_cogs": 140100.00 },
"operating_expenses": { "total_operating_expenses": 203350.00 }
}
}
}
P&L Trend
Track a single P&L metric over time with configurable granularity. Returns time-series data suitable for charts and sparklines.
Request
POST /api/v1/pnl/trend
Authorization: Bearer {access_token}
Content-Type: application/json
Request Body
{
"start_date": "2025-07-01",
"end_date": "2026-01-31",
"metric": "net_revenue",
"granularity": "month",
"brand_id": "brand-001"
}
| Field | Type | Required | Description |
|---|---|---|---|
start_date | string (YYYY-MM-DD) | Yes | Trend period start date |
end_date | string (YYYY-MM-DD) | Yes | Trend period end date |
metric | string | Yes | Metric to track (see List Metrics) |
granularity | string | No | Time granularity: day, week, or month (default: month) |
brand_id | string | No | Filter to a specific brand |
Response
{
"success": true,
"data": {
"metric": "net_revenue",
"granularity": "month",
"brand_id": "brand-001",
"period": {
"start_date": "2025-07-01",
"end_date": "2026-01-31"
},
"data_points": [
{ "date": "2025-07-01", "value": 210000.00 },
{ "date": "2025-08-01", "value": 225000.00 },
{ "date": "2025-09-01", "value": 218000.00 },
{ "date": "2025-10-01", "value": 232000.00 },
{ "date": "2025-11-01", "value": 248000.00 },
{ "date": "2025-12-01", "value": 265000.00 },
{ "date": "2026-01-01", "value": 245000.00 }
],
"summary": {
"min": 210000.00,
"max": 265000.00,
"avg": 234714.29,
"trend_direction": "up",
"change_pct": 16.7
}
}
}
List Available Metrics
Returns all available P&L metrics organized by category. Use these metric names in trend and comparison requests.
Request
GET /api/v1/pnl/metrics
Authorization: Bearer {access_token}
Response
{
"success": true,
"data": {
"revenue": [
{ "name": "gross_revenue", "label": "Gross Revenue", "type": "currency" },
{ "name": "discounts", "label": "Discounts", "type": "currency" },
{ "name": "refunds", "label": "Refunds", "type": "currency" },
{ "name": "net_revenue", "label": "Net Revenue", "type": "currency" }
],
"cost_of_goods_sold": [
{ "name": "food_cost", "label": "Food Cost", "type": "currency" },
{ "name": "beverage_cost", "label": "Beverage Cost", "type": "currency" },
{ "name": "total_cogs", "label": "Total COGS", "type": "currency" },
{ "name": "food_cost_percentage", "label": "Food Cost %", "type": "percentage" }
],
"profitability": [
{ "name": "gross_profit", "label": "Gross Profit", "type": "currency" },
{ "name": "gross_margin", "label": "Gross Margin", "type": "percentage" },
{ "name": "operating_income", "label": "Operating Income", "type": "currency" },
{ "name": "operating_margin", "label": "Operating Margin", "type": "percentage" },
{ "name": "net_income", "label": "Net Income", "type": "currency" },
{ "name": "net_margin", "label": "Net Margin", "type": "percentage" }
],
"operating_expenses": [
{ "name": "labor_cost", "label": "Labor Cost", "type": "currency" },
{ "name": "labor_percentage", "label": "Labor %", "type": "percentage" },
{ "name": "occupancy_cost", "label": "Occupancy", "type": "currency" },
{ "name": "utilities", "label": "Utilities", "type": "currency" },
{ "name": "marketing", "label": "Marketing", "type": "currency" }
]
}
}
List Available Sections
Returns available report sections for use with the custom report builder.
Request
GET /api/v1/pnl/sections
Authorization: Bearer {access_token}
Response
{
"success": true,
"data": [
{ "type": "revenue", "name": "Revenue", "description": "Sales and income" },
{ "type": "cogs", "name": "Cost of Goods Sold", "description": "Direct costs of products" },
{ "type": "operating_expenses", "name": "Operating Expenses", "description": "Indirect operational costs" },
{ "type": "other_income", "name": "Other Income", "description": "Non-operational income" },
{ "type": "other_expenses", "name": "Other Expenses", "description": "Non-operational expenses" }
]
}
Error Responses
All error responses follow a consistent format:
{
"detail": "Error description"
}
Common Errors
| Status | Condition | Detail |
|---|---|---|
| 400 | Missing tenant | Tenant ID required |
| 400 | Fewer than 2 brands in comparison | At least 2 brands required for comparison |
| 400 | Invalid granularity value | Invalid granularity. Must be one of: ['day', 'week', 'month'] |
| 401 | Missing or invalid JWT | Not authenticated |
| 403 | Insufficient role permissions | Insufficient permissions |
| 500 | ClickHouse query failure | Failed to generate P&L: {error} |
| 500 | Brand P&L failure | Failed to generate brand P&L: {error} |
| 500 | Regional P&L failure | Failed to generate regional P&L: {error} |
| 500 | Comparison failure | Failed to compare brands: {error} |
| 500 | Custom report failure | Failed to generate custom report: {error} |
| 500 | Trend failure | Failed to get trend data: {error} |
Related Documentation
- Inventory API - Inventory management and cost tracking
- Orders API - Order processing and revenue data
- Marketing API - Campaign spend tracking