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: langar (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.