https://github.com/mdfarhankc/odoo_rest_api
Decorator-based REST API framework for Odoo — create clean, standardized REST endpoints with a FastAPI-like developer experience. Odoo 16+ compatible.
https://github.com/mdfarhankc/odoo_rest_api
odoo python rest-api
Last synced: about 1 month ago
JSON representation
Decorator-based REST API framework for Odoo — create clean, standardized REST endpoints with a FastAPI-like developer experience. Odoo 16+ compatible.
- Host: GitHub
- URL: https://github.com/mdfarhankc/odoo_rest_api
- Owner: mdfarhankc
- License: lgpl-3.0
- Created: 2026-03-04T10:46:53.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-03-13T18:43:30.000Z (3 months ago)
- Last Synced: 2026-03-14T06:52:42.108Z (3 months ago)
- Topics: odoo, python, rest-api
- Language: Python
- Homepage:
- Size: 77.1 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# odoo-rest-api



A decorator-based REST API framework for Odoo. Create clean, standardized REST endpoints inside your Odoo modules with a FastAPI-like developer experience.
## Features
- **Decorator-based routing** — `@api.get()`, `@api.post()`, `@api.put()`, `@api.patch()`, `@api.delete()`
- **Standardized JSON responses** — Consistent `{success, data, error}` format
- **Automatic recordset serialization** — Return `env['res.partner'].search()` directly, recordsets are auto-converted to dicts
- **Automatic request parsing** — JSON body, query params, and path params injected via signature inspection
- **Error handling** — Exception classes map to proper HTTP status codes
- **Pluggable authentication** — Bring your own auth logic
- **Multi-file support** — Share one API instance across partner.py, order.py, etc.
- **Odoo 16+ compatible**
## Installation
```bash
pip install odoo-rest-api
```
No Odoo module dependency needed — just a pip package.
## Quick Start
### Single file
```python
# my_addon/controllers/partner_api.py
from odoo_rest_api import OdooRestAPI, NotFound, BadRequest
api = OdooRestAPI(prefix='/api/v1')
@api.get('/partners')
def list_partners(env, **params):
limit = min(int(params.get('limit', 80)), 1000)
offset = int(params.get('offset', 0))
return env['res.partner'].search_read(
[], ['name', 'email', 'phone'], limit=limit, offset=offset
)
@api.get('/partners/{id}')
def get_partner(env, id):
partner = env['res.partner'].browse(int(id))
if not partner.exists():
raise NotFound('Partner not found')
return partner.read(['name', 'email', 'phone'])[0]
@api.post('/partners')
def create_partner(env, body):
if not body or not body.get('name'):
raise BadRequest("'name' is required")
partner = env['res.partner'].create(body)
return partner.read(['name', 'email', 'phone'])[0]
@api.put('/partners/{id}')
def update_partner(env, id, body):
partner = env['res.partner'].browse(int(id))
if not partner.exists():
raise NotFound('Partner not found')
partner.write(body)
return partner.read(['name', 'email', 'phone'])[0]
@api.delete('/partners/{id}')
def delete_partner(env, id):
partner = env['res.partner'].browse(int(id))
if not partner.exists():
raise NotFound('Partner not found')
partner.unlink()
return {'deleted': True}
api.register()
```
```python
# my_addon/controllers/__init__.py
from . import partner_api
```
### Multi-file (recommended)
Share one API instance across multiple files:
```python
# controllers/app.py — shared instance
from odoo_rest_api import OdooRestAPI
api = OdooRestAPI(prefix='/api/v1')
# controllers/partner.py
from .app import api
from odoo_rest_api import NotFound
@api.get('/partners')
def list_partners(env, **params): ...
@api.get('/partners/{id}')
def get_partner(env, id): ...
# controllers/order.py
from .app import api
from odoo_rest_api import NotFound
@api.get('/orders')
def list_orders(env, **params): ...
@api.get('/orders/{id}')
def get_order(env, id): ...
# controllers/__init__.py — import routes then register
from . import partner
from . import order
from .app import api
api.register()
```
Test:
```bash
curl http://localhost:8069/api/v1/partners
curl http://localhost:8069/api/v1/partners/1
curl http://localhost:8069/api/v1/orders
curl -X POST -H "Content-Type: application/json" \
-d '{"name": "John Doe", "email": "john@example.com"}' \
http://localhost:8069/api/v1/partners
```
## Response Format
### Success
```json
{
"success": true,
"data": [{"id": 1, "name": "Alice", "email": "alice@example.com"}],
"error": null
}
```
### Error
```json
{
"success": false,
"data": null,
"error": {
"type": "NotFound",
"message": "Partner not found"
}
}
```
## Authentication
By default, routes have no authentication (`auth="none"`). You add auth by providing your own handler — a function that takes `request` and returns a `user_id`.
### Option 1: Inline auth handler
```python
from odoo import SUPERUSER_ID, api as odoo_api
from odoo_rest_api import OdooRestAPI, Unauthorized
def my_auth(request):
api_key = request.httprequest.headers.get('X-API-Key')
if not api_key:
raise Unauthorized('Missing X-API-Key header')
env = odoo_api.Environment(request.env.cr, SUPERUSER_ID, {})
expected = env['ir.config_parameter'].sudo().get_param('my_api.secret_key')
if api_key != expected:
raise Unauthorized('Invalid API key')
return SUPERUSER_ID # or a specific user_id
api = OdooRestAPI(prefix='/api/v1', auth_handler=my_auth)
```
### Option 2: Named handler (reusable across multiple APIs)
```python
from odoo_rest_api import register_auth_handler
register_auth_handler('my_key', my_auth)
api = OdooRestAPI(prefix='/api/v1', auth='my_key')
```
### Option 3: Odoo's built-in API keys
```python
from odoo import SUPERUSER_ID, api as odoo_api
from odoo_rest_api import OdooRestAPI, Unauthorized
def odoo_apikey_auth(request):
api_key = request.httprequest.headers.get('X-API-Key')
if not api_key:
raise Unauthorized('Missing X-API-Key header')
try:
env = odoo_api.Environment(request.env.cr, SUPERUSER_ID, {})
uid = env['res.users']._api_key_authenticate(api_key)
except Exception:
raise Unauthorized('Invalid API key')
return uid
api = OdooRestAPI(prefix='/api/v1', auth_handler=odoo_apikey_auth)
```
See [`examples/`](examples/) for a complete working addon with auth and multi-file routing.
## Handler Signature
Handler arguments are injected based on parameter names:
| Parameter | Value |
|---|---|
| `env` | Odoo Environment (authenticated if auth handler provided) |
| `body` | Parsed JSON request body (POST/PUT/PATCH) |
| `{name}` matching path param | Path parameter value (e.g. `id` from `/partners/{id}`) |
| `**params` or `**kwargs` | Remaining query string parameters |
| Named param matching query key | Individual query parameter |
## Exceptions
| Exception | Status Code |
|---|---|
| `BadRequest` | 400 |
| `Unauthorized` | 401 |
| `Forbidden` | 403 |
| `NotFound` | 404 |
| `MethodNotAllowed` | 405 |
| `Conflict` | 409 |
| `ValidationError` | 422 |
| `RateLimitExceeded` | 429 |
## Recordset Serialization
Return recordsets directly — they're auto-converted to dicts via `.read()`:
```python
@api.get('/partners')
def list_partners(env):
return env['res.partner'].search([]) # Auto-serialized to list of dicts
```
## License
LGPL-3.0