Cost and Usage Reports (cur:*)
Protocol: JSON 1.1
Header: X-Amz-Target: AWSOrigamiServiceGatewayService.<Action>
Endpoint prefix: cur
Floci emulates the legacy AWS Cost and Usage Report (CUR) API. Report
definitions are persisted in Floci's storage backend; emission produces
real Parquet artifacts in Floci's S3 service via the floci-duck sidecar.
Supported Operations
| Operation | Notes |
|---|---|
PutReportDefinition |
Creates a new report; rejects duplicates with DuplicateReportNameException; enforces a 5-report-per-account limit |
ModifyReportDefinition |
Replaces an existing report's mutable fields |
DescribeReportDefinitions |
Returns every report owned by the calling account |
DeleteReportDefinition |
Idempotent; removing a missing report returns 200 |
TagResource / UntagResource / ListTagsForResource |
Stub responses (empty bodies) so SDK clients that probe for them succeed |
Validation rules
ReportName: alphanumerics +-_, max 256 charsTimeUnit:HOURLY/DAILY/MONTHLYFormat:Parquet(CSV emission not yet implemented;textORcsvreturnsValidationException)Compression:Parquet(ZIP/GZIPnot yet implemented)ReportVersioning:CREATE_NEW_REPORT/OVERWRITE_REPORTAdditionalArtifacts: subset ofREDSHIFT/QUICKSIGHT/ATHENAAdditionalSchemaElements: subset ofRESOURCES/SPLIT_COST_ALLOCATION_DATA/MANUAL_DISCOUNT_COMPATIBILITY- Required:
ReportName,TimeUnit,Format,Compression,S3Bucket,S3Region
Storage keys
Report definitions are persisted as
<accountId>::<region>::<reportName> so the same name is allowed in
different regions or different accounts.
Emission
Emission produces a Parquet artifact at
s3://<S3Bucket>/<S3Prefix>/<reportName>/<runId>.parquet. Each run gets a
fresh UUID, so concurrent emissions never clobber each other.
The pipeline:
- The shared
EmissionEnginecollectsUsageLinerows from every service that implements theResourceUsageEnumeratorSPI introduced in Cost Explorer. FocusRowProjectorconverts those rows to FOCUS 1.2 / CUR 2.0 column shape using the bundled Pricing snapshot.- The rows are staged as newline-delimited JSON in
s3://floci-cur-staging/cur-staging/<reportName>/<runId>.ndjson. - The
floci-ducksidecar runsCOPY (SELECT * FROM read_json_auto(...)) TO 's3://...' (FORMAT PARQUET)to produce the final Parquet object back in Floci S3. - The staging object is deleted in a best-effort
finallyblock.
FLOCI_SERVICES_CUR_EMIT_MODE
| Value | Behavior |
|---|---|
synchronous (default) |
Emit on every PutReportDefinition / ModifyReportDefinition |
daily |
Emit every 24h via a CUR-owned scheduled executor (separate from EventBridge Scheduler) |
off |
Management plane only — no emission |
synchronous mode swallows emission errors so the management mutation
always succeeds; the failure is reflected in
ReportStatus.LastStatus = ERROR on the persisted definition.
Configuration
| Variable | Default | Description |
|---|---|---|
FLOCI_SERVICES_CUR_ENABLED |
true |
Enable or disable the service |
FLOCI_SERVICES_CUR_EMIT_MODE |
synchronous |
Run mode (see above) |
FLOCI_SERVICES_CUR_STAGING_BUCKET |
floci-cur-staging |
S3 bucket used to stage NDJSON before DuckDB writes Parquet |
The floci-duck sidecar is started lazily on the first emission, the
same way Athena starts it.
Examples
export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
aws cur put-report-definition --report-definition '{
"ReportName": "monthly-report",
"TimeUnit": "MONTHLY",
"Format": "Parquet",
"Compression": "Parquet",
"AdditionalSchemaElements": ["RESOURCES"],
"S3Bucket": "my-billing",
"S3Prefix": "reports",
"S3Region": "us-east-1",
"AdditionalArtifacts": ["ATHENA"],
"ReportVersioning": "OVERWRITE_REPORT"
}'
aws cur describe-report-definitions
aws s3 ls s3://my-billing/reports/monthly-report/
import boto3, json
cur = boto3.client(
"cur",
endpoint_url="http://localhost:4566",
region_name="us-east-1",
)
cur.put_report_definition(ReportDefinition={
"ReportName": "monthly-report",
"TimeUnit": "MONTHLY",
"Format": "Parquet",
"Compression": "Parquet",
"AdditionalSchemaElements": ["RESOURCES"],
"S3Bucket": "my-billing",
"S3Prefix": "reports",
"S3Region": "us-east-1",
})
# Read the resulting Parquet via DuckDB or pyarrow.
import pyarrow.dataset as ds
table = ds.dataset("s3://my-billing/reports/monthly-report/", format="parquet").to_table()
print(table.column_names)
Out of Scope
- Resource-tag-keyed report selection beyond what the bundled enumerators emit
RefreshClosedReportssemantics (accepted but not retroactively re-emitted)- Server-side validation of S3 bucket policies on the destination bucket (Floci's S3 service still applies its own bucket-policy checks during the write)