# API /json/2 mới của Odoo: có gì thay đổi và chuyển đổi thế nào trước hạn

> API /json/2 của Odoo thay xác thực bằng mật khẩu bằng API key dạng bearer và cuối cùng cũng trả về mã lỗi HTTP thật. Có gì thay đổi, viết lại lời gọi ra sao, và hạn gỡ bỏ.

**Date:** 2026-06-22
**Source:** <https://trobz.com/vi/insights/odoo-json2-api-migration/>

---



Bài này nói về API `/json/2` mới là gì, nó khác với những thứ trước đó ra sao, cách tạo API key, và cách viết lại các khuôn mẫu tích hợp phổ biến nhất.

---

## Vì sao API cũ sắp bị bỏ

Các endpoint XML-RPC và JSON-RPC đã là API đối ngoại của Odoo từ những ngày đầu. Chúng chạy được, nhưng mang theo nhiều năm quyết định thiết kế vốn hợp lý vào thời điểm đó và nay đã thành vướng víu.

Có hai vấn đề nổi lên trên thực tế.

Thứ nhất là xác thực. Cả hai API đều xác thực bằng tên đăng nhập và mật khẩu. Với các tích hợp máy nói chuyện với máy, điều đó nghĩa là phải lưu thông tin đăng nhập của một người dùng ở đâu đó, phải đổi lại mỗi khi người đó đổi mật khẩu, và phải chấp nhận rằng một thông tin đăng nhập bị lộ là trao trọn quyền ở mức người dùng. API key là cách giải quyết tiêu chuẩn cho vấn đề này, nhưng API cũ chưa bao giờ dùng tới.

Thứ hai là cách báo lỗi. Cả endpoint XML-RPC lẫn JSON-RPC đều trả về **HTTP 200 khi có lỗi**. Thông tin lỗi bị chôn trong phần thân phản hồi. Điều đó làm hỏng mọi công cụ giám sát HTTP tiêu chuẩn, mọi thư viện thử lại, và trực giác của mọi lập trình viên về ý nghĩa của một mã 200. Bạn phải phân tích từng phản hồi mới biết được lời gọi thành công hay không.

`/json/2` sửa cả hai.

---

## /json/2 hoạt động ra sao

Cấu trúc rất thẳng thắn: `POST /json/2/<model>/<method>`.

Model là tên kỹ thuật của model (`res.partner`, `sale.order`). Method là thứ bạn đang gọi (`search`, `read`, `search_read`, `create`, `write`, `unlink`, hoặc bất kỳ phương thức tùy chỉnh nào được phơi ra bên ngoài). Phần thân request là một đối tượng JSON chứa các tham số của phương thức theo tên.

### Header của request

| Header            | Bắt buộc    | Giá trị                                                                     |
| ----------------- | ----------- | --------------------------------------------------------------------------- |
| `Authorization`   | Có          | `bearer <API_KEY>`                                                          |
| `Content-Type`    | Có          | `application/json` (nên kèm charset)                                        |
| `X-Odoo-Database` | Khi cần     | Tên database — chỉ bắt buộc khi một domain phục vụ nhiều database           |
| `User-Agent`      | Nên có      | Tên phần mềm của bạn                                                        |

### Thân request

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

Tham số truyền theo tên. Không có chế độ truyền theo vị trí — bạn phải dùng đúng tên tham số được định nghĩa trên phương thức của Odoo (`ids`, `domain`, `fields`, `vals`, `vals_list`, v.v.).

### Phản hồi

Thành công trả về **HTTP 200** kèm giá trị trả về của phương thức dưới dạng JSON.

Lỗi trả về mã **4xx hoặc 5xx** kèm một đối tượng lỗi JSON:

```json
{
  "name": "werkzeug.exceptions.Unauthorized",
  "message": "Invalid apikey",
  "arguments": ["Invalid apikey", 401],
  "context": {},
  "debug": "Traceback (most recent call last): ..."
}
```

Đây là một thay đổi có ý nghĩa. Với mã trạng thái đúng chuẩn, hệ thống giám sát của bạn phân biệt được một tích hợp đang chạy tốt với một tích hợp đang hỏng mà không phải soi từng phần thân phản hồi.

### Hành vi transaction

Mỗi lời gọi tới `/json/2` chạy trong một transaction SQL riêng. Thành công thì commit; lỗi thì hủy bỏ. Bạn không thể nối nhiều lời gọi vào cùng một transaction qua API đối ngoại.

---

## Lấy API key

API key gắn với từng người dùng và có thời hạn. Người dùng thường tạo được key với thời hạn tối đa **90 ngày**. Quản trị viên tạo được key không hết hạn.

**Để tạo key thủ công:**

1. Vào `Preferences > Account Security`
2. Nhấp **New API Key**
3. Nhập mô tả (hãy dùng thứ gì đó cho biết key này dùng ở đâu — đừng chỉ ghi "API key")
4. Đặt ngày hết hạn — để ngắn nếu dùng theo kiểu tương tác
5. Nhấp **Generate Key**

Key là một giá trị ngẫu nhiên 160 bit. Nó chỉ hiện ra đúng một lần. Hãy chép ngay và cất ngoài mã nguồn — một trình quản lý secret, một biến môi trường, hoặc một vault. Nếu làm mất, bạn phải thu hồi và tạo key mới.

> ⚠️ Với người dùng thường, key sống tối đa 90 ngày. Nghĩa là các tích hợp cần một quy trình xoay vòng key. Hoặc tự động hóa việc xoay vòng bằng code (xem bên dưới), hoặc đặt lịch nhắc trước mỗi lần key hết hạn.

### Tạo key bằng code

Nếu bạn cần xoay vòng key mà không phải thao tác tay trên giao diện:

```python
import requests

API_KEY = ...  # current key, from a secure location

res = requests.post(
    "https://mycompany.example.com/json/2/res.users.apikeys/generate",
    headers={"Authorization": f"bearer {API_KEY}"},
    json={
        "name": "my-integration-service",
        "expiration_date": "2026-09-22",
        "scope": None,
    },
)
res.raise_for_status()
new_key = res.json()  # store this securely, then retire the old key
```

---

## Một tích hợp chạy được bằng Python

```python
import requests

BASE_URL = "https://mycompany.example.com/json/2"
API_KEY = ...  # from a secure location

headers = {
    "Authorization": f"bearer {API_KEY}",
    "X-Odoo-Database": "mycompany",
    "User-Agent": "my-integration " + requests.utils.default_user_agent(),
}

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

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

Không phân tích XML. Không có lớp bao RPC. Không phải đi tìm mã trạng thái bị chôn trong một payload JSON. Tầng HTTP mang luôn phần ngữ nghĩa.

---

## Việc chuyển đổi thực tế trông ra sao

Dưới đây là cùng một thao tác — đọc danh sách đối tác — đặt cạnh nhau giữa kiểu XML-RPC cũ và `/json/2`.

**Cũ (XML-RPC, `xmlrpc.client` của Python):**

```python
import xmlrpc.client

url = "https://mycompany.example.com"
db = "mycompany"
username = "admin"
password = "admin_password"

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",
    [[["name", "ilike", "%deco%"]]],
    {"fields": ["name"], "limit": 10}
)
```

**Mới (/json/2, `requests`):**

```python
import requests

partners = requests.post(
    "https://mycompany.example.com/json/2/res.partner/search_read",
    headers={"Authorization": "bearer <API_KEY>", "X-Odoo-Database": "mycompany"},
    json={"domain": [["name", "ilike", "%deco%"]], "fields": ["name"], "limit": 10},
).json()
```

Bản cũ cần hai vòng đi về (xác thực, rồi mới gọi). Bản mới chỉ cần một. Bản cũ truyền thông tin đăng nhập ở mọi lời gọi. Bản mới gửi một key không bao giờ để lộ mật khẩu của người dùng.

### Cần để ý gì khi viết lại

**Chỉ truyền tham số theo tên.** XML-RPC cho phép truyền theo vị trí. `/json/2` thì không. Nếu lời gọi của bạn dùng `execute_kw` với một danh sách theo vị trí, bạn phải tra chữ ký phương thức và đặt tên tham số một cách tường minh. `write` nhận `vals`, không phải một dict theo vị trí. `create` nhận `vals_list` khi tạo hàng loạt.

**Không có thứ tương đương cho service `db` hay `common`.** Các service RPC cũ là `db` và `common` (liệt kê database, lấy phiên bản máy chủ, xác thực) không nằm trong `/json/2`. Thông tin phiên bản chuyển sang `GET /web/version`. Việc lấy ID người dùng làm qua `res.users/context_get` mà không cần `ids` — API tự rút người dùng ra từ key.

**Chỉ thay `/xmlrpc/2/object`, không thay `/xmlrpc/2/db`.** Endpoint `/json/2` thay cho service `object`. Việc quản trị database qua RPC không được chuyển sang — và đó là chủ ý.

---

## Các khác biệt chính trong một bảng

|                          | XML-RPC / JSON-RPC               | /json/2                       |
| ------------------------ | -------------------------------- | ----------------------------- |
| Xác thực                 | Tên đăng nhập + mật khẩu         | API key dạng bearer           |
| Thời hạn của key         | Còn người dùng thì còn hiệu lực  | Tối đa 90 ngày (người dùng thường) |
| Cách báo lỗi             | Luôn HTTP 200, phải soi phần thân | HTTP 4xx / 5xx               |
| Kiểu truyền tham số      | Theo vị trí hoặc theo tên        | Chỉ theo tên                  |
| Cấu trúc URL             | Cố định `/xmlrpc/2/object`       | `/json/2/<model>/<method>`    |
| Phạm vi transaction      | Tùy trường hợp                   | Một transaction mỗi lời gọi   |
| Hỗ trợ số nguyên lớn     | Hạn chế (XML-RPC)                | Đầy đủ                        |
| Tài liệu động            | Không có                         | `/doc` theo từng database     |
| Thời điểm gỡ bỏ          | Odoo 22 / Online 21.1            | Hiện hành và đang được duy trì |

---

## Lộ trình ngừng hỗ trợ

| Nền tảng           | Ngừng hỗ trợ | Gỡ bỏ                     |
| ------------------ | ------------ | ------------------------- |
| Odoo Online (SaaS) | Odoo 19      | Online 21.1 (đông 2027)   |
| Odoo.sh            | Odoo 19      | Odoo 22 (thu 2028)        |
| On-premise         | Odoo 19      | Odoo 22 (thu 2028)        |

Nếu bạn đang ở Odoo Online, quỹ thời gian khoảng 18 tháng. Trên Odoo.sh hoặc on-prem, bạn có tới mùa thu 2028. Trong cả hai trường hợp, thời điểm hợp lý để chuyển đổi là trước lần nâng cấp lớn kế tiếp, không phải tuần trước hạn chót.

> ⚠️ Lưu ý: việc ngừng hỗ trợ áp dụng cho `/xmlrpc`, `/xmlrpc/2` và `/jsonrpc`. Nó **không** ảnh hưởng tới các controller nội bộ `@route(type='jsonrpc')` mà web client của chính Odoo dùng — đó là một hệ thống khác và không bị gỡ bỏ.

---

## Khám phá API ngay trên hệ thống của bạn

Mọi instance Odoo 19 đều phơi ra tài liệu động tại `/doc`. Trang này cho bạn thấy chính xác các model, phương thức, trường và nhóm quyền có trong database của bạn, kèm ví dụ chạy được. Nếu đang xây một tích hợp mà chưa chắc nên gọi phương thức nào hay nó nhận tham số gì, đó là nơi nên bắt đầu.

