Overview
This guide describes how Odoo — the open-source ERP suite — connects to TrustBill, a UAE FTA-accredited service provider (ASP), so Odoo users in the UAE can issue PINT-AE compliant e-invoices without switching ERPs.
Odoo covers the full ERP stack — Accounting, Sales, Purchase, Inventory, Manufacturing, CRM. UAE customers usually install the l10n_ae localization module for the local chart of accounts, VAT taxes and TRN handling. But Odoo does not ship native PINT-AE XML generation or a UAE-Peppol connector, so invoices posted in Odoo cannot reach the FTA network by themselves.
TrustBill fills that gap. Invoices are pulled from Odoo through the TrustBill Odoo Module (installed as a normal Odoo app), or via XML-RPC / JSON-RPC, or via Automated Actions webhooks. TrustBill translates the payload into PINT-AE XML, validates it in real time, delivers it over Peppol via a certified ASP (Tron-Stride or Storecove), and the FTA acknowledgement is written back onto the Odoo invoice as a custom field.
What this integration delivers
- Certified TrustBill Odoo module — installed with
pip-style copy into the Odooaddons/path, then Apps → Install - Works with Odoo 15 LTS, 16, 17 and 18 — Community and Enterprise, self-hosted, Odoo.sh, or Odoo Online (with limitations)
- Customer invoices (
out_invoice), credit notes (out_refund) and vendor bills (in_invoice) mapped to PINT-AE document types - Odoo's UAE taxes (
l10n_aemodule) auto-mapped to FTA tax categories - Live FTA status written back onto
account.move.x_trustbill_statusandx_trustbill_ack_ref - Bulk migration path for historical Odoo invoices via CSV export
- Multi-company Odoo databases handled — one company per TrustBill tenant
- Compatible with Odoo Subscriptions (recurring), Sales Orders → Invoice, and Vendor Bills flows
Why Odoo Isn't PINT-AE Ready
Odoo Enterprise ships Peppol e-invoicing modules for several countries — Belgium, France, Poland, Spain, Netherlands, Singapore — but the UAE PINT-AE profile is not on that list as of Odoo 18. What that means concretely:
- Odoo's built-in
account_ediandaccount_peppolmodules will not generate PINT-AE XML — they target UBL 2.1 with country-specific profiles that don't include AE - Odoo's Peppol Access Point service does not currently route to UAE ASPs (Tron-Stride / Storecove)
- The UAE FTA requires accreditation of the transmitting ASP; Odoo Access Point is not accredited in the UAE as of this document's date
TrustBill provides the missing three pieces: PINT-AE XML generation, Schematron validation, and delivery over an accredited UAE ASP. Odoo continues to be the system of record for accounting and stays authoritative for the invoice number, VAT computation and customer master data.
Supported Odoo Versions & Editions
Odoo releases a new major version every October. TrustBill supports the current version and the three previous LTS releases.
| Odoo version | Release status | Community | Enterprise | Support level |
|---|---|---|---|---|
| Odoo 18 | Current stable (Oct 2024) | Yes | Yes | Full — recommended |
| Odoo 17 | Previous stable | Yes | Yes | Full |
| Odoo 16 | Older stable | Yes | Yes | Full |
| Odoo 15 | LTS — Enterprise support until Oct 2027 | Yes | Yes | Full |
| Odoo 14 and older | End of support | Yes | EOL | Best-effort — upgrade before UAE FTA go-live |
| OpenERP 7 / 8 / 9 | Legacy | EOL | EOL | Migrate first — no direct connector |
| Odoo 18.1 / 18.2 point releases | Bug-fix rollups | Yes | Yes | Full — same connector, no changes |
Community vs Enterprise: both editions work — the TrustBill Odoo module has no Enterprise-only dependencies. Enterprise-only features that enhance the flow (Studio for custom fields, Subscriptions app, Documents app) are supported when present but not required.
Deployment Model Compatibility
Odoo runs in three deployment models. Not every connection method works in every model — Odoo Online is the most restrictive because it does not allow custom modules.
| Deployment | Custom modules? | XML-RPC / JSON-RPC? | Automated Actions webhook? | Recommended path |
|---|---|---|---|---|
| Self-hosted (Docker / Debian / source) | Yes | Yes | Yes | TrustBill Odoo module (fastest, richest) |
| Odoo.sh (Odoo's own PaaS) | Yes — via GitHub branch | Yes | Yes | TrustBill Odoo module (push to your .sh repo) |
| Odoo Online (SaaS at odoo.com) | No — modules blocked | Yes — with API key | Yes — v16+ Automated Actions with HTTP request | Automated Actions webhook + XML-RPC pull. TrustBill module cannot be installed. |
Odoo Online users, please read: Odoo's SaaS platform explicitly forbids third-party module installation. The TrustBill Odoo module cannot be uploaded there. Your two options are: (1) migrate to Odoo.sh (Odoo will do the migration for free), which restores full custom-module support, or (2) use the API-only path documented below — XML-RPC pull + Automated Actions webhook + write-back via Odoo's own external API. Both deliver equivalent compliance; the API-only path just has a slightly higher setup cost.
Prerequisites
On the Odoo side
- Odoo 15, 16, 17 or 18 — Community or Enterprise
- The
l10n_aeUAE localization module installed (Apps → search "United Arab Emirates - Accounting" → Install). This module ships the UAE chart of accounts and default VAT taxes. - Company set to United Arab Emirates with a valid 15-digit UAE TRN in
res.company.vat(Settings → Companies → Update Info → Tax ID) - Customers (
res.partner) populated with TRN in thevatfield for B2B - Taxes (
account.tax) configured for UAE — 5% Standard, 0% Zero-Rated, Exempt - Fiscal Positions for GCC and non-GCC customers (if applicable)
- An Odoo user with API access — settings admin can enable API keys via Settings → Users → Enable API keys. Odoo 14+ supports personal API keys (recommended over shared passwords).
On the TrustBill side
- A TrustBill SME account (one account = one TRN = one Odoo company)
- Completed KYB (business verification) — trade licence + TRN certificate uploaded
- Submission mode set to
testorsandboxduring onboarding,livefor production - Per-tenant API credential (issued during the Sources → Add Odoo flow)
Integration Architecture
Two paths, depending on whether you can install a module in Odoo:
Push mode — TrustBill Odoo Module (self-hosted, Odoo.sh)
- An invoice is posted in Odoo (
account.move.action_post). - The TrustBill module's
@api.modelhandler triggers onstate = 'posted'. - The handler serialises the invoice into TrustBill's
InvoiceCreateInputand POSTs tohttps://api.trustbill.ae/v1/invoiceswith a per-tenant Bearer JWT. - TrustBill stores the invoice as
draft. - If auto-submit is enabled,
POST /v1/invoices/submitfires immediately; otherwise the user clicks Submit to FTA from the invoice's TrustBill tab. - Pipeline runs:
queued → transformed → validated → delivered. ASP forwards to FTA over Peppol.
Pull mode — XML-RPC + Automated Actions webhook (Odoo Online)
- Odoo user configures an Automated Action on
account.movetriggered by On Update whenstatebecomesposted. - The action's Server Action calls Odoo's HTTP Request feature (v16+) to POST to
https://api.trustbill.ae/v1/webhooks/odoo/{tenant}with the invoice ID. - TrustBill's webhook receiver acknowledges immediately, then fetches the full invoice + partner + lines via XML-RPC using the stored API key.
- The rest matches Push mode from step 4 onwards.
Inbound write-back (both modes)
- ASP posts an inbound webhook to TrustBill on FTA acknowledgement.
- TrustBill's Odoo worker calls the Odoo API to set
account.move.x_trustbill_status = 'acknowledged'andx_trustbill_ack_ref = <FTA ref>. - The invoice's TrustBill tab updates on the next Odoo page refresh.
Connection Methods
Six paths, ordered from easiest (module install) to most flexible (custom middleware).
1. TrustBill Odoo Module (recommended for self-hosted + Odoo.sh)
A regular Odoo module named trustbill_einvoice published under LGPL-3. Drop into your addons/ path (or add via your Odoo.sh GitHub branch), then Apps → Install.
- Adds a TrustBill tab on
account.movewith Submit / Status / History - Adds custom fields:
x_trustbill_status,x_trustbill_ack_ref,x_trustbill_reject_reason - Adds a Settings page for tenant credentials + tax mapping
- Uses Odoo's built-in
ir.cronfor retry and status refresh — no external scheduler needed - Ships translation files for English + Arabic
2. XML-RPC / JSON-RPC (Odoo's canonical external API)
TrustBill pulls invoices from Odoo using the built-in xmlrpc/2/object or jsonrpc/object endpoints. Works on every Odoo deployment including Odoo Online.
- No Odoo module required — read-only integration
- Uses an Odoo API key (v14+) instead of a user password
- Polls
account.movefiltered bystate=postedandwrite_date >= last_seen - Write-back also uses XML-RPC (
account.move.write) — custom fields must exist first (added via Studio on Enterprise, or via a small Odoo module on Community)
3. Automated Actions webhook (v16+, works on Odoo Online)
Configure an Automated Action in Odoo to POST an HTTP request to TrustBill on account.move state change. Odoo 16 added the HTTP Request Server Action out of the box.
- Real-time push — no polling delay
- Works on Odoo Online (which forbids custom modules but permits Automated Actions with Studio subscription)
- TrustBill still needs XML-RPC credentials to fetch the full invoice body
- Best combined with method 2 for the fetch-back leg
4. Odoo 18 REST API
Odoo 18 introduced a first-party REST API surface. TrustBill's connector uses it in preference to XML-RPC when Odoo 18+ is detected.
- Odoo 15–17 users can install the community
muk_restmodule for a similar surface - Same auth model (API key) as XML-RPC
- Faster and easier to debug than XML-RPC for large invoice batches
5. Zapier / Make / n8n (no-code)
TrustBill publishes Odoo connectors on Zapier, Make and self-hosted n8n. Build a "New Odoo invoice posted → Create TrustBill invoice → Submit" flow in minutes.
- Best for orgs that already standardise on one of these platforms
- Handles retry, error branching, and Slack/email notification on failure
- Adds latency (Zapier polls Odoo every 1–15 minutes on paid plans)
6. CSV Export (one-off migrations)
Export invoices from Odoo (Accounting → Customers → Invoices → Actions → Export) and upload to TrustBill's bulk import endpoint.
Which method should I use?
On self-hosted / Odoo.sh, install the TrustBill Odoo Module. On Odoo Online, combine the Automated Actions webhook (push trigger) with XML-RPC or Odoo 18 REST (fetch body + write-back). Use Zapier only if your team already uses it.
Authentication (API Keys)
Odoo supports two credential models. TrustBill uses API keys (available since Odoo 14) — never user passwords.
Creating an Odoo API key
- Log into Odoo as the integration user (create a dedicated user TrustBill Integration for audit clarity)
- Click the user avatar → Preferences → Account Security tab
- Click New API Key — copy the generated key immediately (Odoo shows it once)
- Paste it in the TrustBill Sources → Odoo wizard along with the Odoo URL, database name, and login
Connection parameters TrustBill stores
| Parameter | Example |
|---|---|
| Odoo URL | https://mycompany.odoo.com or https://erp.example.ae |
| Database name | mycompany-main-19281 (Odoo Online) or prod (self-hosted) |
| Login (email) | trustbill@yourcompany.ae |
| API key | Stored KMS-encrypted; never re-shown after save |
Idempotency-Key derivation for Odoo
Every TrustBill write endpoint requires an Idempotency-Key. For Odoo, derive it from the account.move ID (integer, stable across renumbering):
Idempotency-Key: odoo::{database}::account.move::{id}Automated Action retries or module ir.cron retries reuse the same key — TrustBill returns the cached response, so retries never create duplicate FTA submissions.
Odoo → PINT-AE Field Mapping
The TrustBill connector maps Odoo's account.move record (with move_type in ['out_invoice', 'out_refund', 'in_invoice']) into InvoiceCreateInput, which is then rendered into PINT-AE XML.
Header-level mapping (account.move)
| Odoo field | TrustBill field | Notes |
|---|---|---|
name | number | Sequence-generated ("INV/2026/00042"); 1–64 chars |
id | Idempotency-Key + sellerInvoiceReference | Odoo internal integer ID |
invoice_date | issueDate | ISO YYYY-MM-DD |
invoice_date_due | dueDate | Optional |
currency_id.name | currency | Must be AED; multi-currency invoices converted at currency_rate |
move_type | invoiceType | out_invoice → sale, in_invoice → purchase, out_refund → credit_note, in_refund → debit_note |
company_id.vat | seller.trn | 15-digit UAE TRN |
company_id.name | seller.name | — |
partner_id.name | buyer.name | Customer master |
partner_id.vat | buyer.trn | 15-digit TRN; leave empty for B2C |
partner_id.country_id.code | buyer.country | ISO 3166-1 alpha-2 (Odoo stores it natively) |
partner_id.street, street2, city, state_id, zip | buyer.address.{line1,line2,city,state,postalCode} | — |
reversed_entry_id.name | referencedInvoiceNumber | On out_refund — links credit note to the original invoice |
narration | notes | Visible in FTA-facing XML |
ref | buyerReference | Customer PO / reference |
Line-level mapping (account.move.line where display_type = 'product')
| Odoo field | TrustBill field | Notes |
|---|---|---|
name | lines[].description | Non-empty; supports Arabic |
quantity | lines[].quantity | Must be > 0 (positive on credit notes too — see convention below) |
price_unit | lines[].unitPrice | VAT-exclusive by default; if partner_id.property_position is a tax-inclusive Fiscal Position, connector strips VAT |
discount | lines[].discountPercent | Odoo stores discount as a percentage on the line |
product_id.default_code | lines[].sellerItemId | Falls back to product_id.id if no internal reference |
product_uom_id | lines[].unit | Odoo UOM mapped to UN/ECE Rec 20 (Units → C62, Hours → HUR, kg → KGM, Litre → LTR) |
tax_ids (many2many with account.tax) | lines[].vatRate + lines[].taxCategory | Mapped via the l10n_ae Tax Mapping table |
analytic_distribution | Not sent to FTA | Preserved on the Odoo side; TrustBill ignores |
Totals are computed server-side
Do not send amount_untaxed / amount_tax / amount_total. TrustBill re-computes totals from lines[] and returns them so the Odoo module can reconcile. If Odoo's amount_total differs from TrustBill's by more than ±0.01 AED (Odoo's rounding is per-line, TrustBill's is per-invoice), the connector logs a variance and blocks submission until you re-post the invoice in Odoo (Reset to Draft → Post) so its rounding refreshes.
Create Invoice (draft)
Endpoint: POST /v1/invoices
A real Odoo invoice mapped to the TrustBill payload. Odoo invoice: INV/2026/00042 — Sale of two IoT sensors to a Dubai buyer at 5% VAT.
Request body
{
"number": "INV/2026/00042",
"issueDate": "2026-09-08",
"dueDate": "2026-10-08",
"currency": "AED",
"invoiceType": "sale",
"buyer": {
"name": "Smart Buildings LLC",
"trn": "100234512300007",
"country": "AE",
"email": "ap@smartbuildings.example",
"address": {
"line1": "Al Barsha Heights, Office 2210",
"city": "Dubai",
"country": "AE"
}
},
"seller": {
"name": "Odoo Trader LLC",
"trn": "100100100100013",
"country": "AE"
},
"lines": [
{
"description": "IoT Environmental Sensor Kit",
"sellerItemId": "IOT-ENV-KIT",
"quantity": 2,
"unit": "C62",
"unitPrice": 1250.00,
"vatRate": 0.05,
"taxCategory": "standard_5"
}
],
"sellerInvoiceReference": "Odoo account.move id 4128",
"notes": "Delivery: 5 working days"
}Response (201 Created) — abbreviated:
{
"id": "0d4f7e2a-....-uuid",
"number": "INV/2026/00042",
"status": "draft",
"invoiceType": "sale",
"totals": { "subtotal": 2500, "tax": 125, "total": 2625, "currency": "AED" },
"pipeline": [],
"errors": [],
"submissionMode": "sandbox",
"source": "odoo-module"
}Credit Notes & Refunds
Odoo posts credit notes as account.move records with move_type = 'out_refund'. The reversed_entry_id field links back to the original invoice.
| Odoo document | invoiceType | Required extra fields |
|---|---|---|
Credit Note reversing an invoice (reversed_entry_id set) | credit_note | referencedInvoiceNumber = reversed_entry_id.name, reasonCode |
Standalone Credit Note (no reversed_entry_id) | credit_note | reasonCode = OtherAdjustment |
Vendor Refund (in_refund) | debit_note | referencedInvoiceNumber = original vendor bill number |
Sign convention: Odoo credit notes carry positive quantities and positive rates (unlike vendor-side journal entries). Send them the same way to TrustBill — the invoiceType tells the FTA that the document reverses the reference invoice; PINT-AE handles the sign in the XML.
Subscriptions & Recurring Invoices
Odoo Enterprise ships a Subscriptions module (sale_subscription) that generates recurring invoices on a schedule. The connector treats each generated child invoice as a fresh event:
- Each recurring cycle creates a new
account.movewith its ownid— so a fresh Idempotency-Key, a fresh TrustBill draft, a fresh FTA submission - The subscription order (
sale.order) itself is not pushed to TrustBill — only the invoices it produces - Pro-rated first / last invoices flow through the same mapping; period start/end appear in the line name
- Community edition users can install
oca/contractfor equivalent recurring functionality — same connector behaviour
Auto-submit tip: for subscription cycles, enable TrustBill's auto-submit on ingest. Otherwise every generated child sits as a draft in TrustBill waiting for a human to click Submit.
Submit Invoice(s) for Delivery
Creating a draft in TrustBill does not deliver the invoice to the FTA. Submission is an explicit second call.
Endpoint: POST /v1/invoices/submit
{ "ids": ["0d4f7e2a-....-uuid"] }Odoo module UX: the TrustBill module adds a Submit to FTA button on the invoice's TrustBill tab. Auto-submit-on-ingest is the default for tenants with more than 50 invoices/month, so the button is rarely needed.
Status Tracking & Field Write-back
The TrustBill module (or the XML-RPC write path on Odoo Online) writes status changes back onto three custom fields on account.move:
| TrustBill status | Meaning | Odoo field written |
|---|---|---|
| draft | Created in TrustBill, not submitted | x_trustbill_status = 'Draft' |
| queued | Submitted, waiting for pipeline | x_trustbill_status = 'Queued' |
| validated | Passed schema + Schematron | x_trustbill_status = 'Validated' |
| delivered | Sent to FTA via ASP | x_trustbill_status = 'Delivered' |
| acknowledged | FTA confirmed receipt / validity | x_trustbill_status = 'Acknowledged', x_trustbill_ack_ref = <FTA ref> |
| rejected | FTA or ASP rejected the invoice | x_trustbill_status = 'Rejected', x_trustbill_reject_reason = <code> |
Add the fields to the Invoices list view so you can filter and colour-code by TrustBill status directly in Odoo: Invoicing → Customers → Invoices → three-dot menu → Add custom filter. The TrustBill module also adds a Status History row on the invoice's TrustBill tab showing every pipeline transition with its timestamp.
Bulk Migration from Odoo
To migrate historical Odoo invoices (up to 12 months back) into TrustBill:
- In Odoo: Invoicing → Customers → Invoices → filter by date range → select all → Actions → Export
- Pick the fields matching TrustBill's CSV schema below (or use the pre-built TrustBill Migration Template shipped with the module)
- Run the TrustBill odoo-csv-normaliser script to remap any custom fields
- Upload in
testmode viaPOST /v1/invoices/bulk - Review the summary; repeat in
sandboxandlive
TrustBill CSV column headers
number,issueDate,dueDate,invoiceType,currency,sellerTrn,sellerName,buyerTrn,buyerName,buyerCountry,buyerAddressLine1,buyerCity,lineDescription,lineQty,lineUnit,lineUnitPrice,lineVatRate,lineTaxCategory,referencedInvoiceNumber,notes| Limit | Value |
|---|---|
| Max rows per file | 1,000 invoices |
| Max file size | 5 MB |
| Partial success | Yes — bad rows reported without rolling back the batch |
Test / Sandbox / Live Modes
Two orthogonal switches: Odoo databases (test vs prod) × TrustBill submission modes (test/sandbox/live).
Odoo side
- Self-hosted: spin up a second PostgreSQL database (
--db-filter=prod|staging) or use-d staging_2026_09 - Odoo.sh: use the built-in Staging branch — every push creates a fresh staging database with a subdomain like
staging-2b1a4.dev.odoo.com - Odoo Online: create a duplicate database from My Databases → Duplicate — free for 15 days
TrustBill side
| TrustBill mode | Purpose | Reaches FTA? |
|---|---|---|
test | Local integration testing — no ASP call | No |
sandbox | End-to-end dry run against the ASP sandbox | FTA sandbox only |
live | Real, legally binding e-invoicing | Yes — the FTA production network |
Recommended pairing: Odoo Staging database + TrustBill sandbox mode during onboarding. Flip both to production together on go-live day.
l10n_ae Tax Mapping
The l10n_ae module installs a preset of UAE taxes. The connector maps them out of the box; custom-named taxes need to be mapped once in TrustBill Settings → Odoo → Tax Mapping.
account.tax record | amount | tax_group_id | TrustBill taxCategory |
|---|---|---|---|
| VAT 5% (Sales) | 5.0 | VAT 5% | standard_5 |
| Zero-Rated (Export) | 0.0 | Zero Rated | zero_rated_export |
| Zero-Rated (Healthcare) | 0.0 | Zero Rated | zero_rated_healthcare |
| Zero-Rated (Education) | 0.0 | Zero Rated | zero_rated_education |
| Exempt (Financial) | 0.0 | Exempt | exempt_financial |
| Exempt (Residential Rent) | 0.0 | Exempt | exempt_residential_rent |
| Reverse Charge (Designated Zone) | 0.0 | Reverse Charge | reverse_charge_designated_zone |
| Out of Scope | 0.0 | Out of Scope | out_of_scope |
The connector reads account.tax.name, amount and tax_group_id.name to guess the mapping. If your l10n has been customised (e.g. localised into Arabic), map each custom tax name in the TrustBill Settings once — the mapping survives Odoo upgrades.
UAE-Specific Scenarios
Free Zone / Designated Zone sellers
Add a boolean field x_designated_zone to res.company (the TrustBill module ships it). When true, the connector sets seller.designatedZone: true and PINT-AE emits the free-zone declaration.
Fiscal Positions for non-GCC and designated-zone customers
Odoo's Fiscal Position mechanism maps default taxes to region-specific replacements. The connector reads the resolved account.move.line.tax_ids (post-Fiscal-Position mapping), so if you configure Fiscal Positions correctly, the correct taxCategory flows through without further work.
Margin scheme (used cars, antiques, second-hand goods)
The TrustBill module adds a per-product.template boolean x_margin_scheme. When true, the connector sends lines[].marginScheme: true and uses the item's standard_price (cost) as the margin base.
Reverse charge on imports
Vendor bills where the vendor's country_id is outside GCC and the line uses the Reverse Charge tax are mapped to invoiceType: purchase + taxCategory: reverse_charge_designated_zone. PINT-AE emits the ReverseCharge markers.
B2C simplified tax invoices
Customers with no vat field populated are treated as B2C. Invoices under 10,000 AED become simplified tax invoices under FTA rules; TrustBill emits the correct PINT-AE profile automatically.
Multi-Company Odoo
Odoo natively supports multiple companies in one database, each with its own TRN, chart of accounts and invoice sequences. Each Odoo company = one TrustBill tenant.
- Configure one TrustBill Sources entry per Odoo company, keyed on
company_id - The module reads the invoice's
company_idand routes to the matching TrustBill tenant credential - Accountants managing many client Odoo instances install the TrustBill module once per instance
- The TrustBill Accounting Firm account type surfaces every client tenant on a single dashboard
Invoice numbering across companies: Odoo's default sequence is per-company, so INV/2026/00042 in Company A and Company B are two different invoices with the same number. TrustBill's uniqueness constraint is per-tenant + submission mode, so they don't collide as long as you use one tenant per company.
Error Handling
TrustBill errors follow the shape { code, humanMessage, meta? }. The Odoo module surfaces the humanMessage on the invoice's TrustBill tab and writes the code to x_trustbill_reject_reason.
| HTTP | Typical code | Odoo root cause | Action |
|---|---|---|---|
| 400 | invalid_body | Partner missing vat, or line has no tax_ids | Fix the partner / line in Odoo; module auto-resubmits on next state change |
| 400 | totals_mismatch | Odoo per-line rounding vs TrustBill per-invoice; typical with tax-inclusive Fiscal Positions | Reset invoice to Draft → Post again in Odoo — this refreshes rounding |
| 400 | unknown_tax | Custom-named account.tax not in the connector's map | Map the tax in TrustBill Settings → Odoo → Tax Mapping |
| 400 | l10n_ae_missing | l10n_ae module not installed on the Odoo company | Apps → search "United Arab Emirates - Accounting" → Install |
| 401 | odoo_api_key_invalid | API key revoked, or user disabled | Regenerate the API key in Odoo Preferences → Account Security, update in TrustBill Sources |
| 401 | odoo_db_not_found | Database name changed (common after Odoo.sh production promotion) | Update the database name in TrustBill Sources |
| 403 | kyb_required | TrustBill tenant not yet approved by compliance | Contact TrustBill support |
| 409 | invoice_mode_mismatch | Odoo Production instance connected but TrustBill still in sandbox | Switch mode in TrustBill dashboard |
| 409 | invoice_number_duplicate | Two Odoo companies pushed the same name | Use per-company sequences (Odoo default) + one TrustBill tenant per company |
| 422 | idempotency_required | Custom middleware missed the header | Always use odoo::db::account.move::id |
Setup Checklist
- Confirm Odoo version (15 / 16 / 17 / 18) and deployment (self-hosted / .sh / Online)
- Install
l10n_aeand set the company country to United Arab Emirates - Fill the TRN on
res.company.vat - Populate customer TRNs in
res.partner.vatfor B2B - Create a dedicated integration user (TrustBill Integration) with Accounting → User access rights, then generate an API key from Preferences → Account Security
- Create your TrustBill SME account and complete KYB (trade licence + TRN certificate)
- Install the TrustBill Odoo module (self-hosted / .sh) OR configure the Automated Actions webhook (Odoo Online)
- Paste the Odoo URL, DB, login, API key into TrustBill Sources → Add Odoo
- Confirm the tax mapping — the connector auto-maps
l10n_aetaxes; custom names need one-time mapping - Enable
sandboxmode in TrustBill - Post a test invoice in Odoo — confirm
x_trustbill_status = 'Acknowledged'within 3 minutes - Reconcile Odoo's Reporting → Tax Report against TrustBill's Delivered Invoices report
- Switch to
livemode when FTA registration is complete
Suggested test cases before go-live
- B2B customer invoice, single line, 5% VAT — expect
acknowledged - B2C invoice under 10,000 AED (partner with empty
vat) — expect PINT-AE "simplified" profile - Credit note reversing an earlier invoice (
out_refundwithreversed_entry_id) — expect matching pair - Export sale using Fiscal Position "Export outside GCC" — expect
zero_rated_export - Designated-zone customer — expect reverse-charge markers
- Multi-line invoice with mixed VAT (5% + 0%) — expect two
taxCategoryvalues - Subscription cycle child invoice — expect fresh FTA submission per cycle
- Multi-currency invoice (USD → AED via Odoo
currency_rate) — expect conversion, matching totals - Multi-company database (Company A + Company B) — expect routing to the correct TrustBill tenant per company
- Bulk upload of 500 historical invoices via CSV — expect partial success reported per row
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Module install fails on Odoo.sh | Module not in the enterprise or community branch's addons path | Push the module to <repo>/addons/trustbill_einvoice then rebuild the .sh branch |
| Invoice posted in Odoo, nothing in TrustBill | ir.cron disabled, or Automated Action condition not matching | Enable the TrustBill: sync pending invoices cron; verify Automated Action trigger is On Update with state == 'posted' |
| Custom fields don't appear on the invoice | Module installed but the user's view rights don't include them | Reload the browser (Odoo caches views per user); verify the user has read access to x_trustbill_* |
Invoice stuck in queued | Insufficient TrustBill credit or KYB pending | Check the TrustBill dashboard banner; top up credit |
Invoice reaches rejected with "buyer_trn_invalid" | res.partner.vat is 14 digits or has spaces | Fix on the partner form; module auto-retries on next state change or via the Retry button |
Arabic descriptions render as ? | PostgreSQL database not on UTF-8 (rare on modern Odoo) | Check SHOW SERVER_ENCODING; — should be UTF8; if not, dump/restore the DB with --encoding=UTF8 |
totals_mismatch on tax-inclusive invoices | Odoo per-line rounding vs TrustBill per-invoice | In Odoo: Settings → Accounting → Taxes → Rounding Method = Round Globally; recompute the invoice |
| Automated Action webhook fails with 401 | TrustBill webhook URL includes tenant token that was regenerated | Regenerate the webhook URL in TrustBill Sources; paste the new URL into the Automated Action |
| Duplicate FTA submissions after Odoo cron restart | Module's local queue lost the "already-sent" marker | Confirm Idempotency-Key formula uses account.move.id — duplicates are safely deduped on TrustBill side |
| Status write-back not appearing on Odoo invoice | API key user lacks write access to account.move | Grant the integration user "Show Full Accounting Features" access group |
Support & Contact
Technical Support
- WhatsApp: +971 52 260 9313
- Email: info@trustbill.ae
- Documentation hub: trustbill.ae/en/documentation
- Odoo integration marketing page: trustbill.ae/en/integrations/odoo
- Zoho integration: /documentation/zoho
- QuickBooks Online integration: /documentation/quickbooks-online
- QuickBooks Desktop integration: /documentation/quickbooks-desktop
- Tally integration: /documentation/tally
- ERP Partner API guide: /documentation/erp-partner-api
Escalation
For Odoo go-live escalations, please include: your TrustBill tenant ID, the Odoo version + edition (from Settings → About Odoo), the database name, the account.move.id of the failing invoice, and the timestamp of the failed pipeline run. TrustBill support responds to Odoo-tagged tickets within one business hour during UAE working days.