Automation API của Business Central: publish extension, đổ dữ liệu, rà quyền không cần mở giao diện
Automation API là nhóm API Microsoft làm riêng để dựng và quản trị môi trường Business Central bằng lệnh: tạo company, chạy RapidStart, cài gỡ extension, rà user và permission set. Bài ghi lại những gì đã chạy thật trên sandbox SaaS và các bẫy gặp phải.
Automation API là nhóm API Microsoft làm riêng cho việc dựng và quản trị môi trường Business Central: tạo company, chạy RapidStart package, cài và gỡ extension, xem user và permission set, bật tắt tính năng trong Feature Management. Nhập từng đơn bán hay hoá đơn thì dùng API chuẩn api/v2.0, nhưng đổ hàng loạt rồi post thì RapidStart qua Automation API làm được, phần dưới có nói.
Tài liệu gốc: Introduction to automation APIs.
Bài này viết cho consultant và người làm triển khai. Mọi lệnh bên dưới tôi đã chạy trên một môi trường sandbox BC 28 SaaS, bằng một Entra app đăng nhập kiểu service-to-service (S2S), không mở trình duyệt.
Ba loại API của Business Central, đừng lẫn#
| Loại | Đường dẫn | Dùng để |
|---|---|---|
| API chuẩn | api/v2.0/companies({id})/salesOrders | Dữ liệu nghiệp vụ: khách hàng, mặt hàng, đơn bán, đơn mua, bút toán |
| Automation API | api/microsoft/automation/v2.0/companies({id})/extensions | Quản trị môi trường: company, RapidStart, extension, user, quyền, tính năng |
| API tự viết | api/<publisher>/<group>/v1.0/companies({id})/<entitySet> | Những gì hai loại trên không có, do extension của dự án định nghĩa |
Một điểm hay làm người mới bối rối: Automation API vẫn nằm dưới companies({id}), kể cả với những việc áp cho cả môi trường như cài extension. Company nào cũng được, miễn là company có thật. Kết quả không bị giới hạn trong company đó.
Những endpoint đã gọi được#
Kiểm ngày 21 và 23/09/2026 trên một sandbox, với Entra app đã đăng ký trong BC:
| Endpoint | Trả về | Để làm gì |
|---|---|---|
automationCompanies | 12 company | Tạo, đổi tên company |
configurationPackages | 15 package | Tạo, upload, import, apply RapidStart |
extensions | 96 extension | Xem đã cài gì, bản nào; cài, gỡ, unpublish |
extensionUpload | Đưa file .app lên và cài | |
extensionDeploymentStatus | 170 lần deploy | Theo dõi lần cài đang chạy thành công hay hỏng |
users | 47 user | Bật tắt user, ngày hết hạn |
permissionSets | Danh sách permission set | |
usersPermissions, userPermissionSets, aggregatePermissionSets | 35 user, và các phân quyền | Rà ai có quyền gì, chỉ đọc |
features | 14 tính năng | Bật tắt tính năng trong Feature Management |
Ứng dụng 1: publish extension không cần VS Code#
Đây là việc tôi dùng nhiều nhất. Build file .app bằng compiler AL chạy dòng lệnh, rồi đẩy lên bằng bốn lệnh:
POST .../extensionUpload
{"schedule": "Current version", "schemaSyncMode": "Add"}
PATCH .../extensionUpload({id})/extensionContent
Content-Type: application/octet-stream
If-Match: *
<noi dung file .app>
POST .../extensionUpload({id})/Microsoft.NAV.upload
GET .../extensionDeploymentStatus
Lệnh thứ ba trả về ngay, còn BC cài ở phía sau. Phải hỏi extensionDeploymentStatus mỗi vài giây cho tới khi trạng thái thành Completed hoặc Failed. Với một extension khoảng 140 object, mỗi lần cài mất từ 90 đến 135 giây.
Completed vẫn chưa đủ để báo "đã cài". Tôi luôn đọc lại extensions và so đúng số version với bản vừa build. Đã có lần tin vào trạng thái rồi báo xong, trong khi bản đang cài vẫn là bản cũ.
schemaSyncMode có hai giá trị. Add là mặc định an toàn. Force Sync cho phép xoá bảng hoặc trường, và dữ liệu của trường bị xoá mất theo. Chỉ dùng khi đã hỏi người có quyền quyết.
Microsoft đã báo: extensionUpload là đường cũ, dự kiến gỡ ở 2027 release wave 1. Đường thay thế là API của Business Central Admin Center. Script nào đang dùng extensionUpload thì nên lên kế hoạch chuyển trong năm 2027.
Ứng dụng 2: gỡ extension sạch sẽ#
Gỡ một extension gồm hai bước, dùng packageId lấy từ GET extensions:
POST .../extensions({packageId})/Microsoft.NAV.uninstall
POST .../extensions({packageId})/Microsoft.NAV.unpublish
uninstall giữ lại dữ liệu của extension, cài lại là thấy. Muốn xoá luôn bảng của extension thì dùng Microsoft.NAV.uninstallAndDeleteExtensionData, và bước này không hoàn tác được. unpublish chỉ có từ bản 25.4 trở đi, và chỉ chạy với extension đã uninstall.
Tôi dùng cặp lệnh này cho một thói quen: công cụ phụ để test (API liệt kê object, hàm tạo dữ liệu mẫu) không nhét vào extension của khách mà để trong một extension tạm riêng, cài khi cần, gỡ khi xong. Extension của khách không bị lẫn mã test, và dải ID của khách không bị chiếm.
Ứng dụng 3: đổ dữ liệu và post chứng từ bằng RapidStart#
Consultant nào cũng quen RapidStart trên giao diện. Qua Automation API thì làm được cùng việc đó bằng lệnh, lặp lại cho nhiều company hoặc nhiều môi trường:
POST .../configurationPackages
{"code": "MIG-ITEM", "packageName": "Item master"} -> 201, trả về id
PATCH .../configurationPackages({packageId})/file('MIG-ITEM')/content -> 204
Content-Type: application/octet-stream
If-Match: *
<file RapidStart>
POST .../configurationPackages({packageId})/Microsoft.NAV.import -> 200
POST .../configurationPackages({packageId})/Microsoft.NAV.apply -> 200
Hai lệnh cuối trả về ngay rồi chạy tiếp ở phía sau. Trạng thái đọc bằng GET configurationPackages({id}): importStatus đi từ No sang Scheduled rồi Completed, applyStatus đi từ No sang InProgress rồi Completed.
Đọc hai con số sau khi apply cho đúng: numberOfRecords là số dòng còn lại trong package, không phải số dòng đã ghi, vì dòng nào apply xong thì bị xoá khỏi package. Apply sạch thì nó về 0. numberOfErrors là số dòng hỏng, và những dòng đó ở lại. Nội dung lỗi không có trong API, phải mở Config. Package Card trên BC để đọc.
Một hạn chế: không tải package có sẵn về bằng API được. Segment file('CODE') chỉ tồn tại sau khi mình đẩy file lên. Package do người khác tạo trong BC thì phải export trên giao diện.
File .rapidstart là XML nén GZip#
Biết điều này thì việc sửa một package trở nên cơ học: giải nén, sửa XML, nén lại, đẩy lên. Không cần mở BC.
Việc hay dùng nhất là dời ngày cho dữ liệu demo. Bộ dữ liệu mẫu của LS Central mang ngày 2022-2023, đem đi demo năm 2026 thì khuyến mãi nào cũng hết hạn. Bốn quy tắc khi dời:
- Cộng cùng một khoảng cho mọi trường ngày, đừng đặt hết về hôm nay, để cặp Từ ngày/Đến ngày giữ nguyên khoảng cách.
- Dời theo bội số của 7 ngày, không dời tròn năm. Validation period của LS có cài theo thứ trong tuần, dời tròn năm thì "khuyến mãi thứ Bảy" rơi vào hôm khác.
- Ngày trống là
0001-01-01, giữ nguyên là trống. - Không đụng vào trường date formula (
1M,CM+10D), đó không phải ngày.
Khung XML dưới đây là package tự viết, BC nhận (encode UTF-16 có BOM, đúng như bản BC xuất ra):
<?xml version="1.0" encoding="UTF-16" standalone="yes"?>
<DataList MinCountForAsyncImport="5000" ProductVersion="" PackageName="Ten" Code="MA-PACKAGE">
<PaymentTermsList>
<TableID>3</TableID>
<PaymentTerms>
<Code PrimaryKey="1" ProcessingOrder="1">ZZRS01</Code>
<DueDateCalculation ValidateField="1" ProcessingOrder="2">14D</DueDateCalculation>
<Description ValidateField="1" ProcessingOrder="3">Mo ta</Description>
</PaymentTerms>
</PaymentTermsList>
</DataList>
Node bảng tên là tên bảng cộng List, node con đầu tiên bắt buộc là TableID, mỗi bản ghi một node tên bằng tên bảng. Tên node trường lấy từ tên trường sau khi bỏ dấu cách và các ký tự . , / \ + & ( ) % :. Các thuộc tính PrimaryKey, ValidateField, CreateMissingCodes, ProcessingOrder chỉ cần đặt ở bản ghi đầu tiên. BC dựng danh sách trường của package từ đó.
Package post được chứng từ, không chỉ đổ master data#
Bảng Config. Table Processing Rule cho gắn một hành động vào từng bảng trong package, và hành động đó chạy ngay trong bước apply, với những bản ghi vừa được ghi vào:
| Bảng | Action cho phép |
|---|---|
| Sales Header | Invoice, Ship |
| Purchase Header | Invoice, Receive |
| Gen. Journal Line, Gen. Journal Batch | Post |
| Transfer Header | Ship, Receive |
| Item | Post |
| Bảng bất kỳ | Custom, chạy một codeunit tự viết |
Nghĩa là một package tạo được đơn bán và dòng đơn bán rồi post luôn thành hoá đơn. Dựng lịch sử bán hàng cho môi trường demo không cần viết extension nào.
Hai điều phải nhớ khi dùng:
- Ngày post lấy từ cột trong package, đừng để trống. Phiên đăng nhập S2S không có Work Date, để trống thì BC lấy ngày hệ thống và lịch sử demo dồn hết vào hôm nay.
- Rule hỏng thì apply không dừng. Lỗi ghi vào danh sách lỗi của package rồi chạy tiếp dòng sau. Nên
applyStatusbằngCompletedchưa có nghĩa là mọi chứng từ đã post, phải đọcnumberOfErrors.
Codeunit cho action Custom có một chỗ dễ sai: nó nhận Record "Config. Package Record", không nhận bản ghi vừa tạo. Viết OnRun(var Rec: Record Item) rồi gắn vào là không chạy.
Bản export của BC không kèm bảng Processing Rule, nhưng tự thêm bảng 8631 vào XML thì BC nạp được. Tôi kiểm bằng hai package: một rule hợp lệ apply sạch, một rule sai luật (Payment Terms + Invoice) trả về đúng một lỗi, tức là dòng thật sự đi vào bảng và bị chặn ở đó. Vậy một package mang theo được cả dữ liệu lẫn luật xử lý của nó.
Khi nào nên đi đường này#
- Dựng company training hoặc company test lặp lại nhiều lần. Tạo company bằng
automationCompanies, đổ bộ package chuẩn vào, xong trong một lần chạy script. - Kiểm thử chuyển dữ liệu nhiều vòng trước go-live. Mỗi vòng chạy lại đúng một chuỗi lệnh, không phụ thuộc người bấm nhớ đúng thứ tự.
- Bảng không có API chuẩn, như các bảng của LS Central. Bảng nào có sẵn trong
api/v2.0thì gọi thẳng API nhanh hơn.
Company tạo bằng automationCompanies là company trống, chưa khởi tạo gì. Tài liệu Microsoft ghi rõ điều này.
Ứng dụng 4: rà quyền trước go-live#
Năm endpoint chỉ đọc cho biết ai có quyền gì, không sửa được gì:
| Endpoint | Trả lời câu hỏi |
|---|---|
usersPermissions | Có những user nào, loại license gì, đang bật hay tắt |
userPermissionSets | User nào được gán permission set nào, ở company nào, qua security group nào |
accessControls | Bảng gán quyền gốc: user, company, permission set |
aggregatePermissionSets | Permission set nào có trong môi trường, thuộc app nào |
expandedPermissionSets | Từng permission set cho quyền đọc, thêm, sửa, xoá, chạy trên object nào |
Trước go-live, xuất userPermissionSets ra Excel là có ngay danh sách để khách hàng ký duyệt phân quyền, thay vì chụp màn hình từng user.
Ứng dụng 5: bật tính năng đồng đều giữa các môi trường#
GET features liệt kê các tính năng trong Feature Management kèm trạng thái. Bật một tính năng:
POST .../features({featureId})/Microsoft.NAV.activate
{"updateInBackground": false}
Có ba môi trường Dev, UAT, Production thì chạy cùng một script cho cả ba, tránh trường hợp UAT bật một tính năng mà Production quên bật, rồi người dùng thấy hai màn hình khác nhau.
Những bẫy đã gặp#
| Hiện tượng | Nguyên nhân | Cách xử lý |
|---|---|---|
| PATCH trả 400 về concurrency token | Thiếu header | Thêm If-Match: * |
Gán permission set cho chính Entra app bằng userPermissions trả 403 | Lệnh ghi vào bảng Access Control bị chặn với đăng nhập S2S | Gán trên giao diện, trang Microsoft Entra Applications |
getNewUsersFromOffice365 không chạy | Không hỗ trợ đăng nhập S2S, vì cần quyền SUPER ở mọi company | Chạy bằng tài khoản người thật, hoặc bấm trên giao diện |
Deploy báo Failed, API không nói lý do | Automation API không trả thông báo chi tiết | Mở trang Extension Deployment Status trên BC để đọc, hoặc viết một API nhỏ đọc lại lỗi |
Cài lại đúng version vừa unpublish thì Failed | Chưa rõ nguyên nhân, API không trả lý do | Tăng version mỗi lần cài thì cài được |
| Unpublish trả lỗi | Extension còn đang cài, hoặc môi trường dưới 25.4 | Uninstall trước, kiểm version môi trường |
Apply package xong, numberOfRecords bằng 0, tưởng không ghi được gì | Dòng apply xong bị xoá khỏi package | Đọc numberOfErrors, và kiểm lại dữ liệu thật bằng api/v2.0 |
GET file của package có sẵn trả 404 ở segment file | Chỉ có file khi chính mình đẩy lên | Export package trên giao diện |
Chuẩn bị một lần#
Để gọi được Automation API bằng S2S cần ba việc, đều làm trên giao diện và chỉ làm một lần cho mỗi môi trường:
- Đăng ký app trong Microsoft Entra ID, tạo client secret.
- Cấp quyền ứng dụng
Automation.ReadWrite.Allcho Dynamics 365 Business Central, kèm admin consent. - Trong BC, mở trang Microsoft Entra Applications, thêm app đó và gán permission set cần thiết.
Sau đó mọi lệnh trong bài đều gọi được từ Python, PowerShell hay Power Automate, không cần ai ngồi bấm.