Odoo API Integration: REST, JSON-RPC, and XML-RPC Guide
Odoo 19 exposes three API interfaces that cover everything from simple data retrieval to complex workflow automation. Whether you're building a custom mobile app, syncing with a third-party platform, or extending Odoo's capabilities with external microservices, mastering the Odoo API layer is foundational to any serious integration project.
This guide provides working code examples, authentication flows, and architectural recommendations for REST, JSON-RPC, and XML-RPC integrations — the three primary interfaces available in Odoo 19 Enterprise.
Key Takeaways
- Odoo 19 offers REST (OpenAPI 3.0), JSON-RPC 2.0, and XML-RPC interfaces
- Authentication uses API keys (recommended) or session-based login
- JSON-RPC is the most feature-complete interface for complex operations
- REST API follows OpenAPI 3.0 spec and supports standard HTTP verbs
- XML-RPC is legacy but still fully supported for backward compatibility
- Rate limiting and error handling must be implemented on the client side
- Webhooks in Odoo 19 push data to external systems on record changes
- All API calls respect Odoo's access rights and record rules
API Interface Comparison
Before writing a single line of code, choose the right API interface for your use case:
| Feature | REST API | JSON-RPC | XML-RPC |
|---|---|---|---|
| Protocol | HTTP/HTTPS | HTTP/HTTPS | HTTP/HTTPS |
| Payload format | JSON | JSON | XML |
| OpenAPI spec | Yes (Swagger) | No | No |
| CRUD operations | Yes | Yes | Yes |
| Method calls | Limited | Full | Full |
| Workflow triggers | Via actions | Via execute_kw | Via execute |
| Recommended for | New integrations | Complex logic | Legacy systems |
| Python library | requests | odoo-xmlrpc / requests | xmlrpc.client |
When to use REST: Building a mobile app, integrating with webhook-native platforms (Shopify, Stripe), or when your team is more comfortable with REST conventions.
When to use JSON-RPC: Executing complex Odoo server-side methods, reading large datasets with domain filters, or when you need access to methods not exposed via REST.
When to use XML-RPC: Maintaining existing integrations built before REST was available, or when your platform has mature XML-RPC client libraries.
Authentication
API Key Authentication (Recommended)
Odoo 19 supports API key authentication for all three interfaces. Generate an API key under Settings → Users → Your User → API Keys.
import requests
ODOO_URL = "https://your-odoo.com"
API_KEY = "your_api_key_here"
DATABASE = "your_database"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}"
}
API keys are scoped to a specific user and inherit that user's access rights. Create dedicated service users with minimal required permissions for integration accounts.
Session-Based Authentication (JSON-RPC / XML-RPC)
For JSON-RPC, authenticate using the /web/dataset/call_kw endpoint after establishing a session:
import requests
import json
session = requests.Session()
# Authenticate
auth_payload = {
"jsonrpc": "2.0",
"method": "call",
"params": {
"db": "your_database",
"login": "admin",
"password": "your_password"
}
}
response = session.post(
f"{ODOO_URL}/web/session/authenticate",
json=auth_payload
)
uid = response.json()['result']['uid']
print(f"Authenticated as UID: {uid}")
For XML-RPC, use the standard two-step authentication:
import xmlrpc.client
url = "https://your-odoo.com"
db = "your_database"
username = "admin"
password = "your_password"
# Step 1: Get UID
common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common")
uid = common.authenticate(db, username, password, {})
# Step 2: Use UID for subsequent calls
models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object")
REST API: OpenAPI 3.0
Odoo 19 introduces a full REST API with OpenAPI 3.0 specification. Access the interactive documentation at https://your-odoo.com/api/docs.
Listing Records
# GET /api/sale.order — list all sales orders
response = requests.get(
f"{ODOO_URL}/api/sale.order",
headers=headers,
params={
"domain": '[["state", "=", "sale"]]',
"fields": '["name", "partner_id", "amount_total", "state"]',
"limit": 50,
"offset": 0
}
)
orders = response.json()
Reading a Single Record
# GET /api/sale.order/{id}
order_id = 123
response = requests.get(
f"{ODOO_URL}/api/sale.order/{order_id}",
headers=headers
)
order = response.json()
Creating a Record
# POST /api/sale.order
payload = {
"partner_id": 42,
"order_line": [
{
"product_id": 7,
"product_uom_qty": 5,
"price_unit": 100.0
}
]
}
response = requests.post(
f"{ODOO_URL}/api/sale.order",
headers=headers,
json=payload
)
new_order = response.json()
Updating a Record
# PATCH /api/sale.order/{id}
response = requests.patch(
f"{ODOO_URL}/api/sale.order/{order_id}",
headers=headers,
json={"note": "Rush order — priority handling required"}
)
Deleting a Record
# DELETE /api/sale.order/{id}
response = requests.delete(
f"{ODOO_URL}/api/sale.order/{order_id}",
headers=headers
)
JSON-RPC Interface
JSON-RPC provides access to the full Odoo Python API, including server-side methods that are not exposed via REST. The primary endpoint is /web/dataset/call_kw.
Basic Search and Read
def call_kw(model, method, args, kwargs=None):
payload = {
"jsonrpc": "2.0",
"method": "call",
"params": {
"model": model,
"method": method,
"args": args,
"kwargs": kwargs or {}
}
}
response = session.post(
f"{ODOO_URL}/web/dataset/call_kw",
json=payload
)
return response.json().get('result')
# Search for confirmed sales orders
order_ids = call_kw(
"sale.order",
"search",
[[["state", "=", "sale"]]],
{"limit": 100, "order": "date_order desc"}
)
# Read specific fields
orders = call_kw(
"sale.order",
"read",
[order_ids],
{"fields": ["name", "partner_id", "amount_total", "date_order"]}
)
Search Read (Combined)
orders = call_kw(
"sale.order",
"search_read",
[[["partner_id.country_id.code", "=", "US"]]],
{
"fields": ["name", "partner_id", "amount_total"],
"limit": 50,
"offset": 0,
"order": "amount_total desc"
}
)
Creating Records
new_id = call_kw(
"sale.order",
"create",
[{
"partner_id": 42,
"order_line": [
(0, 0, {
"product_id": 7,
"product_uom_qty": 10,
"price_unit": 150.0
})
]
}]
)
Calling Server-Side Methods
JSON-RPC gives access to all Python methods defined on Odoo models:
# Confirm a sales order (triggers workflow)
call_kw("sale.order", "action_confirm", [[order_id]])
# Validate an inventory transfer
call_kw("stock.picking", "button_validate", [[picking_id]])
# Get the action for a button (useful for understanding what a button does)
action = call_kw("sale.order", "action_quotations_with_onboarding", [[]])
XML-RPC Interface
XML-RPC is the original Odoo API and remains fully supported. The interface consists of two endpoints:
/xmlrpc/2/common— unauthenticated methods (authenticate, version)/xmlrpc/2/object— all model operations (requires UID)
import xmlrpc.client
url = "https://your-odoo.com"
db, username, password = "mydb", "admin", "mypassword"
common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common")
uid = common.authenticate(db, username, password, {})
models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object")
# Search for products
product_ids = models.execute_kw(
db, uid, password,
'product.template', 'search',
[[['sale_ok', '=', True]]],
{'limit': 100}
)
# Read product data
products = models.execute_kw(
db, uid, password,
'product.template', 'read',
[product_ids],
{'fields': ['name', 'list_price', 'categ_id']}
)
# Create a new product
new_product_id = models.execute_kw(
db, uid, password,
'product.template', 'create',
[{
'name': 'My New Product',
'list_price': 99.99,
'type': 'consu'
}]
)
Domain Filters
Odoo's domain filter syntax is used across all three API types. Understanding domains is essential for efficient data retrieval.
# Basic operators: =, !=, >, <, >=, <=, like, ilike, in, not in, child_of
domain = [
["state", "in", ["sale", "done"]], # Confirmed or done orders
["amount_total", ">=", 1000], # Total at least 1000
["partner_id.country_id.code", "=", "US"] # US customers (related field)
]
# Logical operators: & (AND, default), | (OR), ! (NOT)
domain = [
"|",
["state", "=", "draft"],
["state", "=", "cancel"]
]
# Complex: orders from US or UK customers with total > 5000
domain = [
"|",
["partner_id.country_id.code", "=", "US"],
["partner_id.country_id.code", "=", "GB"],
["amount_total", ">", 5000]
]
Webhooks and Event-Driven Integration
Odoo 19 supports outbound webhooks triggered by record changes. Configure webhooks under Settings → Technical → Webhooks.
Webhook configuration:
- Navigate to Settings → Technical → Webhooks → Create
- Set the Model (e.g.,
sale.order) - Select Trigger: create, write, unlink, or custom method
- Enter the Endpoint URL of your receiving service
- Optionally set Domain to filter which records trigger the webhook
- Configure Fields to include in the payload
Receiving webhook events in a Flask service:
from flask import Flask, request, jsonify
import hmac, hashlib
app = Flask(__name__)
WEBHOOK_SECRET = "your_webhook_secret"
@app.route("/odoo-webhook", methods=["POST"])
def handle_webhook():
# Verify signature
signature = request.headers.get("X-Odoo-Signature")
body = request.get_data()
expected = hmac.new(
WEBHOOK_SECRET.encode(),
body,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return jsonify({"error": "Invalid signature"}), 401
event = request.json
model = event.get("model")
record_id = event.get("id")
# Process the event
if model == "sale.order":
handle_order_event(record_id, event)
return jsonify({"status": "ok"}), 200
Error Handling and Retry Logic
Robust integrations must handle Odoo API errors gracefully.
import time
import requests
from requests.exceptions import RequestException
def api_call_with_retry(url, payload, headers, max_retries=3, backoff=2):
for attempt in range(max_retries):
try:
response = requests.post(url, json=payload, headers=headers, timeout=30)
response.raise_for_status()
data = response.json()
if "error" in data:
error = data["error"]
code = error.get("code", 0)
message = error.get("data", {}).get("message", "Unknown error")
# Don't retry validation errors
if code in [200, 100]:
raise ValueError(f"Odoo validation error: {message}")
raise RuntimeError(f"Odoo API error {code}: {message}")
return data.get("result")
except (RequestException, RuntimeError) as e:
if attempt == max_retries - 1:
raise
wait = backoff ** attempt
print(f"Attempt {attempt + 1} failed: {e}. Retrying in {wait}s...")
time.sleep(wait)
Common error codes:
| Code | Meaning | Action |
|---|---|---|
| 100 | Server error | Check Odoo logs |
| 200 | Access denied | Verify user permissions |
| 300 | Missing record | Check record ID exists |
| 304 | Missing required field | Review payload |
Performance Best Practices
Batch operations: Never call the API in a loop for individual records. Use create_multi and write with lists:
# Bad: loop with individual creates
for product in products:
call_kw("product.template", "create", [product])
# Good: batch create
call_kw("product.template", "create", [products])
Field selection: Always specify the fields parameter to avoid fetching all fields:
# Good: only fetch needed fields
orders = call_kw(
"sale.order", "search_read",
[[["state", "=", "sale"]]],
{"fields": ["name", "amount_total"], "limit": 1000}
)
Pagination: For large datasets, paginate using limit and offset:
def fetch_all_records(model, domain, fields, batch_size=500):
records = []
offset = 0
while True:
batch = call_kw(
model, "search_read", [domain],
{"fields": fields, "limit": batch_size, "offset": offset}
)
records.extend(batch)
if len(batch) < batch_size:
break
offset += batch_size
return records
Frequently Asked Questions
What is the difference between JSON-RPC and the REST API in Odoo 19?
JSON-RPC provides access to the complete Odoo Python API including all server-side methods, while REST follows OpenAPI 3.0 conventions and exposes a more limited but standardized interface. For new integrations where REST covers your use case, REST is preferred for its discoverability. For complex workflow automation or access to custom Python methods, use JSON-RPC.
How do I handle large data exports (100k+ records) efficiently?
Use pagination with search_read and a batch size of 500–1000 records. For very large exports, consider using Odoo's export feature via the UI for one-time extractions, or schedule background jobs using Odoo's ir.cron model to process data in chunks during off-peak hours rather than making real-time API calls.
Can I use API keys instead of username/password for XML-RPC?
Yes. In Odoo 13+, API keys can be used as passwords in the XML-RPC authenticate call. Generate an API key from your user profile and use it in place of your password: common.authenticate(db, username, api_key, {}). This is the recommended approach for service accounts.
How do I create Many2many and One2many records via the API?
Use Odoo's command tuples: (0, 0, vals) creates a new related record, (1, id, vals) updates an existing related record, (2, id, 0) deletes a related record, (4, id, 0) links an existing record, (5, 0, 0) unlinks all related records. These commands work identically across JSON-RPC, XML-RPC, and REST.
How do I trigger a workflow action (like confirming an order) via the API?
Call the corresponding method on the model. For confirming a sales order, call action_confirm on sale.order. For validating a delivery, call button_validate on stock.picking. These methods are visible in Odoo's source code and can be discovered by inspecting the button's name attribute in the UI's developer mode.
What rate limits does Odoo impose on API calls?
Odoo does not enforce API rate limits natively at the application level. Rate limiting must be configured at the reverse proxy (Nginx) or infrastructure level. A sensible default is 60 requests per minute per IP for external integrations. For high-throughput integrations, use a queue-based approach with a dedicated service user.
Next Steps
Building a reliable Odoo API integration requires more than working code examples — it demands proper error handling, monitoring, credential management, and alignment with Odoo's data model.
ECOSIRE's integration team has built production-grade connections between Odoo and dozens of platforms including Shopify, Amazon, GoHighLevel, Power BI, custom ERPs, and proprietary systems. We handle authentication architecture, webhook design, data transformation, and ongoing monitoring.
Talk to ECOSIRE About Your Odoo Integration Project →
Whether you're starting a new integration or fixing a broken one, our engineers will review your requirements and deliver a solution that handles edge cases from day one.
Written by
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Transform Your Business with Odoo ERP
Expert Odoo implementation, customization, and support to streamline your operations.
Related Articles
Odoo vs NetSuite Mid-Market Comparison: Complete Buyer's Guide 2026
Odoo vs NetSuite for mid-market in 2026: feature-by-feature scoring, 5-year TCO for 50 users, implementation timelines, industry fit, and two-way migration guidance.
Tally to Odoo Migration 2026: Step-by-Step Guide for Indian SMBs
Tally to Odoo migration playbook for Indian SMBs in 2026: data model mapping, 12-step plan, GST handling, COA translation, parallel run, UAT, and cutover.
AI-Powered Customer Segmentation: From RFM to Predictive Clustering
Learn how AI transforms customer segmentation from static RFM analysis to dynamic predictive clustering. Implementation guide with Python, Odoo, and real ROI data.