Mohamed ARKID logo

Command Palette

Search for a command to run...

Command Palette

Search for a command to run...

Blog

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.

Updated 7 min read

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:

  1. An engineer logs into the AWS Billing Console.
  2. They export a CSV of costs grouped by service.
  3. They email the CSV to the finance team.
  4. A bookkeeper manually enters the numbers into the ERP system (Odoo, QuickBooks, NetSuite).
  5. 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  │
  └─────────────────┘      └─────────────────────┘
StageWhat HappensTool
ExtractPull daily cost data from AWSAWS Cost Explorer API
TransformMap AWS services to Odoo analytic accountsPython script
LoadCreate journal entries in OdooOdoo XML-RPC API
ScheduleRun automatically every dayn8n / AWS Lambda
AlertNotify finance of anomaliesSlack / 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 costs

Step 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 entry

Step 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_id as 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.

Free Architecture Offer

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.

✓ 15-Minute Quick Call✓ Direct Tech Analysis✓ Zero Commitments
Mohamed ARKID
Mohamed ARKID

DevOps Consultant & Cloud Engineer

I build systems that run reliably, scale efficiently, and deploy intelligently. See how I can help your team.