Building an engineering Scope 3 data pipeline from scratch
Anand Srinivasan
17 Jul 2026 · 7 min read
For a software company, Scope 3 is the emissions story. No factory, no fleet, Scope 1 and 2 are rounding errors. Category 1 (cloud, CI/CD, SaaS tools) and Category 11 (employee hardware) are where the numbers actually live.
This is the system we built for EmitCI's Engineering Scope 3 feature. Every architectural decision below is live in the product.
What EmitCI tracks
EmitCI's /scope3 page covers five layers: cloud compute (EC2, Cloud Run, Azure VMs, RDS, S3), CI/CD pipeline runners, SaaS tools via spend-based estimation, employee hardware embodied carbon, and network/storage. Together these cover GHG Protocol Cat 1 and Cat 11.
SaaS: DEFRA's 0.14 kgCO2e/USD spend factor converts monthly invoices to carbon with no vendor telemetry needed. Slack, GitHub, Jira, Figma, Datadog and others are preset. Add any tool with a monthly spend figure and it appears in the summary immediately.
Cat 11 is embodied hardware carbon. A MacBook Pro M3 carries ~147 kgCO2e from manufacturing before it is powered on. At 50 engineers on a 3-year refresh cycle: 2,450 kgCO2/year from laptops alone, often more than total cloud compute. Most carbon dashboards count neither category. EmitCI counts both.
The data model
One time-series store: one row per billing record (service × region × period), holding both cost and carbon on the same record. Cost and carbon share a schema, not separate systems. Every record tracks which data source produced its carbon figure — the foundation for confidence scoring — plus the cost-allocation tags for attribution.
Three supporting stores: SaaS tool emissions (tool + monthly spend + estimated kgCO2/month), hardware declarations (headcount + device type + refresh cycle), and locked base year periods on the organisation record.
Multi-account is built in from the start. Multiple AWS accounts or GCP projects sync into the same carbon store and aggregate on the /scope3 page.
The ingestion pipeline
EmitCI's sync pipeline runs in four stages: fetch, calculate, merge, store.
┌──────────────────────────────────────────────────────┐
│ INGESTION │
│ AWS Cost Explorer · GCP Billing · Azure Cost Mgmt │
│ pulls usage by service, region, and billing period │
│ extracts cost-allocation tags: Team, Env, Project │
└────────────────────────┬─────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ ENRICHMENT │
│ Energy = usage_hours × (watts / 1000) │
│ CO₂ = energy × regional grid intensity (IEA) │
│ Accuracy = ±30-50% │
│ If provider-native carbon API available: │
│ CO₂ = native MTCO₂e (measured) ±10% │
└────────────────────────┬─────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ UNIFIED CARBON STORE │
│ cost · energy · co2 · tags · data source per row │
│ + SaaS tool emissions (spend × DEFRA factor) │
│ + Hardware emissions (headcount × embodied CO₂) │
└───────────────┬──────────────────────────────────────┘
│
┌──────────┴──────────────┐
▼ ▼
┌──────────────────┐ ┌───────────────────────┐
│ Cost & Carbon │ │ Compliance Exports │
│ Dashboards │ │ ESRS E1 CSV │
│ Tag attribution │ │ CDP C6.5 Excel │
│ Anomaly alerts │ │ Methodology PDF │
└──────────────────┘ └───────────────────────┘Fetch calls AWS Cost Explorer, GCP Billing API, and Azure Cost Management. Usage is pulled grouped by service and region. Cost-allocation tags (Team, Environment, Project) are extracted here and stored on every carbon row so the same filter FinOps uses for cost attribution works for carbon attribution with zero extra setup.
Calculate applies two public lookups: instance TDP in watts from manufacturer specs, and IEA regional grid intensity in gCO2/kWh. Energy = usage_hours × (watts ÷ 1000). CO2 = energy × intensity. This is the ±30-50% path.
Merge is where accuracy improves. AWS's Sustainability API (which replaced the deprecated CCFT in June 2026) returns measured MTCO2e per service and region. When available, it replaces the energy × intensity number and accuracy goes from ±30-50% to ±10%. For GCP, customers who have the BigQuery Carbon Footprint export enabled can supply their dataset ID and get the same improvement. Azure continues on energy × intensity only — the Microsoft EMIS API requires separate portal registration and is being retired March 2027.
Store writes one enriched record per billing row holding both cost and carbon. Every cost query becomes a carbon query.
Tag attribution
Cost-allocation tags from your cloud provider (Team, Environment, Project) are stored alongside each carbon record. The /scope3 page exposes a tag filter dropdown — selecting a team instantly scopes the entire summary to that team's emissions. If 40% of spend has no Team tag, 40% of carbon is unattributed. EmitCI surfaces that coverage gap explicitly, auditors expect to see it quantified.
Confidence scoring
CSRD requires disclosure of which emission factors were used, their source, and the uncertainty range. Every record in EmitCI's carbon store tracks its data source. The summary view computes a CO2-weighted blended confidence across all sources for the installation.
Data Source Accuracy Confidence ─────────────────────────────────────────────────────────── Provider-native API (AWS, GCP) ±10% ████████ 90% IEA regional annual averages ±30–50% █████ 55% IEA global average fallback ±50%+ ████ 40% DEFRA spend-based (SaaS tools) ±40% █████ 60% ─────────────────────────────────────────────────────────── Blended score = CO₂-weighted average across all sources Target ≥ 70% measured for a defensible CSRD disclosure
A blended score of 70%+ is defensible for a first filing. The /scope3 summary strip shows the blended score alongside total CO2. A disclosure with no confidence metadata is incomplete.
The base year trap
CSRD requires a base year for every material Scope 3 category. EmitCI's default retention window is 365 days for free/starter plans, 1,095 days for Enterprise. Start measuring in 2025, retention runs Jan 2026 — FY2024 data is gone, no YoY comparison, filing fails.
Q1 2025 Jan 2026 Jul 2026
│ │ │
▼ ▼ ▼
Start Retention CSRD filing
measuring → deletes → base year
emissions FY2024 data MISSING
↑
← THE TRAP ─────────────────────
Base year gone before filing.
YoY comparison impossible.
─────────────────────────────────────────────────
Solution: lock periods BEFORE retention runs
Mark FY2024, FY2025 as closed
→ Retention skips locked years permanently
→ Base year preserved for audit, foreverEmitCI solves this with period locking. The Base Year card on the /scope3 page lets you set a base year and close prior periods. Locked years are permanently exempted from the retention sweep, preserved for audit regardless of plan limits. If you have 2024 data in EmitCI today, lock it now. It takes seconds.
The compliance export chain
Three documents, all generated from the same underlying data store. Available on Team and Enterprise plans from the /scope3 page.
Cloud emissions + SaaS tool emissions + Hardware emissions
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
ESRS E1 CSV CDP C6.5 xlsx Methodology PDF
───────────── ───────────── ───────────────
One row per One row per Org boundary
emission source Scope 3 cat. Data sources
Includes rows MTCO₂e values Emission factors
for EXCLUDED ← Eval. status Uncertainty range
categories % measured Exclusion reasons
↑ Completeness
Auditors check statement sheet Required for any
this first third-party auditESRS E1 CSV: one row per emission source, plus explicit exclusion rows for every Scope 3 category not covered with a stated reason. EmitCI generates these automatically. Most first-time filings are flagged here, auditors check completeness before they check the numbers.
CDP C6.5 workbook: one row per Scope 3 category in MTCO2e (grams ÷ 1,000,000,000), with a completeness statement sheet. EmitCI outputs this as a ready-to-upload Excel file.
Methodology PDF: org boundary, data sources, emission factors, uncertainty ranges, exclusion list. Generated from the installation's actual data. Auditors read this first. Without it, the numbers are unverifiable and the filing is non-compliant.
What the numbers look like
Layer Share ────────────────────────────────────────── Cloud compute 56% Databases 15% SaaS tools 11% CI/CD runners 9% Hardware 6% Storage 3% ────────────────────────────────────────── 50-engineer team · ~$30k/month cloud spend Hardware + SaaS = ~17% combined Both required by GHG Protocol Most carbon tools count neither
What you get with EmitCI
① Connect cloud accounts AWS / GCP / Azure (multi-account supported)
│ Billing API fetch starts immediately
▼
② Connect GitHub CI/CD runner carbon tracked per workflow run
│
▼
③ Declare SaaS + hardware /scope3 → SaaS Tools card + Hardware card
│ spend × DEFRA factor · headcount × embodied CO₂
▼
④ Lock base year /scope3 → Base Year card
│ Closed periods exempted from retention forever
▼
⑤ Download compliance /scope3 → Compliance Downloads (Team+)
ESRS E1 CSV · CDP C6.5 xlsx · Methodology PDFThe exports fall out of the data model naturally — no separate build required. The only step that has a deadline is locking the base year. Everything else can be added incrementally.