Bridging Cloud and Accounting: Mapping AWS Spending Directly into Odoo ERP for Automated Audits
Stop reconciling AWS bills in spreadsheets. Learn how to build an automated pipeline that syncs AWS Cost Explorer data directly into Odoo ERP analytic accounts, giving your finance team real-time cloud cost visibility without bothering a single engineer.
TL;DR
Your CFO should never need to ask engineering "what did we spend on AWS last month?"
- The Stack: AWS Cost Explorer API (cost retrieval), Python (transformation), Odoo XML-RPC API (accounting entries), n8n or Lambda (scheduling).
- The Verdict: A single integration pipeline eliminates manual spreadsheet reconciliation, gives finance real-time cloud cost visibility, and satisfies auditors who demand traceable IT expenditure records.
Need this built for your team? I integrate AWS billing data into Odoo, NetSuite, and custom ERPs so your finance team gets automated, auditable cloud cost reports.
Book a Free Consultation — let's bridge the gap between your cloud and your books.
The Spreadsheet Problem
Every month, the same ritual plays out in thousands of companies:
- An engineer logs into the AWS Billing Console.
- They export a CSV of costs grouped by service.
- They email the CSV to the finance team.
- A bookkeeper manually enters the numbers into the ERP system (Odoo, QuickBooks, NetSuite).
- The CFO reviews the numbers two weeks later — long after the money is gone.
This process is slow, error-prone, and completely unauditable. If an auditor asks "can you prove that the $14,000 EC2 charge in March matches a specific cost center?", the answer is usually a shrug and a spreadsheet with manual formulas.
The Architecture
Instead of manual exports, we build an automated pipeline:
| Stage | What Happens | Tool |
|---|---|---|
| Extract | Pull daily cost data from AWS | AWS Cost Explorer API |
| Transform | Map AWS services to Odoo analytic accounts | Python script |
| Load | Create journal entries in Odoo | Odoo XML-RPC API |
| Schedule | Run automatically every day | n8n / AWS Lambda |
| Alert | Notify finance of anomalies | Slack / Email |
Step 1: Pulling Costs from AWS
The AWS Cost Explorer API gives you granular, programmatic access to your spending data. Here is how to pull daily costs grouped by service:
aws_costs.py
import boto3
from datetime import datetime, timedelta
def get_daily_costs(days: int = 1) -> list[dict]:
"""Fetch AWS costs for the last N days, grouped by service."""
client = boto3.client("ce", region_name="us-east-1")
end = datetime.now().strftime("%Y-%m-%d")
start = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
response = client.get_cost_and_usage(
TimePeriod={"Start": start, "End": end},
Granularity="DAILY",
Metrics=["UnblendedCost"],
GroupBy=[
{"Type": "DIMENSION", "Key": "SERVICE"},
],
)
costs = []
for result in response["ResultsByTime"]:
date = result["TimePeriod"]["Start"]
for group in result["Groups"]:
service = group["Keys"][0]
amount = float(
group["Metrics"]["UnblendedCost"]["Amount"]
)
if amount > 0.01: # Skip negligible charges
costs.append(
{
"date": date,
"service": service,
"amount": round(amount, 2),
"currency": "USD",
}
)
return costsStep 2: Mapping AWS Services to Odoo Accounts
The critical piece of this integration is the mapping table. Every AWS service must correspond to an Odoo analytic account so costs are categorized correctly for financial reporting:
account_mapping.py
# Map AWS service names to Odoo analytic account codes
AWS_TO_ODOO_MAP: dict[str, dict] = {
"Amazon Elastic Compute Cloud - Compute": {
"account_code": "6100", # IT Infrastructure - Compute
"analytic_account": "AWS-EC2",
"description": "EC2 compute instances",
},
"Amazon Relational Database Service": {
"account_code": "6100", # IT Infrastructure - Compute
"analytic_account": "AWS-RDS",
"description": "RDS database instances",
},
"Amazon Simple Storage Service": {
"account_code": "6110", # IT Infrastructure - Storage
"analytic_account": "AWS-S3",
"description": "S3 object storage",
},
"Amazon Elastic Container Service for Kubernetes": {
"account_code": "6100", # IT Infrastructure - Compute
"analytic_account": "AWS-EKS",
"description": "EKS Kubernetes clusters",
},
"AWS Data Transfer": {
"account_code": "6120", # IT Infrastructure - Network
"analytic_account": "AWS-TRANSFER",
"description": "Cross-region and internet data transfer",
},
}
def map_cost_to_odoo(service: str) -> dict:
"""Resolve an AWS service name to Odoo account codes."""
return AWS_TO_ODOO_MAP.get(service, {
"account_code": "6199", # IT - Uncategorized
"analytic_account": "AWS-OTHER",
"description": f"Unmapped: {service}",
})When your auditor asks "where did the $8,000 in compute charges go?", the analytic account trail leads directly from the AWS API response to the Odoo journal entry. No spreadsheets. No guessing.
Step 3: Creating Journal Entries in Odoo
Odoo exposes an XML-RPC API that lets you programmatically create accounting journal entries. Here is how to push AWS costs directly into your books:
odoo_sync.py
import xmlrpc.client
class OdooClient:
def __init__(self, url: str, db: str, user: str, password: str):
self.url = url
self.db = db
self.uid = xmlrpc.client.ServerProxy(
f"{url}/xmlrpc/2/common"
).authenticate(db, user, password, {})
self.models = xmlrpc.client.ServerProxy(
f"{url}/xmlrpc/2/object"
)
self.password = password
def _execute(self, model: str, method: str, *args):
return self.models.execute_kw(
self.db, self.uid, self.password,
model, method, *args,
)
def create_journal_entry(
self,
date: str,
service: str,
amount: float,
account_code: str,
analytic_account: str,
description: str,
):
"""Create a journal entry for an AWS cost line item."""
# Find the expense and payable account IDs
expense_account = self._execute(
"account.account", "search",
[[["code", "=", account_code]]],
)
payable_account = self._execute(
"account.account", "search",
[[["code", "=", "2100"]]], # Accounts Payable
)
if not expense_account or not payable_account:
raise ValueError(
f"Account {account_code} or 2100 not found in Odoo"
)
entry = self._execute(
"account.move", "create",
[{
"journal_id": 2, # Vendor Bills journal
"date": date,
"ref": f"AWS-{service}-{date}",
"line_ids": [
(0, 0, {
"account_id": expense_account[0],
"name": f"AWS {description}",
"debit": amount,
"credit": 0,
}),
(0, 0, {
"account_id": payable_account[0],
"name": f"AWS {description} (Payable)",
"debit": 0,
"credit": amount,
}),
],
}],
)
return entryStep 4: Tying It Together
The orchestration script connects all three pieces — pull from AWS, map to Odoo accounts, push journal entries:
sync_pipeline.py
from aws_costs import get_daily_costs
from account_mapping import map_cost_to_odoo
from odoo_sync import OdooClient
def run_sync():
odoo = OdooClient(
url="https://your-odoo.com",
db="production",
user="api-user@company.com",
password="your-api-key",
)
costs = get_daily_costs(days=1)
for item in costs:
mapping = map_cost_to_odoo(item["service"])
odoo.create_journal_entry(
date=item["date"],
service=item["service"],
amount=item["amount"],
account_code=mapping["account_code"],
analytic_account=mapping["analytic_account"],
description=mapping["description"],
)
print(
f"Synced: {item['service']} "
f"${item['amount']} -> {mapping['account_code']}"
)
if __name__ == "__main__":
run_sync()Schedule this via a daily n8n cron workflow or an AWS Lambda triggered by EventBridge at 6 AM every morning. By the time your finance team opens their laptops, yesterday's cloud costs are already in the books.
The Operational Reality
- Currency and tax handling. AWS bills in USD. If your Odoo instance runs in EUR or MAD, you need a currency conversion step. Use the European Central Bank API or AWS's own billing currency settings.
- Duplicate prevention. Always check if a journal entry for a given date and service already exists before creating a new one. Idempotency prevents double-booking.
- Multi-account organizations. If you run AWS Organizations with multiple linked accounts, add the
linked_account_idas a grouping dimension in the Cost Explorer query. Map each AWS account to a separate Odoo cost center. - Odoo permissions. The API user needs access to the Accounting module and write permissions on
account.move. Use a dedicated service account, not a personal login.
The Payoff
This integration eliminates the monthly "AWS bill reconciliation" ritual. Your CFO gets daily, automated, auditable cloud cost entries in the ERP system — categorized by service, mapped to cost centers, and ready for financial reporting.
More importantly, when an auditor asks "can you trace the $14,000 EC2 charge in Q1 to a specific cost center and business unit?", the answer is a database query, not a frantic search through email attachments.
Drowning in manual AWS bill reconciliation? If your finance team is still copying numbers from the AWS console into spreadsheets, you are wasting engineering hours and failing audit readiness.
I build automated cloud-to-ERP billing pipelines that give your CFO real-time visibility.
Let's automate your cloud accounting. Book a Free Consultation.
Get weekly DevOps insights
Join engineers who read my deep-dives on Kubernetes, AWS cost optimization, CI/CD, and infrastructure automation.

DevOps Engineer & Cloud Consultant | FinOps, GitOps & Kubernetes Expert
I build systems that run reliably, scale efficiently, and deploy intelligently. See how I can help your team.