外部 JSON-2 API

19.0 新版功能.

Odoo 通常通过模块在内部进行扩展,但它的许多功能和全部数据也可以在外部访问,用于分析或与各种其他软件集成。型号 API 的一部分可以通过 /json/2 端点经 HTTP 轻松访问。

小技巧

实际可用的模型、字段和方法因每个数据库而异,可以在其 /doc 页面中查看。

注解

通过外部 API 访问数据仅在 Custom Odoo 定价计划中可用。外部 API 访问在 One App Free 或 Standard 计划中不可用。更多信息请访问 Odoo 定价页面 或联系您的客户成功经理。

API

请求

向 /json/2/<model>/<method> URL 发送一个 JSON 对象。

HTTP 请求头

Host

必填,服务器的主机名。

Autorization

必填,bearer 后跟 API 密钥。

Content-Type

必填,application/json,建议指定字符集。

X-Odoo-Database

可选,要连接的数据库名称。

User-Agent

建议填写,您的软件的名称。

URL 路径

model

必填,模型的技术名称。

method

必填,要执行的方法。

请求体 JSON 对象

ids

要在其上执行该方法的记录 ID 数组。调用由 @api.model 装饰的方法时为空或省略。

context

可选,一个包含附加值的对象。例如 {"lang": "en_US"}。

param

按需任意多个,即该方法的参数。

示例

POST /json/2/res.partner/search_read HTTP/1.1
Host: mycompany.example.com
X-Odoo-Database: mycompany
Authorization: bearer 6578616d706c65206a736f6e20617069206b6579
Content-Type: application/json; charset=utf-8
User-Agent: mysoftware python-requests/2.25.1

{
    "context": {
        "lang": "en_US"
    },
    "domain": [
        ["name", "ilike", "%deco%"],
        ["is_company", "=", true]
    ],
    "fields": ["name"]
}

响应

成功 时,返回 200 状态,响应体中包含被调用方法的 JSON 序列化返回值。

示例

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[
   {"id": 25, "name": "Deco Addict"}
]

出错 时,返回 4xx/ 5xx 状态,响应体中包含 JSON 序列化的错误对象。

name

所发生 Python 异常的完全限定名称。

message

异常消息,通常与 arguments[0] 相同。

arguments

所有异常参数。

context

请求所使用的上下文。

debug

异常 traceback,用于调试。

示例

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{
  "name": "werkzeug.exceptions.Unauthorized",
  "message": "Invalid apikey",
  "arguments": ["Invalid apikey", 401],
  "context": {},
  "debug": "Traceback (most recent call last):\n  File \"/opt/Odoo/community/odoo/http.py\", line 2212, in _transactioning\n    return service_model.retrying(func, env=self.env)\n  File \"/opt/Odoo/community/odoo/service/model.py\", line 176, in retrying\n    result = func()\n  File \"/opt/Odoo/community/odoo/http.py\", line 2177, in _serve_ir_http\n    self.registry['ir.http']._authenticate(rule.endpoint)\n  File \"/opt/Odoo/community/odoo/addons/base/models/ir_http.py\", line 274, in _authenticate\n    cls._authenticate_explicit(auth)\n  File \"/opt/Odoo/community/odoo/addons/base/models/ir_http.py\", line 283, in _authenticate_explicit\n    getattr(cls, f'_auth_method_{auth}')()\n  File \"/opt/Odoo/community/odoo/addons/base/models/ir_http.py\", line 240, in _auth_method_bearer\n    raise werkzeug.exceptions.Unauthorized(\nwerkzeug.exceptions.Unauthorized: 401 Unauthorized: Invalid apikey\n"
}

配置

API 密钥

手动生成密钥

必须在 Authorization 请求标头中以 bearer 令牌的形式设置 API 密钥。

通过 首选项 ‣ 账户安全 ‣ 新建 API 密钥 为用户创建新的 API 密钥。

../../_images/preferences2.png ../../_images/account-security2.png ../../_images/new-api-key.png

创建新的 API 密钥需要同时提供描述和有效期。描述可用于识别密钥,并在日后判断该密钥是否仍在使用、还是应当删除。有效期决定密钥的生存时间,过期后密钥即失效。对于交互式用途,建议设置较短的有效期(通常为一天)。出于安全原因,无法创建有效期超过三个月的密钥。这意味着长期使用的密钥必须至少每三个月轮换一次。

生成密钥 按钮会创建一个强度较高的 160 位随机密钥。密钥值仅在创建时显示一次,之后无法再获取。请立即复制该密钥并妥善保管。如果密钥已泄露或丢失,请立即将其删除并生成新密钥。

关于 API 密钥管理的更多指导,请参阅 OWASP 机密信息管理速查表。

以编程方式管理密钥

Odoo 提供可通过编程方式生成和撤销 API 密钥的 RPC 方法。

先决条件

默认情况下,以编程方式管理 API 密钥仅限具有 设置 管理访问权限的用户使用。

要让其他用户能够以编程方式管理 API 密钥,请导航至 设置 ‣ 技术 ‣ 系统参数,并将参数 base.enable_programmatic_api_keys 设置为 True。

生成密钥

可以使用 res.users.apikeys.generate() 生成 API 密钥。

该方法接受以下参数:

  • key (字符串):一个现有的有效 API 密钥

  • scope (字符串或 null):分配给新密钥的作用域

  • name (字符串):便于人阅读的标签

  • expiration_date (字符串):以 ISO 8601 格式表示的过期日期(例如 "2026-05-19")

作用域限定了 API 密钥可被使用的位置。

rpc 作用域是通用作用域,用于通过带 auth='bearer' 的控制器进行 RPC 访问。

新密钥通常应使用与现有密钥相同的作用域。不过,在需要时可以从无作用域密钥创建有作用域的密钥。无作用域密钥的作用域值为 null (在 Python 中为 None)。

过期日期会根据分配给用户的角色所允许的 API 密钥最长有效期进行校验。

对于高权限或对外暴露的集成,应使用较短的过期期限;只有在严格控制的内网系统中才使用较长的期限。

示例

import requests

API_KEY = ...  # get it from a secure location

res_apikey = requests.post(
    f"https://mycompany.example.com/json/2/res.users.apikeys/generate",
    headers={"Authorization": f"bearer {API_KEY}"},
    json={
        "key": API_KEY,
        "scope": None,
        "name": "Some service",
        "expiration_date": "2026-05-19",
    },
)
res_apikey.raise_for_status()
new_apikey = res_apikey.json()

# store the new key securely

该方法以字符串形式返回新生成的 API 密钥,此密钥之后无法再获取。

默认情况下,每个用户最多可以以编程方式生成 10 个 API 密钥。此上限可通过系统参数 base.programmatic_api_keys_limit 进行配置。

尝试超出配置的上限会失败,并返回 HTTP 422 Unprocessable Content 状态码。

撤销密钥

可以使用 res.users.apikeys.revoke() 撤销 API 密钥。

该方法接受以下参数:

  • key (字符串):要撤销的 API 密钥

示例

import requests

API_KEY = ...  # get it from a secure location

res_apikey = requests.post(
    f"https://mycompany.example.com/json/2/res.users.apikeys/revoke",
    headers={"Authorization": f"bearer {API_KEY}"},
    json={
        "key": API_KEY,
    },
)
res_apikey.raise_for_status()

如果密钥有效,它会立即被撤销,并且无法再用于后续请求。否则,请求会失败并返回 HTTP 403 Forbidden 状态码。

请注意,被撤销的密钥与 Authorization 标头中提供的密钥无需相同。轮换期间的良好做法是使用新密钥进行身份验证,以撤销旧密钥。

密钥轮换最佳实践

定期轮换 API 密钥可降低密钥泄露后被未授权访问的风险。

轮换 API 密钥时:

  1. 在撤销旧密钥之前先生成新密钥。

  2. 在部署之前安全地存储新密钥。

  3. 确认所有服务都在使用新密钥。

  4. 仅在切换完成后才撤销旧密钥。

  5. 使用符合系统安全要求的过期日期。

  6. 最好为每个服务或集成使用专用的 API 密钥,以简化审计和撤销操作。

访问权限

JSON-2 API 使用 Odoo 标准的 安全模型。所有操作都会根据用户的访问权限、记录规则和字段访问权限进行校验。

对于 交互式用途,例如探索 API 或运行一次性脚本,使用 个人账户 没有问题。

对于 扩展的自动化用途,例如与另一个软件集成,建议创建并使用 专用机器人用户。使用专用机器人用户有以下几项好处:

  • 可以只向机器人授予最低必需的权限,从而限制 API 密钥泄露时的影响。

  • 可以将密码设置为空,以禁用登录名/密码身份验证,降低账户被入侵的可能性。

  • 日志访问记录 使用机器人账户。不会冒用任何用户的身份。

数据库

根据部署方式的不同,可能需要 Host 和/或 X-Odoo-Database 请求标头。Host 标头是 HTTP/1.1 的要求,在 Odoo 与其他 Web 应用一同安装的服务器上需要它,以便 Web 服务器/反向代理能够将请求路由到 Odoo 服务器。当一台 Odoo 服务器托管多个数据库,且 数据库过滤器 未配置为使用 Host 标头时,则需要 X-Odoo-Database 标头。

大多数 HTTP 客户端库会根据连接 URL 自动设置 Host 标头。

事务

对 JSON-2 端点的所有调用都在各自的 SQL 事务中运行。调用成功时提交事务,出错时则丢弃事务。使用 JSON-2 API 时,无法在单个事务中串联多个调用。这意味着在进行多次连续调用时必须谨慎,因为数据库可能会被其他并发事务修改。在执行与预留、付款等相关的操作时,这种情况尤其危险。

解决方案是始终调用单个方法,在单个事务中执行所有相关操作。这样可以保证数据保持一致:要么全部完成(成功,提交),要么什么都不做(出错,回滚)。

在 ORM 中,search_read 方法就是一个在单个事务中执行多个操作(先 search 后 read)的单一方法示例。如果并发的请求删除了 search 检索到的某条记录,那么后续对 read 的调用就可能因记录缺失错误而失败。而在 search_read 中不会出现这种问题,因为系统保证了事务之间的正确隔离。

在业务模型中,这类方法通常以 action_ 为前缀,例如 sale.order 的 action_confirm 方法,它会在确认销售订单之前先校验订单是否有效。

如果某一组相关操作还没有对应的方法,可以在专用模块中新建一个方法。

更多内容

代码示例

以下示例展示如何在托管于虚拟网站 https://mycompany.example.com 的虚拟数据库 mycompany 上执行两个 常用 ORM 方法。其 动态文档 可在 https://mycompany.example.com/doc 获取。

import requests

BASE_URL = "https://mycompany.example.com/json/2"
API_KEY = ...  # get it from a secure location
headers = {
    "Authorization": f"bearer {API_KEY}",
    "X-Odoo-Database": "mycompany",
    "User-Agent": "mysoftware " + requests.utils.default_user_agent(),
}

res_search = requests.post(
    f"{BASE_URL}/res.partner/search",
    headers=headers,
    json={
        "context": {"lang": "en_US"},
        "domain": [
            ("name", "ilike", "%deco%"),
            ("is_company", "=", True),
        ],
    },
)
res_search.raise_for_status()
ids = res_search.json()

res_read = requests.post(
    f"{BASE_URL}/res.partner/read",
    headers=headers,
    json={
        "ids": ids,
        "context": {"lang": "en_US"},
        "fields": ["name"],
    }
)
res_read.raise_for_status()
names = res_read.json()
print(names)

上面的示例等价于运行以下代码:

Model = self.env["res.partner"].with_context({"lang": "en_US"})
records = Model.search([("name", "ilike", "%deco%"), ("is_company", "=", True)])
return json.dumps(records.ids)

然后,在一个新事务中:

records = self.env["res.partner"].with_context({"lang": "en_US"}).browse(ids)
names = records.read(["name"])
return json.dumps(names)

动态文档

正在建设中

从 XML-RPC / JSON-RPC 迁移

位于 /xmlrpc、/xmlrpc/2 和 /jsonrpc 端点的 XML-RPC 与 JSON-RPC API 均已弃用。这两个 RPC API 都提供相同的三个服务:common、db(数据库)和 object。db 服务已在 Odoo 20(2026 年秋季)和 Online 19.1(2025 年冬季)中移除。common 和 object 服务计划在 Odoo 22(2028 年秋季)和 Online 21.1(2027 年冬季)中移除。

注解

其他 @route(type='jsonrpc') 控制器(在 Odoo 18 之前称为 type='json')不受此弃用通知的影响。

common 服务

common 服务定义了 3 个函数:

  1. version()

  2. login(db, login, password)

  3. authenticate(db, login, password, user_agent_env)

version 函数已被 /web/version 端点取代。

GET /web/version HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json

{"version_info": [19, 0, 0, "final", 0, ""], "version": "19.0"}

login 和 authenticate 这两个函数在登录成功后返回该用户对应的用户 ID。用户 ID 和密码是后续调用 object 服务的 RPC 所必需的。JSON-2 API 使用不同的身份验证方案,既不使用用户 ID 也不使用密码。仍然可以通过向 res.users/context_get 发送不带 ID 的 JSON-2 请求来获取用户自己的 ID(当前用户从 API 密钥中提取)。

db 服务

db 服务定义了 13 个函数:

  1. create_database(master_pwd, db_name, demo, lang, user_password, login, country_code, phone)

  2. duplicate_database(master_pwd, db_original_name, db_name, neutralize_database)

  3. drop(master_pwd, db_name)

  4. dump(master_pwd, db_name, format)

  5. restore(master_pwd, db_name, data, copy)

  6. change_admin_password(master_pwd, new_password)

  7. rename(master_pwd, old_name, new_name)

  8. migrate_databases(master_pwd, databases)

  9. db_exist(db_name)

  10. list()

  11. list_lang()

  12. list_countries(master_pwd)

  13. server_version()

上述函数中有许多可通过 /web/database 控制器访问。这些控制器与位于 /web/database/manager 的 HTML 表单配合使用,并可通过 HTTP 访问。

以下控制器使用动词 POST 和内容类型 application/x-www-form-urlencoded。

  1. /web/database/create 接受 master_pwd、name、login、password、demo、lang 和 phone 作为输入。

  2. /web/database/duplicate 接受 master_pwd、name、new_name 和 neutralize_database 作为输入(默认不进行中和)。

  3. /web/database/drop 接受 master_pwd 和 name 作为输入。

  4. /web/database/backup 接受 master_pwd、name 和 backup_format (默认为 zip)作为输入,并在 HTTP 响应中返回备份文件。

  5. /web/database/change_password 接受 master_pwd 和 master_pwd_new 作为输入。

以下控制器使用动词 POST 和内容类型 multipart/form-data。

  • /web/database/restore 接受 master_pwd、name、copy (默认不复制)和 neutralize (默认不中和)作为输入,还接受一个文件输入 backup_file。

以下控制器使用动词 POST 和内容类型 application/json-rpc。

  • /web/database/list 接受一个空 JSON 对象作为输入,并在 JSON 响应的 result 条目下返回数据库列表。

其余函数包括:server_version,它存在于 /web/version 下;list_lang 和 list_countries,它们通过 JSON-2 在 res.lang 和 res.country 模型上提供;以及 migrate_databases,它目前还没有可编程 API。

object 服务

object 服务定义了 2 个函数:

  1. execute(db, uid, passwd, model, method, *args)

  2. execute_kw(db, uid, passwd, model, method, args, kw={})

两者都可用于访问所有公共模型方法,包括通用的 ORM 方法。

这两个函数都是无状态的。这意味着每次调用都需要提供数据库、用户 ID 和用户密码。此外还必须提供模型、方法和参数。execute 函数接受所需数量的额外位置参数。execute_kw 函数接受由位置参数组成的 args 列表以及可选的关键字参数字典 kw。

记录 ID 从第一个 args 中提取。当被调用的方法带有 @api.model 装饰器时,不会提取记录 ID,args 保持原样。只有使用 execute_kw 才能传入上下文,因为它是从名为 context 的关键字参数中提取的。

示例

要运行以下代码:

(env['res.partner']
    .with_user(2)  # admin
    .with_context(lang='en_US')
    .browse([1, 2, 3])
    .read(['name'], load=None)
)

使用 XML-RPC(JSON-RPC 类似):

from xmlrpc.client import ServerProxy
object = ServerProxy(...)
ids = [1, 2, 3]
fields = ['name']
load = None

object.execute("database", 2, "admin", "res.partner", "read", ids, fields, load)
object.execute("database", 2, "admin", "res.partner", "search", [
    ids,
    fields,
], {
    "context": {"lang": "en_US"},
    "load": load,
})

JSON-2 API 取代了 object 服务,但有一些差异。只有在同一域名下存在多个可用数据库的系统中,才需要(通过 X-Odoo-Database HTTP 标头)提供数据库。登录名/密码身份验证方案被 API 密钥取代(通过 Authorization: bearer HTTP 标头)。model 和 method 放在 URL 中。请求体是一个 JSON 对象,包含方法的所有参数以及 ids 和 context。所有参数都是具名的;在 JSON-2 中无法使用位置参数调用函数。

示例

使用 JSON-2:

import requests

DATABSE = ...
DOMAIN = ...
API_KEY = "6578616d706c65206a736f6e20617069206b6579"

requests.post(
    f"https://{DOMAIN}/json/2/res.partner/read",
    headers={
        # "X-Odoo-Database": DATABASE,  # only when DOMAIN isn't enough
        "Authorization": f"bearer {API_KEY}",
    },
    json={
        "ids": [1, 2, 3],
        "context": {"lang": "en_US"},
        "fields": ["name"],
        "load": None,
    },
).json()