REST API Module¶
RESTful interface for Odoo data access with multiple authentication methods.
| Field | Value |
|---|---|
| Technical Name | restapi |
| Version | 2.0.0 |
| Category | Tools |
| Dependencies | base, web, base_automation |
| Python Dependencies | oauthlib, jwt, cryptography |
Overview¶
The Odoo REST API module provides:
- Standard HTTP methods (GET, POST, PUT, DELETE)
- Multiple authentication methods (OAuth1, OAuth2, JWT, API Key)
- Full CRUD operations on any Odoo model
- Method calls for custom business logic
- Report printing
- IP restriction support
- Configurable token expiry
Authentication Methods¶
| Method | Use Case | Stateless | Best For |
|---|---|---|---|
| Client Credentials | Server-to-server | No | AWS Lambda, cron jobs |
| Client Credentials + JWT | High-performance serverless | Yes | Frequent API calls |
| API Key | Simple integrations | N/A | Scripts, testing |
| OAuth2 Authorization Code | Web apps with user login | No | User-facing apps |
| OAuth1 | Legacy integrations | No | Existing OAuth1 clients |
Quick Start¶
1. Get Access Token¶
curl -X POST https://your-odoo.com/restapi/1.0/common/oauth2/token \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
Response:
2. Make API Requests¶
# Search partners
curl "https://your-odoo.com/restapi/1.0/object/res.partner?limit=10" \
-H "Authorization: Bearer YOUR_TOKEN"
# Create partner
curl -X POST https://your-odoo.com/restapi/1.0/object/res.partner \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "New Partner"}'
JWT Authentication¶
For high-performance scenarios, use JWT tokens:
curl -X POST https://your-odoo.com/restapi/1.0/common/oauth2/token \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "token_format=jwt"
JWT tokens are verified cryptographically without database lookups.
API Key Authentication¶
For simple scripts:
# Using X-API-Key header
curl "https://your-odoo.com/restapi/1.0/object/res.partner" \
-H "X-API-Key: YOUR_API_KEY"
# Or using Authorization header
curl "https://your-odoo.com/restapi/1.0/object/res.partner" \
-H "Authorization: ApiKey YOUR_API_KEY"
Data Models¶
auth.auth¶
Main authentication configuration record.
| Field | Type | Description |
|---|---|---|
name |
Char | Application name |
description |
Text | Application description |
organization_name |
Char | Organization name |
consumer_key |
Char | Client ID for OAuth |
consumer_secret |
Char | Client Secret for OAuth |
user_id |
Many2one | User to act as |
client_credentials_enabled |
Boolean | Enable client credentials grant |
token_expiry_seconds |
Integer | Token validity (default: 3600) |
api_key |
Char | API Key (generated) |
api_key_active |
Boolean | API Key active status |
jwt_enabled |
Boolean | Enable JWT tokens |
allowed_ips |
Text | IP whitelist (one per line) |
redirect_uris |
One2many | OAuth redirect URIs |
auth.access.token¶
Stores active access tokens.
| Field | Type | Description |
|---|---|---|
access_token |
Char | Token value |
access_token_validity |
Datetime | Expiration time |
auth_id |
Many2one | Link to auth.auth |
Configuration¶
1. Create REST API Client¶
Navigate to Settings > Technical > REST API > Auth:
- Click Create
- Fill in fields:
| Field | Value |
|---|---|
| Name | "My Application" |
| User | Select API user |
| Enable Client Credentials | True |
| Token Expiry (seconds) | 3600 |
| Enable JWT Tokens | True (optional) |
| Allowed IPs | Optional whitelist |
- Save - Consumer Key and Secret are auto-generated
2. Generate API Key (Optional)¶
- Open the auth record
- Click Generate API Key
- Copy the API key (shown once)
3. Add Redirect URIs (for OAuth2 Authorization Code)¶
In the Redirect URIs tab:
- Add your callback URL(s)
- Example: https://your-app.com/oauth/callback
API Reference¶
Base URL¶
Authentication Endpoints¶
| Endpoint | Method | Description |
|---|---|---|
/common/oauth2/token |
POST | Get access token |
/common/oauth2/authorize |
GET | OAuth2 authorization |
/common/oauth2/revoke |
POST | Revoke token |
/common/version |
GET | Get Odoo version |
Token Request Parameters¶
| Parameter | Required | Description |
|---|---|---|
grant_type |
Yes | client_credentials or authorization_code |
client_id |
Yes | Consumer key |
client_secret |
Yes | Consumer secret |
token_format |
No | Set to jwt for JWT tokens |
code |
For auth code | Authorization code |
redirect_uri |
For auth code | Redirect URI |
Data Endpoints¶
| Endpoint | Method | Description |
|---|---|---|
/object/{model} |
GET | Search and read records |
/object/{model} |
POST | Create new record |
/object/{model}/{id} |
GET | Read single record |
/object/{model}/{id} |
PUT | Update record |
/object/{model}/{id} |
DELETE | Delete record |
/object/{model}/{id}/{method} |
POST | Call record method |
/object/{model}/{method} |
POST | Call model method |
Query Parameters¶
| Parameter | Type | Description | Example |
|---|---|---|---|
domain |
string | Search filter (Odoo domain) | [('name','ilike','test')] |
fields |
string | Fields to return | ['name','email'] |
offset |
int | Skip first N records | 10 |
limit |
int | Maximum records | 50 |
order |
string | Sort order | name asc |
Python Client¶
import requests
import time
class OdooAPI:
def __init__(self, base_url, client_id, client_secret):
self.base_url = base_url.rstrip('/')
self.client_id = client_id
self.client_secret = client_secret
self.access_token = None
self.token_expires_at = 0
def get_token(self):
"""Get or refresh access token."""
if self.access_token and time.time() < self.token_expires_at - 60:
return self.access_token
response = requests.post(
f"{self.base_url}/restapi/1.0/common/oauth2/token",
data={
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret
}
)
response.raise_for_status()
data = response.json()
self.access_token = data['access_token']
self.token_expires_at = time.time() + data['expires_in']
return self.access_token
def _headers(self):
return {'Authorization': f'Bearer {self.get_token()}'}
def search_read(self, model, domain=None, fields=None, limit=None, offset=None, order=None):
"""Search and read records."""
params = {}
if domain: params['domain'] = str(domain)
if fields: params['fields'] = str(fields)
if limit: params['limit'] = limit
if offset: params['offset'] = offset
if order: params['order'] = order
response = requests.get(
f"{self.base_url}/restapi/1.0/object/{model}",
headers=self._headers(),
params=params
)
response.raise_for_status()
return response.json()
def read(self, model, record_id, fields=None):
"""Read single record."""
params = {}
if fields: params['fields'] = str(fields)
response = requests.get(
f"{self.base_url}/restapi/1.0/object/{model}/{record_id}",
headers=self._headers(),
params=params
)
response.raise_for_status()
return response.json()
def create(self, model, values):
"""Create new record."""
response = requests.post(
f"{self.base_url}/restapi/1.0/object/{model}",
headers={**self._headers(), 'Content-Type': 'application/json'},
json=values
)
response.raise_for_status()
return response.json()
def write(self, model, record_id, values):
"""Update record."""
response = requests.put(
f"{self.base_url}/restapi/1.0/object/{model}/{record_id}",
headers={**self._headers(), 'Content-Type': 'application/json'},
json=values
)
response.raise_for_status()
return response.json()
def delete(self, model, record_id):
"""Delete record."""
response = requests.delete(
f"{self.base_url}/restapi/1.0/object/{model}/{record_id}",
headers=self._headers()
)
response.raise_for_status()
return response.json()
def call_method(self, model, record_id, method, args=None, kwargs=None):
"""Call method on record."""
response = requests.post(
f"{self.base_url}/restapi/1.0/object/{model}/{record_id}/{method}",
headers={**self._headers(), 'Content-Type': 'application/json'},
json={'args': args or [], 'kwargs': kwargs or {}}
)
response.raise_for_status()
return response.json()
Usage Example¶
api = OdooAPI(
base_url='https://your-odoo.com',
client_id='your_client_id',
client_secret='your_client_secret'
)
# Search partners
partners = api.search_read('res.partner',
domain=[('customer_rank', '>', 0)],
fields=['name', 'email'],
limit=10
)
# Create partner
new_id = api.create('res.partner', {
'name': 'New Customer',
'email': 'customer@example.com'
})
# Update partner
api.write('res.partner', new_id, {'phone': '555-1234'})
# Call method
result = api.call_method('fsm.order', 123, 'action_mark_signature_complete')
Error Handling¶
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | invalid_request |
Missing or invalid parameters |
| 401 | invalid_client |
Invalid credentials |
| 401 | token_expired |
Token has expired |
| 403 | access_denied |
Insufficient permissions |
| 403 | ip_restricted |
IP not in whitelist |
| 404 | not_found |
Record not found |
| 500 | server_error |
Internal server error |
Error Response Format¶
Module Structure¶
restapi/
├── __manifest__.py
├── __init__.py
├── controllers/
│ ├── __init__.py
│ └── main.py # API endpoints
├── models/
│ ├── __init__.py
│ ├── auth.py # Authentication models
│ ├── jwt_utils.py # JWT utilities
│ ├── ir_models.py # Model extensions
│ └── res_users.py # User extensions
├── views/
│ ├── auth_view.xml # Auth configuration views
│ ├── restapi_cron.xml # Cron jobs
│ └── ir_models_view.xml
├── security/
│ └── ir.model.access.csv
├── data/
│ ├── auth_data.xml # Default data
│ └── mail_template.xml # Email templates
├── tests/
│ ├── test_client_credentials.py
│ ├── test_jwt_utils.py
│ ├── test_endpoints.py
│ └── test_api_key.py
└── doc/
└── DEVELOPER_GUIDE.md
Security Best Practices¶
- Use HTTPS - Always use HTTPS in production
- Rotate credentials - Regenerate secrets periodically
- IP whitelist - Restrict access to known IPs
- Minimal permissions - Use user with limited access
- JWT for high throughput - Reduces database load
- Monitor access - Review access logs regularly
Best Practices¶
- Cache tokens - Don't request new token for every call
- Use JWT for high throughput - Eliminates DB lookups
- Request only needed fields - Improves performance
- Implement retry logic - Handle transient failures
- Set appropriate timeouts - Prevent hanging requests
- Use pagination - Don't fetch all records at once
Troubleshooting¶
Invalid Credentials¶
- Verify consumer key and secret are correct
- Check client credentials grant is enabled
- Ensure auth record is active
Token Expired¶
- Request new token before expiry
- Implement automatic token refresh in client
IP Restricted¶
- Check your IP is in the whitelist
- Verify IP format (one per line)
- Clear whitelist to allow all IPs
Permission Denied¶
- Check API user has required permissions
- Verify record access rules
- Test with admin user to isolate issue