Odoo 14 introduced API keys as a first-class authentication mechanism. An API key is a long random token associated with a specific user. It grants the same access as that user's password but can be revoked independently, generated per integration, and never exposes the user's actual password.
Generating an API Key#
My Profile → Account Security → API Keys → New API Key
- Enter a label (e.g., "Production ERP Sync")
- If 2FA is enabled, enter the current TOTP code
- Copy the key from the dialog - it is shown once and not stored in recoverable form
Store it in a secrets manager, not in a config file.
Using an API Key in XML-RPC#
import xmlrpc.client
url = 'https://your-odoo.example.com'
db = 'mydb'
username = 'integration@example.com'
api_key = 'your_api_key_here'
common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')
uid = common.authenticate(db, username, api_key, {})
models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object')
result = models.execute_kw(db, uid, api_key, 'sale.order', 'search_read',
[[['state', '=', 'sale']]], {'fields': ['name', 'partner_id', 'amount_total']})The API key is passed wherever the password would go.
JSON-RPC (HTTP) with API Key#
import requests
session = requests.Session()
session.post(
'https://your-odoo.example.com/web/session/authenticate',
json={'jsonrpc': '2.0', 'method': 'call', 'params': {
'db': 'mydb',
'login': 'integration@example.com',
'password': 'your_api_key_here',
}}
)
result = session.post(
'https://your-odoo.example.com/web/dataset/call_kw',
json={'jsonrpc': '2.0', 'method': 'call', 'params': {
'model': 'res.partner', 'method': 'search_read',
'args': [[['customer_rank', '>', 0]]],
'kwargs': {'fields': ['name', 'email']},
}}
)API Key vs Password Auth#
| API Key | Password | |
|---|---|---|
| Revocable independently | Yes | No |
| Multiple per user | Yes | No |
| Works with 2FA-enabled accounts | Yes (bypasses TOTP) | No |
| Visible to admin | Label only | Never |
| Expiry | Manual only | Password policy |
API keys bypass TOTP because external systems cannot enter a 6-digit code interactively.
Scoping: Use Dedicated Integration Users#
- Create a dedicated user:
integration@example.com - Assign only the groups needed (e.g., Inventory / User, not full admin)
- Generate one API key per external system
- Label keys descriptively: "WMS Sync - Prod"
Listing and Revoking Keys#
Users see their keys under My Profile → Account Security → API Keys. Click the trash icon to revoke immediately.
env['res.users.apikeys'].sudo().search([
('user_id', '=', user.id),
('name', '=', 'Old Integration Key'),
]).unlink()Common Mistakes#
- Storing keys in git - use a secrets manager; committing keys is a security incident even in private repos
- Using an admin user for integrations - a leaked admin key is a full compromise; use least-privilege dedicated users
- Not labeling keys - unlabeled keys become impossible to audit; record the system, environment, and date
- Forgetting to rotate after personnel changes - revoke keys provisioned by developers who leave; there is no automatic expiry

