Some AWS services bill by workload, not by declared capacity. A bucket, queue, or topic can appear in a diff while the diff says nothing about storage, request volume, or data transfer. CloudBurn prices S3, SQS, and SNS usage from monthly assumptions that live in your repository, and every assumed cost in the report is labeled as an assumption, never presented as a fact.
This page is the schema reference for that file: .cloudburn/usage-assumptions.json at the root of your repository.
How the file reaches CloudBurn
The CloudBurn GitHub App never reads repository files. The aws-cdk-diff-pr-commenter and terraform-plan-pr-commenter actions read .cloudburn/usage-assumptions.json at the pull request head and base commits and embed both snapshots into the diff comment inside a collapsed block. The app prices the head snapshot as the current state and the base snapshot as the previous state, so a pull request that only edits assumptions still shows its real cost delta.
Three producer rules to know:
- A file over 65,536 bytes is not embedded; the app reports it as a configuration error.
- Invalid JSON, duplicate JSON keys, or unknown fields fail the whole assumptions block with one explicit error. CloudBurn flags the estimate as incomplete rather than silently pricing without your numbers.
- Repositories without the file get the same diff comment and cost report as before — assumptions are opt-in.
Top-level fields
{
"schemaVersion": 2,
"revision": "2026-07-01",
"freeTier": "disabled",
"accountContext": {},
"organizationDefaults": {},
"repositoryDefaults": {},
"resourceTypeDefaults": {},
"resources": {}
}
| Field | Type | Purpose |
|---|---|---|
schemaVersion | 1 or 2 | Required. Use 2; accountContext and free-tier handling need it. |
revision | string or number | Optional label for your own change tracking. |
freeTier | "enabled"/"disabled" | Free-tier policy. Defaults to "disabled". Also accepted per layer and per resource. |
accountContext | object | Account-wide usage and remaining free-tier allowances. Schema v2 only. |
organizationDefaults | object | Broadest defaults, explicitly copied into the repository. |
repositoryDefaults | object | Repository-wide defaults. |
resourceTypeDefaults | object | Defaults per CloudFormation resource type, such as AWS::S3::Bucket. |
resources | object | Per-resource overrides keyed by the stable IaC address. |
Supported resource types for usage pricing: AWS::S3::Bucket, AWS::SQS::Queue, and AWS::SNS::Topic.
Resolution precedence
A value resolves from the narrowest layer that supplies it. The layers below are listed in application order from broadest to narrowest — when several define the same field, the last (narrowest) matching layer wins:
organizationDefaults— broadestrepositoryDefaultsresourceTypeDefaults["AWS::S3::Bucket"]resources["<address>"]— narrowest, always wins
Keys under resources are stable IaC addresses: the CDK construct path such as StorageStack/ReportsBucket, or the Terraform address such as module.storage.aws_s3_bucket.reports. Wildcard selectors are rejected. A bare CDK construct name without its stack works only while it matches exactly one changed resource; an ambiguous selector fails the whole block. The report attributes every assumed component to the scope and field that supplied its quantity.
Quantities and units
Every quantity is monthly and carries an explicit unit. A bare number uses the measure's canonical unit; an object form converts to it:
{ "value": 2, "unit": "TB" }
| Measure | Canonical unit | Accepted units |
|---|---|---|
storage | GB-Mo | GB-Mo, GB-month, GB, TB-Mo, TB |
requests | requests | requests, thousand-requests, k-requests, million-requests |
dataTransfer | GB | GB, GB-Mo, TB, TB-Mo |
count | objects | objects, thousand-objects, million-objects |
Unknown units, negative values, and any period other than "month" are rejected.
S3 fields
Set these under an s3 group at any layer. A missing field affects only its own cost component.
| Field | Measure | Prices |
|---|---|---|
s3.storage.standardGbMonth | storage | Standard storage (carries the opt-in free tier) |
s3.storage.standardIaGbMonth | storage | Standard-IA storage |
s3.storage.oneZoneIaGbMonth | storage | One Zone-IA storage |
s3.storage.glacierIrGbMonth | storage | Glacier Instant Retrieval storage |
s3.storage.glacierFlexibleGbMonth | storage | Glacier Flexible Retrieval storage |
s3.storage.deepArchiveGbMonth | storage | Glacier Deep Archive storage |
s3.storage.intelligentTieringFaGbMonth | storage | Intelligent-Tiering frequent-access storage |
s3.requests.tier1 | requests | PUT, COPY, POST, and LIST requests |
s3.requests.tier2 | requests | GET, SELECT, and all other requests |
s3.lifecycleTransitions.standardIa | requests | Transitions to Standard-IA |
s3.lifecycleTransitions.oneZoneIa | requests | Transitions to One Zone-IA |
s3.lifecycleTransitions.glacierIr | requests | Transitions to Glacier Instant Retrieval |
s3.lifecycleTransitions.intelligentTiering | requests | Transitions to Intelligent-Tiering |
s3.lifecycleTransitions.glacierFlexible | requests | Transitions to Glacier Flexible Retrieval |
s3.lifecycleTransitions.deepArchive | requests | Transitions to Glacier Deep Archive |
s3.retrieval.glacierIrGb | dataTransfer | Glacier Instant Retrieval data retrieval |
s3.retrieval.glacierFlexibleGb | dataTransfer | Glacier Flexible Retrieval data retrieval |
s3.retrieval.deepArchiveGb | dataTransfer | Glacier Deep Archive data retrieval |
s3.dataTransfer.outToInternetGb | dataTransfer | Outbound data transfer to the internet |
s3.monitoring.intelligentTieringObjects | count | Intelligent-Tiering monitoring, billed per object |
s3.monitoring.objects | count | Fallback monitored-object count, used when Intelligent-Tiering is configured but the specific field above is not set |
CloudBurn never infers usage from existence: it does not guess request volume from object counts or stored bytes from a bucket's presence. Two fields stay intentionally ambiguous and report as unknown: s3.dataTransfer.gb (no direction) and the legacy s3.requests.lifecycleTransition (AWS prices each transition destination differently).
SQS fields
| Field | Measure | Prices |
|---|---|---|
sqs.requests | requests | Standard or FIFO billable request units per month |
The adapter picks the Standard or FIFO price from the queue's declared FifoQueue property. Supply already-normalized AWS billable request units: account for batching, then round each payload up in 64 KB chunks. One API action with a 256 KB payload contributes four request units. All queues share one sqs.requests account pool and free-tier pool, and Standard requests claim the shared allowance before FIFO requests so resource order cannot change the total.
SNS fields
| Field | Measure | Prices or result |
|---|---|---|
sns.publishedMessages | requests | Standard or FIFO billable publish request units |
sns.deliveries.http | requests | Standard HTTP/S billable delivery units |
sns.deliveries.email | requests | Standard email billable delivery units |
sns.deliveries.sqs | requests | Standard or FIFO SQS billable delivery units |
sns.deliveries.lambda | requests | Standard Lambda billable delivery units |
sns.deliveries.mobilePush | requests | Standard mobile push billable delivery units |
sns.deliveries.messages | requests | Ambiguous without a delivery protocol; reported as unknown |
sns.deliveries.sms | requests | Unsupported — SMS pricing needs destination-specific inputs; stays unknown |
These fields also take normalized billable units: apply publish batching first, then round Standard publish and non-SMS delivery payloads up in 64 KB chunks. The adapter selects Standard or FIFO pricing from the topic's declared FifoTopic property. FIFO topics support SQS delivery pricing only; FIFO payload-volume charges and data transfer remain explicitly unknown until payload-size assumptions are supported.
Account usage and graduated tiers
AWS applies graduated pricing tiers across an account, not per resource. Put the billable usage that already occupies each tier under accountContext.usage, and CloudBurn starts the changed resources after that quantity, advancing one shared pool per account as it prices each resource.
| Account pool | Canonical unit |
|---|---|
s3.storage.standard, s3.storage.standardIa, s3.storage.oneZoneIa | GB-Mo |
s3.storage.glacierIr, s3.storage.glacierFlexible, s3.storage.deepArchive | GB-Mo |
s3.storage.intelligentTieringFa | GB-Mo |
s3.requests.standardTier1, s3.requests.standardTier2 | requests |
s3.lifecycleTransitions.<destination> | requests |
s3.retrieval.glacierIr, s3.retrieval.glacierFlexible, s3.retrieval.deepArchive | GB |
s3.monitoring.intelligentTiering | objects |
awsDataTransfer.outboundInternet | GB |
sqs.requests | requests |
sns.requests.standard | requests |
sns.deliveries.http, sns.deliveries.email, sns.deliveries.mobile | requests |
For example, 100 GB of new Standard storage after 51,150 GB of existing usage spans 50 GB in the first tier and 50 GB in the second. A graduated component stays unknown when its account pool is missing — CloudBurn doesn't guess where your account sits in the tier.
Free tier
CloudBurn doesn't guess whether your account still has free allowance. Set "freeTier": "enabled" at the file, layer, or resource scope, then provide the remaining allowance under accountContext.remainingFreeTier:
{
"schemaVersion": 2,
"freeTier": "enabled",
"accountContext": {
"usage": { "s3.storage.standard": 0 },
"remainingFreeTier": { "s3.storage.standard": 5 }
}
}
The remaining 5 GB is consumed once across all buckets in a stable resource order. If free tier is enabled but a pool's remaining allowance is missing, that component stays unknown. Free-tier pools exist for sqs.requests, sns.requests.standard, sns.deliveries.http, sns.deliveries.email, sns.deliveries.mobile, s3.storage.standard, s3.requests.standardTier1, s3.requests.standardTier2, and awsDataTransfer.outboundInternet.
Validation limits
| Limit | Value |
|---|---|
| Maximum file size | 65,536 bytes |
| Maximum nesting depth | 8 |
| Maximum key length | 128 |
Maximum resources selectors | 500 |
| Maximum resource-type selectors | 200 |
| Maximum fields per profile | 300 |
Any violation, duplicate JSON key, or unknown field fails the whole assumptions block with one explicit configuration error in the cost report.
Complete example
{
"schemaVersion": 2,
"revision": "2026-07-01",
"freeTier": "disabled",
"accountContext": {
"usage": {
"s3.storage.standard": 50000,
"s3.requests.standardTier1": 1000000,
"s3.requests.standardTier2": 5000000,
"awsDataTransfer.outboundInternet": 10000
}
},
"repositoryDefaults": {
"s3": { "requests": { "tier2": { "value": 5, "unit": "million-requests" } } }
},
"resourceTypeDefaults": {
"AWS::S3::Bucket": {
"s3": { "storage": { "standardGbMonth": 100 } }
}
},
"resources": {
"StorageStack/ReportsBucket": {
"s3": {
"storage": { "standardGbMonth": { "value": 2, "unit": "TB" }, "deepArchiveGbMonth": 10000 },
"requests": { "tier1": { "value": 200, "unit": "thousand-requests" } },
"lifecycleTransitions": { "deepArchive": { "value": 50, "unit": "thousand-requests" } },
"retrieval": { "deepArchiveGb": 50 },
"dataTransfer": { "outToInternetGb": 1000 }
}
}
}
}
Here StorageStack/ReportsBucket overrides the resource-type storage default and adds archival storage, destination-specific transitions, retrieval, and outbound transfer. Every other AWS::S3::Bucket in the diff inherits 100 GB of Standard storage and 5 million monthly Tier 2 requests from the broader layers.
What's next
| Read this | To learn |
|---|---|
| GitHub App overview | How the app turns diff comments into cost reports |
| aws-cdk-diff-pr-commenter | The action that embeds assumptions for CDK diffs |
| terraform-plan-pr-commenter | The action that embeds assumptions for Terraform plans |