Đến nội dung chính
ERP Thực Chiến
Thư viện
Menu

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ẫnDùng để
API chuẩnapi/v2.0/companies({id})/salesOrdersDữ liệu nghiệp vụ: khách hàng, mặt hàng, đơn bán, đơn mua, bút toán
Automation APIapi/microsoft/automation/v2.0/companies({id})/extensionsQuản trị môi trường: company, RapidStart, extension, user, quyền, tính năng
API tự viếtapi/<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:

EndpointTrả vềĐể làm gì
automationCompanies12 companyTạo, đổi tên company
configurationPackages15 packageTạo, upload, import, apply RapidStart
extensions96 extensionXem đã cài gì, bản nào; cài, gỡ, unpublish
extensionUploadĐưa file .app lên và cài
extensionDeploymentStatus170 lần deployTheo dõi lần cài đang chạy thành công hay hỏng
users47 userBật tắt user, ngày hết hạn
permissionSetsDanh sách permission set
usersPermissions, userPermissionSets, aggregatePermissionSets35 user, và các phân quyềnRà ai có quyền gì, chỉ đọc
features14 tính năngBậ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:

http
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:

http
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:

http
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
<?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ảngAction cho phép
Sales HeaderInvoice, Ship
Purchase HeaderInvoice, Receive
Gen. Journal Line, Gen. Journal BatchPost
Transfer HeaderShip, Receive
ItemPost
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 applyStatus bằng Completed chưa có nghĩa là mọi chứng từ đã post, phải đọc numberOfErrors.

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.0 thì 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ì:

EndpointTrả lời câu hỏi
usersPermissionsCó những user nào, loại license gì, đang bật hay tắt
userPermissionSetsUser nào được gán permission set nào, ở company nào, qua security group nào
accessControlsBảng gán quyền gốc: user, company, permission set
aggregatePermissionSetsPermission set nào có trong môi trường, thuộc app nào
expandedPermissionSetsTừ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:

http
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ượngNguyên nhânCách xử lý
PATCH trả 400 về concurrency tokenThiếu headerThêm If-Match: *
Gán permission set cho chính Entra app bằng userPermissions trả 403Lệnh ghi vào bảng Access Control bị chặn với đăng nhập S2SGán trên giao diện, trang Microsoft Entra Applications
getNewUsersFromOffice365 không chạyKhông hỗ trợ đăng nhập S2S, vì cần quyền SUPER ở mọi companyChạ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ý doAutomation API không trả thông báo chi tiếtMở 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ì FailedChưa rõ nguyên nhân, API không trả lý doTăng version mỗi lần cài thì cài được
Unpublish trả lỗiExtension còn đang cài, hoặc môi trường dưới 25.4Uninstall 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 fileChỉ có file khi chính mình đẩy lênExport 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:

  1. Đăng ký app trong Microsoft Entra ID, tạo client secret.
  2. Cấp quyền ứng dụng Automation.ReadWrite.All cho Dynamics 365 Business Central, kèm admin consent.
  3. 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.

Đọc tiếp