10. Internal Apply Bridge
10.1 Purpose
Shumoul.MultiTenancyApi generates the settings patch but cannot write it — only Shumoul.BackEnd can
resolve a tenant's own database and call IAppSettingService. The internal apply bridge is the one narrow
HTTP call that connects them.
Shumoul.MultiTenancyApi (HttpOnboardingTenantSettingsApplier)
→ POST {BackEndBaseUrl}/api/internal/onboarding/apply-settings-patch
Header: X-Shumoul-Internal-Key: {shared secret}
Header: X-Correlation-Id: {guid}
Header: TenantId: {tenant identifier}
→ Shumoul.BackEnd (OnboardingInternalController)
→ InternalOnboardingSettingsPatchService.ApplyAsync(...)
→ IAppSettingService
This endpoint is service-to-service only. It is not part of the public API surface, must never be called by Angular, and is deliberately unreachable by any user JWT — it doesn't accept one at all.
10.2 Request contract
{
"tenantId": "555001",
"tenantIdentifier": null,
"sessionId": "9f250000-9d35-f628-3eb5-08dedcf802a3",
"settingsPatchJson": "{\"CashierAppsSettings\":{\"Enable_Cash_Sales\":true}}",
"requestSource": "Shumoul.MultiTenancyApi",
"correlationId": "0d4c21c8-5ed5-4c19-9862-89c410bdf2a3"
}
tenantIdentifier is reserved/unused in this codebase — tenant resolution is entirely driven by the
TenantId header (see §10.4) and the tenantId field is only used for the hard-fail consistency check.
10.3 Response contract
{
"succeeded": true,
"sessionId": "9f250000-9d35-f628-3eb5-08dedcf802a3",
"tenantId": "555001",
"beforeSettingsJson": "{...}",
"afterSettingsJson": "{...}",
"appliedPatchJson": "{\"CashierAppsSettings\":{\"Enable_Cash_Sales\":true}}",
"appliedGroups": ["CashierAppsSettings"],
"ignoredGroups": [],
"warnings": [],
"errors": [],
"correlationId": "0d4c21c8-5ed5-4c19-9862-89c410bdf2a3"
}
10.4 Security model
| Check | Behavior |
|---|---|
Missing X-Shumoul-Internal-Key header | 401 Unauthorized |
Header present but doesn't match InternalServiceAuthSettings:OnboardingApplyApiKey | 403 Forbidden |
Key valid, but the resolved tenant (from the TenantId header, via Shumoul.BackEnd's own HeaderStrategy) doesn't match the request body's tenantId | 502 Bad Gateway, "Tenant could not be resolved." — before IAppSettingService is ever touched |
sessionId empty or tenantId blank | 400 Bad Request |
The tenant-mismatch check exists specifically so a bad or missing TenantId header can never silently fall
through to the wrong tenant's — or the host's own — database. This is enforced by
InternalOnboardingSettingsPatchService.ApplyAsync's very first check:
_tenantInfo is null || _tenantInfo.Id != request.TenantId.
10.5 Hidden from Swagger
OnboardingInternalController carries [ApiExplorerSettings(IgnoreApi = true)] — it never appears in
Shumoul.Api's generated swagger.json, so it won't show up for API consumers browsing the documented
surface. This was independently re-verified (0 matches for apply-settings-patch in the full generated
document) as part of the Phase 1.4–1.6 security hardening passes.
10.6 Correlation
Every call carries an X-Correlation-Id (generated by the caller if not already present); both hosts log it
alongside tenant id, session id, applied/ignored group counts, and warning/error counts — use it to trace a
single apply attempt across both hosts' logs.
