Organization Settings API
The seller-of-record identity — the block printed at the top of every generated PDF document. Three endpoints: GET/PATCH /admin/settings/organization and GET /admin/settings/fx-rates.
Every /admin/settings/* route requires an authenticated super_admin session. Any other role receives 403 AUTH_FORBIDDEN.
Why an integrator cares
These settings are not cosmetic configuration. They are read on every document render — GET /documents/:type/:entityId.pdf fetches them and hands them to the template. See Documents.
What they control:
- The seller header: legal name (language-resolved), CR number, VAT registration number and address. Fields that are unset are simply omitted from the header.
- The support-contact footer line, built from
supportEmailandsupportPhone. - Whether a VAT line appears at all.
Settings that were never saved are not an error. The service returns null, and templates omit the seller header and the support footer entirely rather than failing. The document still renders — with no seller identity on it.
VAT behaviour
The VAT line appears only when settings exist and showVatOnReceipts is true and vatNumber is present. The schema enforces the last pairing: enabling the toggle without a VAT number is a validation error.
VAT is presented as included in the shown total, derived as total × rate / (100 + rate), half-up rounded, computed in integer halalas. Enabling it never changes the total the document shows — the line only states how much VAT that total already contains.
Documents are always titled "Payment receipt / إيصال دفع" and always carry a disclaimer that they are not a tax invoice. That is true regardless of the VAT toggle.
The Organization Settings Object
Defined by organizationSettingsSchema in packages/shared/src/schemas/organization-settings.ts. It is stored as one row in the generic platform_settings key/value store under the key organization.
| Field | Type | Required | Description |
|---|---|---|---|
legalNameEn | string | Yes | Legal name in English, non-empty after trimming |
legalNameAr | string | Yes | Legal name in Arabic, non-empty after trimming |
vatNumber | string | No | VAT registration number. Required when showVatOnReceipts is true. |
crNumber | string | No | Commercial registration number |
addressEn | string | No | Address in English |
addressAr | string | No | Address in Arabic |
supportEmail | string | No | Support email address |
supportPhone | string | No | Support phone |
showVatOnReceipts | boolean | No | Defaults to false |
vatRatePercent | number | No | 0–100, defaults to 15 |
Both legalName fields are required because the document picks one by the render language. An Arabic render with no Arabic legal name would print nothing where the seller belongs.
GET /admin/settings/organization
Read the current settings.
Requires super_admin.
Response
200 OK — configured:
{
"data": {
"legalNameEn": "Skyte Communications Co.",
"legalNameAr": "شركة سكايت للاتصالات",
"vatNumber": "310000000000003",
"crNumber": "1010101010",
"addressEn": "Riyadh, Saudi Arabia",
"addressAr": "الرياض، المملكة العربية السعودية",
"supportEmail": "support@skyte.sa",
"supportPhone": "+966800000000",
"showVatOnReceipts": true,
"vatRatePercent": 15
}
}200 OK — never saved:
{ "data": null }Error Responses
| Code | Status | Condition |
|---|---|---|
AUTH_REQUIRED | 401 | Missing token |
AUTH_FORBIDDEN | 403 | Caller is not super_admin |
PATCH /admin/settings/organization
Save the settings.
Requires super_admin.
Despite the verb this is a whole-object upsert, not a partial merge. The body is validated against the full schema and replaces the stored value. Omitting an optional field clears it. Send the complete object every time.
Every successful update writes an org.settings_updated audit row carrying the before and after values, the actor, the client IP and the user agent.
Request Body
The full Organization Settings Object.
Example Request
{
"legalNameEn": "Skyte Communications Co.",
"legalNameAr": "شركة سكايت للاتصالات",
"crNumber": "1010101010",
"supportEmail": "support@skyte.sa",
"showVatOnReceipts": false,
"vatRatePercent": 15
}Response
200 OK — the stored object, with defaults applied.
Error Responses
| Code | Status | Condition |
|---|---|---|
VALIDATION_ERROR | 400 | Schema validation failed, including showVatOnReceipts enabled without a vatNumber |
AUTH_REQUIRED | 401 | Missing token |
AUTH_FORBIDDEN | 403 | Caller is not super_admin |
GET /admin/settings/fx-rates
Read the current USD exchange rates. Read-only. Rates are never set manually.
Requires super_admin. Gated by FEATURE_ESIM_STORE.
Response
200 OK:
{
"base": "USD",
"rates": [
{ "currency": "AED", "ratePerUsd": 3.6725, "fetchedAt": "2026-08-12T00:00:00.000Z", "source": "exchangerate-api" },
{ "currency": "EGP", "ratePerUsd": 48.5, "fetchedAt": "2026-08-12T00:00:00.000Z", "source": "exchangerate-api" },
{ "currency": "EUR", "ratePerUsd": 0.92, "fetchedAt": "2026-08-12T00:00:00.000Z", "source": "exchangerate-api" },
{ "currency": "GBP", "ratePerUsd": 0.79, "fetchedAt": "2026-08-12T00:00:00.000Z", "source": "exchangerate-api" },
{ "currency": "SAR", "ratePerUsd": 3.75, "fetchedAt": "2026-08-12T00:00:00.000Z", "source": "exchangerate-api" }
]
}Error Responses
| Code | Status | Condition |
|---|---|---|
STORE_DISABLED | 422 | FEATURE_ESIM_STORE is off |
AUTH_REQUIRED | 401 | Missing token |
AUTH_FORBIDDEN | 403 | Caller is not super_admin |