Create the connection
- Open Settings -> Integrations and choose FOCUS push.
- Pick the tenant the charges belong to and name the connection.
- Copy the connection id. It looks like
fpc_...and identifies this stream.
409.
Create the API key
- Open Settings -> API keys and create a key.
- Grant it the
focus:writescope. Nothing else is required.
Send the records
422 response names the offending row index.
Request body
Row fields
Costs and quantities are strings, not numbers, so no precision is lost in JSON. Timestamps are RFC 3339.Required on every row
Constrained when present
Repaired rather than rejected
A vendor that publishes FOCUS rarely publishes it exactly, and the deviations are usually repairable. CostGraph repairs these on the way in rather than refusing the batch:
Your own category is not discarded. Unless you set
ServiceSubcategory yourself, the
value you declared is kept there, so a row filed under Compute still says
Compute Engine GPU in breakdowns.
The FOCUS
ServiceCategory enum is AI and Machine Learning, Analytics,
Business Applications, Compute, Databases, Developer Tools, Identity,
Integration, Internet of Things, Management and Governance, Media,
Migration, Mobile, Multicloud, Networking, Security, Storage, Web,
Other. Sending one of these exactly is always cleanest - the mapping is a safety
net, not a substitute for saying what you mean.Optional
Everything else in FOCUS 1.2 is accepted and stored, and omitting it is fine:AvailabilityZone, BillingAccountName, BillingAccountType, CapacityReservationId,
CapacityReservationStatus, ChargeFrequency, CommitmentDiscountCategory,
CommitmentDiscountId, CommitmentDiscountName, CommitmentDiscountQuantity,
CommitmentDiscountStatus, CommitmentDiscountType, CommitmentDiscountUnit,
ConsumedQuantity, ConsumedUnit, ContractedUnitPrice, InvoiceId, InvoiceIssuer,
ListUnitPrice, PricingCategory, PricingCurrency,
PricingCurrencyContractedUnitPrice, PricingCurrencyEffectiveCost,
PricingCurrencyListUnitPrice, PricingQuantity, PricingUnit, Publisher,
RegionId, RegionName, ResourceId, ResourceName, ResourceType,
ServiceSubcategory, SkuId, SkuMeter, SkuPriceId, SkuPriceDetails,
SubAccountId, Tags.
Send
ResourceId wherever you have one. Without it a charge can be totalled but not
attributed to a thing, so it will not appear in resource-level breakdowns.Multi-tenancy with SubAccountId
One connection can carry charges for many of your own customers. Set
SubAccountId on each row to the identifier of the customer the charge belongs
to, and optionally SubAccountName for a human label and SubAccountType to say
what kind of thing that identifier names.
SubAccountId is part of the identity of a row, alongside the billing account,
provider, resource and charge period. Two customers billed for the same SKU over
the same period stay separate records rather than collapsing into one, and a
replayed window updates each customer’s rows in place.
SubAccountName and SubAccountType are only accepted when SubAccountId is
set. A name without an id has nothing to attach to and the batch is rejected.SubAccountId values on every push. Changing the identifier for a
customer starts a new sub account rather than renaming the old one; the name is
free to change.
The window and the complete flag
Each push declares the charge period it covers, and what you are claiming about it.
complete: true deletes everything previously ingested for that connection inside
the window and replaces it with this batch. That is what makes re-sending a month
idempotent, and it is how you correct a restatement: send the corrected month again.
Every row’s ChargePeriodStart must fall inside the declared window, and all rows in
a batch must carry the same provider. The first push sets the connection’s provider;
later pushes must agree with it.
Batch size
The endpoint accepts 32 MB per request by default. A larger payload is rejected with a413. Either narrow the window - a month is usually the natural unit - or
page the export, below.
Only one ingest runs per connection at a time. A concurrent push gets a 409, so
retry rather than posting two batches to one connection in parallel.
Paging a large export
A month of a busy account can exceed the size limit at any window you would want to send. Split it into pages, and tell CostGraph that is what you are doing:batch_idnames the export run. Use the same value on every page of it, and on any retry of a page. Letters, digits, dot, dash and underscore, up to 128 characters.row_offsetis the index of that page’s first row within the whole export. Page one is0, and each page adds the number of rows the previous one carried.
complete: true. It clears the window once; the pages
after it append to what the run has already written, so page two never wipes page
one. Clearing spares whatever this batch_id has already written, so retrying the
first page rewrites its own rows and leaves the pages that followed it alone.
Why the offset matters
A provider may bill one line item several times in a period, and FOCUS has no field that separates the copies: five load balancers of the same shape, in one project, on one day, are five identical rows.row_offset gives each row a slot in the export,
so the copies stay distinct.
That slot is also what makes a retry safe. A page resent after a timeout addresses
the same slots and updates those rows in place; the next page addresses new slots
and both pages survive. Retry a failed page with exactly the same batch_id and
row_offset it had the first time.
Start a new batch_id for every export run. Reusing the previous run’s id with
different rows lands them in slots the old run already owns.
A daily loop
Send yesterday once the day closes, withcomplete: true so a re-run is harmless:
batch_id per run.
Then re-send the whole month after your own books close, again with complete: true.
The month replaces the days it covers, so late charges and restatements correct
themselves without any deletion on your side.