Skip to content

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 supportEmail and supportPhone.
  • 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.

FieldTypeRequiredDescription
legalNameEnstringYesLegal name in English, non-empty after trimming
legalNameArstringYesLegal name in Arabic, non-empty after trimming
vatNumberstringNoVAT registration number. Required when showVatOnReceipts is true.
crNumberstringNoCommercial registration number
addressEnstringNoAddress in English
addressArstringNoAddress in Arabic
supportEmailstringNoSupport email address
supportPhonestringNoSupport phone
showVatOnReceiptsbooleanNoDefaults to false
vatRatePercentnumberNo0–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:

json
{
  "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:

json
{ "data": null }

Error Responses

CodeStatusCondition
AUTH_REQUIRED401Missing token
AUTH_FORBIDDEN403Caller 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

json
{
  "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

CodeStatusCondition
VALIDATION_ERROR400Schema validation failed, including showVatOnReceipts enabled without a vatNumber
AUTH_REQUIRED401Missing token
AUTH_FORBIDDEN403Caller 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:

json
{
  "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

CodeStatusCondition
STORE_DISABLED422FEATURE_ESIM_STORE is off
AUTH_REQUIRED401Missing token
AUTH_FORBIDDEN403Caller is not super_admin

Internal documentation - Activation System