API лавлах — API Reference
Платформын бүх HTTP endpoint-ийн бүрэн жагсаалт. Backend кодоос
(backend/internal/platform/server.go,
platform_admin.go ба модуль бүрийн
RegisterRoutes) гаргаж авсан.
Ерөнхий зарчим
- Base URL:
/api/v1(health, metrics, OAuth2, OIDC discovery-гээс бусад). - Танилт: session токен
session_tokenHttpOnly cookie эсвэлAuthorization: Bearer <token>толгойгоор дамжина. Cookie эхэлж шалгагдана. - Хэл:
Accept-Language: mn|enтолгой (эсвэл?lang=) — цэс, апп сторын текст серверээс орчуулагдаж ирнэ. Анхдагчmn. - Алдааны формат:
{"error": "message"}. Gov Services модуль нэмэлт тогтвортой машин код буцаана:{"error": "...", "code": "CONFLICT_VERSION"}. - Апп хаалт (app gate): модулийн маршрут бүр тухайн
тенантад апп суулгагдаж идэвхжсэн эсэхийг шалгана — үгүй бол
403 Forbidden. - Эрхийн автомат зурагдал: ихэнх модульд
GET/HEAD → <prefix>.read, бусад арга →<prefix>.manage. Админ эрхтэй хэрэглэгч бүх эрхийн шалгалтыг алгасна.
1. Нээлттэй endpoint-ууд (танилтгүй)
| Арга | Зам | Тайлбар |
|---|---|---|
| GET | /health |
Амьд байдал — үргэлж {"status":"ok"} |
| GET | /ready |
Бэлэн байдал — DB ping; холбогдохгүй бол 503 |
| ANY | /metrics |
Prometheus хэмжүүр. METRICS_TOKEN тохируулсан бол
Authorization: Bearer <token> шаардана (эс бөгөөс
нээлттэй) — DEPLOYMENT_GUIDE §7 |
| GET | /.well-known/openid-configuration |
OIDC discovery |
| GET | /.well-known/jwks.json |
id_token-ыг шалгах RS256 нийтийн түлхүүрүүд. Access token нь opaque
хэвээр — түүнийг /oauth2/introspect-ээр шалгана |
| GET | /oauth2/auth |
Authorization code урсгалын эхлэл (RFC 6749 §4.1.1). Session cookie
өөрөө уншина: нэвтрээгүй бол /auth/sign-in, workspace-гүй
бол /workspaces, зөвшөөрөл дутуу бол
/auth/consent руу чиглүүлнэ. redirect_uri
яг тааруулна; code_challenge +
code_challenge_method=S256 заавал. Client
эсвэл redirect шалгагдаагүй үеийн алдааг тэр redirect руу буцаахгүй
(open redirect) |
| GET · POST | /oauth2/userinfo |
OIDC UserInfo.
Authorization: Bearer <access_token>; буцаах claim нь
олгогдсон scope-оор хязгаарлагдана (openid шаардлагатай).
Хоёр арга нь ижил handler — OIDC Core §5.3 хоёуланг зөвшөөрдөг |
| POST | /oauth2/token |
authorization_code, refresh_token,
client_credentials grant; Basic эсвэл form client танилт.
Public client (token_endpoint_auth_method=none) нууц
үггүйгээр эхний хоёрыг л ашиглана. Код нэг удаагийн — дахин үзүүлбэл
түүнээс гарсан бүх refresh хэлхээ устана; refresh нь эргэлддэг ба дахин
ашиглалт хэлхээг алдана |
| POST | /oauth2/introspect |
RFC 7662 — client танилт шаардана |
| POST | /oauth2/revoke |
Токен цуцлах — client танилт шаардана |
| GET · POST | /api/v1/oauth2/consent |
Зөвшөөрлийн дэлгэцийн хос: GET нь апп юу хүсэж байгааг (нэр + scope)
хэлнэ, POST нь {params, approved} шийдвэрийг бүртгээд
{redirect_to} буцаана. Session шаардана; POST нь CSRF
хамгаалалтын дор |
| POST | /api/v1/auth/login |
{identifier, password} — identifier нь
и-мэйл эсвэл гар утасны дугаар; сервер өөрөө таана.
Бичсэн сувгаа баталгаажуулсан байх ёстой. Session нь ямар ч
workspace-д хамаарахгүй нээгдэнэ (tenant_id: "") —
workspace-ийг /auth/switch-workspace олгоно. Хуучин
email талбар мөн ажиллана. Rate limit: 5/мин, burst 5 (IP
тус бүр). Баталгаажаагүй бол 403 EMAIL_NOT_VERIFIED /
403 PHONE_NOT_VERIFIED. Google/E-ID-гээр үүссэн бүртгэлд
нууц үг байхгүй тул 401 |
| POST | /api/v1/auth/eid/start |
E-ID хүсэлт илгээх (reg_number) →
{session_id, verification_code}. Иргэн өөрийн E-ID апп дээр
энэ 4 оронтой кодыг шалгаж PIN1 оруулна. Холбоогүй регистрт push
илгээхгүй (403) — эс бөгөөс хэн ч танихгүй хүний утсанд
мэдэгдэл цохиж чадна |
| POST | /api/v1/auth/eid/qr/start |
QR / App2App нэвтрэлт эхлүүлэх (регистр шаардахгүй) →
{session_id, verification_code, device_link_url}. Хэн
нэгнийг оноодоггүй тул холбоосын шалгалтгүй — шалгалт нь
complete дээр хэвээр |
| POST | /api/v1/auth/eid/complete |
Session-ийг poll хийх (session_id) — QR ба регистрийн
аль ч урсгалд нийтлэг → {"state":"RUNNING"} эсвэл
{state:"COMPLETE", token, user, identity}; иргэн татгалзвал
401. Танилт баталгаажсан ч ERP бүртгэлд холбогдоогүй бол 403 +
{"code":"IDENTITY_NOT_LINKED"} — энэ нь танилтын
алдаа биш, дахин оролдоод шийдэгдэхгүй тул UI нь холбох алхмуудыг
харуулна |
| POST | /api/v1/auth/dan/login |
ДАН гарц (одоогоор mock горим л ажиллана) |
| POST | /api/v1/auth/logout |
Session-ийг сервер талд цуцалж cookie арилгана |
| POST | /api/v1/auth/register |
Өөрөө бүртгүүлэх:
{email │ phone, password, password_confirmation} —
яг нэг танигч. Нэр, workspace асуухгүй: нэр нь
танигчаас гарна (дараа засна), workspace нь нэвтэрсний дараах сонгогчийн
ажил. Нууц үгийн бодлого сервер талд: 10+ тэмдэгт (rune-ээр), төрлийн
оноо, түгээмэл жагсаалт → 400 WEAK_PASSWORD /
400 PASSWORD_MISMATCH. И-мэйл бол баталгаажуулах холбоос,
утас бол SMS код явна; session олгохгүй. Rate limit: 5/мин |
| POST | /api/v1/auth/register/phone/confirm |
Утсаар хийсэн бүртгэлийг дуусгана: {phone, code}. Зөв
код нь дугаарыг баталгаажуулж workspace-гүй session
нээнэ — дараагийн дэлгэц нь сонгогч. Нэвтрэлтгүй ажилладаг (данс сувгаа
хараахан батлаагүй тул session байхгүй); аюулгүйг нэг удаагийн код + 5
оролдлогын хязгаар хангана |
| GET | /api/v1/auth/verify?token= |
Баталгаажуулах холбоосыг хэрэглэнэ. Нэг удаагийн, 24 цагийн
хугацаатай. Хугацаа дууссан / хэрэглэсэн / байхгүй бүгд ижил
400 {"code":"INVALID_VERIFICATION_TOKEN"} — амьд токен хайх
боломжгүй |
| GET | /api/v1/auth/providers |
Энэ deploy ямар нэвтрэлт санал болгож байгааг хэлнэ
(password, eid, google). Google
нь credential бүртгэгдсэн үед л true — ажиллахгүй товч
харуулахаас сэргийлнэ |
| GET | /api/v1/auth/google/start |
Google-ийн зөвшөөрлийн дэлгэс рүү 302. ?next= нь зөвхөн
энэ аппын зам байж болно (open redirect хамгаалалт). State нь
санд хадгалагдана — replica хооронд callback таарах
ёстой |
| GET | /api/v1/auth/google/callback |
Google-ийн буцаах цэг. Амжилттай бол session cookie тавиад
/auth/callback руу 302; алдаа бүр
/auth/sign-in?error=<code> руу очно (redirect-ээр
ирсэн browser JSON body уншиж чадахгүй). Session токен хэзээ
ч URL-д ордоггүй |
| POST | /api/v1/auth/eid/register |
Баталгаажсан боловч данс байхгүй E-ID session-ыг данс болгоно:
{registration_ticket, email}. Регистрийн дугаарыг
ticket-ээс авна — иргэн зөвхөн хаягаа сонгоно, хэн
болохоо биш. Хэрэглэгч + identity link нэг гүйлгээнд үүсээд
workspace-гүй session олгоно. Хаяг өөр дансанд байвал
409 EMAIL_TAKEN |
| POST | /api/v1/auth/verify/resend |
Шинэ холбоос илгээнэ. Хаяг байгаа эсэхээс үл хамааран үргэлж 200 — эс бөгөөс бүртгэл тандах хэрэгсэл болно |
| GET | /api/v1/auth/invitations?token= |
Урилгын холбоос юуг заасныг нэвтрэхээс өмнө уншина →
{workspace_name, invited_by, role}. Хугацаа дууссан /
ашигласан / цуцлагдсан / байхгүй бүгд ижил
404 INVALID_INVITATION. Rate limit — токены орон зайг
шүүрдэж болохгүй |
auth_method утгууд: EID_PIN1 (танилтын
session — иргэнийг таних) ба EID_PIN2 (гарын үсгийн
session). Хуучин PKI_DIGITAL_SIGNATURE /
MOBILE_OTP / BANK_SSO /
BIOMETRIC_FACE жагсаалт устсан — RP API
v3-д сувгийн сонголт байхгүй, иргэн апп дотроо PIN/биометрээ өөрөө
сонгоно (backend/internal/platform/eid/eid.go).
2. Танилттай платформ endpoint-ууд
2.1 Хэрэглэгч ба цэс
| Арга | Зам | Нэмэлт шаардлага | Тайлбар |
|---|---|---|---|
| GET | /api/v1/auth/me |
— | Хэрэглэгчийн мэдээлэл + is_admin +
permissions[] |
| POST | /api/v1/auth/identity/link/start |
— | E-ID холбох хүсэлт эхлүүлэх (provider:"eid",
reg_number) →
{session_id, verification_code} |
| POST | /api/v1/auth/identity/link |
— | Холбоог дуусгах: E-ID бол {provider:"eid", session_id}
(poll), ДАН бол нэг алхамт. Өөр хэрэглэгчид холбогдсон регистр →
409 |
| GET | /api/v1/auth/identity/links |
— | Өөрийн холбосон identity-үүд (регистр маскалсан) |
| DELETE | /api/v1/auth/identity/links/{id} |
— | Өөрийн холбоосоо салгах. Өөр хүний холбоос 404 (403
биш — байгаа эсэхийг хэлэхгүй). Салгавал нэвтрэх арга үлдэхгүй бол
409 LAST_SIGN_IN_METHOD — эхлээд нууц үг
эсвэл баталгаажсан утас хэрэгтэй |
| GET | /api/v1/menus |
— | Тенантад идэвхтэй цэс; админ бус хэрэглэгчид эрхээр шүүгдэнэ |
| POST | /api/v1/auth/phone/start |
— | {phone} — дугаарыг бүртгэж 6 оронтой код SMS-ээр
илгээнэ. Дугаар баталгаажаагүй хэвээр бүртгэгдэнэ. Өөр
бүртгэл түүнийг баталгаажуулсан бол
409 PHONE_TAKEN; зөвхөн баталгаажаагүй нэхэмжлэл нь
блоклохгүй (эзэнг нь түгжихээс сэргийлнэ). Нэвтэрсэн байх шаардлагатай —
эс бөгөөс дурын дугаар руу SMS цохих хэрэгсэл болно |
| POST | /api/v1/auth/phone/confirm |
— | {code} — зөв бол phone_verified_at
тавигдана. Код 10 минут, 5 удаа буруу оролдвол шатаана.
Хугацаа дууссан / ашигласан / буруу бүгд ижил хариу |
| POST | /api/v1/auth/phone/resend |
— | Бүртгэлтэй дугаар руу шинэ код. Өмнөх амьд кодууд цуцлагдана — эс бөгөөс resend бүр таах талбайг үржүүлнэ |
| GET | /api/v1/account/score |
— | Бүртгэл хэдэн хувь баталгаажсаныг буцаана:
{score, complete, steps[]}. Гурван нотолгоо —
email, phone, eid —
тэнцүү жинтэй, тус бүр 1/3. Оноо 0 / 33 / 67 / 100.
complete нь зөвхөн гурвуулаа биелсэн үед true.
Нэвтэрсэн байх шаардлагатай: аль суваг нь баталгаажсаныг нэрлэсэн данс
тухайд нь задлан хэлэх нь нээлттэй байх ёсгүй мэдээлэл |
| GET | /api/v1/workspaces |
— | Харьяалагдах workspace-ууд + owned,
owned_limit, can_create. Идэвхтэйг нь
active: true заана |
| POST | /api/v1/workspaces |
— | Шинэ workspace үүсгэх. Нэг хэрэглэгч дээд тал нь 2
үүсгэнэ (урилгаар нэгдэх нь хязгааргүй) → хэтэрвэл
403 {"code":"WORKSPACE_LIMIT_REACHED"} |
| POST | /api/v1/auth/switch-workspace |
— | {workspace_id} — сонгогчийн цөм:
workspace-гүй session-д tenant олгох цорын ганц зам. Гишүүнчлэл нь эрх —
гишүүн бус бол 403. Шинэ session олгож, хуучныг цуцална |
| POST | /api/v1/auth/invitations/accept |
— | {token} — урилгыг гишүүнчлэл болгоно.
Workspace-гүй session дээр зориуд ажиллана: энэ урилгын
улмаас дөнгөж бүртгүүлсэн хүнд workspace байхгүй, workspace шаардвал яг
хүлээн авагчийг нь хаах байв. Токеныг шатаах ба гишүүнчлэл үүсгэх нь
нэг гүйлгээнд — эс бөгөөс шатсан токен хүнийг гацаана,
эсвэл холбоосыг олсон хэн ч дахин ашиглана. Аль хэдийн гишүүн бол
409 ALREADY_MEMBER; хүчингүй токен
404 INVALID_INVITATION |
| GET | /api/v1/workspace/invitations |
админ | Тухайн workspace-ийн урилгууд |
| POST | /api/v1/workspace/invitations |
админ | {email │ phone, role} — яг нэг суваг
(хоёуланг нь эсвэл аль нь ч биш бол 400). Утас нь монгол
гар утасны дугаар болж хэвийшинэ (400 INVALID_PHONE). Урих
нь эрх олгох үйлдэл тул requireAdmin-ий цаана |
| DELETE | /api/v1/workspace/invitations/{id} |
админ | Урилгыг цуцлах |
2.2 Апп стор
| Арга | Зам | Нэмэлт шаардлага | Тайлбар |
|---|---|---|---|
| GET | /api/v1/store/apps |
— | Каталог + тенантын суулгалтын төлөв
(installed/enabled) |
| GET | /api/v1/store/apps/{slug} |
— | Нэг аппын дэлгэрэнгүй |
| GET | /api/v1/installed-apps |
— | Суулгагдсан аппуудын жагсаалт |
| POST | /api/v1/store/apps/{slug}/install |
админ | Хамаарлын дарааллаар суулгаж, эрхүүдийг role-уудад олгоно.
Гадаад апп бол manifest дэх event захиалгыг мөн адил
транзакцад үүсгэж, гарын үсгийн нууцыг webhook_secret-ээр
нэг л удаа буцаана (дахин уншигдахгүй — аппын зохиогч
руу дамжуулах цорын ганц зам нь суулгасан админ) |
| POST | /api/v1/store/apps/{slug}/enable |
админ | Идэвхжүүлэх — гадаад аппын захиалга дахин идэвхжинэ |
| POST | /api/v1/store/apps/{slug}/disable |
админ | Идэвхгүй болгох — гадаад аппын захиалга унтарна (мөр нь үлдэнэ, нууц нь хэвээр) |
2.3 Эрхийн удирдлага (админ)
| Арга | Зам | Тайлбар |
|---|---|---|
| GET | /api/v1/admin/access/overview |
Role, permission, гишүүдийн нэгдсэн тойм |
| POST | /api/v1/admin/access/roles |
Role үүсгэх (^[a-z][a-z0-9_.-]{1,62}[a-z0-9]$ код) |
| PUT | /api/v1/admin/access/roles/{id} |
Role шинэчлэх (system role идэвхгүй болгохгүй — 409) |
| DELETE | /api/v1/admin/access/roles/{id} |
Role устгах (system role устгахгүй — 409) |
| PUT | /api/v1/admin/access/roles/{id}/permissions |
Эрхүүд солих (admin role-д хориотой) |
| PUT | /api/v1/admin/access/memberships/{id}/roles |
Гишүүний role-ууд; сүүлчийн админыг хасахыг хориглоно (409) |
Өөрчлөлт бүр access_change_events хүснэгтэд before/after
JSON-той бүртгэгдэнэ.
2.4 AI pipeline
Rate limit: 20/мин, burst 10 (IP тус бүр).
| Арга | Зам | Нэмэлт шаардлага | Тайлбар |
|---|---|---|---|
| POST | /api/v1/ai/chat |
— | {prompt, lang, history} — Gemini + тенантын өгөгдөлд
суурилсан tool call (max 4 round). Gemini тохируулаагүй бол
degraded:true fallback |
| POST | /api/v1/ai/copilot |
— | Хуучин copilot endpoint (/ai/chat-аар солигдсон) |
| POST | /api/v1/ai/stt |
— | Ярианаас текст (webm/ogg/wav/mp4/mpeg, base64 ≤950k тэмдэгт) |
| POST | /api/v1/ai/tts |
— | Текстээс яриа — WAV base64 (анхдагч хоолой Kore) |
| POST | /api/v1/ai/translate |
— | Орчуулга (mn/en/ru/zh/ko/ja) |
| GET | /api/v1/ai/stock-forecast |
— | Агуулахын нөөцийн энгийн таамаглал |
| GET | /api/v1/admin/ai/prompts |
админ | System prompt-ууд (scope,
instructions) |
| PUT | /api/v1/admin/ai/prompts/{key} |
админ | Prompt шинэчлэх (тенант түвшний override) |
| GET | /api/v1/admin/ai/knowledge |
админ | Мэдлэгийн сан жагсаах |
| POST | /api/v1/admin/ai/knowledge |
админ | Мэдлэгийн сан руу бичлэг нэмэх |
Алдааны семантик (writeAIError,
server.go):
| Нөхцөл | Статус | Бие |
|---|---|---|
Дуудагчийн буруу (ai.ErrInvalidInput — аудио формат,
хэмжээ, base64) |
400 |
Тодорхой шалтгаан |
Gemini тохируулаагүй (GEMINI_API_KEY хоосон) |
502 |
the AI assistant is not configured on this deployment |
Түр зуурын доголдол (gemini.ErrUnavailable — сүлжээ,
5xx, quota дуусах) |
503 + Retry-After: 30 |
the AI assistant is busy; try again shortly |
| Провайдерын бусад татгалзал (буруу түлхүүр, safety block) | 502 |
the AI assistant is temporarily unavailable |
Провайдерын түүхий хариултыг хэзээ ч клиент рүү
дамжуулахгүй — дэлгэрэнгүйг серверийн лог
(AI request failed upstream) агуулна. Өмнө нь бүх алдаа
400 болж, Google-ийн алдааны биеийг хуулж илгээдэг байсан:
буруу түлхүүр нь хэрэглэгчийн асуулт буруу мэт харагддаг байв.
503 ба 502-ын ялгаа нь «хэнийх нь буруу вэ»
биш, «дахин оролдох нь тус болох уу»: 503
бол буц гэсэн урилга (gemini клиент backoff-той аль хэдийн оролдсон),
502 бол хүн засах ёстой зүйл. Quota дуусахыг 429
болгодоггүй нь зориуд — 429 нь «та хэт олон удаа асуулаа» гэсэн
үг, харин түлхүүр нь байрлуулалтынх бөгөөд бүх tenant хуваалцдаг тул нэг
хүнд өөр хүний хэрэглээний талаар худал хэлэх болно.
/ai/chat нь Gemini-д хүрэхгүй үед алдаа
биш, degraded:true бүхий локал fallback
буцаана — UI үүнийг тэмдэглэж харуулна.
2.5 ХУР (XYP) ба интеграц
| Арга | Зам | Нэмэлт шаардлага | Тайлбар |
|---|---|---|---|
| POST | /api/v1/xyp/citizen |
— | Иргэний бүртгэл (WS100101); live горимд XYP_API_KEY
шаардана |
| POST | /api/v1/xyp/company |
— | Хуулийн этгээд (WS100201) |
Webhook интеграц (/integrations, бүгд
зөвхөн админ, тенантад хамааралтай тэсвэртэй webhook —
хуучин in-memory connector manager устсан):
| Арга | Зам | Тайлбар |
|---|---|---|
| GET | /api/v1/integrations |
Бүртгэлтэй webhook-уудын жагсаалт →
{integrations: [{id, name, target_url, event_types[], active, created_at}]}
(шинэ нь эхэнд). Нууц буцаагдахгүй |
| POST | /api/v1/integrations |
Бүртгэх: {name, target_url, secret?, event_types[]}.
Амжилттай бол 201 + үүсгэсэн endpoint.
target_url http(s) биш эсвэл хувийн/loopback хост руу
заавал 400 (SSRF хамгаалалт — бүртгэх үед нэг удаа, дараа
нь dial-time дээр дахин шалгагдана). event_types-д
* = бүх төрлийг авна |
| DELETE | /api/v1/integrations/{id} |
Устгах → {"status":"deleted"}; тенантад тухайн endpoint
байхгүй бол 404. Устгахад түүний хүргэлтийн лог cascade-аар
устана |
| GET | /api/v1/integrations/{id}/deliveries |
Хүргэлтийн лог (сүүлийн 50) →
{deliveries: [{id, event_type, status, attempts, response_status?, last_error?, created_at, delivered_at?}]} |
secret нь write-only — бүртгэх body-д л
илгээгдэнэ, GET хариултад хэзээ ч буцаагдахгүй. Хүргэлт бүр
X-ERP-Event толгойд төрлөө, X-ERP-Signature
толгойд нууцаар тооцсон HMAC-SHA256 гарын үсгээ (hex)
авч явна — хүлээн авагч тал үүгээр эх сурвалжийг баталгаажуулна.
Амжилтгүй хүргэлт backoff-той дахин оролдож, 6 удаа бүтэлгүйтсэний дараа
failed төлөвт last_error-той үлдэнэ.
2.6 Платформын консол (superadmin)
Платформыг ажиллуулдаг хүмүүсийн гадаргуу — tenant-ийн админаас
дээш, өөр эрх мэдэл. requirePlatformAdmin
хаалганы ард (tenant админ энд 403 авна) ба
accountScope бүлэг дотор байрлана: оператор ямар ч
workspace-д харьяалагдахгүй байж болох тул хүсэлтүүд tenant-гүй
ажиллана.
| Арга | Зам | Тайлбар |
|---|---|---|
| GET | /api/v1/platform/tenants |
Бүх байгууллага: хэрэглэгч/апп тоо, төлөв, schema provision хийгдсэн эсэх |
| GET | /api/v1/platform/tenants/{id} |
Дэлгэрэнгүй + гишүүдийн жагсаалт |
| POST | /api/v1/platform/tenants/{id}/suspend ·
/activate |
Түдгэлзүүлэх / сэргээх. Түдгэлзсэн workspace-д доторх session
TENANT_SUSPENDED кодоор татгалзана, switch-workspace босгон
дээрээ хаагдана. Хоёулаа audit-тай |
| POST | /api/v1/platform/tenants/{id}/provision |
Tenant schema-г шаардлагаар үүсгэх (idempotent) |
| POST | /api/v1/platform/tenants/{id}/impersonate |
{reason} заавал. 30 минутын түр session нээж, оператор
тухайн workspace дотор админ эрхтэй ажиллана. Session нь
tenant_id-гүй, impersonated_tenant_id-тай;
оператор эрхээ алдвал тэр дороо хүчингүй болно |
| GET | /api/v1/platform/tenants/{id}/export |
Байгууллагын бүх бизнес мөрийг нэг JSON-оор. Schema-гүй tenant-д
409 — эхлээд provision (дээрх мөр) |
| GET | /api/v1/platform/users |
Хэрэглэгчид (?q= хайлт), платформ админ
тэмдэглэгээтэй |
| GET | /api/v1/platform/audit |
Аудитын мөр, ?limit=&offset= |
| GET | /api/v1/platform/health |
Тоонууд (tenant/хэрэглэгч/session/хүлээгдэж буй эвент) + үндэсний
холболтуудын mock/live төлөв + /metrics-ийн зам |
| GET | /api/v1/platform/apps |
Аппууд, суулгалтын тоотой |
Эхний операторыг PLATFORM_ADMIN_EMAIL орчны хувьсагчаар
олгоно (данс нь өмнө нь бүртгэгдсэн байх ёстой; зөвхөн нэмнэ, хэзээ ч
хасахгүй).
3. Бизнес модулиудын endpoint-ууд (апп хаалттай)
3.1 Contacts
(io.example.contacts)
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/contacts/ |
contacts.read |
Жагсаалт (нэрээр эрэмбэлсэн) |
| POST | /api/v1/contacts/ |
contacts.manage |
Үүсгэх — name заавал |
| PUT | /api/v1/contacts/{id} |
contacts.manage |
Шинэчлэх — олдохгүй бол 404 |
| GET | /api/v1/contacts/{id}/attachments |
contacts.read |
Харилцагчид хавсаргасан баримтууд, шинэ нь эхэнд. Файлын сан
суулгаагүй бол алдаа биш, [] — сонголттой
апп байхгүй нь бичлэгийн доголдол биш. Мөр бүр
{id, state, attached_by, attached_at, owner_type, owner_id}
ба state нь available үед л
file_id, name, mime, size_bytes, version нэмэгдэнэ.
state=trashed нь файл хогийн саванд байгааг (нэр нь
хэвээр), state=restricted нь дуудагч тэр файлыг унших
эрхгүйг хэлнэ — тэр мөрөнд нэр ч, id ч
илгээгдэхгүй |
| POST | /api/v1/contacts/{id}/attachments |
contacts.manage |
{file_id} → 201 + хавсралт. Файл нь
файлын санд байгаа байх ёстой — энэ зам ачаалалт
хийхгүй. 404 нь «тэр файл боломжгүй» гэсэн ганц утга:
байхгүй, хогийн саванд, эсвэл дуудагчид нээгдээгүй — гурвыг
зориуд ялгахгүй. Файлын сан суулгаагүй бол
501 |
| DELETE | /api/v1/contacts/{id}/attachments/{attachmentID} |
contacts.manage |
Заагчийг салгана → 204. Файл өөрөө
хөндөгдөхгүй — өөр бичлэг дээрх хавсралтууд нь ч хэвээр. Тэр
бичлэг дээр байхгүй заагч → 404 |
3.2 Products
(io.example.products)
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/products/ |
products.read |
Жагсаалт |
| POST | /api/v1/products/ |
products.manage |
Үүсгэх — sku+name заавал;
tax_class_code сонголттой; давхардсан SKU эсвэл байхгүй
татварын анги → 409 |
| PUT | /api/v1/products/{id} |
products.manage |
Шинэчлэх |
| GET | /api/v1/products/tax/classes |
products.read |
Тенантын татварын ангиуд →
[{code, name, treatment, rate_bp}]. treatment
нь standard/zero_rated/exempt,
rate_bp нь basis point (1000 = 10%). Эхний
хэрэглээнд гурван анги (VAT10, ZERO,
EXEMPT) залхуу байдлаар суулгагдана |
| GET | /api/v1/products/tax/settings |
products.read |
Workspace-ийн үнийн суурь →
{prices_include_tax, default_class_code} |
| PUT | /api/v1/products/tax/settings |
products.manage |
Тэрхүү суурийг хадгална; default_class_code заавал
(400), байхгүй анги нэрлэвэл мөн 400 |
Татварын хоёр зам /products дор байрладаг нь энэ модуль
тэдгээрийн дээр internal.TaxCalculator-ыг нийлүүлдэгтэй
холбоотой; хоёр статик сегмент нь /{id}-тэй хэзээ ч
мөргөлдөхгүй. Бүтээгдэхүүн дээрх tax_class_code нь
null байж болно — тэр нь «тенантын анхдагч юу ч бай» гэсэн
үг бөгөөс «чөлөөлөгдсөн» гэсэн нэхэмжлэлээс өөр зүйл.
3.3 Inventory
(io.example.inventory)
Хамаарал: contacts ^1.0.0,
products ^1.0.0.
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/inventory/warehouses |
inventory.read |
Агуулахууд |
| POST | /api/v1/inventory/warehouses |
inventory.manage |
Үүсгэх — давхардсан код → 409 |
| GET | /api/v1/inventory/stock-levels |
inventory.read |
Үлдэгдэл |
| GET | /api/v1/inventory/movements |
inventory.read |
Хөдөлгөөний түүх (append-only) |
| POST | /api/v1/inventory/adjustments |
inventory.manage |
Гүйлгээт тохируулга — SELECT FOR UPDATE, сөрөг үлдэгдэл
хориотой |
3.4 Billing
(io.example.billing)
Хамаарал: contacts ^1.0.0. НӨАТ = 10%.
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/billing/invoices |
billing.read |
Жагсаалт — contact_id (мастер өгөгдлийн холбоос) +
e-Barimt талбарууд (ebarimt_status,
ebarimt_receipt_id, ebarimt_lottery,
ebarimt_qr_data, ebarimt_submitted_at,
ebarimt_last_error) |
| POST | /api/v1/billing/invoices |
billing.manage |
Үүсгэх — contact_id (зөвлөмжит) эсвэл
хуучин contact_name, + эерэг бүхэл
amount (integer MNT, бутархай → 400); НӨАТ = amount/10,
e-Barimt илгээлт background-оор |
| GET | /api/v1/billing/payments |
billing.read |
Төлбөрийн жагсаалт. Шүүлт: ?contact_id=,
?status= (POSTED/CANCELLED),
?unallocated=true (урьдчилгаа —
хуваарилагдаагүй үлдэгдэлтэй төлбөрүүд) |
| POST | /api/v1/billing/payments |
billing.manage |
Төлбөр бүртгэх — contact_id/contact_name,
method
(bank_transfer|qpay|card|cash),
эерэг бүхэл amount, paid_at
заавал (YYYY-MM-DD эсвэл RFC3339), сонголтоор
allocations: [{invoice_id, amount}] — нэг гүйлгээнд
бичигдэнэ |
| POST | /api/v1/billing/payments/{id}/cancel |
billing.manage |
Цуцлах — хуваарилалт бүрийг тайлж, дэвтэрт буцаах бичилт үүсгэнэ. Мөр устахгүй |
| POST | /api/v1/billing/payments/{id}/allocations |
billing.manage |
Дараа нь хуваарилах — {invoice_id, amount} |
| POST | /api/v1/billing/allocations/{id}/reverse |
billing.manage |
Хуваарилалтыг тайлах — сөрөг шинэ мөр, засвар ч устгал ч биш |
| GET | /api/v1/billing/invoices/{id}/allocations |
billing.read |
Тухайн нэхэмжлэхийн хуваарилалтын түүх (хуваарилалт ба тайлалт хоёулаа) |
| POST | /api/v1/billing/invoices/{id}/cancel |
billing.manage |
Цуцлах — дэвтэрт буцаах бичилт, e-Barimt баримтыг
хүчингүй болгоно. Төлбөр хуваарилагдсан бол 409 |
| POST | /api/v1/billing/invoices/{id}/credit-notes |
billing.manage |
Залруулах нэхэмжлэх — amount (НӨАТ-гүй
суурь; орхивол бүтэн нэхэмжлэх), reason. Өөрийн
CN- дугаартай шинэ баримт үүсч, эх нэхэмжлэхэд шууд
хуваарилагдана |
| POST | /api/v1/billing/credit-notes/{id}/allocations |
billing.manage |
Залруулгын үлдэгдлийг өөр нэхэмжлэхэд хуваарилах —
{invoice_id, amount} |
contact_id өгвөл контактыг contacts модулиас ID-гаар
шийдэж, тухайн үеийн нэрийг нэхэмжлэх дээр snapshot
болгон буулгана (дараа нь контакт нэрээ солиход гарсан баримт
өөрчлөгдөхгүй) — энэ үед илгээсэн contact_name үл
тоомсорлогдоно. Өөр тенантын эсвэл байхгүй contact_id →
400. Хоёулаа хоосон бол мөн 400. contact_name дангаараа =
холбоосгүй legacy зам.
Нэхэмжлэхийн хоёр төлөв. status нь
баримт амьд эсэх (POSTED / CANCELLED),
settlement_status нь мөнгө ирсэн эсэх (UNPAID
/ PARTIAL / PAID). Сүүлийнх нь
allocated_amount-аас өгөгдлийн санд гарна — хэн ч гараар
бичихгүй. Урьд нь ганц status хоёуланг үүрэх гэж оролдож,
PENDING гэж бичигдээд хэзээ ч өөрчлөгддөггүй байв.
Цуцлах уу, залруулах уу. Цуцлах нь «энэ баримт огт
байх ёсгүй байсан» — дэвтрийн бичилт буцаагдаж, ДДТД нь ТЕГ дээр
хүчингүй болно. Залруулах нь «байсан, буруу байсан» — тусдаа баримт,
өөрийн дугаартай, эх баримтын ДДТД-г inactiveId-д зааж
бүртгэгдэнэ. Төлбөр хүрсэн нэхэмжлэхийг цуцлах нь 409:
мөнгө нь бодит, хаашаа явахыг хүн шийднэ.
status нь POSTED →
CANCEL_REQUESTED → CANCELLED. Дунд төлөв нь
шийдвэрийн хүлээлт биш — сүлжээний дуудлагын хүлээлт:
дэвтэр аль хэдийн буцаагдсан, ТЕГ-т хэлэх нь дэвсгэрээр давтагдаж байна.
Хариултын ebarimt_status нь дуусахад VOIDED,
бүтэлгүйтвэл ERROR болно.
Гарсан баримт хөлдөнө.
PUT/PATCH байхгүй бөгөөд өгөгдлийн сан ч
татгалздаг: дүн, татварын snapshot, харилцагчийн нэр,
due_date нь гарсны дараа өөрчлөгдөхгүй. Өөрчлөх ганц зам нь
цуцлалт эсвэл залруулга.
Алдааны гурван анги. 404 = ийм мөр
байхгүй; 409 = мөр нь энэ үйлдлийг татгалзах төлөвтэй
(цуцлагдсан төлбөр, аль хэдийн тайлагдсан хуваарилалт, үлдэгдлээс их
дүн); 400 = илгээсэн зүйл хүсэлт биш. Дунд ангид
400 буцаах нь аль хэдийн зөв байсан маягтыг засуулахаар
хэрэглэгчийг буцаана.
Хариултын мөнгөн дүнгүүд бүгд integer MNT: amount нь
НӨАТ-гүй суурь, vat_amount = amount/10 (бүхэл хуваалт),
e-Barimt баримтын нийт дүн нь amount + vat_amount
(PosAPI-гийн нийлбэр татвар шингэсэн байдаг).
3.5 Documents
(io.example.documents)
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/documents/ |
documents.read |
Жагсаалт |
| POST | /api/v1/documents/ |
documents.manage |
Үүсгэх — төрөл:
CONTRACT/REQUEST/APPROVAL |
| GET · POST | /api/v1/documents/templates |
documents.read · .manage |
Загварын жагсаалт · үүсгэх |
| PUT · DELETE | /api/v1/documents/templates/{id} |
documents.manage |
Загвар засах · устгах |
| POST | /api/v1/documents/templates/{id}/use |
documents.manage |
Загвараас баримт үүсгэх |
| GET · PUT | /api/v1/documents/policies[/{docType}] |
documents.read · .manage |
Гарын үсгийн бодлого (нэрлэсэн гарын үсэг шаардах эсэх, eID/ДАН зөвшөөрөх) |
| GET · PUT | /api/v1/documents/workflows[/{docType}] |
documents.read · .manage |
Батламжийн гинж (алхам, дараалал, нэрлэсэн гарын үсэг зурагч) |
| GET · PUT | /api/v1/documents/retention[/{docType}] |
documents.read · .manage |
Хадгалалтын хугацааны дүрэм |
| GET | /api/v1/documents/{id}/signatures ·
/steps |
documents.read |
Тухайн баримтын гарын үсгүүд · батламжийн алхмууд |
| POST | /api/v1/documents/{id}/route ·
/reject |
documents.manage · documents.sign |
Дараагийн алхам руу шилжүүлэх · татгалзах |
| POST | /api/v1/documents/{id}/sign/eid/start ·
/sign/eid/poll |
documents.sign |
eID гарын үсгийн ёслол эхлүүлэх · төлөв асуух |
| POST | /api/v1/documents/{id}/sign/dan |
documents.sign |
ДАН-аар гарын үсэг зурах |
3.6 Developer
Portal (io.example.developer_portal)
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/developer/apps/ |
developer.read |
OAuth2 client-ууд (нууц үг нуугдсан) |
| POST | /api/v1/developer/apps/ |
developer.manage |
Client бүртгэх — нууц зөвхөн энэ 201 хариултад ил гарна.
redirect_uris заавал (хамгийн багадаа нэг,
үнэмлэхүй, fragment-гүй, loopback-аас бусад нь HTTPS).
"public": true бол нууц үггүй client
(token_endpoint_auth_method=none) — PKCE-ээр нотолно,
client_credentials ашиглахгүй |
| PATCH | /api/v1/developer/apps/{client_id} |
developer.manage |
client_name, redirect_uris,
scopes засах. Redirect URI-г бүртгэхтэй яг
ижил дүрмээр шалгана — буруу бол засаж залруулахгүй,
татгалзана. grant_types,
token_endpoint_auth_method засагдахгүй |
| POST | /api/v1/developer/apps/{client_id}/secret |
developer.manage |
Нууцыг сольж шинэчлэх. Шинэ нууц зөвхөн энэ
хариултад. Хуучин нь тэр дор нь үхнэ; аль хэдийн олгогдсон
токенууд хүчинтэй хэвээр. Public client бол 400 |
| POST | /api/v1/developer/apps/{client_id}/disable |
developer.manage |
Client-г идэвхгүй болгох: /oauth2/token,
/oauth2/auth, introspect, revoke бүгд татгалзана, олгогдсон
access ба refresh токен бүгд цуцлагдана. Мөр устахгүй — зөвшөөрөл, түүх
нь үлдэнэ |
| POST | /api/v1/developer/apps/{client_id}/enable |
developer.manage |
Буцааж идэвхжүүлэх. Цуцлагдсан токенууд сэргэхгүй; зөвшөөрөл нь хэвээр тул хэрэглэгч дахин асуугдахгүй |
| DELETE | /api/v1/developer/apps/{client_id} |
developer.manage |
Зөвхөн огт ашиглагдаагүй client устгана. Токен,
refresh токен, зөвшөөрөл, эсвэл authorization code-ийн аль нэг нь байсан
бол 409 — түүхийг устгахын оронд идэвхгүй болгохыг
зөвлөнө |
Client болон токенууд миграц 00015-аас хойш өгөгдлийн
санд хадгалагдана: client-ууд oauth2_clients (нууц
нь client_secret_hash, SHA-256), олгогдсон токенууд
oauth2_tokens (token_hash, SHA-256). Тиймээс
сервер дахин асахад токен хүчинтэй хэвээр, олон replica хооронд
introspection ажиллана.
Дээрх зам бүр дуудагчийн workspace-аар
хязгаарлагдана: өөр workspace-ийн client_id нэрлэвэл байхгүй
client-тэй ижил 404 ирнэ.
catalog/manifests/-ийн kind: "external"
аппуудын client нь ачаалах үед manifest-аас үүсдэг бөгөөд ямар ч
workspace-д харьяалагддаггүй — тиймээс жагсаалтад ч гарахгүй,
дээрх verb-үүдээр ч хөндөгдөхгүй. Тэднийг manifest нь удирдана.
Жагсаалт нь бүртгэсэн workspace-ынхаа client-уудыг л буцаана. Өмнө нь аль ч workspace-ийн гишүүн платформ дээрх бүх client_id ба redirect URI-г уншиж чаддаг байсан — redirect URI бол authorization code хүргэгддэг газар, тэр жагсаалт нь халдагчийн хүсдэг яг тэр газрын зураг юм.
3.7 E-Sign
(io.example.esign)
Хоёр төмөр зам нэг баримтын тавиур дээр: HSM (Gerege eSign төхөөрөмж — синхрон) ба eID Mongolia (иргэний төхөөрөмж PIN2-оор зөвшөөрдөг тул асинхрон: init → poll → download). Зөвхөн eID зам нь хуулийн хүчинтэй (qualified) гарын үсэг гаргадаг.
Баримт:
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/esign/documents |
esign.read |
Баримтын жагсаалт. ?status=, ?q=,
?limit=/?offset=; ?paginated=true
бол {items, total, limit, offset} бүрхүүлээр |
| POST | /api/v1/esign/documents |
esign.manage |
PDF upload (base64, ≤15MB, %PDF- шалгалттай) |
| POST | /api/v1/esign/documents/upload |
esign.manage |
Мөн PDF upload, гэхдээ multipart/form-data — том
файлыг base64-оор 33% үрэлгэн болгохгүй. Хэмжээ хэтэрвэл
413 |
| GET | /api/v1/esign/documents/{id} |
esign.read |
Нэг баримтын мета |
| GET | /api/v1/esign/documents/{id}/download |
esign.read |
?variant=signed → гарын үсэгтэй хувилбар |
| DELETE | /api/v1/esign/documents/{id} |
esign.manage |
Soft-delete: жагсаалтаас гарна, мөр устахгүй |
HSM зам:
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| POST | /api/v1/esign/cert/check |
esign.manage |
Сертификат шалгах — phone_no +
civil_id |
| POST | /api/v1/esign/documents/{id}/sign |
esign.sign |
HSM-ээр гарын үсэг зурах — доорх cert-check заавал |
eID Mongolia зам (esign.sign +
GET-үүдэд esign.read):
| Арга | Зам | Тайлбар |
|---|---|---|
| POST | /api/v1/esign/sign/init |
eID ёслол нээнэ: {document_id} эсвэл multipart PDF.
Холбогдоогүй данс signer_id (регистр) нэрлэнэ —
NO_SIGNER_IDENTITY. Credential-гүй орчинд
503 EID_NOT_CONFIGURED |
| GET | /api/v1/esign/sign/{id} |
Ёслолыг ажиглана: pending → completed/failed/expired/rejected. Сервер талдаа eID-г long-poll хийдэг |
| GET | /api/v1/esign/sign/{id}/download |
Гарын үсэгтэй PDF (зөвхөн completed) |
| POST | /api/v1/esign/sign/{id}/cancel |
Ёслолыг зогсооно — иргэний утас руу дахин дуудахгүй |
| GET | /api/v1/esign/organizations |
Гарын үсэг зурагчийн нэрийн өмнөөс нь зурж болох байгууллагууд (eID-гээс) |
Багц гарын үсэг:
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/esign/batches |
esign.read |
Багцын жагсаалт, хуудаслалттай (анхдагч 25) |
| POST | /api/v1/esign/batches |
esign.manage |
{name, provider, document_ids[]} — багц бүрдүүлэх |
| GET | /api/v1/esign/batches/{id} |
esign.read |
Нэг багц + гишүүдийн төлөв |
| POST | /api/v1/esign/batches/{id}/run |
esign.sign |
Багцыг ажиллуулах. Бүрдүүлэх нь manage, зурах нь
sign — энэ хоёрын хил бол багц дээр ч хэвээр |
| POST | /api/v1/esign/batches/{id}/cancel |
esign.manage |
Багцыг зогсоох |
Лог ба тохиргоо:
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/esign/logs |
esign.read |
Гарын үсгийн лог. Шүүлт: ?action=,
?outcome=, ?provider=,
?document_id=, ?q=, ?from=,
?to=, ?limit=/?offset= |
| GET | /api/v1/esign/logs/export |
esign.read |
Тэрхүү шүүлтийн бүтэн гаргалт. Хуудас биш, гэхдээ хязгаартай хэвээр — нэг тенант хязгааргүй үр дүн урсгаж чадахгүй |
| GET | /api/v1/esign/settings |
esign.read |
Гарын үсгийн байрлал + бодлого |
| PUT | /api/v1/esign/settings/placement |
esign.manage |
Гарын үсэг хуудсан дээр хаана буухыг тохируулна |
| PUT | /api/v1/esign/settings/policy |
esign.manage |
Аль замыг зөвшөөрөхийг тохируулна. eID холболт тохируулаагүй байхад
require_eid тавихыг татгалзана — тэгвэл тенант юу ч зурж
чадахгүй болно |
| POST | /api/v1/esign/settings/hsm/test |
esign.manage |
HSM холболтыг шалгах |
Энэ модуль эрхээ хоёр удаа шалгуулдаг. Платформын
автомат зурагдал (GET/HEAD → esign.read, бусад →
esign.manage, /sign төгсгөлтэй эсвэл
/sign/ агуулсан зам → esign.sign) нь хүсэлтийг
handler хүртэл авчирдаг, ба handler бүр өөрийн шаардлагыг дахин нэрлэдэг
(m.require). Үр дүнгийн шаардлага нь хоёрын
нэгдэл — жишээ нь GET /sign/{id}/download нь
автомат зурагдлаас esign.read, handler-ээс
esign.sign шаардана.
Хуулийн хүчинтэй гарын үсэг зурах нь баримт байршуулахаас нарийн эрх
— PDF оруулж чадсан хүн бүр тенантын нэрийн өмнөөс гарын үсэг зурж
болохгүй. manager role нь суулгах үедээ
esign.sign авдаг; өмнө нь esign.manage-тай
байсан role-уудад миграц 00016 буцаан олгосон
(appRequestPermission, server.go).
Гарын үсэг зурахын өмнө сертификат шалгах нь заавал.
/sign нь ижил tenant + хэрэглэгч + phone_no +
civil_id дээр сүүлийн 10 минутын дотор
амжилттай хийгдсэн /cert/check бичлэгийг
(esign_cert_checks) шаардана. Гарын үсэг зурагчийн
нэр/регистр нь тэр шалгалтын SubjectDN-ээс авагдана — client-ээс ирсэн
нэр/регистр огт хэрэглэгдэхгүй.
| Статус | Код | Хэзээ |
|---|---|---|
| 428 Precondition Required | CERT_CHECK_REQUIRED |
Хүчинтэй (≤10 мин) cert-check байхгүй |
| 409 Conflict | ALREADY_SIGNED_OR_IN_PROGRESS |
Баримт аль хэдийн гарын үсэгтэй, эсвэл зэрэгцээ өөр хүсэлт түүнийг барьж авсан (atomic claim) |
| 404 | — | Баримт тухайн тенантад олдсонгүй |
3.8 Gov Services
(io.example.gov_services)
Хамаарал: contacts ^1.0.0. Бүх зам
/api/v1/gov доор. Эрх нь handler бүрт тусад нь шалгагдана
(автомат зурагдал үйлчлэхгүй). Төлөвийн машин, шилжилтийн дэлгэрэнгүйг
GOV_SERVICES_WORKFLOW.md-аас
үзнэ үү.
| Арга | Зам | Эрх |
|---|---|---|
| GET | /api/v1/gov/services |
gov.read |
| POST | /api/v1/gov/services |
gov.configure |
| PUT | /api/v1/gov/services/{id}/configuration |
gov.configure |
| GET | /api/v1/gov/units |
gov.read |
| GET | /api/v1/gov/units/tree |
gov.read |
| POST | /api/v1/gov/units |
gov.configure |
| POST | /api/v1/gov/units/members |
gov.configure |
| GET | /api/v1/gov/workflow-templates |
gov.read |
| GET | /api/v1/gov/workflows |
gov.read |
| POST | /api/v1/gov/workflows |
gov.configure |
| GET | /api/v1/gov/workflow-versions/{id} |
gov.read |
| POST | /api/v1/gov/workflow-versions/{id}/publish |
gov.configure |
| GET | /api/v1/gov/routing-rules |
gov.read |
| POST | /api/v1/gov/routing-rules |
gov.configure |
| POST | /api/v1/gov/requests/ingest |
gov.apply |
| GET | /api/v1/gov/requests/{id} |
gov.read + нэгжийн хамрах хүрээ |
| GET | /api/v1/gov/tasks |
gov.read + нэгжийн хамрах хүрээ |
| POST | /api/v1/gov/tasks/{id}/actions |
Шилжилт бүрийн өөрийн эрх |
| GET | /api/v1/gov/dashboard |
gov.report |
| GET | /api/v1/gov/outbox |
gov.configure |
| GET | /api/v1/gov/connectors |
gov.configure |
| POST | /api/v1/gov/connectors |
gov.configure |
| GET | /api/v1/gov/applications |
gov.read |
| POST | /api/v1/gov/applications |
gov.apply |
| GET | /api/v1/gov/applications/{id}/timeline |
gov.read |
| POST | /api/v1/gov/applications/{id}/cancel |
gov.apply (CANCEL шилжилтийн эрх) |
| GET | /api/v1/gov/appointments |
gov.read |
| POST | /api/v1/gov/appointments |
gov.apply |
| GET | /api/v1/gov/officer/queue |
gov.read |
| POST | /api/v1/gov/officer/queue/{id}/decide |
Шилжилт бүрийн өөрийн эрх |
Шилжилтийн эрхүүд (/tasks/{id}/actions,
/officer/queue/{id}/decide,
/applications/{id}/cancel — workflow.go-гийн
baseTransitions):
| Үйлдэл | Шаардах эрх |
|---|---|
ASSIGN, START, COMPLETE,
CLOSE, REQUEST_INFO, REJECT |
gov.process |
DELEGATE |
gov.delegate |
VERIFY, RETURN |
gov.verify |
CANCEL |
gov.apply |
REQUEST_INFO, REJECT, RETURN
нь comment заавал шаардана.
Gov Services-ийн алдааны машин кодууд (HTTP
статустай хамт): ROW_VERSION_REQUIRED (400),
CONFLICT_VERSION (409), IDEMPOTENCY_CONFLICT
(409), TARGET_UNIT_REQUIRED (400), мөн 401/403/404-т
зурагдсан бусад кодууд. Төлөв өөрчлөх бүх хүсэлт body-доо
row_version заавал агуулна (optimistic concurrency).
Дээд системийн connector
(/gov/connectors). Хүсэлт ingest хийгдэхдээ
source_system ба external_request_id авч
явдаг; түүнээс хойшхи төлөвийн өөрчлөлт бүр тэр
source_system-ийн нэрлэсэн connector руу хаяглагдан
gov_delivery_outbox-д дараалалд орж, dispatcher нь гарын
үсэг зурж, дахин оролдох шаттай хүргэнэ.
| Арга | Зам | Бие / хариулт |
|---|---|---|
| GET | /api/v1/gov/connectors |
Тенантын connector-ууд, source_system-оор эрэмбэлсэн.
Идэвхгүйг нь мөн буцаана — унтраасан хаягийг дахин
асаахын тулд эхлээд харах ёстой |
| POST | /api/v1/gov/connectors |
{source_system, target_url, secret_ref?, active?} →
201 + connector. active нь анхдагчаар
true |
(tenant_id, source_system) нь unique тул POST нь
upsert: дахин бүртгэх нь хаягийг ЗӨӨХ арга бөгөөд
операторын бодитоор хийдэг үйлдэл ч тэр. Хоёр идэвхтэй мөр байвал
төлөвийн мэдэгдэл аль хаяг руу очих нь планнерын дараалалаас хамаарах
байв.
target_url нь үнэмлэхүй http эсвэл
https байх ёстой (INVALID_INPUT), эс
бөгөөс сервер дуудагчийн сонгосон зүйлийг татаж авах хэрэгсэл болно.
secret_ref нь гарын үсгийн нууцын нэр
бөгөөд нууц өөрөө биш — энэ дэлгэцийг уншиж буй хүн өөрийн тохиргоогоо
шалгах ёстой тул буцаагдана. Нууц утга нь процессоос гардаггүй.
3.9 Ledger
(io.example.ledger)
Давхар бичилтийн ерөнхий дэвтэр. Хоёр эх сурвалжаас бичилт авна: биллингийн гурван факт (event bus-ийн эхний хэрэглэгч), мөн хүн өөрөө бичилт хийж, дансны төлөвлөгөөгөө засаж, эхний үлдэгдлээ оруулж, тайлант үеэ түгжиж чадна.
| Event | Бичилт | Огноо |
|---|---|---|
billing.invoice.created |
DR авлага / CR борлуулалт + НӨАТ | issued_on |
billing.payment.posted |
DR касс эсвэл харилцах / CR авлага | paid_on — мөнгө хөдөлсөн өдөр |
billing.payment.cancelled |
Дээрх бичилтийн буцаалт | Цуцалсан өдөр |
billing.invoice.cancelled |
Нэхэмжлэхийн бичилтийн буцаалт | Цуцалсан өдөр |
billing.credit_note.created |
DR борлуулалт + DR НӨАТ / CR авлага | Залруулга бичсэн өдөр |
Залруулга нь буцаалт биш, өөрийн бичилт: хэсэгчилсэн залруулга юуг ч бүрэн буцаадаггүй, ба бүтэн ч байсан өөрийн дугаар, өөрийн огноотой баримт. Буцаалт гэж тэмдэглэвэл тэр нэхэмжлэхийг дараа нь цуцлахад идемпотентын түлхүүр эзлэгдсэн байж, цуцлалт чимээгүй юу ч бичихгүй байх байсан.
Хуваарилалт нь дэвтэрт юу ч бичихгүй — энэ нь орхигдол биш: авлага нэхэмжлэх дээр дебетлэгдэж, төлбөр дээр кредитлэгдсэн бөгөөд аль төлбөр аль нэхэмжлэхийг хаасныг тохируулах нь дэд дэвтрийн асуулт. Дахин бичвэл тухайн дансны хөдөлгөөн хоёр дахин гарна (Business Central-ийн ижил хил). Бүх мөнгөн дүн integer MNT (бүхэл төгрөг, бутархайгүй).
Огноо хоёр байдаг. entry_date нь
гүйлгээ болсон өдөр (YYYY-MM-DD,
Улаанбаатарын хуанли) — тайлан бүр үүгээр бүлэглэнэ.
posted_at нь мөр хэзээ бичигдсэн аудитын тамга. 8-р сарын
2-нд оруулсан, 7-р сарын 31-ний нэхэмжлэх нь долдугаар сарынх.
Эрх: ledger.read (GET) ба
ledger.manage (бусад бүх арга) — server.go-ийн
prefix зурагдлаар. Бичилт хийх, дансны төлөвлөгөө засах, түгжээ тавих
гурав нь нэг эрхийн дор: тэдгээр нь нэг ажил (данс хөтлөх).
Журнал
| Арга | Зам | Тайлбар |
|---|---|---|
| GET | /api/v1/ledger/entries |
Бичилтүүд, шинэ дугаар нь эхэнд → {entries: [...]}.
Шүүлт: ?source_type= (ж: billing.invoice,
manual, opening,
ledger.reversal), ?from=/?to=
(бизнесийн огноогоор, YYYY-MM-DD; буруу
бол 400), ?limit= (анхдагч 100, дээд тал нь
500 — хязгаараас гадуур утга нь анхдагчид буцаж унана) |
| POST | /api/v1/ledger/entries |
Гараар бичилт хийх → 201 + бичилт хөлүүдтэйгээ. Бие:
{entry_date, description, lines: [{account_code, debit, credit}]} |
| GET | /api/v1/ledger/entries/{id} |
Нэг бичилт + хөлүүд нь. Олдохгүй бол 404 |
| POST | /api/v1/ledger/entries/{id}/reverse |
Буцаах бичилт хийх → 201 + шинэ
бичилт. Бие нь сонголттой: {entry_date} — байхгүй бол
өнөөдөр |
Бичилтийн биет: id, number
(тенант тутмын хүнд харагдах дугаарлал), description,
source_type, source_id?,
reverses_id?, entry_date,
posted_at (RFC 3339), created_by? (хүн бичсэн
бол; платформ эвэнтээс бичсэн бол хоосон), total (дебетийн
нийлбэр = кредитийн нийлбэр). Дэлгэрэнгүй харах үед lines[]
нэмэгдэнэ: account_code, account_name,
debit, credit.
POST /entries-ийн source_type нь
үргэлж manual ба хүсэлтээс авагддаггүй.
Тэр нь idempotency түлхүүрийн нөгөө хагас; дуудагчид сонгуулбал
биллингийн бичилттэй мөргөлдөх, эсвэл түүнийг чимээгүй өмчлөх боломж
гарна.
Бичилтийн дүрмүүд (бүгд 400): хөл дор хаяж хоёр байх;
хөл бүр дебет эсвэл кредит, хоёулаа биш; сөрөг дүн
байхгүй; дебетийн нийлбэр кредитийнхтэй тэнцүү; entry_date
заавал. Хоёр талдаа тэг хөл нь бичигдэхээсээ өмнө хаягдана (жижиг
нэхэмжлэх дээрх тэг НӨАТ-ын мөр).
Буцаалт бол засвар БИШ
Бичигдсэн бичилтийг хэзээ ч дахин бичдэггүй.
Залруулга нь шинэ, эсрэг бичилт: дебет бүр кредит, кредит бүр дебет
болж, reverses_id-гээр эхнийх рүүгээ заана. Алдаа ба
залруулга хоёулаа дэвтэрт үлдэнэ — нягтлан бодогчийн хүлээдэг зүйл ч яг
тэр.
Тиймээс зам нь POST .../reverse, DELETE
биш: дэвтэр мөр авдаг, алддаггүй. Өгөгдлийн сан ч мөн
адил боддог — журнал дээрх DELETE болон нягтлан бодох утга
агуулсан баганын UPDATE-ыг триггер татгалзана (DATABASE_SCHEMA.md,
tenant/0014), ба тэр татгалзал API дээр 409
болж гарна.
Буцаалтын огноо нь буцааж буй бичилтийнх биш: наймдугаар сард илэрсэн алдааг наймдугаар сард залруулна, наймдугаар сарын дэвтэр өөрөөр хэлээгүй л бол. Хаагдсан үе рүү буцаалт бичих нь бусад бичилттэй ижил татгалзалд орно.
Хоёр дахь буцаалт 409; буцаалтыг өөрийг нь буцаах нь мөн
409 (тэр нь хууль ёсны давхар бичилт боловч энэ товчны
хэлэх зүйл биш — хүсвэл гараар бич).
Дансны төлөвлөгөө ба бичилтийн үүрэг
| Арга | Зам | Тайлбар |
|---|---|---|
| GET | /api/v1/ledger/accounts |
Тенантын дансны төлөвлөгөө, кодоор эрэмбэлсэн →
{accounts: [...]}. ?include_inactive=true нь
идэвхгүйжүүлсэн дансыг мөн буцаана |
| POST | /api/v1/ledger/accounts |
Данс нэмэх → 201 + данс. Бие:
{code, name, kind, parent_code?, statement_line?, active?} |
| PATCH | /api/v1/ledger/accounts/{id} |
Данс засах (нэрлэх, эцгийг нь солих, тайлангийн мөрийг нь солих, идэвхгүй болгох). Илгээсэн талбарууд л өөрчлөгдөнө |
| GET | /api/v1/ledger/account-roles |
Платформын мэддэг бүх үүрэг, зурагдсан эсэхээс үл
хамааран →
{roles: [{role, account_code?, account_name?}]} |
| PUT | /api/v1/ledger/account-roles/{role} |
Үүргийг тенантын нэг данс руу заана. Бие:
{account_code} |
Дансны биет: id, code,
name, kind
(asset/liability/equity/
income/expense), parent_code?,
statement_line?, active, role?,
has_postings. Сүүлийн хоёр нь дэлгэц юуг өөрчилж болохыг
мэдэхэд хэрэгтэй хоёр факт.
Аль ч уншилтын зам дээр дансны төлөвлөгөө залхуу байдлаар суулгагдана: эхний өдрөө чат нээсэн тенант хоосон хүснэгт биш, монгол жижиг бизнесийн 19 дансыг хардаг. Дахин суулгах нь тенантын засварыг хадгална (нэр, идэвхгүйжүүлэлт, үүргийн зурагдал бүгд хэвээр).
Бичдэг код нь ҮҮРЭГ нэрлэнэ, кодыг хэзээ ч биш.
Авлагаа 1100-аас 1105 болгож дугаарласан тенант нь
PUT /account-roles/receivable гэсэн ганц дуудлага хийнэ;
нэхэмжлэх бичдэг код юу ч анзаарахгүй. Мэдэгдэх үүргүүд:
cash, bank, receivable,
vat_input, vat_output, payable,
retained_earnings, current_year_earnings,
opening_suspense, sales,
rounding_gain, rounding_loss. Зурагдаагүй
үүрэг нь нуугдахгүй жагсаана — тэр нь ирээдүйн бичилт унах шалтгаан
бөгөөд унахаас нь өмнө харагдах ёстой.
Хоёр дүрэм тогтмол: бичилттэй дансны код хөлддөг (нэр нь чөлөөтэй хэвээр) ба үүрэг атгасан данс идэвхгүй болохгүй — эхлээд үүргийг өөр данс руу заа.
Эхний үлдэгдэл
| Арга | Зам | Тайлбар |
|---|---|---|
| GET | /api/v1/ledger/opening-balances |
?year= (анхдагч нь энэ он) →
{year, entry, suspense, suspense_account}. Тухайн онд эхний
үлдэгдэл байхгүй бол entry: null |
| POST | /api/v1/ledger/opening-balances |
Импорт:
{as_of, balances: [{account_code, debit, credit}]} →
201 (шинээр бичигдсэн) эсвэл 200 (ижил импорт
дахин ирсэн) + {year, suspense, created, entry} |
Эхний үлдэгдлийн тусдаа хүснэгт байхгүй — тэр нь
ердийн журналын бичилт, зөвхөн source_type нь
opening. Ижил тэнцлийн дүрэм, ижил «зөвхөн нэмэгддэг»
баталгаа, ижил шалгах баланс.
Тэнцэхгүй тоог татгалзахгүй. Нүүж ирж буй бизнес
тоогоо өөр системээс хэсэг хэсгээр нь хуулж бичдэг тул тэнцэхгүй байх нь
хэвийн. Зөрүү нь түр данс руу
(opening_suspense үүрэг) орно. Түр дансны үлдэгдэл тэг
болох нь нүүлт дууссаны шинж — suspense талбар нь яг
түүнийг хэлнэ. Импортыг татгалзвал бизнес тоо нь төгс болтол эхэлж
чадахгүй байх байсан бөгөөд тоо нь оруулж, тулгаснаараа
төгс болдог.
Түр дансанд өөрөө тоо өгөх нь 400: тэр нь тэнцээгүй
үлдэгдлийг хүлээж авдаг данс. Ижил тоог дахин илгээх нь 200
(юу ч бичигдээгүй); өөр тоо илгээх нь 409
— бичигдсэн бичилтийг дахин бичихгүй, буцаагаад залруулга бич.
Түгжээний огноо
| Арга | Зам | Тайлбар |
|---|---|---|
| GET | /api/v1/ledger/settings |
{lock_date?, tax_lock_date?, hard_lock_date?, fiscal_year_start_month}.
Тохируулаагүй дэвтэр юуг ч түгжихгүй |
| PUT | /api/v1/ledger/settings |
Илгээсэн талбарууд л өөрчлөгдөнө; хоосон мөр нь түгжээг тайлна (хатуу түгжээнээс бусад нь) |
Хүчинтэй түгжээ нь хамаарах огноонуудын максимум —
түгжээ нэмэх нь хэзээ ч сулруулахгүй. tax_lock_date нь
НӨАТ-ын данс хөндсөн бичилтэд л нэмэгдэнэ, тул мэдүүлсэн сар нь дэвтэр
нээлттэй байхад ч хамгаалагдана. hard_lock_date нь
зөвхөн урагшилна: буцаах гэсэн оролдлого
409. Буцааж чаддаг түгжээ нь юуг ч нотлохгүй.
Түгжигдсэн үе рүү бичих оролдлого нь 409 бөгөөд аль
түгжээ зогсоосныг нэрлэнэ. Хоцорсон бичилтийн огноог чимээгүй
урагшлуулахгүй — тэр нь хэрэглэгчийн бичээгүй тоог хадгалах
явдал.
fiscal_year_start_month нь дотоод удирдлагын тайланд
зориулагдсан. Монголын хууль ёсны тайлант жил нь хуанлийн жил тул хууль
ёсны тайлангууд түүнийг зориуд үл тоомсорлоно.
Тайлан
| Арга | Зам | Тайлбар |
|---|---|---|
| GET | /api/v1/ledger/reports/trial-balance |
Шалгах баланс. ?from=, ?to=
(YYYY-MM-DD; анхдагч нь оны эхнээс өнөөдөр хүртэл),
?include_empty=true (хөдөлгөөнгүй дансыг мөн харуулна) |
Хариулт: {from, to, rows: [...], totals, balanced}. Мөр
бүр нь account_code, account_name,
kind, statement_line?, opening
(from-оос өмнөх бүхэн, дебет хасах кредит),
period_debit, period_credit,
closing (to-г оруулаад бүхэн). to
нь from-оос өмнө байвал 400.
balanced нь тайлангийн өөрийнх нь нотолгоо: бүх дансны
нийлбэр авахад дебет = кредит, opening = 0,
closing = 0 байх ёстой. Үгүй бол дэвтэр эвдэрсэн бөгөөд
тайлан нь зөвхөн мэдээлэгч.
400
ба 409-ийн ялгаа — зориудынх, ба ачаа үүрдэг
Энэ модулийн алдааны гадаргуу хоёр хагасаас бүрдэнэ, ба хоёр нь өөр зүйл хэлдэг:
400— дуудагч буруу зүйл илгээсэн ба өөр хүсэлт илгээвэл ажиллана. Тэнцэхгүй бичилт, огноо байхгүй, байхгүй дансны код, мэдэгдэхгүй үүрэг, хоосон эхний үлдэгдэл, буруу форматтай огноо, бурууkind.409— хүсэлт зөв байсан ба дэвтрийн төлөв татгалзаж байна. Үе хаагдсан, буцаалт аль хэдийн байгаа, тухайн оны эхний үлдэгдэл өөр тоотой байгаа, код давхардсан, данс бичилттэй тул код нь хөлдсөн, үүрэгт данс зурагдаагүй, хатуу түгжээ ухрахыг хүсэв.
Дэлгэц энэ хоёрыг ижилхэн харуулах ёсгүй.
409 бол баталгаажуулалтын мессеж биш: талбарыг улаан болгох
нь дуудагчид засах зүйл байгаа мэт хэлэх ба хаагдсан үе рүү бичих
оролдлогыг «энэ талбар буруу» гэж уншсан хүн үүнийг алдаа гэж мэдээлнэ.
Зөв хариулт нь юу болсныг хэлээд (аль түгжээ, аль бичилт) дараагийн
алхмыг санал болгох — огноог өөрчлөх, эсвэл залруулгыг нээлттэй үед
бичих.
Бусад статус: 404 тухайн тенантад ийм бичилт/данс
байхгүй; 401 session-гүй; 403 апп суулгаагүй
эсвэл эрх дутуу; 500 бусад бүх зүйл (серверийн лог дээр).
Бие нь үргэлж {"error": "..."}.
3.10 Office
(io.example.office)
Жинхэнэ Word/Excel/PowerPoint — нээлттэй эхийн OnlyOffice
Document Server-ийг embed хийсэн. Модуль нь өөрийн тавиуртай
(docx/xlsx/pptx) бөгөөд мөн Drive дээрх файлын
засварлагч (internal.EditableFileStore чадвар,
docs/DESIGN_DRIVE.md §6).
{id} нь тавиурыг ч нэрлэнэ:
<uuid> = Office-ийн өөрийн баримт,
drive-<uuid> = файл сангийн файл. Доорх бүх зам
хоёуланд нь ажиллана (/sign-аас бусад).
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/office/ |
office.read |
Баримтын жагсаалт (загварууд орохгүй) |
| POST | /api/v1/office/ |
office.manage |
{title, kind} — docx, xlsx
эсвэл pptx. Хоосон файл нь embed хийсэн template-ээс |
| GET | /api/v1/office/templates |
office.read |
Загваруудын жагсаалт |
| POST | /api/v1/office/{id}/save-as-template |
office.manage |
{title?} — баримтыг хуулж загвар
үүсгэнэ (эх нь хэвээр) |
| POST | /api/v1/office/templates/{id}/use |
office.manage |
{title?} — загвараас бие даасан шинэ баримт |
| GET | /api/v1/office/{id}/config |
office.read |
Засварлагчийн JWT-signed тохиргоо + docserver-ийн хаяг.
?lang=, ?fresh=1 (кэш хордсон үед шинэ
document key). drive-<uuid> бол төрлийг
файлын нэрээр тодорхойлно; засварлаж болохгүй өргөтгөл
бол 400, файл сан суугаагүй бол 501 |
| POST | /api/v1/office/{id}/sign |
office.manage |
Хадгалагдсан хувилбарыг PDF болгож E-Sign тавиур руу. Илгээхийн өмнө
нээлттэй засварыг албадан хадгална. Зөвхөн өөрийн
тавиур — drive- бол 501 |
| PUT · DELETE | /api/v1/office/{id} |
office.manage |
Нэр солих · устгах |
Хаалтгүй хоёр зам (document server session барьдаггүй тул):
| Арга | Зам | Эрх мэдэл |
|---|---|---|
| GET | /api/v1/office/files/{id} |
URL дотор богино настай HMAC токен (t= tenant,
exp=, token=) — тохиргоо гаргах үед
үүсгэгдэнэ. Токен нь tenant-ыг өөрөө агуулдаг тул хүсэлт зөв schema-д
уягдана |
| POST | /api/v1/office/files/{id}/callback |
Мөн HMAC токен, дээр нь docserver-ийн JWT
co-signature. Хадгалалт бүр шинэ version —
drive- бол Drive-ын шинэ хувилбар (дарж бичихгүй) |
Угтвар нь HMAC-ийн субъектын нэг хэсэг: нэг тавиурт зориулж үүсгэсэн токеныг нөгөө дээр нь ашиглах боломжгүй.
Хоёулаа route_policy_test.go-ийн
publicRoutes-д шалтгаантайгаа нэрлэгдсэн.
ONLYOFFICE_* тохируулаагүй бол /config нь
503 буцаана — файл хадгалагдсаар байна.
3.11 Drive
(io.example.drive)
Workspace тутмын файлын сервер: хавтасны мод, ачаалалт, хувилбар,
хогийн сав, Range дэмждэг татаж авалт, хуваалцах, од, үйл ажиллагааны
лог, хайлт. Дизайн ба урсгалууд: DESIGN_DRIVE.md; хүснэгтүүд: DATABASE_SCHEMA.md
(tenant/0006, tenant/0009,
tenant/0011).
Эрх нь модулийн өөрийнх. Gov Services-ийн адил Drive
нь цөмийн appRequestPermission зурагдалд зориуд
ороогүй: платформын автомат зурагдал арга бүрээс нэг эрх
гаргадаг ба POST /nodes/{id}/copy (manage) ба POST
хэлбэртэй уншилтыг ялгаж чадахгүй. Маршрут бүр drive.read
эсвэл drive.manage бүлгийн аль нэгэнд гараар байрлана.
Алдааны зурагдал (fail,
drive.go): 404 олдсонгүй; 400 нэр
буруу / энэ нь хавтас / энэ нь файл / role буруу; 409 нэр
эзэлсэн, мөчлөг, эх хавтас нь хогийн саванд, хогийн саванд байгаагүй;
413 файл хэт том; бусад бүх зүйл 500 +
серверийн лог. Бие нь {"error": "..."}.
Node биет (GET-үүдийн буцаадаг хэлбэр):
id, parent_id (язгуурт null),
kind (folder/file),
name, path (материалчлагдсан),
depth, size_bytes, mime,
version, trashed_at, created_by,
created_at, updated_at. Блобын sha нь
гадагш гардаггүй.
v1 — мод, байт, хувилбар
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/drive/nodes |
drive.read |
Хавтасны хүүхдүүд → node массив (дээд тал нь 2000).
?parent= (хоосон эсвэл root = язгуур; байхгүй
хавтас → 404), ?sort=name│size│updated (анхдагч: хавтас
эхэлж, дараа нь нэр), ?order=asc│desc. Хогийн саванд
байгааг оруулахгүй |
| GET | /api/v1/drive/nodes/{id} |
drive.read |
Нэг node + өвөг залгамжлагчид: {node, breadcrumb[]}.
breadcrumb нь язгуураас эхэлнэ, node өөрөө орохгүй ба
parent_id-гаас гаргагдана (path-ыг задлан
таамаглахгүй) |
| POST | /api/v1/drive/folders |
drive.manage |
{parent_id?, name} → 201 + node. Ах дүү
нэр давхардвал 409 |
| POST | /api/v1/drive/files |
drive.manage |
multipart/form-data ачаалалт → 201 +
node. Талбарууд: parent_id?, name?,
file. Дараалал чухал — хэсгүүд урсгалаар
уншигдана (ParseMultipartForm биш) тул
parent_id ба name нь file-аас
өмнө ирнэ; байт эхлэхээс өмнө очих газар нь мэдэгдсэн
байх ёстой. name хоосон бол файлын нэрийн сүүлчийн сегмент.
Байгаа файлын нэр дээр ачаалах нь орлуулахгүй — шинэ
хувилбар нэмнэ; тэр нэрийг хавтас эзэлсэн бол
409. Нэг файлын хязгаар нь
DRIVE_MAX_FILE_BYTES (анхдагч 100 MiB) — хэтэрвэл
413. Content-Type-ыг сервер өөрөө үнэрлэж
тогтооно, клиентийн зарласныг хэзээ ч дахин ашиглахгүй |
| GET | /api/v1/drive/nodes/{id}/download |
drive.read |
Одоогийн контентыг урсгана. Range дэмжигдэнэ
(206 + Content-Range, хамрахгүй муж →
416). Үргэлж X-Content-Type-Options: nosniff.
Анхдагчаар attachment; ?disposition=inline нь
скрипт ажиллуулж чадахгүй төрөлд л олгогдоно (HTML/SVG
нь энэ origin дээр inline рендер хийгдэхгүй). Хавтас → 400,
хогийн саванд байгаа → 404 |
| PATCH | /api/v1/drive/nodes/{id} |
drive.manage |
{name} — нэр солих. Дэд модны бүх зам нэг гүйлгээнд
дахин бичигдэнэ |
| POST | /api/v1/drive/nodes/{id}/move |
drive.manage |
{parent_id?} — зөөх
(null/""/"root" = язгуур).
Хавтсыг өөрийнх нь дотор зөөвөл 409 (мөчлөг) |
| POST | /api/v1/drive/nodes/{id}/copy |
drive.manage |
{parent_id?, name?} → 201 + шинэ node.
Хавтас бол дэд мод нь бүхэлдээ хуулагдана (гүн дээд тал нь 64); блоб нь
ref_count-оор хуваалцагдана — диск дээр хоёр дахь хуулбар
үүсэхгүй |
| DELETE | /api/v1/drive/nodes/{id} |
drive.manage |
Хогийн сав руу (soft). Дэд мод нь хамт орж, түүн доторх бүх амьд холбоос нэг статементэд цуцлагдана — хаясан файл нь URL нь хэн нэгний гарт байсан ч татагдахгүй болно |
| POST | /api/v1/drive/nodes/{id}/restore |
drive.manage |
Хогийн савнаас буцаана. Эх хавтас нь өөрөө хогийн саванд эсвэл
байхгүй бол 409; нэр нь чөлөөлөгдөж, өөр зүйл эзэлсэн бол
мөн 409 |
| DELETE | /api/v1/drive/nodes/{id}/purge |
drive.manage |
Бүрмөсөн устгах → 204. Зөвхөн хогийн саванд
байгаа зүйлд ажиллана (409) — нэг ч хүсэлт шууд
устгаж чадахгүй |
| POST | /api/v1/drive/nodes/{id}/sign |
drive.manage |
Хадгалагдсан PDF-ийг гарын үсгийн модульд шууд өгнө
(татаж, дахин ачаалахгүй) →
{status, esign_document_id, node_id}. Байтууд урсгалаар
дамжина; хэмжээний хязгаар нь хүлээн авагчийнх. PDF биш
→ 400; хогийн саванд эсвэл хавтас → 404; гарын
үсгийн модуль суулгаагүй бол 501; хүлээн авагч татгалзвал
502. Эх файл өөрчлөгдөхгүй, хувилбар нэмэгдэхгүй |
| GET | /api/v1/drive/trash |
drive.read |
Хогийн сав → node массив. Хаясан язгуурууд л жагсана: хавтасны агуулга түүнтэй хамт орсон тул тэднийг хажууд нь харуулбал нэг устгал зуу мэт харагдана |
| GET | /api/v1/drive/nodes/{id}/versions |
drive.read |
Файлын түүх, шинэ нь эхэнд:
[{id, node_id, version, size_bytes, comment?, created_by?, created_at}].
Хавтас → 400 |
| POST | /api/v1/drive/nodes/{id}/versions/{version}/restore |
drive.manage |
Хуучин хувилбарыг шинэ хувилбар болгож буцаана
(дарж бичихгүй). {version} эерэг бүхэл байх ёстой, эс
бөгөөс 400 |
| GET | /api/v1/drive/usage |
drive.read |
{total_bytes, stored_bytes, file_count, folder_count, trashed_count}.
total_bytes нь хүн тоолох ёсоор нийлбэр;
stored_bytes нь диск бодитоор эзэлж буй хэмжээ — хоёр node
эсвэл хоёр хувилбар контентоо хуваалцах бүрд бага байна |
| GET | /api/v1/drive/search |
drive.read |
Шүүлттэй хайлт — доорх «Хайлт» хэсгийг үз |
v2 — хуваалцах, од, лог
| Арга | Зам | Эрх | Тайлбар |
|---|---|---|---|
| GET | /api/v1/drive/nodes/{id}/shares |
drive.read |
Нэг node-ийн хуваалцлын самбар нэг хүсэлтээр:
{members[], links[], my_role}. members нь
энэ node дээр шууд нэрлэгдсэн грантууд (өвгөөс өвлөсөн
нь орохгүй — энэ бол цуцалж чадах зүйлсийн жагсаалт); links
нь токенгүй холбоосын мөрүүд; my_role нь дуудагчийн
өвлөлтийг тооцсон бодит эрх ("", viewer,
editor) |
| POST | /api/v1/drive/nodes/{id}/shares |
drive.read + node дээрх эрх |
{subject_id, role} — role нь
viewer эсвэл editor (400). Дахин
хуваалцах нь role-ыг солино, хоёр дахь мөр үүсгэхгүй.
201 + share биет. Хогийн саванд байгаа node-ыг хуваалцахгүй
(404) |
| DELETE | /api/v1/drive/nodes/{id}/shares/{shareID} |
drive.read + node дээрх эрх |
Гишүүний грантыг авах → 204 |
| POST | /api/v1/drive/nodes/{id}/links |
drive.read + node дээрх эрх |
Холбоос үүсгэх — доорх «Холбоосын токен» хэсгийг үз |
| DELETE | /api/v1/drive/nodes/{id}/links/{linkID} |
drive.read + node дээрх эрх |
Холбоосыг цуцлах → 204. Мөр нь
revoked_at-тай үлдэнэ (устахгүй) |
| GET | /api/v1/drive/shared-with-me |
drive.read |
Дуудагчийг шууд нэрлэсэн node-ууд, шинэ нь эхэнд (дээд тал нь 500). Дэд модыг задлахгүй: «надтай хуваалцсан» дэлгэцэд хуваалцсан хавтас нь харагдах ёстой, түүний арван мянган удам биш |
| GET | /api/v1/drive/starred |
drive.read |
Дуудагчийн од тавьсан зүйлс, шинэ нь эхэнд (дээд тал нь 500). Хогийн саванд байгааг оруулахгүй |
| PUT | /api/v1/drive/nodes/{id}/star |
drive.read |
Од тавих → 204. Идемпотент — хоёр удаа тавих нь нэг л
факт. Байхгүй эсвэл хогийн саванд байгаа node → 404 |
| DELETE | /api/v1/drive/nodes/{id}/star |
drive.read |
Од авах → 204. Байхгүй одыг авах нь алдаа
биш: дуудагч төлөв хүссэн, төлөв нь биеллээ |
| GET | /api/v1/drive/nodes/{id}/activity |
drive.read |
Нэг node-ийн түүх, шинэ нь эхэнд (дээд тал нь 200). Node оршиж байхыг шаардахгүй — тэр нь энэ хүснэгтийн бүх учир |
| GET | /api/v1/drive/activity |
drive.read |
Workspace-ийн урсгал, шинэ нь эхэнд (дээд тал нь 200) |
Үйл ажиллагааны бичлэг:
{id, node_id, action, node_name, node_path, actor_id?, detail, created_at}.
action нь хаалттай олонлог:
created, uploaded, renamed,
moved, copied, trashed,
restored, purged, shared,
unshared, version_restored. Дэлгэц бүрийг хоёр
хэл дээр зурдаг тул чөлөөт текст байж болохгүй.
node_name/node_path нь эвэнтийн агшны
snapshot — тэдгээр нь маргааш байхгүй байж болох
зүйлийг тодорхойлно. Од нь бичигддэггүй: «хэн нэгэн од
тавилаа»-гаар дүүрсэн лог нь файл алга болоход хэн ч уншихгүй лог.
Хуваалцлыг өөрчлөх эрх нь маршрутын биш, node-ийнх.
Дээрх бичдэг таван зам нь drive.read бүлэгт байрлана:
drive.read нь хүсэлтийг энэ хүртэл авчирдаг, харин бичиж
болох эсэхийг node тутамд шийднэ — workspace админ,
эсвэл drive.manage эзэмшигч, эсвэл тухайн node дээр (эсвэл
дээрх хавтас дээр) editor грант авсан хүн. Татгалзал нь
403 (404 биш): дуудагч аль хэдийн node-ыг харж
чадаж байгаа тул нуух нь түүнд шинэ юу ч хэлэхгүй, эрхийн асуудлыг
байхгүй файл мэт харагдуулах байв. editor ба
viewer хоёрын ялгаа яг энэ — үүнгүй бол схем нь хадгалдаг,
юу ч хэрэгжүүлдэггүй ялгаа болох байв.
Хайлт
GET /api/v1/drive/search — drive.read.
Хариулт нь node биет дээр starred (boolean) нэмсэн массив,
updated_at DESC-оор эрэмбэлэгдэж дээд тал нь 200 мөр. Буруу
шүүлт нь чимээгүй үл тоомсорлогдохгүй, 400
болно: ажиллаагүй хэмжээний шүүлт нь хүн хүлээснээс олон файлтай диск
мэт харагдана.
| Параметр | Утга |
|---|---|
q |
Нэрээр хайх. Хоёр нөхцөл нэг дор: дэд мөрийн ILIKE
ба simple tsvector дээрх
plainto_tsquery — нэрийн дундаас гурван үсэг ч, бүтэн үг ч
олдоно |
kind |
file эсвэл folder; өөр утга →
400 |
mime |
Яг тэнцүү харьцуулалт |
min_size, max_size |
Байтын тоо. Байхгүй нь тэгтэй адилгүй — тэг байттай файл байдаг ба «дор хаяж 0 байт» нь юу ч өөрчлөхгүй шүүлт |
from, to |
updated_at муж. RFC 3339 бүтэн тамга эсвэл энгийн огноо
(2026-08-19) |
starred=true |
Зөвхөн дуудагчийн од тавьсан |
shared=me |
Зөвхөн дуудагчтай хуваалцсан (хуваалцсан хавтасны дэд мод хамт) |
Хогийн саванд байгаа болон өөр тенантын зүйл хэзээ ч
буцахгүй; сүүлийнх нь хоёр давхар — schema нь dbguard-аар
тогтоогдоно, дээр нь predicate өөрөө tenant_id авч
явна.
Байрлуулалт нь pg_trgm-тэй эсэхээс үл хамааран
асуулга ижил тул хариулт ч ижил — зөвхөн гүйцэтгэлийн
план өөр (DATABASE_SCHEMA.md,
tenant/0009).
Холбоосын токен — үүсгэх (session шаардана)
POST /api/v1/drive/nodes/{id}/links —
drive.read + node дээрх эрх. Бие:
{password?, expires_at?}. expires_at нь RFC
3339 ба ирээдүйд байх ёстой (эс бөгөөс
400); нууц үг нь дээд тал нь 72 байт (bcrypt-ийн өөрийн
хязгаар — үүнээс цааш авбал хүний бодсоноос богино нууц үг болно).
201-ийн бие:
{
"link": { "id": "...", "node_id": "...", "has_password": true,
"expires_at": "...", "download_count": 0, "created_at": "..." },
"token": "<tenant-uuid>.<secret>",
"path": "/api/v1/drive/links/<tenant-uuid>.<secret>"
}
Энэ хариулт бол токен оршин байх цорын ганц агшин. Хүснэгтэд зөвхөн түүний хэш хадгалагдана — токеныг дахин уншуулж чадах endpoint байхгүй, байх ч ёсгүй. Алдвал хийх зүйл нь «дахин харуул» биш, цуцлаад шинийг үүсгэх; энэ нь цорын ганц шударга хариулт бөгөөд цуцлагдсаны мөр үлдээдэг хариулт.
path нь зам, бүтэн URL биш: префикс
бүхий прокигийн ард сервер нь хөтөч юу бичсэнийг найдвартай мэдэхгүй,
таасан хостоос бүтээсэн холбоос нь юу ч нээхгүй холбоос. Хостыг клиент
өөрөө залгана.
Холбоосын хоёр зам — нээлттэй, session-гүй
Энэ модулийн 30 замаас зөвхөн энэ хоёр нь танилтгүй. Хуваалцсан холбоос гэдэг нь тодорхойлолтоороо session-гүй хүнд өгсөн уншилтын эрх тул tenant хаалга тэдний өмнө зогсож чадахгүй.
| Арга | Зам | Эрх мэдэл |
|---|---|---|
| POST | /api/v1/drive/links/{token} |
Зөвхөн токен. Бие нь сонголттой — нууц үггүй
холбоосыг хоосон POST нээнэ: {password?, node_id?} |
| GET | /api/v1/drive/links/{token}/download |
Зөвхөн токен. Нууц үг нь X-Drive-Link-Password
толгойд — query параметр нь түүнийг лог болгон Referer бүрд
оруулах байв. ?node= нь дэд модны файлыг сонгоно |
Tenant нь токен дотор явна. Токен нь
<tenant-uuid>.<secret> хэлбэртэй ба эхний хагас
нь схемийн нэр болох гэж байгаа тул uuid эсэхийг шалгана. Хүсэлт
бизнесийн хүснэгт хөндөхөөс өмнө tenant.WithTenantID-ээр
гараар уягдана — schema-per-tenant дор платформын замаар хийсэн асуулга
public-ыг шийддэг ба 00049-ээс хойш тэнд
бизнес хүснэгт байхгүй. Хоёулаа
internal/platform/route_policy_test.go-ийн
publicRoutes-д нэрлэгдсэн — нээлттэй зам нэмэх нь review
дээр харагддаг болох цэг.
Татгалзал нь зориуд нэг ижил. Дараах бүх тохиолдол
яг ижил хариулт өгнө — 401 +
{"error":"invalid link or password"}:
- хэзээ ч байгаагүй токен;
- хугацаа нь дууссан холбоос;
- цуцлагдсан холбоос;
- node нь хогийн саванд орсон холбоос;
- зөв токен дээрх буруу нууц үг;
- грантын гадна байгаа
node_id/?node=.
Энэ бол алдаа биш, шийдвэр. Ялгаж хэлбэл endpoint нь токен амьд эсэхийг шалгах хэрэгсэл болно: «буруу нууц үг»-ийг «ийм холбоос байхгүй»-ээс салгаж чадсан хүн аль токен бодит болохыг тандаж чадна, «хугацаа дууссан»-ыг «байгаагүй»-ээс салгаж чадсан хүн энэ workspace энд ямар нэг зүйл хуваалцаж байсныг мэдэж авна. Үнэ нь «энэ холбоосын хугацаа дууссан» гэж хэлж чадахгүй дэлгэц, тэр үнийг зориуд төлсөн. Цаг хугацаа ч хариултын нэг хэсэг: олдоогүй холбоос нь хаях зориулалтын хэш дээр bcrypt ажиллуулж, буруу нууц үгтэй ижил хугацаа зарцуулна.
Тодруулах хүсэлт (POST) нь
{node, root, children, expires_at, has_password, download_path}
буцаана — хавтасны холбоос нь дэд модыг нээх тул children
нь тухайн хавтасны амьд хүүхдүүд. Татаж авалт нь үргэлж
attachment: session-ийн ард байгаа хүн өөрийн
workspace-ийн зургийг inline харж болно, харин энд хүсэлт нь хаана ч
тавигдсан байж болох URL-аас ирдэг ба inline рендер нь энэ origin дээр
болно. Амжилттай татаж авалт бүр download_count,
last_used_at-ыг ахиулна.
4. Гүнзгий мэдээлэл
- Session токен: 32 байт
crypto/rand, DB-д зөвхөн SHA-256 хэш. TTL 12 цаг. - Cookie:
HttpOnly; SameSite=Strict; Secure(production үед). Хөгжүүлэлтийн split-origin орчинд (3000↔︎8080) cookie дамждаггүй тул frontend Bearer токеныг давхар илгээдэг. - OAuth2 access токен:
hydra_at_<64 hex>opaque, TTL 1 цаг, introspection-ээр шалгагдана. Authorization code урсгалаар олгогдсон бол хүн ба workspace-ыг хоёуланг нь заана (sub,tenant). id_token нь эсрэгээрээ RS256 JWT — JWKS-ээр шалгагдана, TTL 10 минут (нэвтэрсэн тухай мэдэгдэл болохоос хадгалж явах эрх биш). Authorization code 60 секунд, refresh 30 хоног. Session-ийн адил DB-д зөвхөн SHA-256 хэш (oauth2_tokens) хадгалагдана; цуцлалтrevoked_at-аар тэмдэглэгдэж бүх replica дээр хүчинтэй болно. Цаг тутмын janitor хугацаа дууссанаас хойш 24 цагийн дараа мөрүүдийг устгана. - JSON body хэмжээний хязгаар: AI chat/stt/translate 1 MiB, TTS 16 KiB, prompt 32 KiB, knowledge 256 KiB.