> For the complete documentation index, see [llms.txt](https://docs.groundcover.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.groundcover.com/integrations/data-sources/aws/aws-cost-and-usage.md).

# AWS Cost & Usage

Connect your AWS Cost and Usage Report (CUR 2.0) as a data source to see cloud spend inside groundcover, right next to your telemetry. Point it at a single account, or at your **management account** for organization-wide cost.

<figure><img src="/files/dsfDDOaYVmuETAGbfL77" alt=""><figcaption></figcaption></figure>

### What you get

* The ready-made **AWS Cost Overview** dashboard from the dashboard catalog, showing total spend, spend over time, and spend by service.
* **Data Explorer** with the **Cloud Costs** source to slice the data yourself. Group by service, account, region, operation, resource, or your cost-allocation tags.
* Four cost measures, because the totals genuinely differ. Each is a per-line-item figure, so none of them is your invoice total on its own: credits, taxes and refunds arrive as their own charge types, which the **Spend by charge type** breakdown separates out.

  * **unblended** - the rate each line item was charged at, with no amortization.
  * **net unblended** - unblended, after your negotiated discounts.
  * **amortized** - spreads Reserved-Instance and Savings-Plan fees across the term they cover, instead of landing them in the month they were billed.
  * **net amortized** - amortized, after discounts.

  Data Explorer opens on unblended; the AWS Cost Overview dashboard uses net unblended.
* Automatic refresh: groundcover scans for new reports every **12 hours** by default. The first report arrives within **24–48 hours** of connecting, and the current day is always partial.

<figure><img src="/files/S6jmlYl0juttob5SKVT5" alt=""><figcaption></figcaption></figure>

### Setup

Start from **Add a data source → AWS Cost & Usage** in the app (Admin only).

Whatever path you take, the one resource that has to be created in your AWS account is an **IAM role** groundcover can assume. The export and the bucket can be ones you already have.

#### New export, or one you already have

Step 1 of the wizard has two toggles. Set them to match what you already have in AWS:

| If you                            | Set the toggles to           | groundcover creates                     |
| --------------------------------- | ---------------------------- | --------------------------------------- |
| have no CUR export yet            | both **on** (the default)    | The bucket, the export, and the role    |
| have a bucket you want reports in | **Create the S3 bucket** off | The export in your bucket, and the role |
| already run a CUR 2.0 export      | both **off**                 | Only the role. Nothing else is touched  |

**Reusing an export** matches it by the **Name** you enter, and works when the export is CUR 2.0 with **hourly** granularity, **resource IDs**, **split cost allocation data**, **overwrite** delivery, and **Parquet** compression. The name also has to be letters, numbers, and dashes, short enough for `<name>-reader` to fit AWS's 64-character role-name limit. If anything doesn't match, including the name, leave **Create the CUR 2.0 Data Export** on and add a second export next to your current one.

{% hint style="info" %}
**Split cost allocation data** also needs an account-level opt-in, which no export setting can do for you. In your management account, go to AWS Billing and Cost Management → **Cost Management preferences** and turn it on for ECS, EKS, or both. See AWS's [Enabling split cost allocation data](https://docs.aws.amazon.com/cur/latest/userguide/enabling-split-cost-allocation-data.html).
{% endhint %}

The bucket must live in **us-east-1**, where AWS Data Exports operates. The **export path prefix** has to match the folder your export actually writes to: the read access groundcover is granted is scoped to that prefix, so a value that doesn't match means every read is denied.

The prefix is required, so an export that delivers to the root of the bucket can't be reused as it is. Point a new export at a prefix instead, or edit the existing one to write under a folder.

#### CloudFormation or Terraform

Step 2 of the wizard offers both, pre-filled with your values, and they create the same resources.

1. Click **Launch CloudFormation** (the stack opens in a new tab, pre-filled), or copy the **Terraform** snippet and apply it as a self-contained `main.tf`.
2. Review and create.
3. Paste the full **`RoleArn`** output (`role_arn` in Terraform) into step 3, for example `arn:aws:iam::123456789012:role/my-cur-connection-reader`.

Use a distinct connection name per CUR connection. The stack and the role are keyed to it, so two connections sharing a name collide on AWS names that are unique per account.

#### Manual setup

Use this when you can't run a stack in the account, for example when CUR lives in an organization payer account you don't provision.

**Already have a CUR export you want to reuse? Skip to step 3.** The IAM role is the only thing you need to add. Steps 1 and 2 create a new export.

**1. Let AWS write to the delivery bucket**

AWS checks the delivery bucket's policy when the export is created and won't create the export if the policy doesn't allow the write.

Creating the export in the console? AWS offers to attach the policy for you, and there is nothing to write by hand.

{% hint style="warning" %}
On an **existing** bucket the console asks you to acknowledge that Data Exports will **overwrite the bucket's current policy**. If that bucket carries policy statements you need to keep, write the policy yourself with those statements merged in, rather than accepting the overwrite.
{% endhint %}

Setting the export up through the API, CloudFormation, or Terraform instead? Nothing attaches the policy for you, so add it before creating the export. This is the statement AWS asks for, with `BUCKET_NAME` and `ACCOUNT_ID` (the account the export is created in) replaced:

```json
{
  "Sid": "EnableAWSDataExportsToWriteToS3",
  "Effect": "Allow",
  "Principal": { "Service": ["bcm-data-exports.amazonaws.com"] },
  "Action": ["s3:PutObject"],
  "Resource": "arn:aws:s3:::BUCKET_NAME/*",
  "Condition": {
    "ArnLike": {
      "aws:SourceArn": "arn:aws:bcm-data-exports:us-east-1:ACCOUNT_ID:export/*"
    },
    "StringEquals": {
      "aws:SourceAccount": "ACCOUNT_ID"
    }
  }
}
```

That is write access to the objects only. It grants no read or delete, and the two conditions keep it to exports created by your own account. See AWS's [Setting up an Amazon S3 bucket for data exports](https://docs.aws.amazon.com/cur/latest/userguide/dataexports-s3-bucket.html) for the canonical version.

A bucket that also serves a **legacy CUR** (pre-2.0) needs a second statement for the older `billingreports.amazonaws.com` principal. CUR 2.0 alone does not.

**2. Create the export**

In AWS Billing → **Data Exports**, create a **Standard data export (CUR 2.0)**:

* Content: **Include resource IDs**, **Split cost allocation data**
* Delivery: **Hourly** granularity, **Overwrite existing data export file**, **Parquet** compression
* Deliver to that same bucket, which must be in **us-east-1**

For the full console walkthrough, see AWS's [Creating Standard data exports](https://docs.aws.amazon.com/cur/latest/userguide/dataexports-create-standard.html) guide.

**3. Locate the export details**

Note the bucket name, the bucket region (**us-east-1**), the export name, and the export path prefix.

{% hint style="warning" %}
**The export path prefix does not include the bucket name.** It is the directory inside the bucket, the same value AWS calls **S3 path prefix** on the export. For a bucket `my-cost-bucket` holding reports under `cost/hourly`, the wizard's S3 bucket is `my-cost-bucket` and the export path prefix is `cost/hourly`, with no leading or trailing `/`. Enter it with the bucket name included and every read is denied, because the two are joined into the path groundcover is granted access to. The prefix is required.
{% endhint %}

groundcover finds new reports by listing that prefix. There is nothing to wire up for notifications.

**4. Create the IAM role**

Create this role **in the account that owns the delivery bucket**. The permissions policy below grants the role access to the bucket, which is enough within one account. A role in a different account would also need a matching grant in the bucket policy (and in the KMS key policy on an encrypted bucket), so keep the two together.

This is the role groundcover's integrations agent assumes to read your reports. Name it `<name>-reader`, matching the connection name you use in the wizard. That is the name the CloudFormation and Terraform paths use, and keeping it consistent means the ARN the wizard suggests is the one you created.

The policy below lets the role do two things, both confined to your export prefix: read the report objects, and set one tag on each report it has processed so the same delivery isn't read twice. Tagging attaches a label to an object that already exists, so the role cannot create, alter or delete anything.

{% hint style="info" %}
**Which agent role ARN?** Use the exact `INTEGRATIONS_AGENT_ROLE_ARN` the wizard shows in the **Setup Manually** tab, not any other integration role you run. It identifies the agent belonging to this workspace, and it is the only one that can assume your `<name>-reader` role.

`AGENT_ACCOUNT_ID` in the trust policy below is not a separate value to go and find: it is the 12-digit account number inside that same ARN, `arn:aws:iam::AGENT_ACCOUNT_ID:role/<name>`.
{% endhint %}

Permissions policy:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadCurObjects",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:GetObjectTagging"],
      "Resource": "arn:aws:s3:::BUCKET_NAME/EXPORT_PATH_PREFIX/*"
    },
    {
      "Sid": "TagCurObjects",
      "Effect": "Allow",
      "Action": ["s3:PutObjectTagging"],
      "Resource": "arn:aws:s3:::BUCKET_NAME/EXPORT_PATH_PREFIX/*"
    },
    {
      "Sid": "ListCurBucket",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::BUCKET_NAME",
      "Condition": {
        "StringLike": {
          "s3:prefix": ["EXPORT_PATH_PREFIX", "EXPORT_PATH_PREFIX/*"]
        }
      }
    },
    {
      "Sid": "GetCurBucketLocation",
      "Effect": "Allow",
      "Action": ["s3:GetBucketLocation"],
      "Resource": "arn:aws:s3:::BUCKET_NAME"
    }
  ]
}
```

The two tagging actions are how groundcover avoids reading the same delivery twice: it tags each data file it has processed and checks that tag on the next scan. Leave them out and ingestion still works, but already-processed deliveries get read again. Existing tags on your objects are preserved.

Replace `EXPORT_PATH_PREFIX` with the same prefix you enter in the wizard. The two have to agree: groundcover lists the bucket with the value on the connection, and this policy is what allows it. On a shared bucket the prefix also keeps the role scoped to your export only.

If the bucket uses SSE-KMS, also allow `kms:Decrypt` and `kms:DescribeKey` on the bucket's key.

Trust policy, using both values from the agent role ARN above:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::AGENT_ACCOUNT_ID:root" },
      "Action": "sts:AssumeRole",
      "Condition": {
        "ArnEquals": { "aws:PrincipalArn": "INTEGRATIONS_AGENT_ROLE_ARN" }
      }
    }
  ]
}
```

**5. Connect groundcover**

In the wizard, enter the name, S3 bucket, and export path prefix, then paste the **Reader role ARN** of the role you just created.

Optionally add **custom labels**. They are stamped on every cost row from this connection, so you can slice spend by team, environment, or owner.

Once data arrives, install the **AWS Cost Overview** dashboard from the [catalog](/use-groundcover/dashboards-and-alerts/dashboard-catalog.md), or explore the raw data yourself in Data Explorer.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.groundcover.com/integrations/data-sources/aws/aws-cost-and-usage.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
