Skill 61 · AWS Marketplace Metering
Subchapter 61.8
references/query-path.mdMarkdown12 KBView on GitHub
The query path is a fast, zero-setup, recent (≤90 days), single-region, single-event-stream
operational lens over the metering SUBMISSIONS you sent to BatchMeterUsage — it answers
debugging/triage questions from CloudTrail Event History. It reflects what was submitted and
how the API responded — the per-record results[].status values Success/DuplicateRecord/CustomerNotSubscribed (CloudTrail lowercases response field names; the direct SDK response is PascalCase Results[].Status), and request-level exceptions like InvalidUsageDimensionException that failed a whole call — including rejected/duplicate records that were not billed.
USES (the skill queries this directly):
LookupEvents) in the seller’s metering region, last ≤90 days,
page-bounded and processed in memory (see “Result completeness” below).RECOMMENDS (out of scope for this query — hand off, do not run here):
| The seller wants… | Why CloudTrail can’t be the answer | Recommend instead |
|---|---|---|
| Billed totals / “what was I actually paid” / authoritative sums | CloudTrail is the submissions view (includes rejected/duplicate), not billing-of-record | Seller Reports (AMMP Insights / SDDS) — billed amounts; reconcile per “Totals don’t match” below |
| Data older than 90 days / history | CloudTrail Event History retains only 90 days | Seller Reports / SDDS — seller-run (one-time “Set up customer data storage”; the seller queries the delivered feeds) |
| Per-product totals with no LicenseArn→productCode mapping | CA BatchMeterUsage events carry no productCode | Explain the limitation; offer only non-product-scoped queries |
| Complex/open-ended analytics (forecasting, arbitrary aggregations, cross-source joins) | Determined by the host agent + model, not the skill | Hand the fetched data to the host agent, or the seller’s own analytics |
| An authoritative full-window total for a high-volume seller (fetch would truncate) | The fetch is page-bounded (see below) | Narrow window / raise --max-pages, or use Seller Reports |
When a question lands outside the USES scope, say so — don’t return a bounded, submissions-only answer as if it were the authoritative/billed total.
Any CloudTrail-based path — CloudTrail Lake OR Athena over a (self-managed) CloudTrail
trail → S3 export — analyzes SUBMISSIONS only: it includes rejected / DuplicateRecord
records (NOT billed), is regional, and for Concurrent Agreements carries no
productCode (no product attribution). So it is the wrong source for billing/usage analytics.
create-event-data-store for metering — not for analytics
and not for lookups.references/seller-reports.md.)trail → S3 (never Lake) + Athena is acceptable ONLY for
long-term raw-SUBMISSION AUDIT retention (from setup forward, no backfill) — NOT as a
billing/usage-analytics source. The destination bucket stores raw BatchMeterUsage/
ResolveCustomer submission data, so it MUST be encrypted at rest with SSE-KMS
(KMSKeyId on the trail, a customer-managed key) and enforce encryption in transit with
a bucket policy denying aws:SecureTransport=false; also block public access and restrict
read to the auditing principals.GenerateDataSet.You MUST ask the user which region their application calls BatchMeterUsage from. CloudTrail Event History is regional — you MUST query the same region where the API was invoked.
Ask the seller which region their application calls BatchMeterUsage from — do not assume a fixed set. You must query CloudTrail in that same region.
Parse the user’s query to identify filters:
| Parameter | Description | Example |
|---|---|---|
customer_id | CustomerAWSAccountId | 123456789012 |
status | Result status | Success, CustomerNotSubscribed, DuplicateRecord |
dimension | Usage dimension name | fgt_cnf_hours |
product_code | Product to scope a product-specific query to (needs a mapping — see below) | prod-abc123 |
start_date / end_date | Date range (YYYY-MM-DD) | 2025-03-01 |
query_type | Type of query | summary, detail, count, list_failures, top_customers |
top_n | Number of top results | 10 |
python3 ./scripts/query_metering.py \
--region <SELLER_METERING_REGION> \
[--days N] [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD] \
[--customer-id CUSTOMER_AWS_ACCOUNT_ID] [--status STATUS] \
[--dimension DIMENSION] [--query-type summary|detail|count|list_failures|top_customers] \
[--product-code PRODUCT_CODE] [--deployment-product-code PRODUCT_CODE] \
[--top-n N] [--limit N] [--max-pages N]If --start-date is omitted, defaults to last 7 days.
CloudTrail Event History (the only source this script queries) retains 90 days. Do NOT
attempt to query older data with this tool. If the requested range is entirely older than 90
days, the script does not query — it returns a recommendation to use the seller-run Seller
Reports / SDDS path (references/seller-reports.md): the seller completes the one-time
“Set up customer data storage” step in AMMP and runs their own query/ETL over the delivered
S3 feeds. If the range only partly predates 90 days, the script queries the in-window portion
and notes that the older portion needs Seller Reports / SDDS.
BatchMeterUsage for Concurrent Agreements (LicenseArn-based) carries no productCode, so
a CloudTrail event cannot be attributed to a product from the API fields alone. Before a
product-specific query (--product-code), establish a mapping from one of these, in order:
--deployment-product-code <productCode> when
the events come from a single-product stack (awsmp-<productCode>-metering/-usage).additionalEventData — if the event carries a LicenseArn→productCode map, the
script uses it automatically.productCode attribute per licenseArn row can serve as the map.If none is available, the script does NOT support the product-specific query: it explains why
and offers only non-product-scoped queries (by customer, dimension, status, or time range).
Legacy ProductCode-based metering carries ProductCode at the request level, so product
attribution is available directly.
| query_type | Output |
|---|---|
| summary | Total records, quantity, breakdowns by status/dimension, top customers |
| top_customers | Ranked list by usage quantity |
| detail | Individual records (capped by --limit) |
| count | Record and quantity totals |
| list_failures | Only non-Success records |
| User Question | query_type | Flags |
|---|---|---|
| “top 10 buyers” | top_customers | --top-n 10 |
| “total usage this month” | summary | --days 30 |
| “how many failures” | list_failures | |
| “records for customer X” | detail | --customer-id X |
| “usage by dimension” | summary | (dimension breakdown included) |
The query fetches CloudTrail Event History with LookupEvents, paginating up to --max-pages
(default 20 pages × 50 = ~1000 events), then filters and aggregates in memory. A single
query is a bounded window, not an exhaustive scan — a high-volume seller’s broad window can
hold more events than the cap fetches.
Every result therefore carries explicit completeness signals:
complete: true — the whole requested window was fetched (truncated: false, windowClipped: false).truncated: true — the page cap was hit with more events available; the result is a
partial sample. fetch.pagesFetched / fetch.eventsProcessed show how much was read.windowClipped: true — the requested start predates the 90-day CloudTrail retention limit,
so the result covers only the last 90 days (a SHORTER range than requested); the output states
the covered start date. complete is false whenever the result is truncated OR clipped.Confirm completeness before presenting a result (double-pass). Because this skill is run by
an AI agent, always inspect these signals on the query output before answering. If complete is
false, either (a) re-run to get a complete answer where feasible — narrow/adjust the range for a
clip, or raise --max-pages / add --customer-id/--dimension/--status for truncation — and
confirm the re-run is complete: true; or (b) present the result while explicitly stating it is
partial (what was clipped/truncated and the covered range) and pointing to Seller Reports for
the authoritative or older portion. For the aggregate query types (summary, count,
top_customers) a truncated or clipped result carries an aggregateWarning — the numbers are a
lower bound, not authoritative. Never report a partial result as covering the full requested
range, and never quote a partial aggregate as a definitive total.
| Status | Meaning | Action |
|---|---|---|
Success | Record accepted and billed | None needed |
CustomerNotSubscribed | No active subscription | ⚠️ Do NOT retry |
DuplicateRecord | First-write-wins: original quantity stays billed; a resubmit with a different quantity is NOT billed | Safe only for an identical retry; do NOT rely on it to correct a quantity (under-billing risk) |
Request-level exceptions (fail the whole BatchMeterUsage call — NOT per-record Results statuses; the pipeline isolates the offending record by bisection so co-batched records still meter):
InvalidUsageDimensionException — a dimension not defined in the catalog (fix the dimension key / define it in AMMP; isolated to RejectedClientSide reason InvalidUsageDimensionException, surfaced on the BatchMeterUsageException metric).TimestampOutOfBoundsException — a record older than 24 hours (fix the timestamp).| Tier | Retention | Cost | Use When |
|---|---|---|---|
| CloudTrail Event History | 90 days | Free | Real-time, last 90 days |
| CloudTrail trail → S3 | Indefinite (your S3 lifecycle) | Low (S3) | Durable raw API-submission audit BEYOND 90 days |
| Seller Reports (AMMP) | Full history | Free | Billed amounts, reconciliation |
| DDB PITR Export | Configurable | Low | Full history from deployed stack |
| SDDS (Seller Delivery Data Feeds) | Full history | Free | SQL over S3-delivered feeds (>90 days, billing) |
“Totals don’t match disbursement?” — CloudTrail shows ALL (including DuplicateRecord + rejected). Seller Reports show only billed. Difference = rejected/duplicate.
“No records found” — Wrong region, too soon (<15 min), wrong filter, or beyond 90 days.
“No errors but not billing” — Dimension mismatch (case-sensitive, no runtime error).
Script slow — Use --max-pages, --limit, or narrow date range.
For full CloudTrail CLI patterns and pagination examples, load
references/query-patterns.md.