Sync AWS Bills to Odoo ERP: Python Integration Guide
Build a custom Python pipeline to sync AWS Cost Explorer billing data into Odoo journal entries automatically. Complete step-by-step code included.
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:
AWS Cost Explorer API Python Script Odoo XML-RPC API
┌─────────────────┐ ┌─────────────────────┐ ┌──────────────────┐
│ EXTRACT │ │ TRANSFORM │ │ LOAD │
│ │─────▶│ │─────▶│ │
│ Pull daily cost │ │ Map AWS services │ │ Create journal │
│ data by service │ │ to Odoo analytic │ │ entries with │
│ │ │ account codes │ │ debit/credit │
└─────────────────┘ └─────────────────────┘ └──────────────────┘
│
n8n / Lambda Slack / Email │
┌─────────────────┐ ┌─────────────────────┐ │
│ SCHEDULE │ │ ALERT │◀──────────────┘
│ │ │ │
│ Run daily at │ │ Notify finance of │
│ 6 AM via cron │ │ anomalies / totals │
└─────────────────┘ └─────────────────────┘
| 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: Pull AWS Costs with the Cost Explorer API
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: Map AWS Services to Odoo Analytic 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: Create Journal Entries in Odoo via XML-RPC
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: The Orchestration Pipeline
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.
This is one piece of a broader cloud cost optimization strategy. Once your costs are visible in your ERP, the next step is actually reducing them.
Frequently Asked Questions
How much does the AWS Cost Explorer API cost?
Each GetCostAndUsage request costs $0.01. Running once per day costs roughly $0.30/month — negligible compared to the engineering hours saved on manual reconciliation.
Does this work with Odoo Community Edition?
Yes. The XML-RPC API is available in both Odoo Community and Enterprise editions. The only requirement is that the Accounting module is installed and your API user has write access to account.move.
How do I handle multi-currency billing (USD to EUR/MAD)?
AWS bills in USD by default. If your Odoo instance uses a different base currency, add a currency conversion step in the Transform phase. Use the European Central Bank exchange rate API or configure AWS to bill in your local currency via the Billing Console.
Can I adapt this for GCP or Azure instead of AWS?
Yes. Replace the AWS Cost Explorer API call with the GCP BigQuery Billing Export or the Azure Cost Management API. The Odoo loading step stays identical — only the extraction source changes.
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 a Free 15-Minute AWS Bill Review
Send me your AWS setup and I'll pinpoint the exact resources bleeding money, then hand you a prioritized roadmap to cut your bill 30–40%. No automated PDF — just the Terraform and Python fixes.

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