Skip to main content
Most integrations are pull: you hand CostGraph a read-only credential and it fetches your bill once a day. Push is the other direction. You send FOCUS 1.2 records to our API yourself, and no vendor credential ever leaves your infrastructure. Use it for billing data CostGraph cannot reach: an internal chargeback system, a data warehouse job, a vendor whose export you already normalise, or a private cloud nobody else bills for. Whatever you send lands beside every other provider in Cost Overview, anomaly detection, and the MCP tools.

Create the connection

  1. Open Settings -> Integrations and choose FOCUS push.
  2. Pick the tenant the charges belong to and name the connection.
  3. Copy the connection id. It looks like fpc_... and identifies this stream.
A connection is created in push mode and stays that way. Posting to a connection CostGraph syncs itself is refused with a 409.

Create the API key

  1. Open Settings -> API keys and create a key.
  2. Grant it the focus:write scope. Nothing else is required.
Install tokens are rejected on this endpoint. Use an ordinary API key.

Send the records

Every row is validated, and a row that fails validation rejects the whole batch, so nothing lands half-applied. The 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.
Any key CostGraph does not recognise is kept as a provider extension rather than rejected, so a dialect that carries extra columns round-trips.

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.
Send the same 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.
complete: true with an empty rows array is refused, because it would silently delete the window. Omit complete if you mean to append nothing. A later page of a paged export may be empty, since its row_offset shows the window was already cleared by an earlier page.
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 a 413. 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_id names 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_offset is the index of that page’s first row within the whole export. Page one is 0, and each page adds the number of rows the previous one carried.
Only the first page carries 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.
An appending push - complete omitted or false - must carry batch_id. Without it CostGraph cannot tell the next page of an export from a retry of the last one, and either loses charges or counts them twice. A complete push does not need one, because it replaces the window outright.

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, with complete: true so a re-run is harmless:
A day rarely needs paging. If a re-sent month does, page it as above with a fresh 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.

What CostGraph does with it

Records land as raw billing rows, are normalised into line items, then roll up into daily cost and reconcile against the invoice totals for each month the window touches.

Responses