# NextFarm Developers > Tài liệu nền tảng mở NextFarm (phần mềm nông trại): REST API bằng API key hoặc OAuth app, webhook có signature, máy chủ MCP cho trợ lý AI, SDK Node.js/Python và CLI `nextfarm`. Mọi danh mục sinh từ mã các dịch vụ. English: every page has an English version under https://developers.nextfarm.vn/en (e.g. https://developers.nextfarm.vn/en/docs/quickstart). ## Bắt đầu - [Bắt đầu nhanh](https://developers.nextfarm.vn/docs/quickstart): tạo API key, gọi lệnh đầu tiên - [API key](https://developers.nextfarm.vn/docs/api-keys): scope, cách gửi khoá, error code - [Ứng dụng & OAuth](https://developers.nextfarm.vn/docs/oauth): app phục vụ nhiều trại, liên kết cài, token - [Webhooks](https://developers.nextfarm.vn/docs/webhooks): sự kiện, signature, giao lại - [SDK](https://developers.nextfarm.vn/docs/sdk): thư viện Node.js/TypeScript (npm @nextfarm/sdk) và Python (PyPI nextfarm-sdk) sinh từ OpenAPI, semver · `npm install @nextfarm/sdk` · `pip install nextfarm-sdk` - [CLI](https://developers.nextfarm.vn/docs/cli): lệnh `nextfarm` v0.3.3 (npm nextfarm): chat với trợ lý nông trại trong terminal, gọi API, API key, webhooks listen, MCP · `brew install nextx-organization/nextfarm/nextfarm` · `npm install -g nextfarm` - [MCP](https://developers.nextfarm.vn/docs/mcp): nối ChatGPT · Claude · Copilot · Cursor · Gemini · Grok (mọi client MCP) vào dữ liệu trại - [Môi trường sandbox](https://developers.nextfarm.vn/docs/sandbox) · [Mô hình dữ liệu](https://developers.nextfarm.vn/docs/data-model) · [Phiên bản & ngừng hỗ trợ](https://developers.nextfarm.vn/docs/versioning) · [Nhật ký thay đổi](https://developers.nextfarm.vn/docs/changelog) ## Tham chiếu API - [Bản đồ & mùa vụ](https://developers.nextfarm.vn/docs/gis): 34 tuyến — Thửa đất, vùng, nông dân, mùa vụ, lớp bản đồ — nền dữ liệu không gian của trang trại. - [IoT (chỉ đọc)](https://developers.nextfarm.vn/docs/iot): 24 tuyến — Thiết bị, số đo cảm biến, cảnh báo, lịch tưới. API key không có API điều khiển thiết bị. - [Thu hoạch & truy xuất](https://developers.nextfarm.vn/docs/harvest): 25 tuyến — Bản ghi thu hoạch, cách ly thuốc, lô truy xuất, tem QR, chứng nhận, giá thị trường. - [Sổ nông trại & công việc](https://developers.nextfarm.vn/docs/office): 57 tuyến — Sổ tự dựng của trại (nhật ký, kho, chi phí…), lịch, luật tự động hoá: đọc bảng, đọc/thêm/sửa dòng. - [Báo cáo](https://developers.nextfarm.vn/docs/report): 6 tuyến — Báo cáo và chi phí công theo kỳ. ## Tệp máy đọc - [OpenAPI](https://developers.nextfarm.vn/openapi.json) · [Postman](https://developers.nextfarm.vn/postman.json) · [Toàn văn cho AI](https://developers.nextfarm.vn/llms-full.txt) # Tham chiếu API (REST) Gốc: https://api-prod.nextfarm.vn · Gửi khoá: Authorization: Bearer nfk_… ## Bản đồ & mùa vụ (/api/v1/gis) Thửa đất, vùng, nông dân, mùa vụ, lớp bản đồ — nền dữ liệu không gian của trang trại. ### GET /api/v1/gis/care-schedules Các kích hoạt lịch chăm sóc (lọc theo thửa) Scope: gis.parcels.view - parcelId (query, GUID) Phản hồi mẫu: ```json [ { "assignee_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "created_at": "2026-10-28T07:30:00Z", "delivery": "…", "enabled": true, "enabled_at": "2026-10-28T07:30:00Z", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "notify_user_ids": [ "0190a3c2-5b1e-7c3d-8e4f-000000000001" ], "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "plot_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "plot_name": "…", "season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "template_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "template_name": "…" } ] ``` ### GET /api/v1/gis/care-schedules/preview Xem trước lịch (không ghi gì) Scope: gis.parcels.view - templateId (query, GUID) - parcelId (query, GUID) - plotId (query, GUID) - seasonId (query, GUID) - from (query, string) - days (query, int) Phản hồi mẫu: ```json { "from": "…", "items": [ { "anchor": "…", "due_on": "…", "material": "…", "quantity": 1.5, "rule_key": "…", "task_type": "…", "title": "…", "trees": 1, "unit": "…", "unknown_age_trees": 1 } ], "to": "…", "warnings": [ "…" ], "weeks": [ { "items": [ { "anchor": "…", "due_on": "…", "material": "…", "quantity": 1.5, "rule_key": "…", "task_type": "…", "title": "…", "trees": 1, "unit": "…", "unknown_age_trees": 1 } ], "week_end": "…", "week_start": "…" } ] } ``` ### GET /api/v1/gis/care-schedules/this-week Việc chăm sóc tuần này (chỉ kích hoạt đang BẬT) Scope: gis.parcels.view - parcelId (query, GUID) Phản hồi mẫu: ```json { "plots": [ { "activation_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "delivery": "…", "items": [ { "anchor": "…", "due_on": "…", "material": "…", "quantity": 1.5, "rule_key": "…", "task_type": "…", "title": "…", "trees": 1, "unit": "…", "unknown_age_trees": 1 } ], "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "plot_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "plot_name": "…", "template_name": "…" } ], "week_end": "…", "week_start": "…" } ``` ### GET /api/v1/gis/care-schedules/{id}/calendar Lịch cả năm của một kích hoạt Scope: gis.parcels.view - id (path, GUID, bắt buộc) - year (query, int) Phản hồi mẫu: ```json { "from": "…", "items": [ { "anchor": "…", "due_on": "…", "material": "…", "quantity": 1.5, "rule_key": "…", "task_type": "…", "title": "…", "trees": 1, "unit": "…", "unknown_age_trees": 1 } ], "to": "…", "warnings": [ "…" ], "weeks": [ { "items": [ { "anchor": "…", "due_on": "…", "material": "…", "quantity": 1.5, "rule_key": "…", "task_type": "…", "title": "…", "trees": 1, "unit": "…", "unknown_age_trees": 1 } ], "week_end": "…", "week_start": "…" } ] } ``` ### GET /api/v1/gis/care-schedules/{id}/generations Lịch sử đã sinh của một kích hoạt Scope: gis.parcels.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "activation_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "created_at": "2026-10-28T07:30:00Z", "delivery": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "item_count": 1, "week_end": "…", "week_start": "…" } ] ``` ### GET /api/v1/gis/crop-types Danh sách loại cây trồng Scope: gis.crop-types.view - category (query, string) - isActive (query, bool) - page (query, int) - pageSize (query, int) - deleted (query, bool) Phản hồi mẫu: ```json { "items": [ { "bearing_age_years": 1, "category": "…", "code": "…", "effective_lifecycle": "…", "grow_days": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "is_active": true, "lifecycle": "…", "lifecycle_guessed": true, "name": "…" } ], "page": 1, "page_size": 1, "page_size_capped": true, "total": 1 } ``` ### GET /api/v1/gis/crop-types/{id} Một loại cây trồng theo mã Scope: gis.crop-types.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "category": "…", "code": "…", "created_at": "2026-10-28T07:30:00Z", "grow_days": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "is_active": true, "name": "…", "updated_at": "2026-10-28T07:30:00Z" } ``` ### GET /api/v1/gis/crop-types/{id}/perennial-profile Hồ sơ cây lâu năm của loài Scope: gis.crop-types.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "bearing_age_years": 1, "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "default_row_spacing_m": 1.5, "default_tree_spacing_m": 1.5, "effective_lifecycle": "…", "life_stages": [ { "code": "…", "from_year": 1, "name": "…", "to_year": 1 } ], "lifecycle": "…", "lifecycle_guessed": true } ``` ### GET /api/v1/gis/crop-types/{id}/varieties Giống và gốc ghép của loài Scope: gis.crop-types.view - id (path, GUID, bắt buộc) - kind (query, string) Phản hồi mẫu: ```json [ { "code": "…", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "kind": "…", "name": "…", "note": "…" } ] ``` ### GET /api/v1/gis/facilities Danh sách chuồng trại Scope: gis.facilities.view - facilityType (query, string) - status (query, string) - sourceParcelId (query, GUID) - branchId (query, GUID) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "area_ha": 1.5, "boundary_geo_json": "…", "branch_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "capacity": 1, "created_at": "2026-10-28T07:30:00Z", "current_stock": 1, "facility_code": "…", "facility_type": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "source_parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "status": "…", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_name": "…" } ], "page": 1, "page_size": 1, "page_size_capped": true, "total": 1 } ``` ### GET /api/v1/gis/facilities/{id} Chi tiết chuồng trại Scope: gis.facilities.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "area_ha": 1.5, "boundary_geo_json": "…", "branch_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "capacity": 1, "created_at": "2026-10-28T07:30:00Z", "current_stock": 1, "facility_code": "…", "facility_type": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "metadata": "…", "name": "…", "source_parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "status": "…", "updated_at": "2026-10-28T07:30:00Z", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_name": "…" } ``` ### GET /api/v1/gis/farmer-groups GET /api/v1/gis/farmer-groups Scope: gis.farmers.view - includeInactive (query, bool) Phản hồi mẫu: ```json [ { "code": "…", "farmer_count": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "is_active": true, "leader_farmer_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "note": "…", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ] ``` ### GET /api/v1/gis/farmers Danh sách nông dân Scope: gis.farmers.view - zoneId (query, GUID) - page (query, int) - pageSize (query, int) - deleted (query, bool) - groupId (query, GUID) - memberType (query, string) Phản hồi mẫu: ```json { "items": [ { "address": "…", "cooperative_name": "…", "full_name": "…", "group_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "group_name": "…", "home_location_geo_json": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "identity_number": "…", "member_type": "…", "phone": "…", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_name": "…" } ], "page": 1, "page_size": 1, "page_size_capped": true, "total": 1 } ``` ### GET /api/v1/gis/farmers/{id} Chi tiết nông dân Scope: gis.farmers.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "address": "…", "cooperative_name": "…", "created_at": "2026-10-28T07:30:00Z", "full_name": "…", "group_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "group_name": "…", "home_location_geo_json": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "identity_number": "…", "member_type": "…", "phone": "…", "public_consent_at": "2026-10-28T07:30:00Z", "public_consent_by": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "public_name_consent": true, "public_phone_consent": true, "updated_at": "2026-10-28T07:30:00Z", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_name": "…" } ``` ### GET /api/v1/gis/layers Danh sách map layers Scope: gis.layers.view - layerType (query, string) - zoneId (query, GUID) - status (query, string) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "captured_at": "2026-10-28", "created_at": "2026-10-28T07:30:00Z", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "layer_type": "…", "name": "…", "source_url": "…", "status": "…", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_name": "…" } ], "page": 1, "page_size": 1, "page_size_capped": true, "total": 1 } ``` ### GET /api/v1/gis/layers/{id} Chi tiết map layer Scope: gis.layers.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "captured_at": "2026-10-28", "created_at": "2026-10-28T07:30:00Z", "file_path": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "layer_type": "…", "name": "…", "ndvi_avg": 1.5, "ndvi_max": 1.5, "ndvi_min": 1.5, "source_url": "…", "status": "…", "style_config": "…", "updated_at": "2026-10-28T07:30:00Z", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_name": "…" } ``` ### GET /api/v1/gis/map/points Điểm nhẹ theo bbox: nhà hộ dân + tâm thửa Scope: gis.parcels.view - bbox (query, string) - zoom (query, int) - limit (query, int) - kind (query, string) - zoneId (query, GUID) Phản hồi mẫu: ```json { "clusters": [ { "count": 1, "latitude": 1.5, "longitude": 1.5 } ], "limit": 1, "mode": "…", "points": [ { "area_ha": 1.5, "farmer_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "kind": "…", "latitude": 1.5, "longitude": 1.5, "name": "…", "parcel_code": "…", "parcel_count": 1, "status": "…" } ], "total": 1, "truncated": true, "zoom": 1 } ``` ### GET /api/v1/gis/parcels Danh sách lô thửa Scope: gis.parcels.view - bbox (query, string) - zoneId (query, GUID) - ownerId (query, GUID) - cropTypeId (query, GUID) - status (query, string) - branchId (query, GUID) - page (query, int) - pageSize (query, int) - deleted (query, bool) Phản hồi mẫu: ```json { "items": [ { "active_season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "active_season_name": "…", "active_seasons": [ { "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "kind": "…", "name": "…", "plot_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ], "area_ha": 1.5, "boundary_geo_json": "…", "branch_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "created_at": "2026-10-28T07:30:00Z", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "crop_type_name": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "latitude": 1.5, "longitude": 1.5, "owner_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "owner_name": "…", "parcel_code": "…", "status": "…", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_name": "…" } ], "page": 1, "page_size": 1, "page_size_capped": true, "total": 1 } ``` ### GET /api/v1/gis/parcels/summary Tổng hợp diện tích thửa theo hộ / vùng / cây Scope: gis.parcels.view - group_by (query, string) - zoneId (query, GUID) - cropTypeId (query, GUID) - status (query, string) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "estimated_area_ha": 1.5, "excluded_area_ha": 1.5, "farmer_count": 1, "implausible_count": 1, "key": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "parcel_count": 1, "parcels_without_boundary_count": 1, "total_area_ha": 1.5 } ], "page": 1, "page_size": 1, "page_size_capped": true, "total": 1 } ``` ### GET /api/v1/gis/parcels/{id} Chi tiết lô thửa Scope: gis.parcels.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "area_ha": 1.5, "boundary_geo_json": "…", "branch_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "created_at": "2026-10-28T07:30:00Z", "crop_type": { "category": "…", "code": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…" }, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "latitude": 1.5, "longitude": 1.5, "ndvi_history": [ { "ndvi_value": 1.5, "recorded_at": "2026-10-28" } ], "owner": { "full_name": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "phone": "…", "public_name_consent": true, "public_phone_consent": true }, "parcel_code": "…", "status": "…", "updated_at": "2026-10-28T07:30:00Z", "zone": { "code": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "zone_type": "…" } } ``` ### GET /api/v1/gis/parcels/{id}/contains-point Điểm GPS có nằm trong ranh thửa không Scope: gis.parcels.view - id (path, GUID, bắt buộc) - lat (query, int) - lng (query, int) Phản hồi mẫu: ```json { "inside": true, "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ``` ### GET /api/v1/gis/parcels/{id}/history Lịch sử thay đổi parcel Scope: gis.parcels.view - id (path, GUID, bắt buộc) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "change_type": "…", "changed_at": "2026-10-28T07:30:00Z", "changed_by": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "new_boundary_geo_json": "…", "notes": "…", "old_boundary_geo_json": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ], "page": 1, "page_size": 1, "page_size_capped": true, "total": 1 } ``` ### GET /api/v1/gis/parcels/{parcelId}/photos Danh sách ảnh của lô thửa Scope: gis.parcels.view - parcelId (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "content_type": "…", "created_at": "2026-10-28T07:30:00Z", "file_name": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "size_bytes": 1, "url": "…" } ] ``` ### GET /api/v1/gis/plots Danh sách khu của một thửa Scope: gis.parcels.view - parcel_id (query, GUID) Phản hồi mẫu: ```json [ { "area_m2": 1.5, "boundary_geo_json": "…", "created_at": "2026-10-28T07:30:00Z", "crop_category": "…", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "crop_type_name": "…", "crops": [ { "crop_category": "…", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "crop_type_name": "…", "estimated_tree_count": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "note": "…", "plot_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "role": "…", "row_spacing_m": 1.5, "tree_spacing_m": 1.5 } ], "custom_trees_geo_json": "…", "density_per1000_m2": 1.5, "estimated_tree_count": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "row_spacing_m": 1.5, "tree_spacing_m": 1.5 } ] ``` ### GET /api/v1/gis/plots/{id}/crops Các loài trồng trong khu (trồng xen) Scope: gis.parcels.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "crop_category": "…", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "crop_type_name": "…", "estimated_tree_count": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "note": "…", "plot_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "role": "…", "row_spacing_m": 1.5, "tree_spacing_m": 1.5 } ] ``` ### GET /api/v1/gis/search Tìm thửa, mùa vụ, nông dân, vùng Scope: tự lọc theo scope của khoá - q (query, string) - limit (query, int) Phản hồi mẫu: ```json { "farmers": [ { "code": "…", "edit_path": "…", "email": "…", "id": "…", "path": "…", "phone": "…", "subtitle": "…", "title": "…", "type": "…" } ], "parcels": [ { "code": "…", "edit_path": "…", "email": "…", "id": "…", "path": "…", "phone": "…", "subtitle": "…", "title": "…", "type": "…" } ], "seasons": [ { "code": "…", "edit_path": "…", "email": "…", "id": "…", "path": "…", "phone": "…", "subtitle": "…", "title": "…", "type": "…" } ], "zones": [ { "code": "…", "edit_path": "…", "email": "…", "id": "…", "path": "…", "phone": "…", "subtitle": "…", "title": "…", "type": "…" } ] } ``` ### GET /api/v1/gis/season-templates Danh sách quy trình mẫu Scope: gis.parcels.view - cropTypeId (query, GUID) Phản hồi mẫu: ```json [ { "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "rules": [ { "age_from": 1, "age_to": 1, "anchor": "…", "day": 1, "day_offset": 1, "doses": [ { "age_from": null, "age_to": null, "quantity_per_tree": null } ], "event": "…", "every_days": 1, "from_day": 1, "from_month": 1, "key": "…", "material": "…", "month": 1, "note": "…", "offset_days": 1, "task_type": "…", "title": "…", "to_day": 1, "to_month": 1, "unit": "…" } ], "stages": [ { "day_offset": 1, "name": "…" } ], "updated_at": "2026-10-28T07:30:00Z" } ] ``` ### GET /api/v1/gis/season-templates/{id} Một quy trình mẫu Scope: gis.parcels.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "rules": [ { "age_from": 1, "age_to": 1, "anchor": "…", "day": 1, "day_offset": 1, "doses": [ { "age_from": 1, "age_to": 1, "quantity_per_tree": 1.5 } ], "event": "…", "every_days": 1, "from_day": 1, "from_month": 1, "key": "…", "material": "…", "month": 1, "note": "…", "offset_days": 1, "task_type": "…", "title": "…", "to_day": 1, "to_month": 1, "unit": "…" } ], "stages": [ { "day_offset": 1, "name": "…" } ], "updated_at": "2026-10-28T07:30:00Z" } ``` ### GET /api/v1/gis/seasons Danh sách mùa vụ Scope: gis.parcels.view - parcelId (query, GUID) - year (query, int) Phản hồi mẫu: ```json [ { "branch_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "closed_on": "2026-10-28", "crop_name": "…", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "expected_harvest_on": "2026-10-28", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "kind": "…", "name": "…", "notes": "…", "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "planted_on": "2026-10-28", "plot_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "stages": [ { "day_offset": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "sort_order": 1 } ], "status": "…", "year": 1, "yield_kg": 1.5 } ] ``` ### GET /api/v1/gis/seasons/groups Nhóm vụ của một năm (cùng cây, cùng tên vụ) Scope: gis.parcels.view - year (query, int) - branchId (query, GUID) Phản hồi mẫu: ```json [ { "branch_ids": [ "0190a3c2-5b1e-7c3d-8e4f-000000000001" ], "crop_name": "…", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "end_date": "2026-10-28", "key": "…", "missing_end_count": 1, "name": "…", "parcel_count": 1, "season_count": 1, "start_date": "2026-10-28", "year": 1 } ] ``` ### GET /api/v1/gis/seasons/{id} Một mùa vụ theo mã Scope: gis.parcels.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "branch_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "closed_on": "2026-10-28", "crop_name": "…", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "expected_harvest_on": "2026-10-28", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "kind": "…", "name": "…", "notes": "…", "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "planted_on": "2026-10-28", "plot_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "stages": [ { "day_offset": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "sort_order": 1 } ], "status": "…", "year": 1, "yield_kg": 1.5 } ``` ### GET /api/v1/gis/zones Danh sách zones Scope: gis.zones.view - zone_type (query, string) - page (query, int) - page_size (query, int) - deleted (query, bool) - include_stats (query, bool) - branch_id (query, GUID) Phản hồi mẫu: ```json { "items": [ { "branch_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "code": "…", "contact_person": "…", "contact_phone": "…", "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "implausible_parcel_count": 1, "main_crop": "…", "name": "…", "parcel_area_ha": 1.5, "parcel_count": 1, "parent_zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "total_area_ha": 1.5, "zone_type": "…" } ], "page": 1, "page_size": 1, "page_size_capped": true, "total": 1 } ``` ### GET /api/v1/gis/zones/{id} Chi tiết zone Scope: gis.zones.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "boundary_geo_json": "…", "branch_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "code": "…", "contact_person": "…", "contact_phone": "…", "created_at": "2026-10-28T07:30:00Z", "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "main_crop": "…", "name": "…", "parent_zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "parent_zone_name": "…", "total_area_ha": 1.5, "updated_at": "2026-10-28T07:30:00Z", "zone_type": "…" } ``` ### GET /api/v1/gis/zones/{id}/stats Thống kê zone Scope: gis.zones.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "active_parcel_count": 1, "disputed_parcel_count": 1, "excluded_area_ha": 1.5, "implausible_parcel_count": 1, "inactive_parcel_count": 1, "parcel_count": 1, "total_area_ha": 1.5, "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_name": "…" } ``` ## IoT (chỉ đọc) (/api/v1/iot) Thiết bị, số đo cảm biến, cảnh báo, lịch tưới. API key không có API điều khiển thiết bị. ### GET /api/v1/iot/alert-rules Danh sách rule cảnh báo Scope: iot.alerts.view - deviceId (query, GUID) Phản hồi mẫu: ```json [ { "consecutive_breach_count": 1, "cooldown_hours": 1, "device_code": "…", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "device_name": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "is_active": true, "sensor_type": "…", "threshold_max": 1.5, "threshold_min": 1.5 } ] ``` ### GET /api/v1/iot/alerts Danh sách cảnh báo Scope: iot.alerts.view - deviceId (query, GUID) - status (query, string) - severity (query, string) - from (query, date) - to (query, date) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "acknowledged_at": "2026-10-28T07:30:00Z", "device_code": "…", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "resolved_at": "2026-10-28T07:30:00Z", "rule_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "sensor_type": "…", "severity": "…", "status": "…", "threshold": "…", "triggered_at": "2026-10-28T07:30:00Z", "value": 1.5 } ], "page": 1, "page_size": 1, "total": 1 } ``` ### GET /api/v1/iot/commands Danh sách lệnh thiết bị Scope: iot.devices.view - deviceId (query, GUID) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "acknowledged_at": "2026-10-28T07:30:00Z", "command_type": "…", "device_code": "…", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "payload": "…", "sent_at": "2026-10-28T07:30:00Z", "sent_by_user_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "status": "…" } ], "page": 1, "page_size": 1, "total": 1 } ``` ### GET /api/v1/iot/devices Danh sách thiết bị IoT Scope: iot.devices.view - status (query, string) - page (query, int) - pageSize (query, int) - parcelId (query, GUID) - unassigned (query, bool) - zoneId (query, GUID) Phản hồi mẫu: ```json { "items": [ { "created_at": "2026-10-28T07:30:00Z", "device_code": "…", "device_type": "…", "dong_ho_nuoc": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "is_online": true, "last_heartbeat": "2026-10-28T07:30:00Z", "location_lat": 1.5, "location_lng": 1.5, "mac_address": "…", "manual_max_min": 1, "mcv_valve_variant": "…", "mqtt_topic_prefix": "…", "name": "…", "niche": "…", "parcel_ids": [ "0190a3c2-5b1e-7c3d-8e4f-000000000001" ], "provision_status": "…", "scene_kind": "…", "shared_in_zone": true, "status": "…", "tran_thoi_luong": "…", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ], "page": 1, "page_size": 1, "total": 1 } ``` ### GET /api/v1/iot/devices/control-states Trạng thái điều khiển nhiều thiết bị (màn hình nhiều nhà) Scope: iot.devices.view - zoneId (query, GUID) Phản hồi mẫu: ```json [ { "catalog": [ { "key": "…", "kind": "…", "label": "…", "valid_values": [ 1 ] } ], "device_code": "…", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "device_outputs": [ { "auto_mode": 1, "family": "…", "index": 1, "key": "…", "kind": "…", "label": "…", "mcv_actual": 1, "mcv_set": 1, "mcv_stat": 1, "port": 1, "role": "…", "state": 1, "type": "…", "valid_values": [ 1 ] } ], "fert1_open": true, "fert2_open": true, "fert_open_mask": 1, "fert_pump_lock": 1, "fert_pump_run": 1, "fert_pump_suy_ra": 1, "fert_pump_why": 1, "flow_lmin": 1.5, "is_gateway_firmware": true, "is_online": true, "mcv_run": null, "outputs": 1, "pump_flushing": true, "pump_irrigating": true, "pump_mode": 1, "pump_on": true, "reported_at": "2026-10-28T07:30:00Z", "shade": 1, "shade_closing": true, "shade_opening": true, "vol_cur_ml": 1 } ] ``` ### GET /api/v1/iot/devices/{deviceId}/control-state Trạng thái điều khiển thực tế của 1 thiết bị Scope: iot.devices.view - deviceId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "catalog": [ { "key": "…", "kind": "…", "label": "…", "valid_values": [ 1 ] } ], "device_code": "…", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "device_outputs": [ { "auto_mode": 1, "family": "…", "index": 1, "key": "…", "kind": "…", "label": "…", "mcv_actual": 1, "mcv_set": 1, "mcv_stat": 1, "port": 1, "role": "…", "state": 1, "type": "…", "valid_values": [ 1 ] } ], "fert1_open": true, "fert2_open": true, "fert_open_mask": 1, "fert_pump_lock": 1, "fert_pump_run": 1, "fert_pump_suy_ra": 1, "fert_pump_why": 1, "flow_lmin": 1.5, "is_gateway_firmware": true, "is_online": true, "mcv_run": null, "outputs": 1, "pump_flushing": true, "pump_irrigating": true, "pump_mode": 1, "pump_on": true, "reported_at": "2026-10-28T07:30:00Z", "shade": 1, "shade_closing": true, "shade_opening": true, "vol_cur_ml": 1 } ``` ### GET /api/v1/iot/devices/{deviceId}/display-prefs GET /api/v1/iot/devices/{deviceId}/display-prefs Scope: iot.devices.view - deviceId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "control_sensors": [ "…" ], "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ``` ### GET /api/v1/iot/devices/{deviceId}/fert-meters Đồng hồ các bồn phân: chỗ cắm, hệ số bo đang dùng, và số đếm xung thô mới nhất Scope: iot.devices.view - deviceId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "channels": [ { "chu_ky_ly_do": "…", "chu_ky_server_tinh": { "lit": 1.5, "moc_tai": "2026-10-28T07:30:00Z", "xung_tu_dau_cu": 1 }, "cu_truoc_lit": 1.5, "dong_bo": { "da_gui": 1, "echo_luc": "2026-10-28T07:30:00Z", "gui_luc": "2026-10-28T07:30:00Z", "trang_thai": "…" }, "idx": 1, "meter_id": 1, "meter_in": 1, "meter_ulp": 1, "meter_ulp_operator": 1, "meter_ulp_operator_at": "2026-10-28T07:30:00Z", "pulse": 1, "server_tinh": { "lit": 1.5, "ly_do": "…", "nguon": "…", "ulp_da_dung": 1 } } ], "chu_ky_bat_dau_luc": "2026-10-28T07:30:00Z", "device_code": "…", "flow_pulse": 1, "muc_tieu_lit_chu_ky": 1, "pulse_at": "2026-10-28T07:30:00Z", "pulse_ml": 1, "pulse_ul": 1, "water_chu_ky_server_tinh": { "lit": 1.5, "ly_do": "…", "nguon": "…", "ulp_da_dung": 1 }, "water_server_tinh": { "lit": 1.5, "ly_do": "…", "nguon": "…", "ulp_da_dung": 1 }, "water_ulp_operator": 1, "water_ulp_operator_at": "2026-10-28T07:30:00Z" } ``` ### GET /api/v1/iot/devices/{deviceId}/output-config/channels Danh sách kênh ngõ ra đã cấu hình (đọc bản backend đã persist từ echo dev_cfg — không phụ thuộc realtime) Scope: iot.devices.view - deviceId (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "auto_mode": 1, "eh": 1, "em": 1, "idx": 1, "kind": 1, "label": "…", "manual_max_min": 1, "online": true, "out_a": 1, "out_b": 1, "reported_at": "2026-10-28T07:30:00Z", "role": "…", "sh": 1, "sm": 1, "thr_off": 1, "thr_on": 1 } ] ``` ### GET /api/v1/iot/devices/{deviceId}/output-config/irrigation Cấu hình hệ tưới đã gán: bơm/van vùng/van phân bón kể cả van Denso (đọc bản backend đã persist từ echo irr_out — không phụ thuộc realtime) Scope: iot.devices.view - deviceId (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "idx": 1, "kind": "…", "label": "…", "manual_max_min_bo": 1, "mcv_id": 1, "meter_echo_at": "2026-10-28T07:30:00Z", "meter_id": 1, "meter_in": 1, "meter_ulp": 1, "meter_ulp_operator": 1, "meter_ulp_operator_at": "2026-10-28T07:30:00Z", "meter_ulp_pushed": 1, "meter_ulp_pushed_at": "2026-10-28T07:30:00Z", "out": 1, "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "role": "…", "updated_at": "2026-10-28T07:30:00Z" } ] ``` ### GET /api/v1/iot/devices/{deviceId}/output-config/mcv-cfg Trạng thái đồng bộ cài đặt van Denso: giá trị đã đặt (desired), bo đang giữ (reported, từ echo dev_cfg), ack và 1 trong 8 trạng thái Scope: iot.devices.view - deviceId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "ack": { "applied": { "count": 1, "count_max": 1, "ec_cap": 1.5, "ec_on": true, "ec_target": 1.5, "enabled": true, "fert": [ 1 ], "k_ul": [ 1 ], "liters": 1.5, "pump_fb": 1, "ratio": [ 1 ], "types": [ 1 ], "v": 1, "wait_min": 1 }, "applied_at": "…", "detail": "…", "err": "…", "ok": true, "seq": 1, "ts": 1 }, "desired": { "count": 1, "count_max": 1, "ec_cap": 1.5, "ec_on": true, "ec_target": 1.5, "enabled": true, "fert": [ 1 ], "k_ul": [ 1 ], "liters": 1.5, "pump_fb": 1, "ratio": [ 1 ], "types": [ 1 ], "v": 1, "wait_min": 1 }, "echo_at": "2026-10-28T07:30:00Z", "pushed_at": "2026-10-28T07:30:00Z", "reported": { "count": 1, "count_max": 1, "ec_cap": 1.5, "ec_on": true, "ec_target": 1.5, "enabled": true, "fert": [ 1 ], "k_ul": [ 1 ], "liters": 1.5, "pump_fb": 1, "ratio": [ 1 ], "types": [ 1 ], "v": 1, "wait_min": 1 }, "seq": 1, "state": "…" } ``` ### GET /api/v1/iot/devices/{deviceId}/output-config/summary Gói tóm tắt cấu hình ngõ ra server đã NHỚ (free_ports…) — trả JSON thô, không phụ thuộc realtime (hết chập chờn). 204 nếu server chưa từng nhận. Scope: iot.devices.view - deviceId (path, GUID, bắt buộc) ### GET /api/v1/iot/devices/{deviceId}/schedules Danh sách lịch hẹn giờ của thiết bị Scope: iot.devices.view - deviceId (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "duration_minutes": 1, "duration_seconds": 1, "enabled": true, "end_date": "2026-10-28", "family": "…", "field": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "idx": 1, "label": "…", "last_fired_on": "2026-10-28", "name": "…", "note": "…", "start_date": "2026-10-28", "stop_mode": "…", "target_liters": 1, "time_of_day": "…", "value": 1, "weekdays": 1, "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ] ``` ### GET /api/v1/iot/devices/{deviceId}/setpoints GET /api/v1/iot/devices/{deviceId}/setpoints Scope: iot.devices.view - deviceId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "alarm_temp": 1, "auto_fan": 1, "auto_shade": 1, "auto_winch1": 1, "auto_winch2": 1, "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "fert1_rate": 1, "fert2_rate": 1, "flow_pulse_ml": 1, "mcv1": 1, "mcv2": 1, "mcv3": 1, "mcv4": 1, "mcv5": 1, "mcv6": 1, "sched_s_eh": 1, "sched_s_em": 1, "sched_s_sh": 1, "sched_s_sm": 1, "sched_w_eh": 1, "sched_w_em": 1, "sched_w_sh": 1, "sched_w_sm": 1, "thresh_fan_hum_off": 1, "thresh_fan_hum_on": 1 } ``` ### GET /api/v1/iot/devices/{id} Chi tiết thiết bị IoT — đầy đủ fields cho 4-tab detail page Scope: iot.devices.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "created_at": "2026-10-28T07:30:00Z", "customer_code": "…", "description": "…", "device_code": "…", "device_type": "…", "dong_ho_nuoc": "…", "firmware_version": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "installed_at": "2026-10-28", "is_online": true, "last_heartbeat": "2026-10-28T07:30:00Z", "location_lat": 1.5, "location_lng": 1.5, "mac_address": "…", "manual_max_min": 1, "mcv_valve_variant": "…", "metadata_json": "…", "mqtt_topic_prefix": "…", "name": "…", "niche": "…", "parcel_ids": [ "0190a3c2-5b1e-7c3d-8e4f-000000000001" ], "provision_status": "…", "provisioned_at": "2026-10-28T07:30:00Z", "scene_kind": "…", "shared_in_zone": true } ``` ### GET /api/v1/iot/emergency-stop Khoá dừng khẩn đang hiệu lực (activeOnly=true, mặc định) hoặc 20 lần gần nhất Scope: iot.devices.view - activeOnly (query, bool) Phản hồi mẫu: ```json [ { "active": true, "already_active": true, "device_ids": [ "0190a3c2-5b1e-7c3d-8e4f-000000000001" ], "failed_count": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "reason": "…", "release_note": "…", "released_at": "2026-10-28T07:30:00Z", "released_by": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "repeat_count": 1, "results": [ { "command_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "device_code": "…", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "error": "…", "error_code": "…", "family": "…", "idx": 1, "label": "…", "ok": true } ], "scope": "…", "stopped_schedules": 1, "triggered_at": "2026-10-28T07:30:00Z", "triggered_by": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ] ``` ### GET /api/v1/iot/reports/irrigation-logs Danh sách lịch sử tưới (phân trang) Scope: iot.telemetry.view - deviceId (query, GUID) - zoneId (query, GUID) - from (query, date) - to (query, date) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "cycle_at": "2026-10-28T07:30:00Z", "device_code": "…", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "duration_sec": 1, "fert_mask": 1, "fert_ml": [ 1 ], "fert_sec": 1, "trigger": 1, "trigger_source": "…", "volume_l": 1.5, "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_mask": 1 } ], "page": 1, "page_size": 1, "total": 1 } ``` ### GET /api/v1/iot/reports/irrigation-summary Tổng hợp báo cáo tưới tiêu Scope: iot.telemetry.view - deviceId (query, GUID) - from (query, date) - to (query, date) Phản hồi mẫu: ```json { "command_count": 1, "from": "2026-10-28T07:30:00Z", "success_command_count": 1, "success_rate": 1.5, "to": "2026-10-28T07:30:00Z", "total_volume_l": 1.5 } ``` ### GET /api/v1/iot/reports/irrigation-summary/export Xuất báo cáo tưới tiêu ra Excel Scope: iot.telemetry.view - deviceId (query, GUID) - from (query, date) - to (query, date) ### GET /api/v1/iot/schedules/upcoming Các cữ tưới sắp chạy của cả trại (gộp theo cữ) Scope: iot.devices.view - from (query, date) - days (query, int) - device_id (query, GUID) Phản hồi mẫu: ```json { "items": [ { "anchor_schedule_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "date": "2026-10-28", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "device_name": "…", "duration_seconds": 1, "exception_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "fired": true, "name": "…", "run_once": true, "skipped": true, "time": "…", "valves": [ "…" ] } ] } ``` ### GET /api/v1/iot/search Tìm thiết bị Scope: iot.devices.view - q (query, string) - limit (query, int) Phản hồi mẫu: ```json { "devices": [ { "code": "…", "edit_path": "…", "email": "…", "id": "…", "path": "…", "phone": "…", "subtitle": "…", "title": "…", "type": "…" } ] } ``` ### GET /api/v1/iot/sensor-readings Tra cứu số liệu cảm biến theo khoảng thời gian Scope: iot.telemetry.view - deviceId (query, GUID) - zoneId (query, GUID) - from (query, date) - to (query, date) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "activity_pct": 1.5, "air_humidity_pct": 1.5, "air_humidity_pct1": 1.5, "air_humidity_pct2": 1.5, "air_humidity_pct3": 1.5, "air_temp_c": 1.5, "air_temp_c1": 1.5, "air_temp_c2": 1.5, "air_temp_c3": 1.5, "co2_ppm": 1.5, "device_code": "…", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "do_mg_l": 1.5, "ec_us_cm": 1.5, "feed_level_pct1": 1.5, "feed_level_pct2": 1.5, "feed_weight_kg1": 1.5, "feed_weight_kg2": 1.5, "fert_ec_us_cm": 1.5, "fert_ph": 1.5, "fw_ver": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "light_lux": 1.5, "nh3_ppm": 1.5, "orp_mv": 1.5 } ], "page": 1, "page_size": 1, "total": 1 } ``` ### GET /api/v1/iot/sensor-readings/latest Giá trị mới nhất của từng thiết bị (30 phút gần nhất) Scope: iot.telemetry.view - zoneId (query, GUID) Phản hồi mẫu: ```json [ { "activity_pct": 1.5, "air_humidity_pct": 1.5, "air_humidity_pct1": 1.5, "air_humidity_pct2": 1.5, "air_humidity_pct3": 1.5, "air_temp_c": 1.5, "air_temp_c1": 1.5, "air_temp_c2": 1.5, "air_temp_c3": 1.5, "co2_ppm": 1.5, "device_code": "…", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "do_mg_l": 1.5, "ec_us_cm": 1.5, "feed_level_pct1": 1.5, "feed_level_pct2": 1.5, "feed_weight_kg1": 1.5, "feed_weight_kg2": 1.5, "fert_ec_us_cm": 1.5, "fert_ph": 1.5, "fw_ver": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "light_lux": 1.5, "nh3_ppm": 1.5, "orp_mv": 1.5 } ] ``` ### GET /api/v1/iot/setup/progress Tiến trình onboarding thiết bị IoT suy từ dữ liệu hiện có Scope: iot.devices.view Phản hồi mẫu: ```json { "completed": 1, "steps": [ { "count": 1, "detail": "…", "done": true, "key": "…", "warning": "…" } ], "total": 1 } ``` ## Thu hoạch & truy xuất (/api/v1/harvest) Bản ghi thu hoạch, cách ly thuốc, lô truy xuất, tem QR, chứng nhận, giá thị trường. ### GET /api/v1/harvest/certifications Danh sách chứng nhận của workspace Scope: harvest.certification.view Phản hồi mẫu: ```json [ { "certificate_number": "…", "expires_in_days": 1, "expiry_date": "2026-10-28", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "is_expired": true, "issued_by": "…", "issued_date": "2026-10-28", "name": "…", "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "scheme": "…" } ] ``` ### GET /api/v1/harvest/certifications/expiring Chứng nhận sắp hết hạn Scope: harvest.certification.view - within_days (query, int) - include_expired (query, bool) Phản hồi mẫu: ```json [ { "certificate_number": "…", "expires_in_days": 1, "expiry_date": "2026-10-28", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "is_expired": true, "issued_by": "…", "issued_date": "2026-10-28", "name": "…", "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "scheme": "…" } ] ``` ### GET /api/v1/harvest/harvest-records Danh sách bản ghi thu hoạch Scope: harvest.harvest.view - status (query, string) - page (query, int) - pageSize (query, int) - filter_rules (query, string) Phản hồi mẫu: ```json { "items": [ { "actual_quantity_kg": 1.5, "created_at": "2026-10-28T07:30:00Z", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "crop_type_name": "…", "declared_quantity_kg": 1.5, "farming_task_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "harvest_date": "2026-10-28", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "needs_review": true, "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "return_reason": "…", "returned_at": "2026-10-28T07:30:00Z", "season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "status": "…", "trace_anchor_date": "2026-10-28", "trace_bundle_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "trace_incident": true, "trace_status": "…" } ], "page": 1, "page_size": 1, "total": 1 } ``` ### POST /api/v1/harvest/harvest-records Khai báo thu hoạch Scope: harvest.harvest.create - confirm (body, bool) - crop_type_id (body, GUID) - declared_quantity_kg (body, int) - farming_task_id (body, GUID) - harvest_date (body, date) - notes (body, string) - organisms (body, object) - parcel_id (body, GUID) - phi_override_reason (body, string) - photo_urls (body, object) - season_id (body, GUID) Phản hồi mẫu: ```json { "created_at": "2026-10-28T07:30:00Z", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "crop_type_name": "…", "declared_quantity_kg": 1.5, "farming_task_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "harvest_date": "2026-10-28", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "needs_review": true, "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "return_reason": "…", "returned_at": "2026-10-28T07:30:00Z", "season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "status": "…", "trace_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "warnings": [ "…" ] } ``` ### GET /api/v1/harvest/harvest-records/preview-trace Kiểm tra trước khi khoá (D2): bó truy xuất sẽ nhận những gì Scope: harvest.harvest.view - parcelId (query, GUID) - seasonId (query, GUID) Phản hồi mẫu: ```json { "certification_expiry": "2026-10-28", "certification_name": "…", "certification_number": "…", "has_season": true, "input_count": 1, "parcel_has_boundary": true, "season_name": "…", "stage_count": 1, "stages_with_photo": 1 } ``` ### GET /api/v1/harvest/harvest-records/summary Tổng hợp sản lượng theo hộ / vùng / cây / thửa Scope: harvest.harvest.view - group_by (query, string) - from (query, date) - to (query, date) - zoneId (query, GUID) - cropTypeId (query, GUID) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "key": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "parcel_count": 1, "record_count": 1, "total_kg": 1.5 } ], "page": 1, "page_size": 1, "total": 1 } ``` ### GET /api/v1/harvest/harvest-records/{id} Chi tiết bản ghi thu hoạch Scope: harvest.harvest.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "actual_quantity_kg": 1.5, "created_at": "2026-10-28T07:30:00Z", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "crop_type_name": "…", "declared_quantity_kg": 1.5, "farming_task_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "harvest_date": "2026-10-28", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "needs_review": true, "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "return_reason": "…", "returned_at": "2026-10-28T07:30:00Z", "season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "status": "…", "trace_anchor_date": "2026-10-28", "trace_bundle_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "trace_incident": true, "trace_status": "…" } ``` ### PUT /api/v1/harvest/harvest-records/{id} Cập nhật khai báo thu hoạch Scope: harvest.harvest.create - id (path, GUID, bắt buộc) - declared_quantity_kg (body, int) - harvest_date (body, date) - notes (body, string) - phi_override_reason (body, string) Phản hồi mẫu: ```json { "created_at": "2026-10-28T07:30:00Z", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "crop_type_name": "…", "declared_quantity_kg": 1.5, "farming_task_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "harvest_date": "2026-10-28", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "needs_review": true, "parcel_code": "…", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "return_reason": "…", "returned_at": "2026-10-28T07:30:00Z", "season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "status": "…", "trace_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "warnings": [ "…" ] } ``` ### GET /api/v1/harvest/market-prices Danh sách giá đã ghi nhận Scope: harvest.market-prices.view - commodityCode (query, object) - market (query, string) - fromDate (query, date) - toDate (query, date) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "items": [ { "commodity_code": "…", "commodity_name": "…", "created_at": "2026-10-28T07:30:00Z", "grade": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "market": "…", "note": "…", "recorded_by_name": "…", "recorded_on": "2026-10-28", "source": "…", "unit": "…", "unit_price": 1.5 } ], "page": 1, "page_size": 1, "total": 1 } ``` ### GET /api/v1/harvest/market-prices/series Chuỗi giá theo thời gian để vẽ biểu đồ Scope: harvest.market-prices.view - commodityCode (query, object) - fromDate (query, date) - toDate (query, date) - unit (query, string) Phản hồi mẫu: ```json { "series": [ { "commodity_code": "…", "commodity_name": "…", "points": [ { "recorded_on": "2026-10-28", "unit_price": 1.5 } ], "unit": "…" } ], "unit": "…" } ``` ### GET /api/v1/harvest/market-prices/{id} Chi tiết một bản ghi giá Scope: harvest.market-prices.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "commodity_code": "…", "commodity_name": "…", "created_at": "2026-10-28T07:30:00Z", "grade": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "market": "…", "note": "…", "recorded_by_name": "…", "recorded_on": "2026-10-28", "source": "…", "unit": "…", "unit_price": 1.5 } ``` ### GET /api/v1/harvest/parcels/phi-active Các thửa đang trong thời gian cách ly Scope: harvest.harvest.view - on (query, date) Phản hồi mẫu: ```json [ { "active": true, "blocking": { "active_ingredient": "…", "field_log_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "kind": "…", "phi_days": 1, "product": "…", "safe_from": "2026-10-28", "sprayed_on": "2026-10-28" }, "on_date": "2026-10-28", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "safe_from": "2026-10-28" } ] ``` ### GET /api/v1/harvest/parcels/{parcelId}/phi-status Thửa có đang trong thời gian cách ly không Scope: harvest.harvest.view - parcelId (path, GUID, bắt buộc) - on (query, date) Phản hồi mẫu: ```json { "active": true, "blocking": { "active_ingredient": "…", "field_log_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "kind": "…", "phi_days": 1, "product": "…", "safe_from": "2026-10-28", "sprayed_on": "2026-10-28" }, "on_date": "2026-10-28", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "safe_from": "2026-10-28" } ``` ### GET /api/v1/harvest/product-infos Danh sách hồ sơ sản phẩm Scope: harvest.trace.view Phản hồi mẫu: ```json [ { "benefits": [ { "description": "…", "title": "…" } ], "crop_name": "…", "display_name": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "image_urls": [ "…" ], "is_active": true, "tagline": "…", "updated_at": "2026-10-28T07:30:00Z", "usage_note": "…", "workspace_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ] ``` ### GET /api/v1/harvest/qr-codes Danh sách mã QR của 1 mùa vụ Scope: harvest.qr-codes.view - seasonId (query, GUID) Phản hồi mẫu: ```json [ { "code": "…", "created_at": "2026-10-28T07:30:00Z", "effective_to": "2026-10-28", "first_scanned_at": "2026-10-28T07:30:00Z", "harvest_date": "2026-10-28", "harvest_record_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "last_scanned_at": "2026-10-28T07:30:00Z", "scan_count": 1, "season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ] ``` ### GET /api/v1/harvest/qr-codes/usage Số tem đã dùng / trần của gói Miễn phí (5 tem tổng / workspace) Scope: harvest.qr-codes.view Phản hồi mẫu: ```json { "limit": 1, "remaining": 1, "used": 1 } ``` ### GET /api/v1/harvest/qr-codes/{id}/scans Lượt quét theo ngày của một mã QR (Q6) Scope: harvest.qr-codes.view - id (path, GUID, bắt buộc) - days (query, int) Phản hồi mẫu: ```json { "code": "…", "days": [ { "day": "2026-10-28", "visitors": 1 } ], "first_scanned_at": "2026-10-28T07:30:00Z", "last_scanned_at": "2026-10-28T07:30:00Z", "qr_code_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "scan_count": 1, "warn_threshold": 1 } ``` ### GET /api/v1/harvest/search Tìm bản ghi thu hoạch, tem QR, chứng nhận Scope: tự lọc theo scope của khoá - q (query, string) - limit (query, int) Phản hồi mẫu: ```json { "certifications": [ { "code": "…", "edit_path": "…", "email": "…", "id": "…", "path": "…", "phone": "…", "subtitle": "…", "title": "…", "type": "…" } ], "harvests": [ { "code": "…", "edit_path": "…", "email": "…", "id": "…", "path": "…", "phone": "…", "subtitle": "…", "title": "…", "type": "…" } ], "qr_codes": [ { "code": "…", "edit_path": "…", "email": "…", "id": "…", "path": "…", "phone": "…", "subtitle": "…", "title": "…", "type": "…" } ] } ``` ### GET /api/v1/harvest/trace Danh sách TraceBundle của workspace (yêu cầu đăng nhập) Scope: harvest.trace.view Phản hồi mẫu: ```json [ { "anchor_date": "2026-10-28", "anchor_is_backfill": true, "anchor_status": "…", "bundle_version": 1, "certification": "…", "content_hash": "…", "created_at": "2026-10-28T07:30:00Z", "farmer": "…", "harvest_date": "2026-10-28", "harvest_record_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "has_open_incident": true, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "is_locked": true, "parcel_code": "…", "product": "…", "season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "stage_count": 1, "zone_name": "…" } ] ``` ### GET /api/v1/harvest/trace-anchors Lượt neo có lô của workspace (D7) + lô đã niêm phong chờ lượt tới Scope: harvest.trace.view - take (query, int) Phản hồi mẫu: ```json { "anchor_schedule_local": "…", "anchors": [ { "anchor_date": "2026-10-28", "attempts": 1, "bitcoin_block_height": 1, "bitcoin_confirmed_at": "2026-10-28T07:30:00Z", "bundles": [ { "bundle_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "content_hash": "…", "harvest_date": "2026-10-28", "harvest_record_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "parcel_code": "…", "product": "…", "version": 1 } ], "confirmed_at": "2026-10-28T07:30:00Z", "created_at": "2026-10-28T07:30:00Z", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "is_backfill": true, "leaf_count": 1, "merkle_root": "…", "polygon_chain_id": 1, "polygon_confirmed_at": "2026-10-28T07:30:00Z", "polygon_tx_hash": "…", "status": "…", "submitted_at": "2026-10-28T07:30:00Z" } ], "pending": [ { "bundle_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "parcel_code": "…", "product": "…", "sealed_at": "2026-10-28T07:30:00Z", "version": 1 } ] } ``` ### GET /api/v1/harvest/trace-certificate/{id} Chứng thư xác thực blockchain (PDF, D4) Scope: harvest.trace.view - id (path, GUID, bắt buộc) ### GET /api/v1/harvest/trace-incidents Sự cố lệch dữ liệu của workspace (D8) Scope: harvest.trace.view - status (query, string) Phản hồi mẫu: ```json [ { "anchored_hash": "…", "bundle_version": 1, "current_hash": "…", "detected_at": "2026-10-28T07:30:00Z", "diff_json": "…", "has_newer_version": true, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "kind": "…", "parcel_code": "…", "product": "…", "resolution": "…", "resolution_note": "…", "resolved_at": "2026-10-28T07:30:00Z", "restore_requested_at": "2026-10-28T07:30:00Z", "restore_requested_by": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "status": "…", "trace_bundle_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "workspace_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ] ``` ### GET /api/v1/harvest/trace-pdf/{code} Tải hồ sơ truy xuất PDF theo mã QR (YÊU CẦU ĐĂNG NHẬP) Scope: harvest.trace.view - code (path, string, bắt buộc) ### GET /api/v1/harvest/trace-seal-document/{id} Tải văn bản niêm phong (bản đầy đủ đã băm) — chỉ người của nhà Scope: harvest.trace.view - id (path, GUID, bắt buộc) ### GET /api/v1/harvest/trace-settings GET /api/v1/harvest/trace-settings Scope: harvest.trace.view Phản hồi mẫu: ```json { "scan_warn_threshold": 1 } ``` ## Sổ nông trại & công việc (/api/v1/office) Sổ tự dựng của trại (nhật ký, kho, chi phí…), lịch, luật tự động hoá: đọc bảng, đọc/thêm/sửa dòng. ### GET /api/v1/office/apps Liệt kê app (phân trang) Scope: office.base.record.view - page (query, int) - pageSize (query, int) - search (query, string) Phản hồi mẫu: ```json { "hasNextPage": true, "hasPreviousPage": true, "items": [ { "createdAt": "2026-10-28T07:30:00Z", "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "updatedAt": "2026-10-28T07:30:00Z" } ], "page": 1, "pageSize": 1, "totalCount": 1, "totalPages": 1 } ``` ### GET /api/v1/office/apps/{appId}/dashboards Liệt kê bảng điều khiển của một ứng dụng Scope: office.base.record.view - appId (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "appId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "createdAt": "2026-10-28T07:30:00Z", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "layout": { "filters": [ { "id": "…", "kind": "…", "label": "…", "targets": [ null ] } ], "widgets": [ { "aggregation": "…", "columns": [ null ], "compare": "…", "dateField": "…", "dateGrouping": "…", "filter": { "conditions": null, "conjunction": null, "groups": null, "hasCriteria": null }, "format": { "decimals": null, "prefix": null, "style": null, "suffix": null }, "groupField": "…", "h": 1, "id": "…", "limit": 1, "orientation": "…", "seriesField": "…", "sort": "…", "stacked": true, "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "target": 1.5, "text": "…", "title": "…", "type": "…", "valueField": "…", "w": 1, "x": 1, "y": 1 } ] }, "name": "…", "position": 1, "updatedAt": "2026-10-28T07:30:00Z" } ] ``` ### GET /api/v1/office/apps/{appId}/tables Liệt kê table của app Scope: office.base.record.view - appId (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "appId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "position": 1 } ] ``` ### GET /api/v1/office/apps/{appId}/tables/{tableId} Lấy chi tiết table Scope: office.base.record.view - appId (path, GUID, bắt buộc) - tableId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "appId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "position": 1 } ``` ### GET /api/v1/office/apps/{id} Lấy chi tiết app Scope: office.base.record.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "createdAt": "2026-10-28T07:30:00Z", "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "updatedAt": "2026-10-28T07:30:00Z" } ``` ### GET /api/v1/office/automation-logs Nhật ký chạy, mới nhất trước Scope: office.automation.view - automationId (query, GUID) - result (query, string) - page (query, int) - pageSize (query, int) - enrollmentId (query, GUID) Phản hồi mẫu: ```json { "items": [ { "automationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "detail": "…", "detailKey": "…", "enrollmentId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "result": "…", "runAt": "2026-10-28T07:30:00Z", "stepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "subjectId": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ], "page": 1, "pageSize": 1, "total": 1 } ``` ### GET /api/v1/office/automation-logs/metrics Chỉ số vận hành của Tự động hoá trong công ty Scope: office.automation.view - days (query, int) Phản hồi mẫu: ```json { "activeRuns": 1, "days": 1, "dueRuns": 1, "failed": 1, "failureRate": 1.5, "oldestDueSeconds": 1, "quota": { "limit": 1, "month": "…", "percent": 1.5, "used": 1 }, "quotaBlocked": 1, "skipped": 1, "success": 1, "total": 1 } ``` ### GET /api/v1/office/automation-logs/runs Lịch sử theo LƯỢT CHẠY, mới nhất trước Scope: office.automation.view - automationId (query, GUID) - page (query, int) - pageSize (query, int) - result (query, string) - createdBy (query, string) - from (query, date) - to (query, date) - search (query, string) Phản hồi mẫu: ```json { "items": [ { "automationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "automationName": "…", "endedAt": "2026-10-28T07:30:00Z", "enrollmentId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "failed": 1, "result": "…", "skipped": 1, "startedAt": "2026-10-28T07:30:00Z", "status": "…", "steps": 1, "subjectId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "subjectName": "…", "succeeded": 1 } ], "page": 1, "pageSize": 1, "total": 1 } ``` ### GET /api/v1/office/automation-logs/stats Bốn ô số liệu trên đầu màn nhật ký Scope: office.automation.view - automationId (query, GUID) - days (query, int) Phản hồi mẫu: ```json { "failed": 1, "skipped": 1, "success": 1, "total": 1 } ``` ### GET /api/v1/office/automation-templates Danh sách mẫu của công ty, mới nhất trước Scope: office.automation.view Phản hồi mẫu: ```json [ { "createdAt": "2026-10-28T07:30:00Z", "createdBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "sourceAutomationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "stepCount": 1, "steps": [ { "actionType": "…", "stepType": "…" } ], "triggerConfig": "…", "triggerType": "…" } ] ``` ### GET /api/v1/office/automations Danh sách automation Scope: office.automation.view - page (query, int) - pageSize (query, int) - search (query, string) - status (query, string) - triggerType (query, string) - createdBy (query, string) - tableId (query, GUID) - actionType (query, string) - sort (query, string) - failing (query, bool) Phản hồi mẫu: ```json { "hasNextPage": true, "hasPreviousPage": true, "items": [ { "createdBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "createdById": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "createdByName": "…", "description": "…", "failed7d": 1, "graphVersion": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "lastRunAt": "2026-10-28T07:30:00Z", "name": "…", "parentAutomationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "parentStepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "runs7d": 1, "source": "…", "status": "…", "stepActionTypes": [ "…" ], "stepCount": 1, "steps": [ { "actionType": "…", "branchIndex": 1, "branchLabel": "…", "conditionConfig": "…", "config": "…", "falseNextStepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "groupId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "groupKind": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "loopAutomationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "nextStepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "order": 1, "splitRatio": 1, "stepType": "…", "trueNextStepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "waitConfig": "…" } ], "triggerConfig": "…", "triggerType": "…", "updatedAt": "2026-10-28T07:30:00Z" } ], "page": 1, "pageSize": 1, "totalCount": 1, "totalPages": 1 } ``` ### GET /api/v1/office/automations/ai-columns/catalog Danh mục chế độ của cột AI Scope: office.automation.view Phản hồi mẫu: ```json { "available": true, "modes": [ { "labelKey": "…", "mode": "…", "needsOptions": true, "needsRange": true } ] } ``` ### GET /api/v1/office/automations/catalog Catalog trigger/action khả dụng để dựng automation Scope: office.automation.view Phản hồi mẫu: ```json { "actions": [ { "configFields": [ { "description": "…", "input": "…", "label": "…", "name": "…", "options": [ null ], "optionsSource": "…", "required": true, "type": "…" } ], "description": "…", "group": "…", "label": "…", "type": "…" } ], "steps": [ { "configFields": [ { "description": "…", "input": "…", "label": "…", "name": "…", "options": [ null ], "optionsSource": "…", "required": true, "type": "…" } ], "description": "…", "group": "…", "label": "…", "type": "…" } ], "triggers": [ { "configFields": [ { "description": "…", "input": "…", "label": "…", "name": "…", "options": [ null ], "optionsSource": "…", "required": true, "type": "…" } ], "description": "…", "group": "…", "label": "…", "type": "…" } ] } ``` ### GET /api/v1/office/automations/merge-fields Danh mục biến {{…}} chèn được vào cấu hình hành động Scope: office.automation.view - tableId (query, GUID) - triggerType (query, string) - automationId (query, GUID) - stepId (query, GUID) - eventType (query, string) Phản hồi mẫu: ```json [ { "fields": [ { "example": "…", "label": "…", "token": "…" } ], "label": "…", "scope": "…", "stepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ] ``` ### GET /api/v1/office/automations/quota Hạn mức lượt chạy tháng của công ty Scope: office.automation.view Phản hồi mẫu: ```json { "limit": 1, "month": "…", "percent": 1.5, "used": 1 } ``` ### GET /api/v1/office/automations/service-events Danh mục sự kiện của phần mềm khác (kích hoạt service_event) Scope: office.automation.view Phản hồi mẫu: ```json [ { "events": [ { "code": "…", "keys": [ { "key": null, "label": null, "token": null, "type": null } ], "label": "…", "perMinuteCap": 1, "requiresFilter": true } ], "label": "…", "service": "…" } ] ``` ### GET /api/v1/office/automations/suggestions Gợi ý tự động hoá riêng cho công ty (tab Gợi ý) Scope: office.automation.view Phản hồi mẫu: ```json [ { "actionType": "…", "eventType": "…", "evidenceCount": 1, "evidenceDays": 1, "existingAutomationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "existingAutomationName": "…", "existingFailed": 1, "existingRuns": 1, "existingStatus": "…", "fieldId": "…", "fieldName": "…", "kind": "…", "offsetDays": 1, "service": "…", "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "tableName": "…", "triggerType": "…" } ] ``` ### GET /api/v1/office/automations/summary Số đếm cho Trung tâm tự động hoá Scope: office.automation.view Phản hồi mẫu: ```json { "active": 1, "archived": 1, "byTrigger": { "key": 1 }, "failing": 1, "inactive": 1, "mine": 1, "total": 1 } ``` ### GET /api/v1/office/automations/{id} Lấy 1 automation (kèm steps) Scope: office.automation.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "createdBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "createdById": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "createdByName": "…", "description": "…", "failed7d": 1, "graphVersion": 1, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "lastRunAt": "2026-10-28T07:30:00Z", "name": "…", "parentAutomationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "parentStepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "runs7d": 1, "source": "…", "status": "…", "stepActionTypes": [ "…" ], "stepCount": 1, "steps": [ { "actionType": "…", "branchIndex": 1, "branchLabel": "…", "conditionConfig": "…", "config": "…", "falseNextStepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "groupId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "groupKind": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "loopAutomationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "nextStepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "order": 1, "splitRatio": 1, "stepType": "…", "trueNextStepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "waitConfig": "…" } ], "triggerConfig": "…", "triggerType": "…", "updatedAt": "2026-10-28T07:30:00Z" } ``` ### GET /api/v1/office/automations/{id}/diagnosis Chẩn đoán: vì sao luật chạy / không chạy Scope: office.automation.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "canRun": true, "checks": [ { "blocking": true, "code": "…", "detail": "…", "ok": true } ], "lastFailure": { "automationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "detail": "…", "detailKey": "…", "enrollmentId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "result": "…", "runAt": "2026-10-28T07:30:00Z", "stepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "subjectId": "0190a3c2-5b1e-7c3d-8e4f-000000000001" }, "recentSkips": [ { "automationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "detail": "…", "detailKey": "…", "enrollmentId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "result": "…", "runAt": "2026-10-28T07:30:00Z", "stepId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "subjectId": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ], "status": "…" } ``` ### GET /api/v1/office/automations/{id}/overlaps Luật khác cùng kích hoạt và cùng làm việc này Scope: office.automation.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "sharedActions": [ "…" ] } ] ``` ### GET /api/v1/office/automations/{id}/webhook/sample Mẫu dữ liệu + cây khoá Scope: office.automation.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "keys": [ { "children": [ { "children": [ null ], "example": "…", "name": "…", "path": "…", "type": "…" } ], "example": "…", "name": "…", "path": "…", "type": "…" } ], "listenUntil": "2026-10-28T07:30:00Z", "listening": true, "sample": null, "sampleAt": "2026-10-28T07:30:00Z", "sampleSource": "…" } ``` ### GET /api/v1/office/calendar-links Tình trạng nối lịch của tôi Scope: office.calendar.view Phản hồi mẫu: ```json { "accountEmail": "…", "calendarId": "…", "connected": true, "lastErrorKey": "…", "lastSyncedAt": "2026-10-28T07:30:00Z", "provider": "…", "providerConfigured": true, "status": "…" } ``` ### GET /api/v1/office/calendar-links/meeting-providers Những nơi cung cấp phòng họp trực tuyến đang bật Scope: office.calendar.view Phản hồi mẫu: ```json [ { "code": "…", "connected": true, "linkFromCalendarEvent": true } ] ``` ### GET /api/v1/office/calendar-preferences Thiết lập màn lịch của tôi Scope: office.calendar.view Phản hồi mẫu: ```json { "dayEndHour": 1, "dayStartHour": 1, "defaultEventMinutes": 1, "defaultView": "…", "showDeclined": true, "showWeekends": true, "timeZone": "…", "weekStartDay": 1 } ``` ### GET /api/v1/office/calendars Lịch tôi đang đăng ký (cột trái) Scope: office.calendar.view Phản hồi mẫu: ```json [ { "color": "…", "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "isMine": true, "isSubscribed": true, "isVisible": true, "kind": "…", "name": "…", "ownerId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "ownerName": "…", "role": "…" } ] ``` ### GET /api/v1/office/calendars/subscribable Lịch có thể đăng ký thêm Scope: office.calendar.view - term (query, string) Phản hồi mẫu: ```json [ { "color": "…", "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "isMine": true, "isSubscribed": true, "isVisible": true, "kind": "…", "name": "…", "ownerId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "ownerName": "…", "role": "…" } ] ``` ### GET /api/v1/office/connection-keys Danh sách khoá đã cấp Scope: office.automation.view Phản hồi mẫu: ```json [ { "createdAt": "2026-10-28T07:30:00Z", "expiresAt": "2026-10-28T07:30:00Z", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "lastUsedAt": "2026-10-28T07:30:00Z", "name": "…", "prefix": "…", "status": "…" } ] ``` ### GET /api/v1/office/dashboards/{id}/widgets/{widgetId}/data Số liệu một ô, tính theo quyền người xem Scope: office.base.record.view - id (path, GUID, bắt buộc) - widgetId (path, string, bắt buộc) Phản hồi mẫu: ```json { "buckets": [ { "count": 1, "key": "…", "value": 1.5 } ], "comparePeriod": { "from": "…", "previousFrom": "…", "previousTo": "…", "to": "…" }, "count": 1, "deltaPercent": 1.5, "groups": [ { "count": 1, "key": "…", "series": [ { "count": 1, "key": "…", "value": 1.5 } ], "value": 1.5 } ], "previousValue": 1.5, "rows": [ { "cells": { "key": null }, "recordId": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ], "seriesKeys": [ "…" ], "target": 1.5, "truncated": true, "type": "…", "unavailable": "…", "value": 1.5, "widgetId": "…" } ``` ### GET /api/v1/office/events Sự kiện trong một khoảng thời gian Scope: office.calendar.view - from (query, date) - to (query, date) - eventType (query, string) - mineOnly (query, bool) Phản hồi mẫu: ```json { "entries": [ { "allDay": true, "attendeeCount": 1, "calendarColor": "…", "calendarId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "contentVisible": true, "endAt": "2026-10-28T07:30:00Z", "eventId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "eventType": "…", "fromPersonalCalendar": true, "id": "…", "isRecurring": true, "meetingUrl": "…", "myResponse": "…", "occurrenceStart": "2026-10-28T07:30:00Z", "organizerId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "roomNames": [ "…" ], "startAt": "2026-10-28T07:30:00Z", "status": "…", "title": "…" } ], "truncated": true } ``` ### GET /api/v1/office/events/{id} Chi tiết một sự kiện kèm người dự và phòng đã giữ Scope: office.calendar.view - id (path, GUID, bắt buộc) - occurrenceStart (query, date) Phản hồi mẫu: ```json { "allDay": true, "attendees": [ { "email": "…", "isOptional": true, "isOrganizer": true, "responseNote": "…", "responseStatus": "…", "userId": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ], "calendarId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "chatChannelId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "description": "…", "endAt": "2026-10-28T07:30:00Z", "eventType": "…", "fromPersonalCalendar": true, "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "location": "…", "meetingProvider": "…", "meetingRemovalPending": true, "meetingUrl": "…", "organizerId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "pushErrorKey": "…", "pushPending": true, "recurrenceRule": "…", "remindBeforeMinutes": 1, "reminders": [ { "channel": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "minutesBefore": 1 } ], "rooms": [ { "bookingId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "name": "…", "roomId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "status": "…" } ], "startAt": "2026-10-28T07:30:00Z", "status": "…", "syncedWithGoogle": true, "timeProposals": [ { "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "note": "…", "occurrenceStart": "2026-10-28T07:30:00Z", "proposedBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "proposedByName": "…", "proposedEndAt": "2026-10-28T07:30:00Z", "proposedStartAt": "2026-10-28T07:30:00Z", "status": "…" } ], "title": "…" } ``` ### GET /api/v1/office/events/{id}.ics Tải tệp lịch .ics của sự kiện Scope: office.calendar.view - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json "…" ``` ### GET /api/v1/office/farm/device-controls Danh sách phiếu lệnh Scope: office.automation.view - status (query, string) - scope (query, string) - take (query, int) Phản hồi mẫu: ```json [ { "approverIds": [ "0190a3c2-5b1e-7c3d-8e4f-000000000001" ], "automationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "canDecide": true, "createdAt": "2026-10-28T07:30:00Z", "decidedAt": "2026-10-28T07:30:00Z", "decidedBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "deviceId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "deviceLabel": "…", "expiresAt": "2026-10-28T07:30:00Z", "family": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "idx": 1, "iotCommandId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "note": "…", "reason": "…", "status": "…", "value": 1 } ] ``` ### GET /api/v1/office/farm/device-controls/emergency-stop Trạng thái TẮT KHẨN điều khiển thiết bị tự động của trại Scope: office.automation.view Phản hồi mẫu: ```json { "canChange": true, "changedAt": "2026-10-28T07:30:00Z", "changedBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "stopped": true } ``` ### GET /api/v1/office/farm/device-controls/rules/{automationId}/approval Trạng thái duyệt tự chạy của một luật Scope: office.automation.view - automationId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "approved": true, "approvedAt": "2026-10-28T07:30:00Z", "approvedBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "automationId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "canApprove": true, "valid": true } ``` ### GET /api/v1/office/meeting-minutes Biên bản của một cuộc họp Scope: office.calendar.view - eventId (query, GUID) Phản hồi mẫu: ```json { "actionItems": [ { "assigneeId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "description": "…", "dueAt": "2026-10-28T07:30:00Z", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "taskId": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ], "artifactsFetchedAt": "2026-10-28T07:30:00Z", "decisions": "…", "eventId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "publishedAt": "2026-10-28T07:30:00Z", "recordedBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "recordingUrl": "…", "summary": "…", "transcriptText": "…" } ``` ### GET /api/v1/office/meeting-rooms Danh sách phòng họp Scope: office.calendar.view - activeOnly (query, bool) - minCapacity (query, int) - equipment (query, object) - building (query, string) Phản hồi mẫu: ```json [ { "autoReleaseMinutes": 1, "building": "…", "capacity": 1, "equipment": [ "…" ], "floor": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "isActive": true, "location": "…", "maxBookingMinutes": 1, "name": "…", "requiresApproval": true } ] ``` ### GET /api/v1/office/meeting-rooms/{id}/busy-slots Khung giờ bận của một phòng Scope: office.calendar.view - id (path, GUID, bắt buộc) - from (query, date) - to (query, date) Phản hồi mẫu: ```json [ { "bookedBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "endAt": "2026-10-28T07:30:00Z", "eventId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "eventTitle": "…", "startAt": "2026-10-28T07:30:00Z" } ] ``` ### GET /api/v1/office/tables/{tableId}/fields Liệt kê field của table Scope: office.base.record.view - tableId (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "isHidden": true, "isPrimary": true, "name": "…", "position": 1, "property": "…", "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "type": 1, "uiType": "…" } ] ``` ### GET /api/v1/office/tables/{tableId}/fields/{fieldId} Lấy chi tiết field Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - fieldId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "description": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "isHidden": true, "isPrimary": true, "name": "…", "position": 1, "property": "…", "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "type": 1, "uiType": "…" } ``` ### POST /api/v1/office/tables/{tableId}/records Tạo record mới Scope: office.base.record.manage - tableId (path, GUID, bắt buộc) - data (body, string) Phản hồi mẫu: ```json null ``` ### POST /api/v1/office/tables/{tableId}/records/aggregate Cộng gộp theo cột (tổng · trung bình · nhỏ nhất · lớn nhất · đếm) Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - fieldIds (body, object) - filter (body, object) Phản hồi mẫu: ```json [ { "average": 1.5, "count": 1, "fieldId": "…", "max": 1.5, "min": 1.5, "numericCount": 1, "sum": 1.5 } ] ``` ### POST /api/v1/office/tables/{tableId}/records/batch Tạo nhiều record (tối đa 1000, all-or-nothing) Scope: office.base.record.manage - tableId (path, GUID, bắt buộc) - records (body, object) Phản hồi mẫu: ```json null ``` ### PUT /api/v1/office/tables/{tableId}/records/batch Cập nhật nhiều record (tối đa 1000, all-or-nothing) Scope: office.base.record.manage - tableId (path, GUID, bắt buộc) - items (body, object) ### POST /api/v1/office/tables/{tableId}/records/groups Nhóm dòng: giá trị cột nhóm + số dòng, theo đúng thứ tự của records/list Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - filter (body, object) - groupFieldId (body, string) - sort (body, object) Phản hồi mẫu: ```json { "fieldHidden": true, "groups": [ { "count": 1, "value": null } ], "tooMany": true, "total": 1 } ``` ### POST /api/v1/office/tables/{tableId}/records/list Liệt kê record (phân trang + filter có cấu trúc trong body) Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - colorRules (body, object) - filter (body, object) - groupFieldId (body, string) - page (body, int) - pageSize (body, int) - sort (body, object) Phản hồi mẫu: ```json { "hasNextPage": true, "hasPreviousPage": true, "items": [ { "createdAt": "2026-10-28T07:30:00Z", "data": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "rowColor": "…", "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "updatedAt": "2026-10-28T07:30:00Z" } ], "page": 1, "pageSize": 1, "totalCount": 1, "totalPages": 1 } ``` ### GET /api/v1/office/tables/{tableId}/records/{recordId} Lấy chi tiết record Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - recordId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "createdAt": "2026-10-28T07:30:00Z", "data": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "rowColor": "…", "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "updatedAt": "2026-10-28T07:30:00Z" } ``` ### PUT /api/v1/office/tables/{tableId}/records/{recordId} Cập nhật record (merge mặc định; replace=true để ghi đè) Scope: office.base.record.manage - tableId (path, GUID, bắt buộc) - recordId (path, GUID, bắt buộc) - data (body, string) - expectedValues (body, string) - replace (body, bool) ### GET /api/v1/office/tables/{tableId}/records/{recordId}/history Lịch sử sửa của record, tách theo từng ô (đã lọc theo quyền cột) Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - recordId (path, GUID, bắt buộc) - limit (query, int) Phản hồi mẫu: ```json [ { "action": "…", "changes": [ { "fieldId": "…", "fieldName": "…", "fieldType": 1, "newValue": null, "oldValue": null } ], "createdAt": "2026-10-28T07:30:00Z", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "userId": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ] ``` ### GET /api/v1/office/tables/{tableId}/trash Thùng rác — record đã xoá mềm của bảng (phân trang) Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - page (query, int) - pageSize (query, int) Phản hồi mẫu: ```json { "hasNextPage": true, "hasPreviousPage": true, "items": [ { "createdAt": "2026-10-28T07:30:00Z", "data": "…", "deletedAt": "2026-10-28T07:30:00Z", "deletedBy": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "updatedAt": "2026-10-28T07:30:00Z" } ], "page": 1, "pageSize": 1, "totalCount": 1, "totalPages": 1 } ``` ### GET /api/v1/office/tables/{tableId}/views Liệt kê view của table Scope: office.base.record.view - tableId (path, GUID, bắt buộc) Phản hồi mẫu: ```json [ { "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "isPublic": true, "name": "…", "property": "…", "publicToken": "…", "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "viewType": "…" } ] ``` ### GET /api/v1/office/tables/{tableId}/views/{viewId} Lấy chi tiết view Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - viewId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "isPublic": true, "name": "…", "property": "…", "publicToken": "…", "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "viewType": "…" } ``` ### GET /api/v1/office/tables/{tableId}/views/{viewId}/calendar-data Record trong khoảng ngày của view kiểu Calendar Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - viewId (path, GUID, bắt buộc) - from (query, date) - to (query, date) Phản hồi mẫu: ```json [ { "data": "…", "end": "2026-10-28T07:30:00Z", "recordId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "start": "2026-10-28T07:30:00Z" } ] ``` ### GET /api/v1/office/tables/{tableId}/views/{viewId}/chart-data Dữ liệu đã gộp của view kiểu Chart Scope: office.base.record.view - tableId (path, GUID, bắt buộc) - viewId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "aggregation": "…", "buckets": [ { "count": 1, "groupKey": "…", "value": 1.5 } ], "groupField": "…", "valueField": "…" } ``` ### GET /api/v1/office/tasks/monitor Màn Theo dõi: sáu ô số liệu + bảng toàn đội + việc của từng người Scope: office.task.view - scope (query, string) - taskListId (query, GUID) Phản hồi mẫu: ```json { "dueSoonDays": 1, "isExact": true, "members": [ { "current": { "elapsedMinutes": 1, "startedAt": "2026-10-28T07:30:00Z", "taskId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "title": "…" }, "dueSoonCount": 1, "hasOnTimeData": true, "inProgressCount": 1, "items": [ { "daysLate": 1, "dueAt": "2026-10-28T07:30:00Z", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "isOverdue": true, "status": "…", "title": "…" } ], "lastActivityAt": "2026-10-28T07:30:00Z", "loadState": "…", "onTimeRate": 1, "openCount": 1, "openCountAll": 1, "openTotal": 1, "overdueCount": 1, "pendingReviewCount": 1, "staleCount": 1, "todoCount": 1, "userId": "0190a3c2-5b1e-7c3d-8e4f-000000000001" } ], "staleAfterDays": 1, "summary": { "dueSoon": 1, "hasOnTimeData": true, "onTimeRate": 1, "open": 1, "overdue": 1, "stale": 1, "unassigned": 1 } } ``` ### GET /api/v1/office/tasks/workload Khối lượng theo người Scope: office.task.view - taskListId (query, GUID) - listGroupId (query, GUID) Phản hồi mẫu: ```json [ { "isOverloaded": true, "openCount": 1, "overdueCount": 1, "overloadThreshold": 1, "totalWorkload": 1.5, "userId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "workloadDay": 1.5, "workloadHour": 1.5, "workloadPoint": 1.5 } ] ``` ### GET /api/v1/office/views/{viewId}/location Lần ngược từ mã khung nhìn lên bảng và ứng dụng chứa nó Scope: office.base.record.view - viewId (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "appId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "appName": "…", "tableId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "tableName": "…", "viewId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "viewName": "…", "viewType": "…" } ``` ## Báo cáo (/api/v1/reports) Báo cáo và chi phí công theo kỳ. ### GET /api/v1/reports/catalog Danh mục báo cáo kèm số khối người xem được thấy Scope: report.view Phản hồi mẫu: ```json [ { "accessible": true, "blocks": 1, "chart": "…", "code": "…", "description": "…", "group": "…", "title": "…", "visibleBlocks": 1 } ] ``` ### GET /api/v1/reports/exports/{id} Trạng thái một lượt xuất: pending · running · completed · failed Scope: report.export - id (path, GUID, bắt buộc) Phản hồi mẫu: ```json { "code": "…", "completedAt": "2026-10-28T07:30:00Z", "createdAt": "2026-10-28T07:30:00Z", "downloadPath": "…", "error": "…", "expiresAt": "2026-10-28T07:30:00Z", "fileName": "…", "format": "…", "id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "sizeBytes": 1, "status": "…", "title": "…" } ``` ### GET /api/v1/reports/exports/{id}/file Tải tệp đã xuất (404 khi quá 24 giờ) Scope: report.export - id (path, GUID, bắt buộc) ### GET /api/v1/reports/labor-cost Chi phí nhân công theo thửa / mùa vụ Scope: report.view - from (query, date) - to (query, date) - parcel_id (query, GUID) - season_id (query, GUID) Phản hồi mẫu: ```json { "freshness": { "lastPayrollFinalizedAt": "2026-10-28T07:30:00Z", "lastPieceworkReceivedAt": "2026-10-28T07:30:00Z", "lastSeasonSyncedAt": "2026-10-28T07:30:00Z" }, "from": "2026-10-28", "rows": [ { "costPerKg": 1, "cropName": "…", "parcelCode": "…", "parcelId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "pieceworkCost": 1, "pieceworkQuantities": [ { "quantity": 1.5, "unit": "…" } ], "seasonId": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "seasonName": "…", "timeCost": 1, "totalCost": 1, "workUnits": 1.5, "workerCount": 1, "yieldKg": 1.5 } ], "to": "2026-10-28", "totals": { "pieceworkCost": 1, "timeCost": 1, "totalCost": 1, "workUnits": 1.5, "workerCount": 1 } } ``` ### GET /api/v1/reports/query Chạy báo cáo bằng GET; thiếu to = hôm nay UTC+7, thiếu from = to - 29 ngày Scope: report.view - code (query, string) - from (query, date) - to (query, date) - grain (query, string) - compare (query, string) - includeSample (query, bool) Phản hồi mẫu: ```json { "asOf": "2026-10-28T07:30:00Z", "blocks": [ { "chart": "…", "dataset": "…", "drill": "…", "groupBy": [ "…" ], "hasSample": true, "hidden": true, "id": "…", "kpis": [ { "compare": 1.5, "deltaPct": 1.5, "key": "…", "value": 1.5 } ], "measures": [ { "good": "…", "key": "…", "label": "…", "op": "…", "unit": "…" } ], "reasonCode": "…", "rows": [ { "date": "2026-10-28", "dims": { "key": null }, "id": "…", "values": { "key": null } } ], "source": "…", "span": 1, "status": "…", "title": "…", "totals": { "key": 1.5 }, "truncated": true, "type": "…", "updatedAt": "2026-10-28T07:30:00Z" } ], "code": "…", "description": "…", "group": "…", "hasSample": true, "hidden": [ "…" ], "labels": { "key": { "key": "…" } }, "period": { "compareFrom": "2026-10-28", "compareTo": "2026-10-28", "from": "2026-10-28", "grain": "…", "to": "2026-10-28" }, "summaryFocus": [ "…" ], "title": "…" } ``` ### GET /api/v1/reports/trace Truy xuất ngược nhiều tầng từ một nút (phiếu thu hoạch, phiếu khoán, chứng từ, phiếu lương, phiếu bán…) Scope: report.view - kind (query, string) - id (query, string) - key (query, string) - depth (query, int) Phản hồi mẫu: ```json { "edges": [ { "from": "…", "to": "…" } ], "nodes": [ { "id": "…", "kind": "…", "level": 1, "node": { "amount": 1.5, "date": "2026-10-28", "facts": [ { "label": null, "value": null } ], "flag": "…", "flagOf": { "id": "…", "key": "…", "kind": "…" }, "href": "…", "id": "…", "key": "…", "kind": "…", "service": "…", "status": "…", "subtitle": "…", "title": "…", "unit": "…" }, "ref": "…", "restricted": true, "service": "…", "sourceError": true } ], "root": "…", "truncated": true } ``` # Công cụ AI (MCP) Máy chủ MCP: https://mcp.nextfarm.vn/mcp — OAuth 2.1 hoặc API key. ## farm_overview — Tổng hợp diện tích thửa Diện tích thửa gộp theo hộ, vùng hoặc cây trồng: số hộ, số thửa, tổng diện tích (ha), số thửa chưa có ranh giới. Xếp theo diện tích giảm dần. Scope OAuth: gis:read ## gis_crop_types_list — Loại cây trồng Danh mục loại cây trồng của trại: mã, tên, nhóm. Dùng mã loại cây để lọc thửa, mùa vụ, sản lượng. Scope OAuth: gis:read ## gis_farmers_list — Danh sách hộ Hộ nông dân / thành viên của vùng nguyên liệu: tên, vùng, nhóm hộ, loại thành viên. Lọc theo vùng, nhóm, loại. Scope OAuth: gis:read ## gis_parcels_get — Chi tiết một thửa Một thửa: tên, mã, diện tích, vùng, chủ hộ, cây đang trồng, trạng thái. Scope OAuth: gis:read ## gis_parcels_list — Danh sách thửa Danh sách thửa của trại: mã, diện tích (ha), vùng, chủ hộ, cây đang trồng, trạng thái. Lọc theo vùng, cây, trạng thái. Scope OAuth: gis:read ## gis_search — Tìm thửa, vùng, hộ, mùa vụ Tìm thửa, mùa vụ, hộ nông dân và vùng theo từ khoá; nhóm nào ngoài quyền của khoá thì trả rỗng. Scope OAuth: gis:read ## gis_seasons_groups — Nhóm vụ trong năm Gộp các vụ cùng cây, cùng tên vụ trên nhiều thửa trong một năm (vd Lúa · Đông Xuân): số thửa, tổng diện tích, ngày trồng sớm/muộn nhất. Scope OAuth: gis:read ## gis_seasons_list — Mùa vụ Mùa vụ theo một thửa (lịch sử canh tác) hoặc theo năm: cây, tên vụ, ngày trồng, ngày dự kiến thu, trạng thái. Cần ít nhất một trong hai: thửa hoặc năm. Scope OAuth: gis:read ## gis_zones_stats — Thống kê vùng Số thửa và tổng diện tích (ha) của một vùng. Scope OAuth: gis:read ## certification_expiring — Chứng nhận sắp hết hạn Chứng nhận (VietGAP, GlobalGAP, hữu cơ…) sắp hết hạn hoặc đã hết hạn, xếp theo ngày hết hạn tăng dần. Scope OAuth: harvest:read ## harvest_phi_active — Thửa đang cách ly thuốc Các thửa, ao, chuồng đang trong thời gian cách ly thuốc bảo vệ thực vật hoặc ngưng thuốc thú y: ngày sớm nhất được thu và lần dùng thuốc đang giữ cách ly. Scope OAuth: harvest:read ## harvest_record_list — Phiếu thu hoạch Phiếu khai thu hoạch: thửa, cây, sản lượng khai (kg), trạng thái (draft · confirmed · verified · collected · void), ngày thu, lô truy xuất. Scope OAuth: harvest:read ## harvest_search — Tìm thu hoạch, tem, chứng nhận Tìm phiếu thu hoạch, tem QR và chứng nhận theo từ khoá (mã phiếu, mã tem, tên chứng nhận). Scope OAuth: harvest:read ## harvest_summary — Tổng hợp sản lượng Sản lượng đã thu (chỉ phiếu đã xác nhận) gộp theo hộ, vùng, cây hoặc thửa, trong khoảng ngày thu hoạch. Xếp theo tổng kg giảm dần. Scope OAuth: harvest:read ## harvest_trace_get — Chi tiết lô truy xuất Một lô truy xuất: nhật ký canh tác, chứng nhận, ảnh — đúng nội dung người mua thấy khi quét tem. Scope OAuth: harvest:read ## harvest_trace_list — Lô truy xuất Các lô truy xuất nguồn gốc đã phát hành của trại: thửa, vùng, sản phẩm, chứng nhận, ngày thu, hộ trồng, số công đoạn. Scope OAuth: harvest:read ## phi_status — Thời gian cách ly của một thửa Thửa còn trong thời gian cách ly thuốc không, ngày sớm nhất được thu, lần dùng thuốc đang giữ cách ly. Scope OAuth: harvest:read ## iot_alerts_list — Cảnh báo cảm biến Cảnh báo cảm biến vượt ngưỡng: thiết bị, chỉ số, giá trị, ngưỡng, mức độ, trạng thái xử lý. Scope OAuth: iot:read ## iot_command_status — Lệnh gửi xuống thiết bị Các lệnh gần nhất gửi xuống một bộ điều khiển: loại lệnh, trạng thái, lúc gửi, thiết bị đã nhận chưa. Scope OAuth: iot:read ## iot_device_list — Thiết bị IoT Thiết bị IoT của trại: tên, loại, đang trực tuyến hay mất kết nối, lần cuối báo về. Lọc theo trạng thái, thửa, vùng. Scope OAuth: iot:read ## iot_devices_search — Tìm thiết bị Tìm thiết bị theo tên hoặc mã. Scope OAuth: iot:read ## iot_irrigation_outputs — Van và bơm của bộ điều khiển Đầu ra tưới của một bộ điều khiển: loại (bơm, van khu, châm phân), số thứ tự và tên người dùng đặt (vd 'Lô A'). Scope OAuth: iot:read ## iot_readings_history — Lịch sử số đo Lịch sử số đo theo thiết bị hoặc vùng trong một khoảng thời gian, mới nhất trước. Scope OAuth: iot:read ## iot_readings_latest — Số đo mới nhất Số đo mới nhất của từng cảm biến (nhiệt độ, độ ẩm, EC, pH…) cho cả trại hoặc một vùng. Scope OAuth: iot:read ## iot_schedule_upcoming — Lịch tưới sắp chạy Các lượt tưới sắp chạy theo lịch: bộ điều khiển, giờ chạy, thời lượng (giây), van, đã hoãn hay đã chạy. Scope OAuth: iot:read ## office_automations_list — Luật tự động Các luật tự động hoá của trại: tên, trạng thái, kiểu kích hoạt, lần chạy gần nhất. Scope OAuth: office:read ## office_books_fields — Cột của bảng Các cột của một bảng: tên, kiểu dữ liệu, tuỳ chọn — dùng để đọc đúng giá trị từng dòng. Scope OAuth: office:read ## office_books_list — Sổ nông trại Các sổ nông trại tự dựng của trại (nhật ký, kho, chi phí…): tên, mô tả, số bảng. Scope OAuth: office:read ## office_books_records — Dòng của bảng Đọc các dòng của một bảng trong sổ, phân trang; giá trị theo mã cột. Scope OAuth: office:read ## office_books_tables — Bảng trong sổ Các bảng trong một sổ nông trại. Scope OAuth: office:read ## report_catalog — Danh mục báo cáo Các báo cáo có sẵn của trại: mã, tên, nhóm và số khối dữ liệu bạn được xem. Scope OAuth: reports:read ## report_query — Chạy báo cáo Chạy một báo cáo theo mã trong khoảng ngày: chỉ số tổng, chuỗi theo thời gian, phân rã, bảng. Thiếu ngày thì lấy 30 ngày gần nhất. Scope OAuth: reports:read # Sự kiện webhook ## iot.alert.raised — Cảnh báo cảm biến mới Cảm biến vượt ngưỡng (nhiệt độ, độ ẩm, EC…). Có thiết bị, chỉ số, giá trị, ngưỡng, mức độ. Scope: iot.alerts.view ```json { "alert_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "device_code": "NK2", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000002", "message": "Nhiệt độ nhà kính 2 vượt 38°C", "metric": "temperature", "parcel_ids": [], "severity": "high", "threshold": 38, "value": 41.2, "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000003" } ``` ## iot.device.offline — Thiết bị mất kết nối Thiết bị ngừng báo về quá thời gian cho phép. Scope: iot.devices.view ```json { "device_code": "NK2", "device_id": "0190a3c2-5b1e-7c3d-8e4f-000000000002", "device_name": "Bộ tưới nhà kính 2", "last_heartbeat": "2026-10-28T06:45:00+00:00", "parcel_ids": [], "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000003" } ``` ## harvest.batch.created — Lô thu hoạch được xác nhận Một bản ghi thu hoạch được khai báo và xác nhận: thửa, loại cây, sản lượng (kg). Scope: harvest.harvest.view ```json { "confirmed_at": "2026-10-28T07:30:00+00:00", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000003", "harvest_record_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000002", "quantity_kg": 1250.5 } ``` ## harvest.batch.verified — Lô thu hoạch được xác minh Cán bộ xác minh lô thu hoạch: sản lượng thực, hạng chất lượng, ngày thu. Scope: harvest.harvest.view ```json { "actual_quantity_kg": 1198, "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000003", "crop_type_name": "Vải thiều", "harvest_date": "2026-10-27", "harvest_record_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000002", "quality_grade": "A", "season_id": null, "verified_at": "2026-10-28T07:30:00+00:00" } ``` ## harvest.qr.created — Tem truy xuất được tạo Một tem QR truy xuất nguồn gốc vừa được tạo cho lô thu hoạch hoặc mùa vụ. Scope: harvest.qr-codes.view ```json { "code": "NF-7Q2K-91XD", "created_at": "2026-10-28T07:30:00+00:00", "harvest_record_id": "0190a3c2-5b1e-7c3d-8e4f-000000000002", "qr_code_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "season_id": null } ``` ## gis.season.created — Mùa vụ mới Một mùa vụ được tạo trên thửa: cây trồng, tên vụ, ngày trồng, ngày dự kiến thu. Scope: gis.parcels.view ```json { "branch_id": null, "crop_name": "Lúa", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000003", "expected_harvest_on": "2027-02-20", "parcel_code": "A-12", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000002", "planted_on": "2026-11-05", "season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "season_name": "Đông Xuân 2026" } ``` ## gis.season.closed — Mùa vụ kết thúc Một mùa vụ được đóng: ngày đóng, sản lượng (kg) nếu có. Scope: gis.parcels.view ```json { "closed_at": "2026-10-28T07:30:00+00:00", "closed_on": "2027-02-21", "season_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "yield_kg": 5400 } ``` ## gis.parcel.created — Thửa mới Một thửa mới được thêm vào trại: vùng, chủ hộ, cây trồng, diện tích (ha). Scope: gis.parcels.view ```json { "area_ha": 1.25, "created_at": "2026-10-28T07:30:00+00:00", "crop_type_id": "0190a3c2-5b1e-7c3d-8e4f-000000000003", "owner_id": null, "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "zone_id": "0190a3c2-5b1e-7c3d-8e4f-000000000002" } ``` ## office.task.completed — Việc hoàn thành Một việc trong trại được báo xong: tên việc, người làm, lúc xong, thửa/mùa vụ gắn kèm. Scope: office.task.view ```json { "branch_id": null, "completed_at": "2026-10-28T07:30:00+00:00", "completed_by": "0190a3c2-5b1e-7c3d-8e4f-000000000002", "parcel_id": "0190a3c2-5b1e-7c3d-8e4f-000000000003", "season_id": null, "task_id": "0190a3c2-5b1e-7c3d-8e4f-000000000001", "title": "Bón thúc đợt 2 thửa A-12" } ``` # Scope API key - gis.parcels.view: Xem thửa và mùa vụ - gis.zones.view: Xem vùng - gis.farmers.view: Xem nông dân - gis.crop-types.view: Xem loại cây trồng - gis.facilities.view: Xem công trình - gis.layers.view: Xem lớp bản đồ - iot.devices.view: Xem thiết bị - iot.telemetry.view: Xem số đo cảm biến - iot.telemetry.export: Xuất số đo cảm biến - iot.alerts.view: Xem cảnh báo - harvest.harvest.view: Xem lô thu hoạch - harvest.harvest.create: Tạo lô thu hoạch (ghi) - harvest.qr-codes.view: Xem tem QR - harvest.trace.view: Xem truy xuất nguồn gốc - harvest.certification.view: Xem chứng nhận - harvest.market-prices.view: Xem giá thị trường - office.base.record.view: Xem dữ liệu sổ - office.base.record.manage: Thêm, sửa dòng sổ (ghi) - office.task.view: Xem công việc - office.calendar.view: Xem lịch - office.automation.view: Xem luật tự động hoá - report.view: Xem báo cáo - report.export: Xuất báo cáo # Error code - 401: Khoá sai, hết hạn hoặc đã bị thu hồi. - 403: Khoá thiếu scope, hoặc tuyến không cho API key (chi tiết ở trường detail). - 404: Không tìm thấy dữ liệu. - 422: Dữ liệu gửi lên không hợp lệ (trường errors liệt kê từng lỗi). - 429: Gọi quá nhanh — đọc Retry-After rồi retry.