Volume 01 · Reference
Billing reads,explicitly scoped.
Chapter the first
Amazon Web Services
Cross-account IAM role, or a long-lived access key.
The cross-account role flow is preferred: credentials are short-lived, the trust policy is gated by an external ID, and revocation is a single console click in your account. Access keys still work, but rotate them on a schedule and treat the long-lived secret as you would any other production credential.
Deploy the bundled CloudFormation template (or paste the JSON below into a new role) in your account. The trust policy locks the principal to tovinio's ingest role and the ExternalId generated for this connection.
Trust policy · paste from the connection form
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "BILLINGDASH_INGEST_ROLE_ARN"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "EXTERNAL_ID_FROM_TOVIN"
}
}
}
]
}Current minimum permissions
Enough for the present validation and Cost Explorer sync. Tag discovery falls back to Project if ce:GetTags is unavailable; granting it lets tovinio choose the active key automatically.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TovinCostExplorerRead",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetTags"
],
"Resource": "*"
},
{
"Sid": "TovinIdentityCheck",
"Effect": "Allow",
"Action": [
"sts:GetCallerIdentity"
],
"Resource": "*"
}
]
}Recommended — matches the bundled template
Headroom for tag, dimension, resource, and forecast reads. This is what the CloudFormation template attaches by default.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TovinCostExplorerRead",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetCostAndUsageWithResources",
"ce:GetTags",
"ce:GetDimensionValues",
"ce:GetCostForecast"
],
"Resource": "*"
},
{
"Sid": "TovinIdentityCheck",
"Effect": "Allow",
"Action": [
"sts:GetCallerIdentity"
],
"Resource": "*"
}
]
}sts:AssumeRole— referenced by the trust policy and gated by the connection's external ID.sts:GetCallerIdentity— confirms the account ID after the role is assumed.ce:GetCostAndUsage— daily UnblendedCost, grouped by the active project tag and by service.ce:GetTags— discovers whether your active cost-allocation key isProject,project, or another spelling configured in your tovinio settings.
On every sync, tovinio asks Cost Explorer which configured project tag key is active, preferring Project then project. Every non-empty tag value becomes a tovinio project with an exact tag rule, if one does not exist already.
Service drill-downs show individual resources — EC2 instance IDs, S3 buckets, RDS clusters — but only after you enable resource-level data in your AWS account. The setting lives exclusively in the AWS console; there is no aws ce, boto3, CloudFormation, or Terraform path for it.
- Sign into the AWS console with the management / payer account — a member account cannot toggle this.
- Open Billing & Cost Management → Settings.
- Scroll to Historical and granular data settings. Under Granular data → Daily granularity, check Resource-level data at daily granularity.
- In Selected services, choose All (or a specific subset). At least one service must be selected.
- Click Save preferences.
- AWS shows "Your data will be enabled within 48 hours". The page locks editing during the propagation window.
- After AWS reports the data is enabled, run Sync now from the Connections page in tovinio. The opt-in banner clears once the sync confirms the toggle is on and resource rows have been ingested for the trailing 14 days.

- If you use access keys instead of a role, attach the same Cost Explorer + STS read policy to the IAM user. Cross-account roles are preferred — credentials are short-lived and the external ID protects against confused-deputy attacks.
- Activate the cost-allocation tag in Billing and Cost Management before relying on tag-based project mapping. tovinio auto-checks
Projectandprojectfirst, then creates missing projects from the values it sees. - Rotation: regenerate the access key (or update the role's trust policy) and re-paste in the connection form. tovinio re-validates before the new credential is encrypted.
Chapter the second
Google Cloud
Service account JSON key with billing and BigQuery read.
Google Cloud cost ingestion reads the BigQuery billing export table. The Cloud Billing API is used only to validate the service account can see billing accounts; daily spend, project labels, service totals, and resource rows come from BigQuery.
GCP onboarding crosses three resource scopes — service account project, Cloud Billing account, and BigQuery export project. The order below is the one that minimizes back-and-forth and IAM- propagation waits. Read it before clicking, then come back to the relevant chapter for detail.
0. Pick a "FinOps" Google Cloud project that owns the credential
and (usually) the billing-export dataset.
1. CREATE the service account
Console: IAM & Admin → Service Accounts → Create
Output: tovinio-billing@FINOPS_PROJECT.iam.gserviceaccount.com
2. ENABLE APIs in the FinOps project
cloudbilling.googleapis.com (validation calls billingAccounts.list)
bigquery.googleapis.com (sync runs queries)
3. GRANT roles/billing.viewer on the BILLING ACCOUNT
Console: Billing → <account> → Account management / Permissions
Add principal: the SA email above.
NOT project IAM — billing-account IAM.
4. ENABLE BigQuery billing export (one-time, per Cloud Billing account)
Billing → Billing export → BigQuery export
Pick "Standard usage cost". Choose a dataset location you can keep.
Wait — first rows can take hours, full backfill can take days.
5. GRANT BigQuery read on the EXPORT project + dataset
roles/bigquery.jobUser on the export project
roles/bigquery.dataViewer on the export dataset
6. CREATE a JSON KEY for the service account; download once.
IAM & Admin → Service Accounts → <SA> → Keys → Add → JSON
7. PASTE in tovinio: Connections → Google Cloud
Service account JSON, then the three BigQuery pointer fields.
See the "Connection-form fields" cheat sheet below.Total clock time on a fresh project: ~10 minutes of clicking, plus a 24–48h wait before the first BigQuery rows land. Validation in tovinio works as soon as the export table exists; Sync now returns rows once Google has written some.
Create a dedicated service account in a stable billing or FinOps project. Do not reuse an application runtime service account; this identity should exist only to read billing metadata and, later, the billing-export dataset.
- Pick the project that will own the credential, usually a separate FinOps, platform, or billing-admin project linked to the same Cloud Billing account.
- Console path: IAM & Admin → Service Accounts → Create service account. Name it
tovinio-billingor your local equivalent. - Skip broad project roles during creation. The billing role must be granted on the Cloud Billing account, not only on the project that contains the service account.
- Enable the Cloud Billing API (
cloudbilling.googleapis.com) in the project that contains the service account. Google checks this API before IAM onbillingAccounts.list. - Open the service account's Keys tab, create a new JSON key, and paste the complete file into the tovinio connection form.
Equivalent gcloud setup
# Pick a project you use for billing / FinOps administration.
FINOPS_PROJECT_ID="my-finops-project"
SA_ID="tovinio-billing"
SA_EMAIL="${SA_ID}@${FINOPS_PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts create "${SA_ID}" \
--project="${FINOPS_PROJECT_ID}" \
--display-name="tovinio billing reader" \
--description="Read-only billing identity for tovinio"
gcloud services enable cloudbilling.googleapis.com \
--project="${FINOPS_PROJECT_ID}"
gcloud services enable bigquery.googleapis.com \
--project="${FINOPS_PROJECT_ID}"
gcloud iam service-accounts keys create ./tovinio-gcp-sa.json \
--iam-account="${SA_EMAIL}" \
--project="${FINOPS_PROJECT_ID}"Google Cloud IAM is resource-scoped. The same service account can need one role on the billing account, optional viewer roles on projects, and BigQuery read roles on the export dataset. Grant each role at the narrowest resource that still lets the integration work.
Current minimum roles
roles/billing.viewer # on the Cloud Billing account
roles/browser # on each project you want visible during setup
roles/bigquery.jobUser # on the billing export project
roles/bigquery.dataViewer # on the billing export dataset- Billing Account Viewer (
roles/billing.viewer) on the Cloud Billing account. This is the role tovinio validates today. It includesbilling.accounts.listandbilling.accounts.get. - Browser (
roles/browser) on each project you want visible for setup and mapping. It exposes project metadata, not resource contents. - BigQuery Job User on the export project and BigQuery Data Viewer on the billing-export dataset let tovinio validate and query the billing export table.
Grant Billing Viewer on the billing account
BILLING_ACCOUNT_ID="000000-000000-000000"
SA_EMAIL="tovinio-billing@my-finops-project.iam.gserviceaccount.com"
gcloud billing accounts add-iam-policy-binding "${BILLING_ACCOUNT_ID}" \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/billing.viewer"Optional project discovery roles
SA_EMAIL="tovinio-billing@my-finops-project.iam.gserviceaccount.com"
for PROJECT_ID in prod-web prod-data shared-platform; do
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/browser"
doneOAuth scope used by validation
OAuth scopes: https://www.googleapis.com/auth/cloud-billing.readonly
https://www.googleapis.com/auth/bigquery
Used at: Cloud Billing billingAccounts.list
BigQuery jobs.query / getQueryResults If you prefer the console, grant the billing account role from the Billing area, not from the project IAM page. Project IAM alone is a common source of failed validation because billingAccounts.list checks billing account permissions.
- Open Google Cloud Billing and select the Cloud Billing account you want to connect.
- Go to Account management → Permissions or Billing account IAM, depending on your console layout.
- Click Add principal, paste the service account email, and assign Billing Account Viewer.
- Save, then wait a minute for IAM propagation before pasting the JSON key into tovinio.
You do not need Billing Account Administrator, Project Editor, Owner, Compute Viewer, or Storage Viewer for current validation.
Google Cloud's detailed spend rows live in BigQuery export tables, not in the Cloud Billing API. Enable the export before adding the connection so validation can dry-run a query against the table.
- Create or select a billing/FinOps project to hold the export dataset. Keep it linked to the same Cloud Billing account.
- Create a BigQuery dataset, commonly
billing_export. Use a supported dataset location;USandEUmulti-regions are the safest choices when you want current and previous-month backfill. - In Billing, open Billing export → BigQuery export and enable Standard usage cost. Enable Detailed usage cost too if you want the resource fields Google provides.
- Copy the actual Project ID that contains the dataset, not the display name shown in Billing. For example, use
steel-watch-419, notMy Compute Engine Project. - In BigQuery → Explorer, expand that project, then
billing_export. Copy the exact table name that starts withgcp_billing_export_v1_. The suffix is generated from the Cloud Billing account, so it is usually not the project ID. - Do not remove Google's
billing-export-bigquery@system.gserviceaccount.comdataset access. Google uses that managed identity to write the export tables. - Grant the service account
roles/bigquery.jobUseron the export project androles/bigquery.dataVieweron the dataset.
Heads up — export lag. The BigQuery billing export typically lags the Cloud Billing Console by hours to days. A successful sync that shows zero current-month spend usually just means Google hasn't written today's rows yet — not that the connection is broken. Tovinio surfaces the latest exported usage date on the Connections card so you can tell the two cases apart.
BigQuery reader project grant
# Only needed when the BigQuery billing-export reader is enabled.
EXPORT_PROJECT_ID="my-finops-project"
DATASET_ID="billing_export"
SA_EMAIL="tovinio-billing@my-finops-project.iam.gserviceaccount.com"
gcloud services enable bigquery.googleapis.com \
--project="${EXPORT_PROJECT_ID}"
gcloud projects add-iam-policy-binding "${EXPORT_PROJECT_ID}" \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/bigquery.jobUser"
bq show --dataset "${EXPORT_PROJECT_ID}:${DATASET_ID}"BigQuery reader dataset grant
Run from the BigQuery SQL editor as a user who can grant dataset access, or grant the same role from the dataset's Sharing panel.
GRANT `roles/bigquery.dataViewer`
ON SCHEMA `my-finops-project`.billing_export
TO "serviceAccount:tovinio-billing@my-finops-project.iam.gserviceaccount.com";Find the generated table name
Replace steel-watch-419 and billing_export with your export project ID and dataset. Use the returned gcp_billing_export_v1_... table in the connection form.
SELECT table_name
FROM `steel-watch-419.billing_export.INFORMATION_SCHEMA.TABLES`
WHERE STARTS_WITH(table_name, 'gcp_billing_export_v1_')
ORDER BY table_name;Quick export sanity query
Run this in BigQuery after the export table appears. Replace the project, dataset, and generated billing-account suffix.
SELECT
usage_start_time,
project.id AS project_id,
service.description AS service,
sku.description AS sku,
cost,
currency,
labels
FROM `EXPORT_PROJECT_ID.DATASET_ID.gcp_billing_export_v1_XXXXXX_XXXXXX_XXXXXX`
WHERE DATE(usage_start_time) >= DATE_SUB(CURRENT_DATE(), INTERVAL 7 DAY)
ORDER BY usage_start_time DESC
LIMIT 20;Every exported GCP billing row carries the Google Cloud project that incurred the charge. That means a one-GCP-project-per-workload account can map cleanly with account/project rules and no labels. Add labels when a single GCP project hosts multiple tovinio projects.
- Use the lower-case label key
projectfor new GCP labels. The mapping UI still accepts explicit rules, but consistent labels make the export easier to reason about. - Labels are resource-specific. Compute Engine, Cloud Storage, Cloud Run, GKE, Cloud SQL, and BigQuery each have their own UI or CLI path, and some SKUs remain project-only or unlabeled.
- Expect lag. Labels can take time to appear in billing exports, and historical rows are not rewritten just because you labeled a resource today.
Example labels
# Existing Compute Engine instance.
gcloud compute instances add-labels INSTANCE_NAME \
--project=PROJECT_ID \
--zone=us-central1-a \
--labels=project=acme-prod,env=prod
# Existing Cloud Storage bucket.
gcloud storage buckets update gs://BUCKET_NAME \
--update-labels=project=acme-prod,env=prodtovinio's GCP connection form has four inputs. The three BigQuery pointers refer to different resources — easy to swap project ID with dataset name, or to paste the display name where the form expects the ID. Use this cheat sheet while you fill it in.
tovinio form field Where it comes from
───────────────────────── ─────────────────────────────────────────────────
Service account JSON The full JSON file you downloaded in step 6.
Paste raw text including private_key. Not a
screenshot, not a wrapped secret-manager dict.
Billing export project ID The Project ID of the project that contains
the BigQuery export dataset.
Console: BigQuery → Explorer → top-level project.
Use the Project ID (e.g. steel-watch-419),
NOT the display name (e.g. "Acme Finops").
Billing export dataset The BigQuery dataset name you created/chose
in step 4. Conventional name: billing_export.
Lower-case, no spaces.
Billing export table The auto-generated table name that begins with
gcp_billing_export_v1_.
Console: BigQuery → Explorer → expand dataset →
copy the exact table name (one per Cloud
Billing account; suffix is the billing account,
NOT the project ID).
If you can't see the table:
• Wait 24–48h after enabling export.
• Or run the table-discovery query below.- Open Connections → Google Cloud.
- Paste the entire JSON file, including
type,project_id,client_email, andprivate_key. - Enter the BigQuery billing export project, dataset, and table name. Use the export project ID, not the display name. The standard table is copied from BigQuery Explorer and is usually named
gcp_billing_export_v1_...; the suffix comes from the Cloud Billing account, not the GCP project ID. - Click Validate & save. tovinio mints an OAuth token using the service account key, calls
billingAccounts.list, dry-runs a BigQuery query against the export table, then stores the credential only after validation passes. - The saved connection subject is the service account key's
project_idwhen present, otherwise the service account email.
A successful save confirms both the billing role and BigQuery table access. Run Sync now after the export table has started receiving rows.
- Malformed JSON — paste the raw key file, not a screenshot, escaped string, or wrapper object from a secret manager.
- Could not mint a token — the key may be disabled, deleted, copied incompletely, or blocked by an organization policy that restricts service account key usage.
- Cannot list billing accounts — grant
roles/billing.vieweron the Cloud Billing account itself. A project-level Viewer role is not enough. - Cloud Billing API has not been used or is disabled — enable
cloudbilling.googleapis.comin the project that owns the service account, then retry after propagation. - Cannot query the BigQuery export table — verify the table name, enable the BigQuery API on the export project, grant
roles/bigquery.jobUseron that project, and grantroles/bigquery.dataVieweron the dataset. - Only some projects appear later — add
roles/browseron the missing projects or use explicit account/project mapping rules.
- To rotate, create a new JSON key for the same service account, paste it into tovinio, and save after validation passes.
- Delete the old key from IAM & Admin → Service Accounts → Keys after the replacement is saved.
- To revoke completely, remove the billing-account IAM binding and delete or disable all user-managed keys for the service account.
- Review user-managed keys periodically with
gcloud iam service-accounts keys listand remove keys that are no longer in use.
Workload Identity / federation for tovinio's GCP integration is tracked but not yet implemented. Until then, JSON key rotation is the supported credential lifecycle.
Chapter the third
DigitalOcean
Read-only personal access token.
DO does not have AWS-style cost-allocation tags or a Cost Explorer surface. tovinio reads the same artifact a finance team would — the per-invoice CSV — line by line, then maps each line to a project using the CSV's project_name column (DO's built-in Projects feature) or account-level rules.
- Sign into the DigitalOcean console.
- Open API → Personal access tokens .
- Click Generate New Token. Name it
tovinio-billing(or whatever your naming convention uses). - Pick a scope strategy. Easiest is the Read Only preset — it covers every endpoint tovinio calls. For least-privilege, choose Custom Scopes and check just
account → Readandbilling → Read. Leave everything else unchecked. - Set an expiration. 90 days is recommended; an indefinite token is technically accepted but discouraged.
- Copy the token from the next screen — DigitalOcean shows it once. Paste it into the tovinio connection form before navigating away.
DO's PAT UI offers three top-level paths: the Read Only preset, the Full Access preset, and Custom Scopes. tovinio works with any of them but only needs two read actions.
Scopes — what to pick in the console
Type: Personal access token
Easy mode: Preset → Read Only (covers every endpoint tovinio calls)
Least privilege (Custom Scopes):
account → Read (1/1)
billing → Read (1/1)
everything else (leave unchecked)
Expiration: 90 days (recommended). Indefinite tokens accepted but discouraged.account → Read— reads the account profile to confirm the token is alive (/v2/account).billing → Read— lists invoices and pulls each invoice's per-line-item CSV (/v2/customers/my/invoicesand…/csv).- No write actions. Skip every Create / Update / Delete checkbox.
Three endpoints, total. Validation hits one; sync hits the other two — the invoices list once, plus one CSV download per invoice.
DO API surface
# Validation — runs on every save and on scheduled re-check:
GET /v2/account
→ { account: { email, uuid, status, ... } }
tovinio asserts account.status == "active".
# Sync — runs on backfill, manual sync, and the daily cron:
GET /v2/customers/my/invoices
→ { invoices: [ { invoice_uuid, invoice_period, amount, ... } ] }
tovinio paginates the trailing 90 days for a backfill.
GET /v2/customers/my/invoices/{invoice_uuid}/csv
→ text/csv (one HTTP call per invoice; usually 3-4 per backfill)
Each row → a raw row: { service, resource_id?, usd, date }. Every row of the CSV becomes a raw row in tovinio. The column mapping: product → service; description may carry a resource_id; USD → usd; start → date.
Sample (truncated)
product,group_description,description,hours,start,end,USD,project_name,category
Droplets,project-a,prod-web-1 (s-2vcpu-2gb),720.000000,2026-04-01,2026-04-30,12.00,project-a,iaas
Spaces,,Spaces Object Storage,720.000000,2026-04-01,2026-04-30,5.00,,iaas
Block Storage,project-a,vol-prod-data-100gb,720.000000,2026-04-01,2026-04-30,10.00,project-a,iaas
Bandwidth (Public),,Outbound bandwidth (overage),1.000000,2026-04-01,2026-04-30,0.18,,bandwidth
Load Balancers,project-b,lb-prod (small),720.000000,2026-04-01,2026-04-30,12.00,project-b,iaas DigitalOcean has no AWS-style cost-allocation tags. tovinio uses the project_name column from each invoice CSV — populated by DO's built-in Projects feature on your account — and surfaces it on the raw row as a synthetic project tag. Two rule kinds work against that:
kind=tag— matches the synthetic tag (e.g. ruleproject = acmematches every CSV line whereproject_nameisacme).kind=account— matches the account subject returned by/v2/account(your DO team's email or uuid). Useful when one DO team maps to one tovinio project entirely.
To populate project_name, assign each Droplet, Space, and load balancer to a Project in the DO console (Manage → Projects). Resources without a Project assignment ship to tovinio with an empty project_name and end up unallocated.
- 5,000
- DO requests / hour / token
- ≈ 5
- requests per backfill
- 60s
- tovinio cooldown / connection
DigitalOcean enforces a per-token rate limit of 5,000 requests per hour. A typical 90-day backfill uses one /account call, one invoice list page, and one CSV download per invoice — roughly three to five HTTP calls total. To prevent accidental hammering, tovinio applies a 60-second cooldown between backfills for the same connection.
- Console → API → Personal access tokens .
- Click the trash icon next to the token. Confirm.
- In tovinio, edit the connection and paste a freshly minted token. Validation will run again before the credential is re-encrypted.
If you revoke without replacing, the next sync fails with a sanitized error and the connection moves to the error state. Live data already ingested stays in tovinio; only future syncs are blocked.
- CSVs reflect billed spend, not real-time usage. Same-day numbers may shift slightly until the invoice is finalized.
- tovinio's parser tolerates added columns. If DigitalOcean grows the CSV schema, integration keeps working as long as the column names above remain.
- No resource-level Cost Explorer-style API exists for DO. "Unattributed" rows are the honest signal that no resource id was attached to the line item.
Frequently asked
What credentials does Tovin.io need?
Read-only access to billing data plus a single identity-validation endpoint per provider. No permission to create, modify, or delete cloud resources — ever.
How is each credential stored?
Every credential is encrypted under KMS envelope encryption at rest, decrypted only during a billing pull, and rotated by pasting a fresh credential — never re-keyed in place.
Which providers are supported?
AWS via cross-account IAM role or access key, Google Cloud via a billing-scoped service account JSON key, and DigitalOcean via a read-only personal access token.
Does Tovin.io ever write back to a cloud account?
No. The connection surface is entirely read-only and the docs above list every endpoint each integration calls — there is nothing else.