State & Resource Cost

The state endpoints price a single Terraform state and return a per-resource, per-component breakdown. All cost endpoints require authentication and return monetary fields as strings.

GET  /api/v1/states/{state_id}/costs             # latest snapshot + per-resource breakdown
POST /api/v1/states/{state_id}/costs/calculate   # recompute now
GET  /api/v1/states/{state_id}/costs/unsupported  # resources that didn't contribute (gaps)

Price a State

Fetch the latest snapshot for a state:

curl -H "Authorization: Bearer $STATEGRAPH_API_KEY" \
  http://localhost:8080/api/v1/states/$STATE_ID/costs
{
  "snapshot_id": "…",
  "calculated_at": "2026-06-09T06:15:01Z",
  "currency": "USD",
  "monthly_cost": "1509.640000",
  "hourly_cost": "2.067000",
  "resource_count": 24,
  "supported_count": 24,
  "priced_count": 12,
  "coverage_percent": 50.0,
  "instance_costs": [ … ]
}

A 404 means the state has never been priced; calculate it first.

The headline fields:

Field Meaning
monthly_cost / hourly_cost Total estimated cost of the priced resources (string; absent if nothing is priced)
currency Currency of the estimate (for example, USD)
resource_count Resources in the state
supported_count Resources whose type the pricing engine recognizes
priced_count Resources that produced a non-zero price
coverage_percent priced_count / resource_count; read every total next to this

Per-Resource Breakdown

instance_costs[] holds one entry per resource, and each entry's components[] breaks the price into line items such as instance hours, storage, and requests. Here is a real aws_db_instance:

{
  "address": "aws_db_instance.primary",
  "type": "aws_db_instance",
  "provider": "aws",
  "monthly_cost": "656.270000",
  "hourly_cost": "0.899000",
  "supported": true,
  "no_price": false,
  "components": [
    {
      "name": "Database instance (on-demand, Multi-AZ, db.r6g.xlarge)",
      "unit": "hours",
      "price": "0.8990000000",
      "hourly_quantity": "1",
      "monthly_quantity": "730",
      "hourly_cost": "0.899000",
      "monthly_cost": "656.270000"
    },
    {
      "name": "Storage (general purpose SSD, gp3)",
      "unit": "GB",
      "price": "0.0000000000",
      "monthly_quantity": "200"
    }
  ],
  "tags": { "Team": "data", "Environment": "production", "Project": "acme" },
  "cloud_resource_id": "arn:aws:rds:us-east-1:…:db:acme-primary"
}

Each resource also carries its tags, which power cost attribution, and its cloud_resource_id when one is known.

Calculate or Refresh

Trigger a fresh recompute. This returns 202 with a task and runs in the background; a new snapshot is written when it finishes. It returns 503 if the server has no pricing service configured.

curl -X POST -H "Authorization: Bearer $STATEGRAPH_API_KEY" \
  http://localhost:8080/api/v1/states/$STATE_ID/costs/calculate
{ "id": "ca019a18-…", "state": "pending" }

Poll the task until it reaches a terminal state, then re-fetch the cost:

curl -H "Authorization: Bearer $STATEGRAPH_API_KEY" \
  http://localhost:8080/api/v1/tasks/$TASK_ID
# { "id": "ca019a18-…", "state": "completed" }

Stop when state is completed; treat failed or aborted as an error. You normally don't need to do this by hand, since snapshots refresh on a schedule and after applies, but it is useful when cost data is missing or stale.

Coverage Gaps

List the resources that did not contribute to the totals, either unsupported types or recognized types with nothing billable (no_price):

curl -H "Authorization: Bearer $STATEGRAPH_API_KEY" \
  http://localhost:8080/api/v1/states/$STATE_ID/costs/unsupported
{
  "snapshot_id": "…",
  "calculated_at": "2026-06-09T06:15:01Z",
  "resources": [
    { "address": "aws_db_parameter_group.postgres16", "type": "aws_db_parameter_group",
      "provider": "aws", "supported": true, "no_price": true },
    { "address": "aws_db_subnet_group.main", "type": "aws_db_subnet_group",
      "provider": "aws", "supported": true, "no_price": true }
  ]
}

Surface this whenever you report a total. A high gap count usually means free-to-create resources, such as parameter groups, subnet groups, and IAM, rather than missing prices.

Next Steps