Skip to content

Endpoints ​

Tất cả đường dẫn /v1/* dưới đây yêu cầu danh tính Cloudflare Access hợp lệ. Một Worker phục vụ cả ba mặt phẳng của Conan1. Các đường công khai (/public/b21/*, /mm/*, /public/mm/*) nằm ở cuối trang.

Danh tính ​

GET /v1/me ​

json
{ "email": "dac2205@gmail.com", "name": "Đắc", "role": "architect", "brand_id": null }

Năng lực chuẩn ​

Chỉ vai trò architect được ghi.

MethodPathMô tả
GET/v1/capabilitiesToàn bộ mô hình chuẩn, sắp xếp theo level và sort
GET/v1/capabilities/:idOrCodeMột năng lực, kèm con và đánh giá của các Brand
POST/v1/capabilitiesTạo mới; ID sinh tự động từ ID cha
PATCH/v1/capabilities/:idSửa; chỉ những cột được gửi mới bị đổi
DELETE/v1/capabilities/:idXoá, hoặc chuyển sang deprecated nếu đã có Brand đánh giá

POST body:

json
{
  "name_vi": "Thu hút tự nhiên",
  "name_en": "Organic Acquisition",
  "definition_vi": "Khả năng ...",
  "definition_en": "The ability to ...",
  "parent_id": "CAP-02",
  "level": 2,
  "sort": 1
}

ID sinh theo cây: con thứ nhất của CAP-02 là CAP-02-01. Nhìn ID là biết vị trí trong mô hình.

Rule 8

DELETE không bao giờ xoá một năng lực đã được Brand đánh giá. Nó chuyển status thành deprecated và trả về { "deprecated": true, "reason": "in_use_by_brands" }. Lịch sử không bị mất.

Brand ​

MethodPathMô tả
GET/v1/brandsDanh sách Brand
POST/v1/brandsTạo Brand (chỉ architect)
PATCH/v1/brands/:codeSửa Brand

Bản đồ năng lực của Brand ​

MethodPathMô tả
GET/v1/brands/:code/capabilitiesMọi năng lực chuẩn, kèm đánh giá của Brand này (assessment_id là null nếu chưa kích hoạt)
PUT/v1/brands/:code/capabilities/:capabilityIdKích hoạt hoặc cập nhật đánh giá
DELETE/v1/brands/:code/capabilities/:capabilityIdBỏ kích hoạt

PUT với body rỗng {} nghĩa là "kích hoạt, chưa chấm điểm". Body có thể chứa bất kỳ tập con nào của:

json
{
  "importance": 5, "priority": 5, "health": 3,
  "maturity": 2, "target_maturity": 4,
  "likelihood": 4, "impact": 5, "detectability": 2,
  "owner": "Đắc", "rationale": "...", "notes": "..."
}

Quyền ghi: architect và executive ghi được mọi Brand; brand_owner chỉ ghi được Brand của mình.

So sánh ​

GET /v1/compare?capability=learner-acquisition ​

Trả về cùng một năng lực dưới góc nhìn của mọi Brand đang hoạt động, kể cả Brand chưa kích hoạt (các trường điểm là null).

Gói kiến trúc ​

GET /v1/architecture ​

Một request trả về toàn bộ tầng thực thi, chuyển đổi và hai mặt phẳng Products, Requirements. Frontend gọi đúng một lần khi khởi động rồi tính mọi màn hình tại client.

KhoáBảng
valueStreams, stages, stageCapabilitiesValue stream và chặng
processes, activitiesQuy trình, hoạt động
applications, dataEntities, technologiesỨng dụng (kèm product_id, repository), dữ liệu, công nghệ
capabilityApplications, capabilityData, applicationTechnologies, applicationDataBảng nối của Compass
initiatives, initiativeCapabilities, kpisChuyển đổi
objectives, keyResultsOKR — Objective và Key Result (xem OKR)
products, productCapabilities, applicationUrls, featuresProducts
requirements, questions, decisions, decisionLinks, evidenceRequirements
acceptanceCriteria, requirementDependenciesTiêu chí chấp nhận và phụ thuộc giữa các yêu cầu
releases, releaseRequirementsRelease

ai_suggestions và ai_runs không nằm trong gói — xem GET /v1/requirements/:id/suggestions.

CRUD chung ​

Các tài nguyên sau dùng chung một bộ endpoint, chỉ khác nhau ở cột (năm tài nguyên của Chiến lược — strategies, strategic-themes, strategic-objectives, annual-priorities, quarterly-focus — cũng dùng đúng bộ này):

GET    /v1/:resource
POST   /v1/:resource
PATCH  /v1/:resource/:id
DELETE /v1/:resource/:id
:resourceBảngPrefix idPhạm viGhi chú
value-streamsvalue_streamsVSbrand
value-stream-stagesvalue_stream_stagesVSSmodel
processesprocessesPRCbrand
activitiesactivitiesACTbrand
applicationsapplicationsAPPmodelcột thêm: product_id, repository
data-entitiesdata_entitiesDATmodel
technologiestechnologiesTECmodel
initiativesinitiativesINIbrand
objectivesobjectivesOKRbrandcode, capability_id, stage_id, strategic_objective_id, owner, period, status. Đặt strategic_objective_id cần architect hoặc executive — nó quyết định màn hình Chiến lược hiện gì
key-resultskey_resultsKRbrandcode, objective_id, kpi_id, unit, direction, baseline, current, target, status; không có description_*. Ghi hoặc xoá còn cần quyền ghi trên Objective cha
productsproductsPRObrandcode, brand_id, proposition_*, domain_*, owner, kind, status
application-urlsapplication_urlsURLmodelcode, application_id, url, kind
featuresfeaturesFEAbrandbrand_id tự điền từ product_id
requirementsrequirementsREQ-NNNNmodelid từ sequences; brand_id tự điền từ product_id
questionsquestionsQ-NNNNmodelchỉ name_*, description_*, draft_answer_*, requirement_id, sort
decisionsdecisionsD-NNNNmodelchỉ name_*, description_*, rationale_*, product_id, capability_id, application_id, feature_id. status và supersedes_id không ghi được ở đây: trạng thái của bộ nhớ quyết định chỉ đổi qua POST /v1/decisions (thay thế ngay lúc tạo) và DELETE /v1/decisions/:id (thu hồi)
evidenceevidenceEVI-NNNNmodelrequirement_id, kind, url, status; brand_id tự điền từ yêu cầu
acceptance-criteriaacceptance_criteriaAC-NNNNmodelrequirement_id, evidence_id, status (pending · met · failed); brand_id tự điền từ yêu cầu
releasesreleasesREL-NNNNmodelapplication_id, version, released_at, status; brand_id tự điền từ ứng dụng
  • name_en và name_vi bắt buộc khi tạo.
  • ID sinh từ prefix cộng slug (PRC-develop-authentic-content), trừ các tài nguyên có sequence: REQ-, Q-, D-, EVI-, AC-, REL- nhận số tuần tự bốn chữ số từ bảng sequences.
  • PATCH chỉ ghi các cột được gửi lên. Cột không có trong danh sách bị bỏ qua lặng lẽ.
  • Với tài nguyên có brandFrom, Worker nạp bản ghi cha (400 nếu thiếu cột cha, 404 nếu không tồn tại) và điền brand_id trước khi kiểm tra quyền; gửi brand_id khác cha → 400.
  • key-results cố tình không dùng brandFrom: một Key Result là con số của một Brand nằm dưới một Objective cấp doanh nghiệp, nên brand của nó được phép khác brand của cha. Đổi lại, Worker kiểm tra quyền hai lần — trên Brand của chính bản ghi và trên Brand của Objective cha (parentGate), ở cả POST, PATCH (cả hai đầu khi đổi cha) và DELETE. Nhờ vậy brand_owner không gắn được Key Result vào Objective không thuộc về mình.
  • kpis không còn là tài nguyên CRUD. Toàn bộ KPI đã thành Key Result (0015/0016); để hai bảng cùng ghi được thì con số sẽ lệch nhau ngay lần đầu có người nhập vào bảng cũ. KPI vẫn được trả về trong gói /v1/architecture làm hồ sơ nguồn gốc (key_results.kpi_id).

Cột chỉ route riêng mới ghi được

final_answer_*, selected_suggestion_id, answered_by/at, status, decision_id của questions và status, supersedes_id, decided_by, decided_by_kind, decided_at, ai_analysis_*, ai_run_id, requirement_id, question_id của decisions không có trong danh sách cột chung. Một PATCH chung mà ghi được status sẽ hồi sinh được một quyết định đã thu hồi; ghi được supersedes_id sẽ thay thế một quyết định mà không có quyết định kế nhiệm. Chúng chỉ được ghi bởi các route ở mục Requirements bên dưới, luôn đóng dấu email từ Access.

Quyền ghi:

Tài nguyênAi được ghi
applications, data-entities, technologies, value-stream-stages, application-urlsarchitect
processes, activities, value-streams, initiatives, objectives, key-results, products, featuresarchitect, executive, hoặc brand_owner với bản ghi thuộc Brand của mình
requirements, questions, decisions, evidence, acceptance-criteria, releaseschỉ architect

WARNING

Bản ghi có brand_id = NULL là bản ghi dùng chung (với sản phẩm: nền tảng Conan1). brand_owner không sửa được chúng — chỉ architect hoặc executive. Đổi brand_id sang Brand khác cũng cần quyền ghi ở Brand đích.

Xoá có lịch sử ​

DELETEKết quả khi bản ghi đã có lịch sử
/v1/decisions/:idLuôn status = reverted, trả { "deprecated": true, "reason": "decision_memory" }
/v1/requirements/:idXoá thật chỉ khi draft và không có câu hỏi / quyết định / bằng chứng; ngược lại dropped, { "deprecated": true, "reason": "has_history" }
/v1/questions/:idXoá thật chỉ khi open / suggested và chưa có decision_id; ngược lại dropped
/v1/products/:idCó yêu cầu → retired, { "deprecated": true, "reason": "has_requirements" }

Quan hệ ​

POST   /v1/links/:link          body: { "a": "...", "b": "..." }
DELETE /v1/links/:link?a=...&b=...
:linkab
stage-capabilitiesstage_idcapability_id
capability-applicationscapability_idapplication_id
capability-datacapability_iddata_entity_id
application-technologiesapplication_idtechnology_id
application-dataapplication_iddata_entity_id
initiative-capabilitiesinitiative_idcapability_id
product-capabilitiesproduct_idcapability_id
release-requirementsrelease_idrequirement_id
requirement-dependenciesrequirement_iddepends_on_id — a chờ b

POST dùng INSERT OR IGNORE, nên gọi lại một quan hệ đã có không gây lỗi. Chỉ architect và executive được đổi quan hệ; riêng release-requirements và requirement-dependencies thuộc mặt phẳng Requirements nên chỉ architect (403 với executive). Quan hệ không có tên trong bảng trên trả 404.

201 phải nghĩa là đã ghi thật

INSERT OR IGNORE nuốt luôn cả vi phạm CHECK và khoá ngoại, chứ không chỉ dòng trùng: một liên kết tới id không tồn tại, hay một yêu cầu phụ thuộc chính nó, từng trả 201 mà không ghi gì. Nay Worker đọc lại đúng cặp đó sau khi chèn; không thấy thì trả 400:

json
{ "error": "Link refused: check that both records exist and that they are not the same record" }

Áp dụng cho mọi quan hệ trong bảng trên, không riêng quan hệ mới. Gọi lại một cặp đã có vẫn là 201 — nó đọc lại được.

Requirements ​

Mọi route dưới đây yêu cầu architect (trừ hai GET, mở cho mọi người đã đăng nhập). Mỗi thao tác ghi tạo dòng audit_log và trả về bản ghi mới.

MethodPathMô tả
POST/v1/questionsTạo câu hỏi (route riêng, cùng URL với CRUD chung): cấp Q-NNNN, điền brand_id, chuyển yêu cầu draft → clarifying
POST/v1/questions/:id/answer{ selected_suggestion_id?, final_answer_en, final_answer_vi } — ghi câu trả lời cuối, đóng dấu answered_by/at, đánh dấu gợi ý selected / rejected, status = answered
POST/v1/questions/:id/decide{ name_*, rationale_*, description_*? } — chỉ khi answered; sinh D-NNNN với decided_by, decided_by_kind, ai_analysis_*, neo từ chuỗi truy vết, dải decision_links; câu hỏi decided. Trả { ...decision, links }
POST/v1/decisionsQuyết định độc lập: name_*, rationale_*, description_*?, product_id?, capability_id?, application_id?, feature_id?, links?: [{ entity, entity_id }] — mỗi mục tiêu được kiểm tra tồn tại (400 Unknown link target)
POST/v1/decisions/:id/links{ entity, entity_id } — thêm vào dải Liên quan
DELETE/v1/decisions/:id/links?entity=&entity_id=Bỏ khỏi dải Liên quan
GET/v1/requirements/:id/suggestions{ items: ai_suggestions[], runs: ai_runs[] } của một yêu cầu
GET/v1/requirements/:id/traceChuỗi truy vết đã giải: { requirement, feature, product, brand, application, capability, initiative }

entity của dải Liên quan là một trong requirement · question · feature · product · application · capability · initiative.

AI ​

Gợi ý sinh bởi Workers AI (DeepSeek) qua AI Gateway conan1 — không cần khoá API. Chi tiết ở Làm rõ có AI hỗ trợ.

MethodPathMô tả
GET/v1/ai/status{ "enabled": true, "model": "@cf/deepseek-ai/deepseek-v4-flash-0731", "gateway": "conan1", "limit": 50, "used": 7 } — enabled là có binding AI hay không; limit là AI_DAILY_LIMIT (0 = không giới hạn), used là số dòng ai_runs của ngày UTC hôm nay (luôn 0 khi enabled = false)
POST/v1/requirements/:id/suggest-questionsTối đa 5 gợi ý câu hỏi (kind = question). Trả 201 { items, run }
POST/v1/questions/:id/suggest-answersĐúng 3 phương án A / B / C, một recommended; câu hỏi open → suggested. Trả 201 { items, run }
POST/v1/suggestions/:id/acceptBiến một gợi ý câu hỏi proposed thành câu hỏi thật (source_suggestion_id), gợi ý → accepted. Trả 201 câu hỏi

Lỗi:

MãerrorKhi nàoGhi ai_runs
503ai_not_configuredKhông có binding AIkhông
429ai_daily_limitĐã dùng hết AI_DAILY_LIMIT lượt của ngày UTC hôm nay; body kèm used, limit, message_en, message_vikhông
429ai_rate_limitedWorkers AI báo giới hạn / hạn mứcfailed
502ai_failedGọi thất bại vì lý do khácfailed
502ai_no_answerJSON không đọc được, sai số phương án, sai nhãnfailed

Quản trị ​

GET /v1/governance ​

Kiểm tra toàn bộ dữ liệu theo các quy tắc quản trị và trả về danh sách vi phạm. brand_code rỗng nghĩa là bản ghi dùng chung, không thuộc Brand nào:

json
{
  "checked": 51,
  "issues": [
    {
      "rule": "rule-5-owner-required",
      "brand_code": "nemo12",
      "capability_id": "CAP-06",
      "capability_code": "assessment",
      "detail": "Importance >= 4 but no owner assigned"
    },
    {
      "rule": "rule-9-requirement-capability",
      "brand_code": "nemo12",
      "capability_id": "",
      "capability_code": "",
      "detail": "REQ-1835 · Parents can export progress"
    }
  ]
}

Rule id: rule-2-definition-required, rule-5-owner-required, rule-6-p5-rationale, rule-7-process-owner, rule-9-requirement-capability, rule-10-decision-rationale, rule-11-product-capability; cảnh báo: target-maturity-missing, capability-without-process, capability-without-application, các cảnh báo về chặng, feature-brand-mismatch, application-without-product. Xem Quy tắc quản trị.

Nhật ký ​

GET /v1/audit?entity=capability&id=CAP-02&limit=50 ​

Mọi thay đổi đều lưu cả trạng thái trước và sau dưới dạng JSON. Trang chi tiết quyết định dùng entity=decisions&id=D-0182 để hiện lịch sử của chính nó; quan hệ dùng entity=link:<tên> với id a|b.

Ngoài Access: mm.conan1.com ​

Markmap dùng đăng nhập Google riêng (bảng mm_users, mm_sessions), không dùng Access. Trình duyệt gọi qua https://mm.conan1.com/api/... (Worker của mm chuyển tiếp qua service binding), nên cookie mm_session là first-party. Khuôn trả về là { "data": ... } / { "error": { "code", "message" } }. Nguồn: src/routes/mm.ts.

ĐườngPhiênÝ nghĩa
GET /mm/configkhông{ google_client_id } cho nút Google
POST /mm/auth/googlekhông{ credential } (ID token của Google) → đặt cookie, trả { user_id, email, display_name, avatar_url }
POST /mm/auth/logouttuỳThu hồi phiên, xoá cookie
GET /mm/mecóNgười đang đăng nhập; 401 nếu chưa
GET /mm/markmapscó{ items, limit: 10 }, mới sửa trước
GET /mm/markmaps/:idcóMột sơ đồ của mình; của người khác trả 404 như không tồn tại
POST /mm/markmapscó{ title, content } → tạo; đủ 10 bản thì 409
PUT /mm/markmaps/:idcóSửa tiêu đề và nội dung
PUT /mm/markmaps/:id/visibilitycó{ public: boolean } bật/tắt share
GET /public/mm/markmaps?q=khôngTối đa 500 sơ đồ đang share; không có danh tính người viết
GET /public/mm/markmaps/:idkhôngMột sơ đồ đang share; chưa/không còn share thì 404

Không có đường xoá sơ đồ (giữ nguyên hành vi của Nemo12).