# Cost query API

> Query reconciled cost data (by service, region, team, tag, or any combination) with grouping, filtering, and time-range pagination.

Source: https://zop.dev/docs/zopnight/api/cost-query

---

## Base URL and auth

```
https://api.zop.dev/v1
```

All requests require a personal access token in the `Authorization: Bearer …` header. The Cost query endpoints are read-only and accept any PAT that has been granted the `cost:read` scope (granted by default to every PAT).

## Query reconciled cost

```
POST /cost/query
```

The cost query endpoint returns reconciled spend across any combination of dimensions, filters, and time ranges. Results are **already reconciled to your actual billing export** (Cost Explorer, BigQuery, Cost Management); no rack-rate conversion, no estimates.

### Request body

```json
{
  "timeRange": {
    "start": "2026-05-01",
    "end":   "2026-05-31",
    "granularity": "day"
  },
  "groupBy": ["service", "team"],
  "filters": {
    "providers": ["aws", "gcp"],
    "environments": ["prod", "stage"],
    "tags": {
      "include": [{ "key": "cost-center", "value": "platform" }],
      "exclude": [{ "key": "ignore-cost", "value": "true" }]
    }
  },
  "currency": "USD",
  "page": 1,
  "pageSize": 1000
}
```

### Example

```bash
curl -X POST "https://api.zop.dev/v1/cost/query" \
  -H "Authorization: Bearer $ZOPNIGHT_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "timeRange": { "start": "2026-05-01", "end": "2026-05-31", "granularity": "day" },
    "groupBy": ["service", "team"],
    "filters": { "providers": ["aws", "gcp"] },
    "currency": "USD"
  }'
```

### Supported `groupBy` dimensions

- `provider`; `aws`, `gcp`, `azure`.
- `account`; cloud account ID.
- `service`; provider service (e.g. `ec2`, `rds`, `cloud-storage`, `azure-functions`).
- `resourceType`; sub-service type.
- `region`; provider region.
- `environment`; `prod`, `stage`, `uat`, `dev`.
- `team`; owning team slug.
- `tag:<key>`; group by a tag key, e.g. `tag:cost-center`.
- `resource`; resource-level breakdown (limited to 10k results per query).

You can group by up to 4 dimensions per query. Order matters for cardinality; put the lowest-cardinality dimension first.

### Granularity

- `day`; daily buckets. Max 92-day range per query.
- `month`; monthly buckets. Max 24-month range per query.
- `hour`; hourly buckets. Max 7-day range per query. Available only on the Enterprise tier.

### Response

```json
{
  "data": [
    {
      "service": "ec2",
      "team": "payments-platform",
      "buckets": [
        { "date": "2026-05-01", "amount": 412.18, "currency": "USD" },
        { "date": "2026-05-02", "amount": 408.94, "currency": "USD" }
      ],
      "total": { "amount": 12438.21, "currency": "USD" }
    }
  ],
  "pagination": { "page": 1, "pageSize": 1000, "total": 47 },
  "reconciliation": {
    "billingExport": "aws-cur-2026-05",
    "lastReconciledAt": "2026-06-01T03:12:00Z"
  }
}
```

## Anomaly detection

```
GET /cost/anomalies
```

Returns anomalies detected against the 7-day rolling average across five dimensions: org, cloud account, resource group, resource, and team. See [Cost anomalies](https://zop.dev/docs/zopnight/reports/anomalies) for the detection method and severity bands.

### Query parameters

| Name | Type | Description |
|---|---|---|
| `since` | date | ISO-8601 date. Default: 7 days ago. |
| `minDelta` | number | Minimum dollar delta to include. Default: $100/day. |
| `dimension` | enum | Filter to one dimension. |

## Budgets

```
GET /cost/budgets
POST /cost/budgets
PATCH /cost/budgets/:id
DELETE /cost/budgets/:id
```

Budgets are computed live from the Cost Graph; there is no nightly snapshot delay. A budget update takes effect on the next query.

### Budget object

```json
{
  "id": "bud_01HK3...",
  "name": "Payments. May 2026",
  "scope": { "team": "payments-platform" },
  "period": "month",
  "amount": 50000,
  "currency": "USD",
  "alerts": [
    { "threshold": 0.7, "channels": ["slack:#payments-finops"] },
    { "threshold": 0.9, "channels": ["slack:#payments-finops", "email:lead@example.com"] }
  ]
}
```

## Export

For large pulls (resource-level, full month, every account), use the **export** flow:

```
POST /cost/export
GET  /cost/export/:id
```

The first call enqueues an export job; the second polls its status. Completed exports are delivered as CSV, JSON, or Parquet to a signed URL; or directly to your S3 / GCS / Azure Blob bucket on the Enterprise tier.

## Rate limits

- **Read endpoints** (`POST /cost/query`, `GET /cost/anomalies`, `GET /cost/budgets`, `GET /cost/export/:id`): 600 requests/min per workspace.
- **Write and export endpoints** (`POST /cost/export`, budget create/update/delete): 60 requests/min per workspace.

Headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` are returned on every response.

## Errors

Errors use standard HTTP status codes. The body carries a machine-readable `code`, a human-readable `message`, and the numeric `status`.

| Status | Meaning |
|---|---|
| `400` | Invalid or malformed parameters (bad `granularity`, range over the granularity limit, unknown `groupBy` dimension). |
| `401` | Missing or invalid token. |
| `403` | Token lacks the required scope (e.g. `cost:read`, or write scope for budget mutations). |
| `404` | Budget or export job not found, or not visible to the token's role. |
| `429` | Rate limited; see [Rate limits](#rate-limits). |
| `5xx` | Server error; safe to retry with backoff. |

```json
{
  "error": {
    "code": "invalid_request",
    "message": "granularity \"hour\" allows a maximum range of 7 days",
    "status": 400
  }
}
```
