All FinOps endpoints require ANALYTICS_CORE_POLICY gating and tenant authentication. Only users with the platform_admin or sre role may access these endpoints. Accessible via the API gateway at /v1/finops/*.
Manage infrastructure costs, predict cash drawer needs, generate dispute evidence, and detect ghost inventory.
Overview
The FinOps Dashboard API provides financial operations tooling across two domains: infrastructure cost management (GCP billing analysis powered by ClickHouse) and restaurant financial operations (cash prediction, dispute handling, inventory verification).
| Feature | Description |
|---|
| Cost Anomaly Detection | Analyze GCP billing data against a 7-day baseline (20% threshold) |
| Cost Summary | Aggregated cost breakdown by GCP service for up to 90 days |
| Cash Prediction | Predict cash drawer and change order needs from 30 days of sales history |
| Dispute Evidence | AI-powered evidence package generation for payment disputes |
| Ghost Inventory | Vision AI comparison of physical shelf counts vs system inventory |
| Health Check | Verify ClickHouse connectivity status |
Related Issues: #583 (Cost Anomaly Detection), Epic #819 (Cash Prediction), Epic #1475 (ClickHouse Migration)
Authentication and Authorization
All endpoints on the /finops router share two layers of protection:
| Layer | Mechanism | Details |
|---|
| Gating Policy | ANALYTICS_CORE_POLICY | Checked via require_gating_policy dependency |
| Tenant Auth | JWT Bearer token | Checked via require_tenant_auth with scope="global" |
| RBAC Roles | platform_admin, sre | Only these roles are permitted access |
All requests must include a valid JWT:
Authorization: Bearer {access_token}
Detect Cost Anomalies
Trigger cost anomaly detection for a specific date. Analyzes GCP billing data and compares daily costs to the 7-day rolling baseline. Services with costs exceeding 20% of baseline are flagged as anomalies. This endpoint can be called by Cloud Scheduler (daily cron) or manually by SRE/platform admins.
Request
POST /api/v1/finops/cost-anomalies/detect
Authorization: Bearer {access_token}
Content-Type: application/json
{
"target_date": "2026-02-18",
"send_alerts": true
}
Request Body
| Field | Type | Required | Default | Description |
|---|
target_date | date (ISO 8601) | No | Yesterday | Date to analyze |
send_alerts | boolean | No | true | Whether to send alerts for detected anomalies |
Response (200)
{
"target_date": "2026-02-18",
"anomalies_found": 2,
"critical_count": 1,
"anomalies": [
{
"service": "Cloud Run",
"date": "2026-02-18",
"daily_cost": 184.50,
"baseline_cost": 120.00,
"percentage_increase": 53.8,
"severity": "critical"
},
{
"service": "Cloud Spanner",
"date": "2026-02-18",
"daily_cost": 95.20,
"baseline_cost": 78.00,
"percentage_increase": 22.1,
"severity": "warning"
}
],
"alerts_sent": true
}
Response Fields
| Field | Type | Description |
|---|
target_date | string | Date that was analyzed (ISO format) |
anomalies_found | integer | Total number of anomalies detected |
critical_count | integer | Number of critical-severity anomalies |
anomalies | array | List of CostAnomalyResponse objects |
alerts_sent | boolean | Whether alerts were dispatched |
Anomaly Severity Levels
| Severity | Threshold | Description |
|---|
warning | 20-49% above baseline | Cost increase worth investigating |
critical | 50%+ above baseline | Requires immediate attention |
Get Cost Summary
Get aggregated cost summary by GCP service for a date range. Returns total costs, average daily costs, and min/max costs per service.
Request
GET /api/v1/finops/cost-summary?
start_date=2026-01-01&
end_date=2026-01-31
Authorization: Bearer {access_token}
Query Parameters
| Parameter | Type | Required | Description |
|---|
start_date | date | Yes | Start date for analysis (ISO 8601) |
end_date | date | Yes | End date for analysis (ISO 8601) |
Constraints:
end_date must be greater than or equal to start_date
- Date range cannot exceed 90 days
Response (200)
{
"start_date": "2026-01-01",
"end_date": "2026-01-31",
"total_cost": 4825.60,
"services": [
{
"service": "Cloud Run",
"total_cost": 2100.00,
"avg_daily_cost": 67.74,
"min_daily_cost": 45.20,
"max_daily_cost": 125.80,
"percentage_of_total": 43.5
},
{
"service": "Cloud Spanner",
"total_cost": 1450.30,
"avg_daily_cost": 46.78,
"min_daily_cost": 42.00,
"max_daily_cost": 55.10,
"percentage_of_total": 30.1
},
{
"service": "Cloud Storage",
"total_cost": 625.30,
"avg_daily_cost": 20.17,
"min_daily_cost": 18.50,
"max_daily_cost": 22.40,
"percentage_of_total": 13.0
},
{
"service": "Pub/Sub",
"total_cost": 350.00,
"avg_daily_cost": 11.29,
"min_daily_cost": 8.00,
"max_daily_cost": 18.50,
"percentage_of_total": 7.3
},
{
"service": "Cloud Logging",
"total_cost": 300.00,
"avg_daily_cost": 9.68,
"min_daily_cost": 7.50,
"max_daily_cost": 14.20,
"percentage_of_total": 6.2
}
]
}
Response Fields
| Field | Type | Description |
|---|
start_date | string | Start date of the analysis period |
end_date | string | End date of the analysis period |
total_cost | float | Total cost in USD across all services |
services | array | Cost breakdown per GCP service |
Health Check
Check if the FinOps service is healthy and ClickHouse is accessible. This endpoint verifies the ClickHouse connection used by cost anomaly detection (Epic #1475: ClickHouse Cloud replaces BigQuery for analytics).
Request
GET /api/v1/finops/health
Authorization: Bearer {access_token}
Response -- Healthy (200)
{
"status": "healthy",
"clickhouse": "connected"
}
Response -- Degraded (200)
{
"status": "degraded",
"clickhouse": "unavailable",
"message": "ClickHouse client not installed or configured"
}
Response -- Unhealthy (200)
{
"status": "unhealthy",
"clickhouse": "error",
"message": "Internal error occurred. Check logs for details."
}
Status Values
| Status | Meaning |
|---|
healthy | ClickHouse is connected and responding |
degraded | ClickHouse client is not installed or configured; cost features unavailable |
unhealthy | Connection attempt failed; check service logs |
Predict Cash Needs
Predict cash drawer float and change order needs for a location based on the last 30 days of historical sales data (Epic #819). Uses HQ Analytics Service to fetch daily sales metrics, then applies a prediction model to recommend drawer setup.
Request
POST /api/v1/finops/cash/predict
Authorization: Bearer {access_token}
Content-Type: application/json
{
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"location_id": "550e8400-e29b-41d4-a716-446655449110"
}
Request Body
| Field | Type | Required | Description |
|---|
tenant_id | string | Yes | Tenant UUID |
location_id | string | Yes | Location UUID |
Response (200)
{
"recommended_float": 200.00,
"change_order_suggestion": {
"ones": 55,
"fives": 22,
"pennies": 50
},
"predicted_cash_sales": 550.00,
"confidence": 0.85
}
Response Fields
| Field | Type | Description |
|---|
recommended_float | float | Recommended starting cash drawer float in USD |
change_order_suggestion | object | Suggested denomination breakdown (key = denomination, value = count) |
predicted_cash_sales | float | Predicted total cash sales for the upcoming period in USD |
confidence | float | Model confidence score (0.0 to 1.0). Returns 0.0 when no historical data is available |
Generate Dispute Evidence
Generate an AI-powered evidence package for a payment dispute. This endpoint runs a LangGraph agent workflow that fetches transaction details from Stripe/PaymentService, retrieves audit logs from Spanner, analyzes win probability using AI, and produces a PDF evidence brief.
Request
POST /api/v1/finops/disputes/{dispute_id}/generate-evidence?
tenant_id=550e8400-e29b-41d4-a716-446655449100
Authorization: Bearer {access_token}
Path Parameters
| Parameter | Type | Description |
|---|
dispute_id | string | The dispute or payment ID to generate evidence for |
Query Parameters
| Parameter | Type | Required | Description |
|---|
tenant_id | string | Yes | Tenant UUID |
Response (200)
{
"tenant_id": "550e8400-e29b-41d4-a716-446655449100",
"dispute_id": "dp_1234567890",
"transaction_data": {
"amount": 4599,
"currency": "usd",
"status": "succeeded",
"payment_method_details": {
"card": {
"brand": "visa",
"last4": "4242",
"present": true,
"emv_auth_data": "8A023030"
}
},
"created": "2026-02-10T14:30:00Z"
},
"audit_logs": [
{
"timestamp": "2026-02-10T14:29:55Z",
"action": "order_created",
"user_id": "550e8400-e29b-41d4-a716-446655449122"
},
{
"timestamp": "2026-02-10T14:30:00Z",
"action": "payment_captured",
"user_id": "550e8400-e29b-41d4-a716-446655449122"
}
],
"analysis": "Win Probability: High. Evidence: Card was present during transaction. EMV Chip was read (Liability Shift applies).",
"evidence_pdf_url": "https://storage.googleapis.com/olympus-disputes/dp_1234567890/evidence.pdf"
}
Response Fields
| Field | Type | Description |
|---|
tenant_id | string | Tenant UUID |
dispute_id | string | Dispute ID |
transaction_data | object | Payment transaction details fetched from Stripe |
audit_logs | array | Audit log entries from Spanner for the transaction timeline |
analysis | string | AI-generated analysis including win probability and evidence summary |
evidence_pdf_url | string | URL to the generated PDF evidence brief (GCS signed URL) |
Agent Workflow
The dispute evidence generation follows a LangGraph state machine:
fetch_data --> analyze --> END
- fetch_data -- Retrieves payment details from Stripe/PaymentService
- analyze -- Evaluates card presence, EMV chip data, and liability shift rules to determine win probability (High, Medium, Low)
Detect Ghost Inventory
Detect discrepancies between system inventory records and physical counts using Vision AI. Captures a frame from a specified camera, runs shelf analysis via the Vision AI edge inference pipeline, and compares the count against the Commerce service inventory record.
Request
POST /api/v1/finops/ghost-inventory/detect?
tenant_id=550e8400-e29b-41d4-a716-446655449100&
camera_id=cam-kitchen-01&
item_type=coke_bottle
Authorization: Bearer {access_token}
Query Parameters
| Parameter | Type | Required | Description |
|---|
tenant_id | string | Yes | Tenant UUID |
camera_id | string | Yes | Camera identifier to capture the frame from |
item_type | string | Yes | Item type to count (e.g., coke_bottle, cup_16oz) |
Response (200)
{
"camera_id": "cam-kitchen-01",
"item_type": "coke_bottle",
"vision_count": 18,
"system_count": 24,
"discrepancy": 6,
"alert": true,
"timestamp": "2026-02-20T10:15:30Z"
}
Response Fields
| Field | Type | Description |
|---|
camera_id | string | Camera that was analyzed |
item_type | string | Item type that was counted |
vision_count | integer | Number of items detected by Vision AI |
system_count | integer | Number of items recorded in the system inventory |
discrepancy | integer | Difference: system_count - vision_count. Positive values indicate ghost inventory |
alert | boolean | true if discrepancy exceeds the threshold (currently 2 units) |
timestamp | string | Timestamp of the vision analysis |
Error Cases
If the camera is not found or no frame is available, the endpoint returns:
{
"error": "Camera not found",
"camera_id": "cam-unknown"
}
{
"error": "No frame available",
"camera_id": "cam-kitchen-01"
}
Error Responses
All FinOps endpoints follow consistent error handling patterns.
Invalid Date Range (400)
{
"detail": "end_date must be greater than or equal to start_date"
}
Date Range Too Large (400)
{
"detail": "Date range cannot exceed 90 days"
}
ClickHouse Unavailable (503)
Returned when the ClickHouse backend is not available (Epic #1475). Cost anomaly detection and cost summary endpoints depend on ClickHouse connectivity.
{
"detail": "ClickHouse client not available. Ensure clickhouse-connect is installed."
}
Internal Server Error (500)
Internal errors are logged with full details but sanitized messages are returned to clients.
{
"detail": "Failed to detect anomalies. Please contact support if the issue persists."
}
Authorization Denied (403)
Returned when the caller does not have the platform_admin or sre role, or the tenant fails the ANALYTICS_CORE_POLICY gating check.
{
"detail": "Forbidden: insufficient permissions"
}
Data Models Reference
DetectAnomaliesRequest
| Field | Type | Required | Default | Description |
|---|
target_date | date | No | Yesterday | Date to analyze (ISO 8601) |
send_alerts | boolean | No | true | Send alerts for detected anomalies |
CostAnomalyResponse
| Field | Type | Description |
|---|
service | string | GCP service name (e.g., "Cloud Run", "Cloud Spanner") |
date | string | Date of the anomaly (ISO format) |
daily_cost | float | Actual daily cost in USD |
baseline_cost | float | Expected cost based on 7-day rolling baseline |
percentage_increase | float | Percentage increase from baseline |
severity | string | Anomaly severity: warning or critical |
CostSummaryResponse
| Field | Type | Description |
|---|
start_date | string | Start date of analysis |
end_date | string | End date of analysis |
total_cost | float | Total cost in USD |
services | array | Cost breakdown by GCP service |
CashPredictionRequest
| Field | Type | Required | Description |
|---|
tenant_id | string | Yes | Tenant UUID |
location_id | string | Yes | Location UUID |
CashPrediction (Response)
| Field | Type | Description |
|---|
recommended_float | float | Recommended starting drawer float (USD) |
change_order_suggestion | object | Denomination breakdown (e.g., {"ones": 50, "fives": 20}) |
predicted_cash_sales | float | Predicted cash sales for the period (USD) |
confidence | float | Model confidence (0.0 to 1.0) |