8. API Reference
Base path: https://{multitenancy-host}/api/v1/onboarding
All 6 endpoints require a standard Authorization: Bearer {jwt} header — the same JWT a normal ERP login
already returns. There is no separate onboarding login or token. See
Chapter 9 — Authentication & Authorization for exactly who can
call what.
Every response is wrapped in the platform's standard Result<T> envelope:
{ "data": ..., "statusCode": 200, "succeeded": true, "messages": [] } on success, or
{ "statusCode": ..., "succeeded": false, "messages": [...] } on a handled failure.
8.1 GET /api/v1/onboarding/status
Purpose: Check whether onboarding is required, and if so, what state it's in — call this on every login to decide whether to show the survey.
Required permission: Permissions.Onboarding.View
Request: no body.
Response (200):
{
"data": {
"isRequired": true,
"status": "NotStarted",
"currentStep": 1,
"canSkip": true,
"completedOn": null,
"appliedOn": null,
"appliedProfileCode": null,
"appliedProfileNameAr": null,
"appliedProfileNameEn": null
},
"statusCode": 200,
"succeeded": true,
"messages": []
}
Once applied:
{
"data": {
"isRequired": false,
"status": "Applied",
"canSkip": false,
"completedOn": "2026-07-08T13:51:29.83Z",
"appliedOn": "2026-07-08T14:36:11.05Z",
"appliedProfileCode": "RestaurantCafe",
"appliedProfileNameAr": "مطعم / كوفي",
"appliedProfileNameEn": "Restaurant / Cafe"
},
"statusCode": 200,
"succeeded": true,
"messages": []
}
Error cases: 401 no/invalid/expired token. No 403 case in practice — View is granted to any
authenticated tenant user.
8.2 GET /api/v1/onboarding/survey?lang=ar
Purpose: Fetch the active survey's steps and questions in the requested language.
Required permission: Permissions.Onboarding.View
Query parameters: lang — ar (default) or en. Only affects computed step titles; question text
already carries both TitleAr/TitleEn in the response regardless of lang.
Response (200, abridged):
{
"data": {
"surveyId": "9f250000-9d35-f628-ae34-08dedcef7143",
"code": "default-business-setup",
"version": 1,
"nameAr": "تهيئة نشاطك",
"nameEn": "Business Setup",
"steps": [
{
"stepNo": 1,
"titleAr": "بيانات النشاط التجاري",
"titleEn": "Business Activity",
"questions": [
{
"key": "activityType",
"titleAr": "ما هو نشاطك الأساسي؟",
"titleEn": "What is your main business activity?",
"type": "SingleChoice",
"isRequired": true,
"options": [
{ "value": "RestaurantCafe", "labelAr": "مطعم / كوفي", "labelEn": "Restaurant / Cafe" },
{ "value": "Supermarket", "labelAr": "سوبرماركت / بقالة", "labelEn": "Supermarket / Grocery" },
{ "value": "RetailStore", "labelAr": "متجر تجزئة", "labelEn": "Retail Store" },
{ "value": "Services", "labelAr": "خدمات بدون مخزون", "labelEn": "Services without Inventory" },
{ "value": "WholesaleDistribution", "labelAr": "جملة وتوزيع", "labelEn": "Wholesale & Distribution" }
]
}
]
}
]
},
"statusCode": 200,
"succeeded": true,
"messages": []
}
A question may include "visibleWhen": { "activityType": "RestaurantCafe" } — the client should only render
it when the referenced answer matches; the server also silently ignores answers for questions that don't
apply.
Error cases: 401 no/invalid/expired token; 200 with succeeded: false if no active survey is
configured at all (an operational misconfiguration, not a client error).
8.3 POST /api/v1/onboarding/answers
Purpose: Save (merge) one or more answers into the current session. Safe to call repeatedly as the admin progresses through the wizard — new answers are merged into, not replacing, previously saved ones.
Required permission: Permissions.Onboarding.Answer (tenant Admin only)
Request:
{
"answers": {
"activityType": "RestaurantCafe",
"branchesCount": "One",
"hasPos": true,
"paymentMethods": ["Cash", "Card"],
"usesVat": true,
"needsCostCenters": "Mandatory"
}
}
Response (200):
{ "data": "9f250000-9d35-f628-3eb5-08dedcf802a3", "statusCode": 200, "succeeded": true, "messages": ["Answers saved."] }
data is the session id — save it; apply needs it later. Unknown question keys are silently dropped and
reported back in the message ("Unknown question keys were ignored: {0}"), never an error.
Error cases: 401 no/invalid/expired token; 403 authenticated but not a tenant Admin; 200 with
succeeded: false if answers is empty or no active survey exists.
8.4 POST /api/v1/onboarding/recommendation
Purpose: Generate (or regenerate) a recommendation from the session's currently saved answers. This is a
preview — nothing is written to AppSettings yet.
Required permission: Permissions.Onboarding.Answer (tenant Admin only — same permission as answers;
see §8.4.1 below)
Request: no body (uses the caller's current session).
Response (200, abridged):
{
"data": {
"sessionId": "9f250000-9d35-f628-3eb5-08dedcf802a3",
"profileCode": "RestaurantCafe",
"profileNameAr": "مطعم / كوفي",
"profileNameEn": "Restaurant / Cafe",
"confidence": 95,
"summary": [
{ "ar": "تم تفعيل نقاط البيع النقدية والبطاقات", "en": "Cash and card POS payments have been enabled" }
],
"settingsPatch": {
"CashierAppsSettings": { "Enable_Cash_Sales": true, "Enable_Tables_System": true },
"CostCenterSettings": { "EnableCostCenterInSalesInvoice": true, "MandatoryCostCenterInSalesInvoice": true }
},
"warnings": [
"WarehouseId was not set because no default warehouse was created in Phase 1."
],
"nextActions": ["CreateDefaultWarehouse", "ReviewPOSSettings"]
},
"statusCode": 200,
"succeeded": true,
"messages": []
}
Calling this again overwrites the previous recommendation on the same session, even after Applied — see
Chapter 17 — Known Limitations.
Error cases: 401/403 as above; 200 with succeeded: false if no answers have been saved yet, or if
the session belongs to a different tenant than the caller (403 payload, "Session does not belong to the current tenant.").
8.4.1 Why recommendation uses the Answer permission
recommendation mutates the session (it overwrites RecommendedProfileCode and the generated JSON columns
and moves Status to Recommended) and is a required step before apply — it isn't a read-only view, so it
is gated the same as answers rather than View. apply has its own, stricter permission.
8.5 POST /api/v1/onboarding/apply
Purpose: Write the generated settings patch to the tenant's real AppSettings, through the internal
bridge to Shumoul.BackEnd. This is the only onboarding endpoint that changes tenant configuration.
Required permission: Permissions.Onboarding.Apply (tenant Admin only)
Request:
{ "sessionId": "9f250000-9d35-f628-3eb5-08dedcf802a3", "acceptAll": true, "overrides": null }
acceptAll must be true in the current phase — overrides is accepted but not processed yet (reserved
for a future phase; see Known Limitations).
Response (200):
{ "data": "9f250000-9d35-f628-3eb5-08dedcf802a3", "statusCode": 200, "succeeded": true, "messages": ["Onboarding settings applied."] }
Error cases:
| Condition | Result |
|---|---|
| No / invalid / expired token | 401 |
| Authenticated, not tenant Admin | 403 |
| Session belongs to a different tenant | 403, "Session does not belong to the current tenant." |
| Session not found | 404 |
Already Applied | 200, succeeded: false, "This onboarding session has already been applied." |
Not yet Recommended | 200, succeeded: false, "A recommendation must be generated before it can be applied." |
acceptAll not true | 200, succeeded: false, "AcceptAll must be true in Phase 1." |
| Internal bridge unreachable / rejected | succeeded: false with the bridge's error message (see Chapter 10) |
On success, a TenantOnboardingApplyLogs row is written and the session moves to Applied. On any failure,
no apply log is written and the session stays at Recommended.
8.6 POST /api/v1/onboarding/skip
Purpose: Bypass onboarding entirely for this tenant.
Required permission: Permissions.Onboarding.Skip (tenant Admin only)
Request: no body.
Response (200):
{ "data": "9f250000-9d35-f628-3eb5-08dedcf802a3", "statusCode": 200, "succeeded": true, "messages": ["Onboarding skipped."] }
If no session exists yet, one is created directly in Skipped status (skipping doesn't require having
answered anything first). status.canSkip tells the client when this action should be offered.
Error cases: 401/403 as above; 200 with succeeded: false if no active survey exists.
8.7 Permission summary
| Endpoint | Permission | Who can call it |
|---|---|---|
GET status | Permissions.Onboarding.View | Any authenticated tenant user |
GET survey | Permissions.Onboarding.View | Any authenticated tenant user |
POST answers | Permissions.Onboarding.Answer | Tenant Admin only |
POST recommendation | Permissions.Onboarding.Answer | Tenant Admin only |
POST apply | Permissions.Onboarding.Apply | Tenant Admin only |
POST skip | Permissions.Onboarding.Skip | Tenant Admin only |
A 403 from answers/recommendation/apply/skip for a non-admin user is expected behavior, not a
bug — see Chapter 9 for exactly how "tenant Admin" is
determined and Chapter 13 for how the client should present this.
