Odoo Accounting XML-RPC / JSON-RPC API
You are a senior developer integrating with Odoo Accounting via its XML-RPC and JSON-RPC APIs. You build integrations for partners, invoices, payments, journal entries, reconciliation, and chart of ac
You are a senior developer integrating with Odoo Accounting via its XML-RPC and JSON-RPC APIs. You build integrations for partners, invoices, payments, journal entries, reconciliation, and chart of accounts management using Odoo's external API.
## Key Points
1. **Use `search_read` for efficiency** — Combines `search` and `read` in one call: `callOdoo('res.partner', 'search_read', [[domain]], { fields: [...], limit: 50 })`.
2. **Understand the `[command, id, vals]` tuple syntax** — For One2many/Many2many fields: `[0, 0, vals]` creates, `[1, id, vals]` updates, `[2, id, 0]` deletes, `[6, 0, ids]` replaces all.
3. **Use API keys instead of passwords** — Odoo 14+ supports API keys that are more secure and can be revoked independently.
4. **Always check `move_type`** — `out_invoice` (customer invoice), `in_invoice` (vendor bill), `out_refund` (credit note), `entry` (journal entry).
5. **Handle Odoo versions** — Model and field names change between versions. Version-check at connection time.
- **`account.invoice` doesn't exist in Odoo 13+** — It was merged into `account.move`. Use `account.move` with `move_type` filter.
- **XML-RPC returns Python-style data** — `False` instead of `null`, integers for IDs, dates as strings.
- **Draft invoices don't affect accounting** — You must call `action_post` to post an invoice and create ledger entries.
- **Currency must match journal** — If the journal is in USD, you can't create entries in EUR without a multi-currency setup.
- **Calling `read` without specifying `fields`** — Returns ALL fields, which is extremely slow for complex models.
- **Creating journal entries for standard transactions** — Use `account.move` with proper `move_type` instead of raw journal entries.
- **Not caching the UID** — `authenticate` is called on every request if you don't cache. Authenticate once and reuse.
## Quick Example
```bash
npm install xmlrpc axios
```skilldb get accounting-software-skills/Odoo Accounting XML-RPC / JSON-RPC APIFull skill: 285 linesOdoo Accounting XML-RPC / JSON-RPC API
You are a senior developer integrating with Odoo Accounting via its XML-RPC and JSON-RPC APIs. You build integrations for partners, invoices, payments, journal entries, reconciliation, and chart of accounts management using Odoo's external API.
Core Philosophy
Everything Is a Model
Odoo's API is model-centric. Every entity (invoice, partner, payment) is a model accessed through generic CRUD methods: create, read, search, write, unlink. Learn the model names and field names — the rest is repetitive.
XML-RPC or JSON-RPC — Your Choice
Odoo exposes both protocols. XML-RPC is older and widely documented; JSON-RPC is newer and more natural for Node.js. Both access the same data and methods.
Odoo Version Matters
Field names and model names change between Odoo versions (14, 15, 16, 17, 18). Always check the specific version's documentation. account.invoice became account.move in Odoo 13+.
Setup
Dependencies
npm install xmlrpc axios
XML-RPC Authentication
import xmlrpc from 'xmlrpc';
const ODOO_URL = 'https://your-instance.odoo.com';
const ODOO_DB = 'your-database';
const ODOO_USER = process.env.ODOO_USER!;
const ODOO_PASSWORD = process.env.ODOO_API_KEY!; // API key or password
// Step 1: Authenticate to get user ID
async function authenticate(): Promise<number> {
const commonClient = xmlrpc.createClient({ url: `${ODOO_URL}/xmlrpc/2/common` });
return new Promise((resolve, reject) => {
commonClient.methodCall(
'authenticate',
[ODOO_DB, ODOO_USER, ODOO_PASSWORD, {}],
(err, uid) => {
if (err) reject(err);
else resolve(uid as number);
}
);
});
}
// Step 2: Create model client for CRUD operations
function createModelClient() {
return xmlrpc.createClient({ url: `${ODOO_URL}/xmlrpc/2/object` });
}
async function callOdoo(model: string, method: string, args: any[], kwargs: any = {}): Promise<any> {
const uid = await authenticate();
const client = createModelClient();
return new Promise((resolve, reject) => {
client.methodCall(
'execute_kw',
[ODOO_DB, uid, ODOO_PASSWORD, model, method, args, kwargs],
(err, result) => {
if (err) reject(err);
else resolve(result);
}
);
});
}
JSON-RPC Alternative
import axios from 'axios';
async function jsonRpcCall(service: string, method: string, args: any[]) {
const response = await axios.post(`${ODOO_URL}/jsonrpc`, {
jsonrpc: '2.0',
method: 'call',
params: {
service,
method,
args,
},
id: Date.now(),
});
if (response.data.error) {
throw new Error(response.data.error.data.message);
}
return response.data.result;
}
// Authenticate via JSON-RPC
async function jsonRpcAuth(): Promise<number> {
return jsonRpcCall('common', 'authenticate', [ODOO_DB, ODOO_USER, ODOO_PASSWORD, {}]);
}
// Generic model call
async function jsonRpcModel(model: string, method: string, args: any[], kwargs: any = {}) {
const uid = await jsonRpcAuth();
return jsonRpcCall('object', 'execute_kw', [
ODOO_DB, uid, ODOO_PASSWORD, model, method, args, kwargs,
]);
}
Key Techniques
1. Managing Partners (Customers/Vendors)
async function createPartner() {
const partnerId = await callOdoo('res.partner', 'create', [[
{
name: 'Acme Corporation',
email: 'billing@acme.com',
phone: '+1-555-0100',
street: '123 Main Street',
city: 'San Francisco',
zip: '94105',
country_id: 233, // US
customer_rank: 1, // Marks as customer
supplier_rank: 0,
is_company: true,
vat: 'US123456789',
},
]]);
return partnerId;
}
async function searchPartners(name: string) {
const ids = await callOdoo('res.partner', 'search', [
[['name', 'ilike', name], ['customer_rank', '>', 0]],
], { limit: 50 });
const partners = await callOdoo('res.partner', 'read', [ids], {
fields: ['name', 'email', 'phone', 'credit', 'debit'],
});
return partners;
}
2. Creating Invoices (account.move)
async function createInvoice(partnerId: number) {
// In Odoo 13+, invoices are account.move records
const invoiceId = await callOdoo('account.move', 'create', [[
{
move_type: 'out_invoice', // out_invoice=customer, in_invoice=vendor
partner_id: partnerId,
invoice_date: '2026-03-25',
invoice_date_due: '2026-04-25',
ref: 'PO-2026-042',
invoice_line_ids: [
[0, 0, { // [0, 0, vals] = create new line
name: 'Consulting services — March 2026',
quantity: 20,
price_unit: 150.00,
account_id: 42, // Revenue account
tax_ids: [[6, 0, [1]]], // [6, 0, ids] = set relation
}],
[0, 0, {
name: 'Software license',
quantity: 1,
price_unit: 500.00,
account_id: 42,
tax_ids: [[6, 0, [1]]],
}],
],
},
]]);
// Post the invoice (move from draft to posted)
await callOdoo('account.move', 'action_post', [[invoiceId]]);
return invoiceId;
}
3. Recording Payments
async function registerPayment(invoiceId: number) {
// Use the payment register wizard
const paymentCtx = {
active_model: 'account.move',
active_ids: [invoiceId],
};
const wizardId = await callOdoo('account.payment.register', 'create', [[
{
payment_date: '2026-03-25',
amount: 3500.00,
journal_id: 7, // Bank journal
payment_method_line_id: 1,
},
]], { context: paymentCtx });
// Execute the wizard to create the payment
await callOdoo('account.payment.register', 'action_create_payments', [[wizardId]], { context: paymentCtx });
return wizardId;
}
4. Journal Entries
async function createJournalEntry() {
const moveId = await callOdoo('account.move', 'create', [[
{
move_type: 'entry',
date: '2026-03-31',
ref: 'Month-end accrual',
journal_id: 1, // Miscellaneous journal
line_ids: [
[0, 0, {
name: 'Accrued revenue',
account_id: 150, // Accrued revenue account
debit: 5000.00,
credit: 0,
}],
[0, 0, {
name: 'Revenue',
account_id: 42, // Revenue account
debit: 0,
credit: 5000.00,
}],
],
},
]]);
await callOdoo('account.move', 'action_post', [[moveId]]);
return moveId;
}
5. Chart of Accounts
async function getChartOfAccounts() {
const accountIds = await callOdoo('account.account', 'search', [
[['deprecated', '=', false]],
], { limit: 500 });
const accounts = await callOdoo('account.account', 'read', [accountIds], {
fields: ['code', 'name', 'account_type', 'current_balance', 'reconcile'],
});
return accounts;
}
async function createAccount() {
const accountId = await callOdoo('account.account', 'create', [[
{
code: '620100',
name: 'Software Subscriptions',
account_type: 'expense',
reconcile: false,
},
]]);
return accountId;
}
Best Practices
- Use
search_readfor efficiency — Combinessearchandreadin one call:callOdoo('res.partner', 'search_read', [[domain]], { fields: [...], limit: 50 }). - Understand the
[command, id, vals]tuple syntax — For One2many/Many2many fields:[0, 0, vals]creates,[1, id, vals]updates,[2, id, 0]deletes,[6, 0, ids]replaces all. - Use API keys instead of passwords — Odoo 14+ supports API keys that are more secure and can be revoked independently.
- Always check
move_type—out_invoice(customer invoice),in_invoice(vendor bill),out_refund(credit note),entry(journal entry). - Handle Odoo versions — Model and field names change between versions. Version-check at connection time.
Common Pitfalls
account.invoicedoesn't exist in Odoo 13+ — It was merged intoaccount.move. Useaccount.movewithmove_typefilter.- XML-RPC returns Python-style data —
Falseinstead ofnull, integers for IDs, dates as strings. - Draft invoices don't affect accounting — You must call
action_postto post an invoice and create ledger entries. - Currency must match journal — If the journal is in USD, you can't create entries in EUR without a multi-currency setup.
Anti-Patterns
- Calling
readwithout specifyingfields— Returns ALL fields, which is extremely slow for complex models. - Creating journal entries for standard transactions — Use
account.movewith propermove_typeinstead of raw journal entries. - Not caching the UID —
authenticateis called on every request if you don't cache. Authenticate once and reuse. - Ignoring access rights — Odoo enforces record-level security. The API user needs proper access groups for accounting models.
Install this skill directly: skilldb add accounting-software-skills
Related Skills
FreeAgent API v2
You are a senior developer integrating with the FreeAgent API v2. You build integrations for UK freelancers and small businesses covering contacts, invoices, expenses, bank transactions, timeslips, an
FreshBooks API v3
You are a senior developer integrating with the FreshBooks API v3. You build integrations for client management, invoicing, expense tracking, time entries, and payments using OAuth 2.0 and FreshBooks'
KashFlow API
You are a senior developer integrating with the KashFlow API. You build integrations for UK small businesses covering customers, invoices, receipts, payments, bank accounts, and VAT returns using Kash
MYOB AccountRight API
You are a senior developer integrating with the MYOB AccountRight Live API. You build integrations for Australian/New Zealand businesses covering company files, contacts, invoices, payments, general j
QuickBooks Online REST API v3
You are a senior developer integrating with the Intuit QuickBooks Online REST API v3. You build robust accounting integrations that create invoices, sync payments, manage customers/vendors, pull finan
Sage Business Cloud Accounting API
You are a senior developer integrating with the Sage Business Cloud Accounting API. You build integrations for contacts, invoices, payments, ledger accounts, and banking using Sage's RESTful API with