Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions plugin/skills/azure-cost-forecast/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
---
name: azure-cost-forecast
description: "Forecast future Azure costs using the Cost Management Forecast API. Builds and executes forecast requests with proper time-period guardrails, training-data validation, and response interpretation. WHEN: forecast Azure costs, predict spending, projected costs, estimate bill, future Azure costs, cost projection, budget forecast, end of month costs, how much will I spend. DO NOT USE FOR: querying historical costs (use azure-cost-query), reducing costs (use azure-cost-optimization)."
license: MIT
metadata:
author: Microsoft
version: "1.0.0"
---

# Azure Cost Forecast Skill

Construct and execute Azure Cost Management Forecast API requests to project future Azure spending with actual-cost context.

## Quick Reference

| Property | Value |
|----------|-------|
| **API Endpoint** | `POST {scope}/providers/Microsoft.CostManagement/forecast?api-version=2023-11-01` |
| **MCP Tools** | `azure__documentation`, `azure__extension_cli_generate`, `azure__get_azure_bestpractices` |
| **CLI** | `az rest` |
| **Best For** | Future cost projections, budget planning, end-of-month estimates |
| **Related Skills** | azure-cost-query, azure-cost-optimization |

## When to Use This Skill

- Forecast future Azure spending for subscriptions, resource groups, or billing accounts
- Project end-of-month or end-of-year costs
- Get daily or monthly cost projections
- Include actual costs alongside forecast data for context
- Estimate future budget impact

> ⚠️ **Warning:** If the user wants **historical** cost data, use azure-cost-query. If they want to **reduce** costs, use azure-cost-optimization.

## Key Differences from Query API

| Aspect | Query API | Forecast API |
|--------|-----------|--------------|
| Purpose | Historical cost data | Projected future costs |
| Time period | Past dates only | Must include future dates |
| Grouping | ✅ Up to 2 dimensions | ❌ **Not supported** |
| `includeActualCost` | N/A | Include historical alongside forecast |
| Response columns | Cost, Date, Currency | Cost, Date, **CostStatus**, Currency |
| Max response rows | 5,000/page | 40 rows recommended |
| Timeframe | Multiple presets + Custom | Typically `Custom` only |

## MCP Tools

| Tool | Purpose | Required |
|------|---------|----------|
| `azure__documentation` | Look up Forecast API parameters and reference | Optional |
| `azure__extension_cli_generate` | Generate `az rest` commands for forecast requests | Optional |
| `azure__get_azure_bestpractices` | Get cost management best practices | Optional |

> 💡 **Tip:** Prefer Azure MCP tools over direct CLI commands where possible.

## Workflow

### Step 1: Determine Scope

Same scope URL patterns as the Query API:

| Scope | URL Pattern |
|-------|-------------|
| Subscription | `/subscriptions/<subscription-id>` |
| Resource Group | `/subscriptions/<subscription-id>/resourceGroups/<resource-group>` |
| Management Group | `/providers/Microsoft.Management/managementGroups/<mg-id>` |
| Billing Account | `/providers/Microsoft.Billing/billingAccounts/<billing-account-id>` |

> 💡 **Tip:** These are scope paths only — not complete URLs. Combine with the API endpoint and version: `{scope}/providers/Microsoft.CostManagement/forecast?api-version=2023-11-01`

### Step 2: Choose Report Type

`ActualCost` is most common for forecasting. `AmortizedCost` for reservation/savings plan projections.

### Step 3: Set Time Period

> ⚠️ **Warning:** The `to` date **MUST** be in the future. The API returns an error for entirely past date ranges.

- Set `timeframe` to `Custom` and provide `timePeriod` with `from` and `to` dates
- `from` can be in the past — shows actual costs up to today, then forecast to `to`
- Minimum 28 days of historical cost data required for forecast to work
- Maximum forecast period: 10 years

> **Full rules:** [Guardrails Reference](./references/guardrails.md)

### Step 4: Configure Dataset

- **Granularity**: `Daily` or `Monthly` recommended
- **Aggregation**: Typically `Sum` of `Cost`
- See [Request Body Schema](./references/request-body-schema.md) for full schema

> ⚠️ **Warning:** Grouping is **NOT supported** for forecast. If the user requests a grouped forecast, inform them this is an API limitation and suggest using `azure-cost-query` for grouped historical data instead.

### Step 5: Set Forecast-Specific Options

| Field | Default | Description |
|-------|---------|-------------|
| `includeActualCost` | `true` | Include historical actual costs alongside forecast |
| `includeFreshPartialCost` | `true` | Include partial cost data for recent days. **Requires `includeActualCost: true`** |

### Step 6: Construct and Execute

**Create forecast query file:**

Create `temp/cost-forecast.json` with:
```json
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "<first-of-month>",
"to": "<last-of-month>"
},
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"sorting": [{ "direction": "Ascending", "name": "UsageDate" }]
},
"includeActualCost": true,
"includeFreshPartialCost": true
}
```

**Execute forecast query:**
```powershell
# Create temp folder
New-Item -ItemType Directory -Path "temp" -Force

# Query using REST API
az rest --method post `
--url "https://management.azure.com/subscriptions/<subscription-id>/providers/Microsoft.CostManagement/forecast?api-version=2023-11-01" `
--body '@temp/cost-forecast.json'
```

### Step 7: Interpret Response

The response includes a `CostStatus` column:

| CostStatus | Meaning |
|------------|---------|
| `Actual` | Historical actual cost (when `includeActualCost: true`) |
| `Forecast` | Projected future cost |

> 💡 **Tip:** "Forecast is unavailable for the specified time period" is not an error — it means the scope has insufficient historical data. Suggest using `azure-cost-query` for available data.

## Key Guardrails

| Rule | Constraint |
|------|-----------|
| `to` date | Must be in the future |
| Grouping | ❌ Not supported |
| Min training data | 28 days of historical cost data |
| Max forecast period | 10 years |
| Response row limit | 40 rows recommended |
| `includeFreshPartialCost` | Requires `includeActualCost: true` |
| Monthly + includeActualCost | Requires explicit `timePeriod` |

> **Full details:** [Guardrails Reference](./references/guardrails.md)

## Error Handling

| Status | Error | Remediation |
|--------|-------|-------------|
| 400 | Can't forecast on the past | Ensure `to` date is in the future. |
| 400 | Missing dataset | Add required `dataset` field. |
| 400 | Invalid dependency | Set `includeActualCost: true` when using `includeFreshPartialCost`. |
| 403 | Forbidden | Needs **Cost Management Reader** role on scope. |
| 424 | Bad training data | Insufficient history; falls back to actual costs if available. |
| 429 | Rate limited | Retry after the `x-ms-ratelimit-microsoft.costmanagement-qpu-retry-after` header value. **Max 3 retries.** |
| 503 | Service unavailable | Check [Azure Status](https://status.azure.com). |

> **Full details:** [Error Handling Reference](./references/error-handling.md)

## Examples

### Forecast Rest of Current Month (Daily)

```json
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "<first-of-current-month>",
"to": "<last-day-of-current-month>"
},
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"sorting": [{ "direction": "Ascending", "name": "UsageDate" }]
},
"includeActualCost": true,
"includeFreshPartialCost": true
}
```

> 💡 **Tip:** Set `from` to the first of the month to see actual costs so far alongside the forecast for remaining days.

More examples: [references/examples.md](./references/examples.md)
48 changes: 48 additions & 0 deletions plugin/skills/azure-cost-forecast/references/error-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Forecast API Error Handling

## HTTP Status Codes

| Status | Error | Cause | Remediation |
|---|---|---|---|
| 400 | Bad Request | Invalid request body, missing `dataset`, past-only dates, invalid field dependency combinations | Check request body structure; ensure `to` date is in the future; verify `includeActualCost`/`includeFreshPartialCost` dependency |
| 401 | Unauthorized | Authentication failure — missing or expired token | Re-authenticate with `az login` or refresh the access token |
| 403 | Forbidden | Insufficient permissions on the scope | Ensure the identity has **Cost Management Reader** role (or higher) on the target scope |
| 404 | Not Found | Invalid scope URL — subscription, resource group, or billing account not found | Verify the scope URL path and resource IDs are correct |
| 424 | Failed Dependency | Bad training data — forecast model cannot compute predictions | Falls back to actual costs if `includeActualCost=true`; otherwise suggest using **azure-cost-query** for historical data |
| 429 | Too Many Requests | Rate limited — QPU quota exceeded | Read `x-ms-ratelimit-microsoft.costmanagement-qpu-retry-after` header and wait before retrying |
| 503 | Service Unavailable | Temporary service issue | Check [Azure Status](https://status.azure.com) for service health. |

## Validation Error Reference

| Error Code | Description | Fix |
|---|---|---|
| `EmptyForecastRequestBody` | Request body is empty or null | Provide a complete request body with `type`, `timeframe`, `timePeriod`, and `dataset` |
| `InvalidForecastRequestBody` | Request body has invalid JSON structure | Check JSON syntax — verify braces, commas, and field names |
| `DontContainsDataSet` | The `dataset` field is missing from the request body | Add the `dataset` object with `granularity` and `aggregation` |
| `DontContainsValidTimeRangeWhileContainsPeriod` | `timePeriod` is present but `from` or `to` is invalid | Ensure both `from` and `to` are valid ISO 8601 datetime strings |
| `DontContainsValidTimeRangeWhileMonthlyAndIncludeCost` | Monthly granularity with `includeActualCost=true` but missing valid `timePeriod` | Add explicit `timePeriod` with valid `from` and `to` dates |
| `DontContainIncludeActualCostWhileIncludeFreshPartialCost` | `includeFreshPartialCost=true` without `includeActualCost=true` | Set `includeActualCost=true` or set `includeFreshPartialCost=false` |
| `CantForecastOnThePast` | Both `from` and `to` dates are in the past | Ensure the `to` date is in the future |

## Forecast-Specific Scenarios

| Scenario | Response | Action |
|---|---|---|
| "Forecast is unavailable for the specified time period" | Valid response with null/empty rows | Not an error — insufficient history (< 28 days). Suggest using **azure-cost-query** for available historical data. |
| "Can't forecast on the past" | 400 error with `CantForecastOnThePast` | Ensure the `to` date is in the future. |
| Bad training data | 424 Failed Dependency | If `includeActualCost=true`, the response falls back to actual cost data only. Otherwise, suggest using **azure-cost-query** for historical data. |
| Parsing exception | 400 Bad Request | Check JSON format — validate braces, quotes, commas, and field types. |

## Retry Strategy

| Status | Retry? | Strategy |
|---|---|---|
| 429 | ✅ Yes | Wait for duration specified in `x-ms-ratelimit-microsoft.costmanagement-qpu-retry-after` header. **Maximum 3 retries.** |
| 400 | ❌ No | Fix the request body based on the validation error code |
| 401 | ❌ No | Re-authenticate — the token is missing or expired |
| 403 | ❌ No | Grant **Cost Management Reader** role on the target scope |
| 404 | ❌ No | Fix the scope URL — verify subscription, resource group, or billing account IDs |
| 424 | ❌ No | Training data issue — retrying will not help. Fall back to actual costs or use **azure-cost-query** |
| 503 | ❌ No | Do not retry. Check [Azure Status](https://status.azure.com) for service health. |

> ⚠️ **Warning:** Do not retry any errors except 429. All other errors indicate issues that must be fixed before re-attempting the request.
125 changes: 125 additions & 0 deletions plugin/skills/azure-cost-forecast/references/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Forecast API Examples

Common forecast patterns with request bodies. Use the [SKILL.md workflow](../SKILL.md) to construct and execute the `az rest` command.

## 1. Forecast Rest of Current Month (Daily)

```json
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "<first-of-month>",
"to": "<last-of-month>"
},
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"sorting": [
{ "direction": "Ascending", "name": "UsageDate" }
]
},
"includeActualCost": true,
"includeFreshPartialCost": true
}
```

> 💡 **Tip:** Set `from` to the first of the month — the response contains `Actual` rows up to today and `Forecast` rows for remaining days.

---

## 2. Forecast Next 3 Months (Monthly)

```json
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "<first-of-month>",
"to": "<3-months-out>"
},
"dataset": {
"granularity": "Monthly",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"sorting": [
{ "direction": "Ascending", "name": "BillingMonth" }
]
},
"includeActualCost": true,
"includeFreshPartialCost": true
}
```

> 💡 **Tip:** Monthly granularity uses the `BillingMonth` column in the response.

---

## 3. Forecast for Resource Group Scope

```json
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "<start-date>",
"to": "<end-date>"
},
"dataset": {
"granularity": "Daily",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"sorting": [
{ "direction": "Ascending", "name": "UsageDate" }
]
},
"includeActualCost": true,
"includeFreshPartialCost": true
}
```

> 💡 **Tip:** Scope is set at the URL level. Use the resource group scope URL to limit the forecast.

---

## 4. Forecast for Billing Account Scope

```json
{
"type": "ActualCost",
"timeframe": "Custom",
"timePeriod": {
"from": "<start-date>",
"to": "<end-date>"
},
"dataset": {
"granularity": "Monthly",
"aggregation": {
"totalCost": { "name": "Cost", "function": "Sum" }
},
"sorting": [
{ "direction": "Ascending", "name": "BillingMonth" }
]
},
"includeActualCost": true,
"includeFreshPartialCost": true
}
```

> 💡 **Tip:** Use URL pattern `/providers/Microsoft.Billing/billingAccounts/<id>/...`. Monthly granularity recommended for billing account forecasts.

---

## Scope URL Reference

| Scope | URL Pattern |
|---|---|
| Subscription | `/subscriptions/<subscription-id>/providers/Microsoft.CostManagement/forecast` |
| Resource Group | `/subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.CostManagement/forecast` |
| Billing Account | `/providers/Microsoft.Billing/billingAccounts/<id>/providers/Microsoft.CostManagement/forecast` |

> 💡 **Tip:** These are path-only patterns — not complete URLs. Append `?api-version=2023-11-01` when constructing the full request URL.
Loading
Loading