Ir al contenido

JSON-RPC / XML-RPC: integrar Odoo con sistemas externos

Autenticación con usuario técnico y endpoints idempotentes
4 de julio de 2026 por
JSON-RPC / XML-RPC: integrar Odoo con sistemas externos
Atemi, Aitor Atencia

Odoo · Integraciones · APIs

Odoo expone su ORM completo por XML-RPC y JSON-RPC desde el primer día: no hace falta escribir un controlador para leer o escribir datos desde fuera. El problema no es la conexión —eso funciona a la primera—, es la autenticación floja, la falta de un usuario técnico y los write sin validar que abren la puerta a cualquier sistema externo mal configurado.

Modelo cliente-servidor Logo Odoo
XML-RPC y JSON-RPC comparten los mismos endpoints del ORM: la diferencia está en el transporte, no en lo que puedes hacer.

Conexión

Autenticar y llamar al ORM desde fuera

import xmlrpc.client

url = 'https://mi-odoo.com'
db, username, password = 'produccion', 'api_tecnico', 'xxx'

common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')
uid = common.authenticate(db, username, password, {})

models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object')
partners = models.execute_kw(
    db, uid, password,
    'res.partner', 'search_read',
    [[['customer_rank', '>', 0]]],
    {'fields': ['name', 'email'], 'limit': 20})

El patrón es idéntico en JSON-RPC, solo cambia el transporte a peticiones HTTP con cuerpo JSON contra /jsonrpc. Para depuración con curl, JSON-RPC es más cómodo; para clientes legados que ya traen librería XML-RPC, no merece la pena reescribir nada.

Autenticación

Un usuario técnico, nunca el admin

  • Crea un usuario dedicado a la integración, con grupos mínimos (no Administración/Ajustes).
  • Usa una clave de API (res.users.apikeys) en lugar de la contraseña del usuario cuando la versión de Odoo lo soporte.
  • Restringe qué modelos puede tocar ese usuario vía grupos y record rules, igual que harías con cualquier otro perfil.

Exponer

Cuando Odoo es el que recibe: controlador propio

class ApiController(http.Controller):

    @http.route('/api/v1/partner', type='json',
                auth='user', methods=['POST'], csrf=False)
    def partner_create(self, **data):
        if not data.get('name'):
            raise UserError("El campo 'name' es obligatorio")
        partner = request.env['res.partner'].sudo().create({
            'name': data['name'],
            'email': data.get('email'),
        })
        return {'id': partner.id}

La ruta valida el payload antes de tocar el ORM y solo expone los campos que decide aceptar; nunca hagas create(**data) a pelo, porque cualquier clave adicional del JSON entrante acabaría escribiéndose sin control. El sudo() aquí es deliberado: el usuario técnico autenticado no necesita permisos de creación directos sobre res.partner, el controlador decide qué se permite crear.

Resiliencia

Errores, idempotencia y reintentos

    @http.route('/api/v1/order', type='json', auth='user', methods=['POST'])
    def order_create(self, **data):
        external_ref = data.get('external_ref')
        existing = request.env['sale.order'].sudo().search(
            [('client_order_ref', '=', external_ref)], limit=1)
        if existing:
            return {'id': existing.id, 'status': 'already_exists'}
        order = request.env['sale.order'].sudo().create({...})
        return {'id': order.id, 'status': 'created'}

Cualquier integración con reintentos automáticos —y casi todas los tienen— acabará enviando la misma petición dos veces. Buscar por una referencia externa única antes de crear evita duplicados sin necesidad de coordinación adicional entre los dos sistemas.

Checklist

Antes de dar acceso RPC a un tercero

✅ ComprobaciónPor qué
Usuario técnico con grupos mínimosLimita el radio de un compromiso
Sin acceso admin/ajustesEvita escalado de privilegios
Payload validado antes de create/writeNo aceptar campos arbitrarios
Búsqueda por referencia externa antes de crearIdempotencia ante reintentos
HTTPS obligatorioCredenciales no viajan en claro

Resumen

Usa un usuario técnico con permisos mínimos, nunca el admin, para conectar Odoo con sistemas externos por XML-RPC o JSON-RPC. Cuando Odoo recibe peticiones, valida el payload explícitamente en lugar de pasar el diccionario entero a create, y diseña cada endpoint para que sea idempotente ante reintentos usando una referencia externa única.

en Odoo
Cron jobs y colas en Odoo: tareas programadas fiables
ir.cron, idempotencia y colas de trabajo sin duplicados