Some AWS services bill by workload, not by declared capacity. A bucket, queue, topic, log group, file system, function, API, stream, or network resource can appear in a diff while the diff says nothing about storage, request volume, message volume, queries, or data transfer. CloudBurn prices that 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. For worked configurations and guidance on where each number comes from, read Usage assumption examples.
One rule runs through every section below: CloudBurn never infers a quantity it was not given. It will not derive request volume from a bucket's existence, a transfer direction from an undirected byte total, or a write volume from a read volume. A missing input produces an unknown component that names what it needs — not a guess, and not a zero. The per-service sections call out where this bites hardest.
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, next to the Resource Change Set the app prices from. 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-08-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. |
Schema v1 and the legacy cloudburn-usage-assumptions comment fence still parse, but graduated account tiers and free-tier deductions need schema v2 accountContext.
Supported resource types
Usage assumptions apply to these resource types. Everything else in the diff keeps its existing fixed-price treatment.
| Service | Resource types |
|---|---|
| Amazon S3 | AWS::S3::Bucket |
| Amazon SQS | AWS::SQS::Queue |
| Amazon SNS | AWS::SNS::Topic |
| CloudWatch Logs | AWS::Logs::LogGroup |
| Amazon EFS | AWS::EFS::FileSystem |
| AWS Lambda | AWS::Lambda::Function |
| Amazon API Gateway | AWS::ApiGateway::RestApi, AWS::ApiGatewayV2::Api |
| Amazon Kinesis | AWS::Kinesis::Stream, AWS::Kinesis::StreamConsumer |
| Networking | AWS::EC2::NatGateway, AWS::EC2::VPNConnection, AWS::DirectConnect::Connection, AWS::DirectConnect::HostedConnection, AWS::GlobalAccelerator::Accelerator, AWS::Route53Resolver::ResolverEndpoint |
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 |
duration | ms | ms, milliseconds, seconds, s |
messages | messages | messages, thousand-messages, million-messages |
connectionMinutes | minutes | minutes, connection-minutes, thousand-minutes, million-minutes, hours |
computeGbSeconds | GB-s | GB-s, GB-seconds, million-GB-s |
payloadUnits | payload-units | payload-units, thousand-payload-units, million-payload-units |
queries | queries | queries, thousand-queries, million-queries, billion-queries |
Unknown units, negative values, and any period other than "month" are rejected.
duration is the one non-monthly measure: lambda.averageDurationMs is an average per invocation, and CloudBurn derives the monthly compute quantity from it. computeGbSeconds appears only in accountContext, never as an assumption field.
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.
A component whose AWS price has more than one tier stays unknown until its account pool is present. CloudBurn does not guess where your account already sits in the tier. Setting a pool to 0 is a valid answer and the right one for a fresh account, and a pool that turns out not to be needed changes nothing — so when in doubt, supply it. Single-rate dimensions such as SNS deliveries to SQS or Lambda need no pool at all.
Only the keys in the table below are accepted. A key that is not listed fails the whole assumptions block as an unsupported account-context field.
| 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.standardIa, s3.lifecycleTransitions.oneZoneIa, s3.lifecycleTransitions.glacierIr | requests |
s3.lifecycleTransitions.intelligentTiering, s3.lifecycleTransitions.glacierFlexible, s3.lifecycleTransitions.deepArchive | 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 |
logs.ingestion.standard, logs.ingestion.infrequentAccess | GB |
logs.storage | GB-Mo |
efs.storage.standard, efs.storage.oneZoneStandard, efs.storage.infrequentAccess | GB-Mo |
efs.storage.infrequentAccessElastic, efs.storage.oneZoneInfrequentAccess, efs.storage.archive | GB-Mo |
efs.access.infrequentAccess, efs.access.archive | GB |
lambda.requests | requests |
lambda.duration.x86, lambda.duration.arm, lambda.storage | GB-s |
apigateway.requests.rest, apigateway.requests.http | requests |
apigateway.websocket.messages | messages |
apigateway.websocket.connectionMinutes | minutes |
resolver.queries | queries |
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.
AWS publishes a separate graduated duration schedule per Lambda architecture, so lambda.duration.x86 and lambda.duration.arm advance independently.
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 }
}
}
Each allowance is consumed once across all changed resources in a stable order, so a report can never multiply the same free tier across resources. If free tier is enabled but an eligible pool's remaining allowance is missing, that component stays unknown. AWS catalog tiers that happen to start at zero are not treated as permission to claim a free tier — CloudBurn removes those embedded tiers and applies only the allowance you configure.
| Free-tier pool | Canonical unit | Full AWS allowance |
|---|---|---|
s3.storage.standard | GB-Mo | 5 |
s3.requests.standardTier1 | requests | 2,000 |
s3.requests.standardTier2 | requests | 20,000 |
awsDataTransfer.outboundInternet | GB | 100 |
sqs.requests | requests | 1,000,000 |
sns.requests.standard | requests | 1,000,000 |
sns.deliveries.http | requests | 100,000 |
sns.deliveries.email | requests | 1,000 |
sns.deliveries.mobile | requests | 1,000,000 |
logs.ingestion.standard | GB | 5 |
lambda.requests | requests | 1,000,000 |
lambda.duration | GB-s | 400,000 |
apigateway.requests.rest | requests | 1,000,000 |
apigateway.requests.http | requests | 1,000,000 |
apigateway.websocket.messages | messages | 1,000,000 |
apigateway.websocket.connectionMinutes | minutes | 750,000 |
Put the allowance you have left this month in remainingFreeTier, not the full figure above, unless the month is genuinely untouched.
Two ordering rules keep totals stable no matter how resources appear in the diff. SQS Standard requests claim the shared SQS allowance before FIFO requests, so the pricier FIFO rate stays billable. Lambda's compute allowance is one account-level pool (lambda.duration) shared by both architectures, and arm64 functions claim it before x86 for the same reason.
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 the bucket declares an enabled Intelligent-Tiering configuration but the specific field above is not set |
CloudBurn never infers usage from existence: it does not guess request volume from object counts, stored bytes from a bucket's presence, or a transfer direction from replication. 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).
CDK bucket properties and Terraform's separate lifecycle, replication, Intelligent-Tiering, and acceleration resources feed the same bucket model. Transfer acceleration, complex replication, S3 Express One Zone, Multi-Region Access Points, Tables, and Vectors stay unknown.
SQS fields
| Field | Measure | Prices |
|---|---|---|
sqs.requests | requests | Standard or FIFO billable request units per month |
CloudBurn 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. A missing request assumption stays unknown instead of being inferred from the queue's existence. CloudBurn records the Standard default for a no-options constructor or a fully visible literal without FIFO settings; indirect, spread, or incomplete options stay unknown rather than assuming Standard.
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. CloudBurn 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 stay explicitly unknown until payload-size assumptions are supported, so never fold raw bytes into sns.publishedMessages. HTTP, email, Lambda, mobile push, and SMS delivery assumptions on a FIFO topic are reported as unsupported unknowns rather than matched to Standard prices.
CloudWatch Logs fields
Set these under a logs group. The declared log group class (LogGroupClass, log_group_class, or the CDK logGroupClass prop) selects the Standard or Infrequent Access ingestion dimension; an omitted class keeps the CloudFormation Standard default. Storage bills under one dimension for both classes.
| Field | Measure | Prices |
|---|---|---|
logs.ingestedGb | dataTransfer | Log ingestion by declared class (carries the opt-in free tier) |
logs.storedGb | storage | Log storage per GB-month |
Stored volume is always assumed, never derived from retention — the declared retention is surfaced alongside the storage component so a reviewer can sanity-check the number. The 5 GB ingestion free tier applies only to Standard-class ingestion, once per report, through the logs.ingestion.standard pool. The Delivery log class, vended-log delivery, Logs Insights queries, Live Tail, metric filters, subscription delivery, and Intelligent Tiering access tiers stay unknown.
EFS fields
Set these under an efs group. A declared availability zone (AvailabilityZoneName, availability_zone_name, or the CDK oneZone flag) selects the One Zone storage dimensions; One Zone and Regional file systems never share a dimension. Archive is Regional-only, so Archive assumptions on a One Zone file system are reported as unsupported unknowns.
| Field | Measure | Prices |
|---|---|---|
efs.storage.standardGbMonth | storage | Standard (or One Zone) storage |
efs.storage.infrequentAccessGbMonth | storage | Infrequent Access (or One Zone-IA) storage |
efs.storage.archiveGbMonth | storage | Archive storage (Regional only) |
efs.access.infrequentAccessReadGb | dataTransfer | Infrequent Access read access |
efs.access.infrequentAccessWriteGb | dataTransfer | Infrequent Access write access |
efs.access.archiveReadGb | dataTransfer | Archive read access (Regional only) |
efs.access.archiveWriteGb | dataTransfer | Archive write access (Regional only) |
The declared throughput mode is a second discriminator. AWS bills Regional Infrequent Access storage under a separate, cheaper dimension on file systems using elastic throughput, so a declared ThroughputMode: elastic selects it; an omitted mode keeps the bursting default that CloudFormation, CDK, and Terraform all document. One Zone Infrequent Access has no elastic variant and is unaffected.
Per-class IA and Archive access charges apply in every throughput mode. On an elastic file system AWS bills them on top of the elastic per-GB read and write charges, so those access components are priced as usual while the elastic read and write volume stays unknown as its own component.
Read and write bill separately, so one direction never stands in for the other — a workload that truly reads or writes nothing in a class states that with an explicit 0. IA or Archive storage assumed without its access assumptions leaves the access charge unknown. A declared lifecycle transition marks only the class it moves data into as expected and never invents its quantity: a file system that transitions to IA with no efs.storage.infrequentAccessGbMonth reports IA storage as unknown, and a return-to-primary policy names no destination so it expects neither. Provisioned throughput is declared capacity rather than usage and is not yet priced; elastic throughput data access and replication also stay unknown.
Lambda fields
Set these under a lambda group. The declared configuration scales and selects the components: MemorySize (CloudFormation default 128 MB) sizes the compute derivation, Architectures (default x86_64) selects the x86 or arm64 price dimensions, and EphemeralStorage above the included 512 MB adds a storage component. A declared value that cannot be resolved to a concrete number leaves the affected components unknown instead of silently pricing the default.
| Field | Measure | Prices or result |
|---|---|---|
lambda.invocations | requests | Requests (per-million rate) and the derived duration and storage below |
lambda.averageDurationMs | duration | Average duration per invocation, not a monthly total |
Compute GB-seconds are derived exactly from the assumptions — invocations × duration × memory ÷ 1024 — and the derivation is stated in the component explanation. Ephemeral storage GB-seconds use the declared megabytes above 512 in place of memory.
Missing lambda.averageDurationMs leaves only the derived duration and storage components unknown. Missing lambda.invocations leaves everything unknown, because every Lambda charge scales with it. SnapStart set to anything other than None, provisioned concurrency, response streaming, and Lambda@Edge invocations stay unknown.
API Gateway fields
Set these under an apigateway group. The declared API type is the discriminator: AWS::ApiGateway::RestApi bills REST requests, and AWS::ApiGatewayV2::Api bills HTTP requests or WebSocket messages and connection minutes depending on its ProtocolType. CDK HttpApi and WebSocketApi constructs imply the protocol; a V2 API whose protocol cannot be resolved stays unknown rather than defaulting to the cheaper HTTP schedule.
| Field | Measure | Prices |
|---|---|---|
apigateway.requests | requests | REST or HTTP API requests by declared API type |
apigateway.websocket.messages | messages | WebSocket messages |
apigateway.websocket.connectionMinutes | connectionMinutes | WebSocket connection minutes |
REST and HTTP APIs never share a price dimension: REST requests bill under the four-tier REST schedule against apigateway.requests.rest, HTTP requests under their lower two-tier schedule against apigateway.requests.http. An assumption that does not apply to the declared type — WebSocket fields on a request-billed API, or apigateway.requests on a WebSocket API — is reported as an unsupported unknown. Messages and connection minutes bill separately, so state a true zero explicitly rather than omitting one.
Stage caching keeps declared-capacity semantics. Data transfer, custom domain certificates, API Gateway Portals, and private API endpoint charges stay unknown or belong to their own resources.
Kinesis fields
Kinesis keeps IaC-declared capacity exact and adds assumed workload charges beside it. Provisioned streams keep their shard-hour and retention-hour rows, on-demand streams keep their declared stream-hour row, and registered enhanced fan-out consumers keep their consumer-shard-hour row.
| Field | Measure | Prices |
|---|---|---|
kinesis.onDemand.ingestedGb | dataTransfer | On-demand data ingestion |
kinesis.onDemand.retrievedGb | dataTransfer | On-demand data retrieval |
kinesis.provisioned.putPayloadUnits | payloadUnits | Provisioned PUT payload units |
kinesis.retention.longTermStoredGbMonth | storage | Long-term retention storage beyond 7 days |
kinesis.enhancedFanOut.retrievedGb | dataTransfer | Enhanced fan-out retrieval for one consumer |
Stream mode selects the applicable fields. On-demand ingestion and retrieval bill separately, so supplying only one leaves the other unknown. PUT payload units and long-term retention apply to provisioned streams only. Enhanced fan-out retrieval belongs on the AWS::Kinesis::StreamConsumer, so each registered consumer carries its own quantity.
Network fields
Network usage requires an explicit billed path. CloudBurn never turns an undirected byte total into a cheaper direction.
| Field | Measure | Prices or result |
|---|---|---|
natGateway.processedGb | dataTransfer | NAT gateway data processing |
vpn.dataTransfer.outToInternetGb | dataTransfer | Site-to-Site VPN outbound internet transfer |
directConnect.dataTransfer.outGb | dataTransfer | Direct Connect transfer out from its declared facility |
globalAccelerator.dataTransfer.inboundFrom<Geography>Gb | dataTransfer | Dominant inbound traffic from a client geography |
globalAccelerator.dataTransfer.outboundTo<Geography>Gb | dataTransfer | Dominant outbound traffic to a client geography |
resolver.queries | queries | Route 53 Resolver DNS queries |
vpn.dataTransfer.gb, directConnect.dataTransfer.gb, globalAccelerator.dataTransfer.gb | dataTransfer | Ambiguous; reported as unknown |
Global Accelerator accepts NorthAmerica, Europe, AsiaPacific, Australia, India, SouthKorea, SouthAmerica, SouthAfrica, and MiddleEast in place of <Geography>, with independent inbound and outbound quantities. The source geography comes from the accelerator's AWS Region. Direct Connect also requires the exact declared Location facility code. VPN outbound internet usage shares the awsDataTransfer.outboundInternet account pool, and Resolver query tiers share resolver.queries.
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.
What's next
| Read this | To learn |
|---|---|
| Usage assumption examples | Worked configurations and where each number comes from |
| GitHub App overview | How the app turns diff comments into cost reports |
| Set up PR cost estimation | Installing and linking the app from your dashboard |