Ir al contenido

API keys en Odoo 19: JSON-2, auth bearer y lo que el scope no protege

La nueva API externa, claves con caducidad y cómo autenticar tus propios endpoints
18 de octubre de 2026 por
API keys en Odoo 19: JSON-2, auth bearer y lo que el scope no protege
ATEMI, Aitor Atencia

Odoo · API · Seguridad

En JSON-RPC / XML-RPC vimos cómo integrar sistemas externos con los endpoints clásicos. Odoo 19 los marca como obsoletos (con retirada prevista para Odoo 22) y ofrece una API nueva, /json/2, que se autentica únicamente con API keys. Esta entrada repasa cómo funciona, cómo gestionar las claves y qué hay que saber sobre su scope antes de confiar en él.

Logo Odoo
Una API key equivale a la contraseña del usuario para todo lo que se haga por RPC.

JSON-2

Un endpoint por modelo y método

La API nueva expone cada método público del ORM en POST /json/2/<modelo>/<método>. Los argumentos van por nombre en el cuerpo JSON, los ids del recordset en su propia clave, y la autenticación en una cabecera Authorization: bearer. No hay sesión, ni login previo, ni el sobre execute_kw de la API antigua.

import requests

BASE = "https://erp.ejemplo.com"
HEADERS = {"Authorization": f"bearer {API_KEY}"}

# Buscar y leer en una sola llamada
resp = requests.post(
    f"{BASE}/json/2/res.partner/search_read",
    headers=HEADERS,
    json={
        "domain": [["is_company", "=", True]],
        "fields": ["name", "vat"],
        "limit": 10,
    },
    timeout=30,
)
resp.raise_for_status()
partners = resp.json()

# Llamar a un método sobre registros concretos
requests.post(
    f"{BASE}/json/2/sale.order/action_confirm",
    headers=HEADERS, json={"ids": [42]}, timeout=30,
).raise_for_status()

Las peticiones se ejecutan como el usuario dueño de la clave, con sus ACL y record rules, y solo pueden llamar a métodos públicos: los que empiezan por guion bajo no son accesibles.

Gestión

Un usuario técnico por integración, claves con caducidad

Las claves se crean desde Preferencias → Seguridad de la cuenta. Odoo solo guarda su hash, así que se muestran una única vez. En Odoo 19 tienen fecha de caducidad: un usuario normal está obligado a ponerla, con un máximo que define el campo api_key_duration de sus grupos; solo un administrador puede crear claves sin caducidad.

  • Un usuario por integración, con los grupos mínimos que necesita. Si la tienda online solo crea pedidos, no necesita ver la contabilidad.
  • Nunca la clave de un administrador en un sistema externo: una fuga daría acceso total.
  • Caducidad y rotación: una clave sin fecha de caducidad es una clave que nadie rotará nunca.
  • Archivar el usuario invalida todas sus claves de golpe si hay que cortar una integración.

El scope

Un scope vacío vale para cualquier scope

Las claves tienen un campo scope, y es tentador pensar que sirve para limitar una clave a un uso concreto. Hay que leer cómo se comprueba: la consulta acepta la clave si scope IS NULL OR scope = <el pedido>. Las claves creadas desde las preferencias del usuario tienen el scope vacío, así que valen para cualquier scope, incluido el tuyo.

# Comprobación en un controlador propio
uid = request.env["res.users.apikeys"]._check_credentials(
    scope="fleet_trip_sync", key=token,
)
# Devuelve el uid tanto para una clave con scope "fleet_trip_sync"
# como para CUALQUIER clave normal del usuario (scope NULL).

Tus endpoints

auth='bearer' en tus controladores

No hace falta reimplementar la lectura de la cabecera. Odoo 19 incluye el tipo de autenticación bearer: si la petición trae una API key válida, el entorno pasa a ser el de su usuario; si no, devuelve un 401. Es el mismo mecanismo que usa /json/2.

from odoo import http
from odoo.http import request


class FleetTripApi(http.Controller):

    @http.route("/api/fleet/trips", type="http", auth="bearer",
                methods=["GET"], csrf=False)
    def list_trips(self, **kwargs):
        # request.env ya es el del usuario dueño de la clave
        trips = request.env["fleet.trip"].search_read(
            [("state", "=", "confirmed")], ["name", "date_start"], limit=100,
        )
        return request.make_json_response(trips)
XML-RPC / JSON-RPCJSON-2
Estado en Odoo 19Obsoleto, retirada prevista en Odoo 22Recomendado
AutenticaciónLogin + contraseña o API key en cada llamadaCabecera Authorization: bearer
Forma de la llamadaexecute_kw(db, uid, key, modelo, método, args, kwargs)POST /json/2/modelo/método con argumentos por nombre
ErroresFault genéricoCódigos HTTP (401, 404, 422…)

Resumen

En Odoo 19, las integraciones nuevas deberían usar /json/2 con API keys en la cabecera Authorization, y las existentes, empezar a migrar antes de Odoo 22. Cada integración con su usuario técnico de permisos mínimos y claves con caducidad. El scope no protege nada si la clave tiene el scope vacío: quien limita el alcance de una fuga son los grupos del usuario.

en Odoo
Migración de datos a Odoo: CSV, external IDs y load()
Importar sin duplicar, sin perder relaciones y pudiendo repetir la carga las veces que haga falta