Skip to main content
Version: Latest

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:

ConditionResult
No / invalid / expired token401
Authenticated, not tenant Admin403
Session belongs to a different tenant403, "Session does not belong to the current tenant."
Session not found404
Already Applied200, succeeded: false, "This onboarding session has already been applied."
Not yet Recommended200, succeeded: false, "A recommendation must be generated before it can be applied."
acceptAll not true200, succeeded: false, "AcceptAll must be true in Phase 1."
Internal bridge unreachable / rejectedsucceeded: 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​

EndpointPermissionWho can call it
GET statusPermissions.Onboarding.ViewAny authenticated tenant user
GET surveyPermissions.Onboarding.ViewAny authenticated tenant user
POST answersPermissions.Onboarding.AnswerTenant Admin only
POST recommendationPermissions.Onboarding.AnswerTenant Admin only
POST applyPermissions.Onboarding.ApplyTenant Admin only
POST skipPermissions.Onboarding.SkipTenant 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.