Skip to main content

odoo-rest-api

PyPI Python License

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

pip install odoo-rest-api

No Odoo module dependency needed — just a pip package.

Quick Start

Single file

# 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()
# my_addon/controllers/__init__.py
from . import partner_api

Multi-file (recommended)

Share one API instance across multiple files:

# 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:

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

{
    "success": true,
    "data": [{"id": 1, "name": "Alice", "email": "alice@example.com"}],
    "error": null
}

Error

{
    "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

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)

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

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/ 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():

@api.get('/partners')
def list_partners(env):
    return env['res.partner'].search([])  # Auto-serialized to list of dicts

License

LGPL-3.0

Metadata

Release files for odoo-rest-api 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for odoo-rest-api 0.2.2
File Size Uploaded
odoo_rest_api-0.2.2.tar.gz 23.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for odoo-rest-api 0.2.2
File Interpreter ABI Platform
odoo_rest_api-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 40.4 kB

Release files / odoo_rest_api-0.2.2.tar.gz

Download URL odoo_rest_api-0.2.2.tar.gz
Size 23.2 kB
Tags Source
SHA-256 checksum
How to use checksums
c6119492e7e2a05ac463ba46e7aa08745887a59869256e9253f9dd5a8ece8650
BLAKE2b-256 checksum
How to use checksums
e2093bde4b0e09fb5a49ce636ca161e063b23ee06765d9b0ccfd710cdc3b3232
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.0

Release files / odoo_rest_api-0.2.2-py3-none-any.whl

Download URL odoo_rest_api-0.2.2-py3-none-any.whl
Size 17.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e939fac9de0abba1be4d95faeac0bc1a028a24c8f08014d2580a5135cc01976b
BLAKE2b-256 checksum
How to use checksums
987ebbaba9fbf30e001af2e50c442e925d1108f8b87d7b66a01394312b32459c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.0

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page