DOCS

Connect your AWS account

The scanner is read-only — it only reads cost, metrics and resource metadata. Grant access with read-only keys or a cross-account role. Both Console and CLI steps are below.

Choose a method

MethodBest forStores a secret?Setup
A. Read-only IAM user (access keys)Testing / your own accountYes (encrypted at rest)Easiest — paste keys
B. Cross-account IAM role (AssumeRole)Production / third-party accountsNoMore secure — paste a Role ARN
💡 Recommendation: use access keys (A) to try it on your own account, and a role (B) for production or accounts you don’t own — nothing secret is stored and you can revoke access instantly.

Permissions the optimizer needs

Option 1 — AWS-managed policy (simplest)

Attach ReadOnlyAccess (arn:aws:iam::aws:policy/ReadOnlyAccess). It covers everything the scanner uses — nothing to maintain.

Option 2 — Minimal custom policy (least privilege)

Grant only what’s used. Create a policy from this document — every action is read-only:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "BillingOptimizerReadOnly",
      "Effect": "Allow",
      "Action": [
        "ce:GetCostAndUsage",
        "ce:GetSavingsPlansCoverage",
        "ce:GetRightsizingRecommendation",
        "ce:GetReservationPurchaseRecommendation",
        "ce:GetSavingsPlansPurchaseRecommendation",
        "ce:GetAnomalies",
        "ec2:Describe*",
        "elasticloadbalancing:Describe*",
        "rds:DescribeDBInstances",
        "rds:DescribeDBSnapshots",
        "rds:DescribeDBClusters",
        "elasticache:DescribeCacheClusters",
        "dynamodb:ListTables",
        "dynamodb:DescribeTable",
        "redshift:DescribeClusters",
        "es:ListDomainNames",
        "es:DescribeElasticsearchDomains",
        "ecs:ListClusters",
        "ecs:ListServices",
        "ecs:DescribeServices",
        "ecs:DescribeTaskDefinition",
        "eks:ListClusters",
        "eks:ListNodegroups",
        "eks:DescribeNodegroup",
        "eks:ListFargateProfiles",
        "lambda:ListFunctions",
        "lambda:ListProvisionedConcurrencyConfigs",
        "s3:ListAllMyBuckets",
        "s3:GetBucketLocation",
        "s3:GetBucketLifecycleConfiguration",
        "cloudwatch:GetMetricStatistics",
        "compute-optimizer:GetEC2InstanceRecommendations",
        "compute-optimizer:GetEBSVolumeRecommendations",
        "compute-optimizer:GetLambdaFunctionRecommendations",
        "pricing:GetProducts",
        "sts:GetCallerIdentity"
      ],
      "Resource": "*"
    }
  ]
}

Method A — Read-only IAM user with access keys

▸ AWS Console
  1. IAM → Users → Create user
  2. User name: billing-optimizer-readonly
  3. Do NOT tick “Provide user access to the AWS Management Console” — we only need programmatic keys
  4. Next → Attach policies directly → search ReadOnlyAccess and select it
    (or least-privilege: Create policy → JSON → paste the policy above → name it BillingOptimizerReadOnly → select it)
  5. Next → Create user
  6. Open the user → Security credentialsCreate access key
  7. Choose “Application running outside AWS”Create access key
  8. Copy the Access Key ID + Secret Access Key (the secret is shown only once)
▸ AWS CLI
# 1. create the user
aws iam create-user --user-name billing-optimizer-readonly

# 2a. attach the managed read-only policy (simplest)
aws iam attach-user-policy \
  --user-name billing-optimizer-readonly \
  --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess

# 2b. (OR) create + attach the minimal custom policy
#     save the JSON above as readonly-policy.json first
aws iam create-policy --policy-name BillingOptimizerReadOnly \
  --policy-document file://readonly-policy.json
aws iam attach-user-policy --user-name billing-optimizer-readonly \
  --policy-arn arn:aws:iam::<YOUR_ACCOUNT_ID>:policy/BillingOptimizerReadOnly

# 3. create the access key (prints AccessKeyId + SecretAccessKey ONCE)
aws iam create-access-key --user-name billing-optimizer-readonly
▸ Use it in the app

Dashboard → + Add account → Access keys → paste the Access Key ID + Secret → leave Regions blank to scan all regions (or type e.g. eu-west-2) → Save. The app validates the keys via STS and auto-detects your account ID.

Method B — Cross-account IAM role (AssumeRole)

No secret is stored — the app assumes a role to get temporary credentials. You need a Hub account ID (the identity the app runs with) and an External ID (any shared secret).

⚠️ For a hosted deployment, the app must run with a base “hub” identity allowed to call sts:AssumeRole. On a plain Render deploy with no AWS credentials, use Method A; use roles when the app runs on AWS (EC2/ECS task role) or with a hub IAM user configured.
▸ AWS Console (in the account to be scanned)
  1. IAM → Roles → Create role
  2. Trusted entity: AWS account → Another AWS account → enter the Hub account ID
  3. Tick Require external ID → enter your External ID (e.g. optimizer-9f3c-7a21)
  4. Next → Permissions → attach ReadOnlyAccess (or your custom policy)
  5. Name it BillingOptimizerReadOnlyCreate role
  6. Copy the Role ARN (e.g. arn:aws:iam::111111111111:role/BillingOptimizerReadOnly)
▸ AWS CLI
# 1. write the trust policy (who may assume the role)
cat > trust-policy.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::HUB_ACCOUNT_ID:root" },
    "Action": "sts:AssumeRole",
    "Condition": { "StringEquals": { "sts:ExternalId": "optimizer-9f3c-7a21" } }
  }]
}
JSON

# 2. create the role with that trust policy
aws iam create-role --role-name BillingOptimizerReadOnly \
  --assume-role-policy-document file://trust-policy.json

# 3. attach read-only permissions
aws iam attach-role-policy --role-name BillingOptimizerReadOnly \
  --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess

# 4. print the Role ARN to paste into the app
aws iam get-role --role-name BillingOptimizerReadOnly \
  --query 'Role.Arn' --output text
▸ Use it in the app

Dashboard → + Add account → Cross-account role → paste the Role ARN and the External IDSave.

Verify & revoke

The app calls sts:GetCallerIdentity when you add an account and rejects bad credentials with a clear message. Test keys yourself:

AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... aws sts get-caller-identity

Clean up when you’re done:

# access keys
aws iam delete-access-key --user-name billing-optimizer-readonly --access-key-id AKIA...
aws iam detach-user-policy --user-name billing-optimizer-readonly \
  --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
aws iam delete-user --user-name billing-optimizer-readonly

# role
aws iam detach-role-policy --role-name BillingOptimizerReadOnly \
  --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
aws iam delete-role --role-name BillingOptimizerReadOnly

In the Console you can also just deactivate/delete the access key or delete the role in IAM.

Security notes

Get started free →