Supercommerce API Docs
Full Module Docs

Settings Module

HTTP surface for platform settings (admin and store scopes managed by platform staff) and per-vendor settings (admin scope for vendor back-office config, store scope for…

HTTP surface for platform settings (admin and store scopes managed by platform staff) and per-vendor settings (admin scope for vendor back-office config, store scope for storefront branding the vendor controls). Storefront has an anonymous read of the subset of platform store-scope settings flagged public: true.

Source: api-modules/settings (registered via SettingsModule.forRoot() in apps/api/src/app.module.ts).

The module is registry-driven: every setting is declared as a SettingDefinition (group + key + zod type + flags like public, forAdmin). Other modules consume settings via SettingsService.getGroup(scope, group) and VendorSettingsService.getGroup(vendorId, scope, group) — never by reading the underlying tables directly.


Conventions

Authentication

Endpoint groupAuthPermission
GET /store/settings/**none— (only registry keys flagged public: true surface)
GET/PATCH /admin/settings/admin*requiredadminSetting: read / update
GET/PATCH /admin/settings/store*requiredstoreSetting: read / update
GET/PATCH /vendor/settings/admin*, /vendor/settings/store*required (vendor session)active vendor in session
GET/PATCH /admin/vendors/:vendorId/settings/admin*, /store*requiredplatformVendorSetting: read / update

The vendor self-service surface refuses to write any key flagged forAdmin: true in the registry (responds 403 FORBIDDEN at the service layer). The platform-admin override surface (/admin/vendors/:vendorId/settings/...) bypasses that guard, so platform staff can set admin-only keys on a target vendor's behalf.

Response envelope

Successful responses are wrapped by ResponseInterceptor:

{
  "data": <payload>,
  "message": "Success",
  "statusCode": 200,
  "metadata": null
}

Error envelope

statusCodeerrorCode examples
400BAD_REQUEST, VALIDATION_ERROR (unknown group/key, value fails the registry's zod)
401UNAUTHORIZED
403FORBIDDEN (missing permission, vendor wrote a forAdmin key)
404NOT_FOUND (storefront — unregistered or non-public key)
500INTERNAL_SERVER_ERROR, DATABASE_ERROR

Architecture

                ┌─────────────────────────────────────────┐
                │            SettingDefinition            │
                │  scope ∈ {admin, store}                 │
                │  group, key, zod, defaultValue          │
                │  public?: bool   (storefront read-out)  │
                │  forAdmin?: bool (vendor write-guard)   │
                └────────────────┬────────────────────────┘

        ┌────────────────────────┼───────────────────────────┐
        ▼                        ▼                           ▼
  Platform registry      Vendor registry        Storefront `public:true` filter
  (api-modules/         (api-modules/           reads only flagged keys
   settings/registry/    settings/registry/     no auth required
   admin/*)              vendor/*)

Two scopes:

ScopeOwned byPurpose
adminplatform staff / vendor back-officeOperational config: payment provider credentials, review moderation rules, shipping defaults, notification preferences. Never read by the storefront
storeplatform staff / vendor brandingStorefront-facing: logo, contact email, social links, terms-and-conditions URL. Subset is public — see below

Two surfaces per actor:

ActorReadsWrites
Anonymous storefrontpublic: true keys in platform store scope only
Platform staffBoth scopes of the platform registry; any vendor's settings (via override surface)Both scopes; any vendor's keys including forAdmin
Vendor userThe active vendor's two scopesBoth scopes except forAdmin: true keys (403)

Audit: every write goes through setMany which records a per-key audit row (actor_id, scope, group, key, before → after).


Domain types

ScopeSettingsResponse (platform admin)

type ScopeSettingsResponse = Record<string, Record<string, unknown>>;
// { groupName: { keyName: value }, ... }

GroupSettingsResponse (platform admin, single group)

type GroupSettingsResponse = Record<string, unknown>;
// { keyName: value, ... }

SettingValueResponse (storefront, single key)

type SettingValueResponse = { value: unknown };

VendorScopeSettingsResponse / VendorGroupSettingsResponse

Diverges from the platform shape by exposing a readOnlyKeys array — the keys in this scope/group flagged forAdmin: true. The vendor UI uses it to disable write controls on those fields.

type VendorScopeSettingsResponse = {
  values: Record<string, Record<string, unknown>>;   // groupkeyvalue
  readOnlyKeys: Record<string, string[]>;            // groupforAdmin keys
};

type VendorGroupSettingsResponse = {
  values: Record<string, unknown>;
  readOnlyKeys: string[];
};

UpdateSettingsInput / UpdateVendorSettingsInput

Bulk update payload — nested by group:

{
  "reviews": {
    "allow_vendor_approve": true,
    "max_images_per_review": 10
  },
  "payment.razorpay": {
    "key_id": "rzp_live_..."
  }
}

Top-level zod is permissive (Record<string, Record<string, unknown>>); the service validates each (group, key, value) triple against the registry. Unknown groups/keys → 400; value-shape mismatch → 400 with the zod issue.


Storefront

Base path: /store/settings. Anonymous. Returns only keys flagged public: true in the platform store registry. Unknown groups/keys throw 404 rather than returning empty — that way an attacker can't probe for the existence (or absence) of admin-only configuration.

GET /store/settings — Full public scope

Response 200ScopeSettingsResponse containing only public keys.

{
  "data": {
    "branding": {
      "logo_url": "https://cdn.example/logo.svg",
      "store_name": "Acme"
    },
    "contact": {
      "email": "hello@acme.example",
      "phone": "+91-80-1234-5678"
    }
  },
  "message": "Success",
  "statusCode": 200
}

GET /store/settings/:group

Response 200GroupSettingsResponse. 404 when the group has no public keys.


GET /store/settings/:group/:key

Response 200SettingValueResponse. 404 when the key isn't registered as public.


Platform — admin scope

Base path: /admin/settings/admin. Required permission: adminSetting:read / :update.

Method + pathNotes
GET /admin/settings/adminEvery admin-scope group keyed { group: { key: value } }
GET /admin/settings/admin/:groupOne group, flat { key: value }
PATCH /admin/settings/adminBulk update. Body { group: { key: value } }. Unknown group/key → 400; value mismatch → 400

The split per-scope routing (separate admin* and store* paths) exists because @RequirePermissions is AND-only — combining both resources on one decorator would require both permissions on the caller.


Platform — store scope

Base path: /admin/settings/store. Required permission: storeSetting:read / :update. Same shape as the admin-scope endpoints.


Vendor self-service

Base path: /vendor/settings. Auth: Better-Auth session with an active vendor. No platform RBAC permission — vendor users are not platform staff.

GET /vendor/settings/admin / GET /vendor/settings/store

Returns VendorScopeSettingsResponse for the active vendor:

{
  "data": {
    "values": {
      "shipping": {
        "flat_rate_subunit": 4900,
        "free_above_subunit": 99900,
        "enabled_providers": ["clickpost", "self-handled"]
      }
    },
    "readOnlyKeys": {
      "shipping": ["admin_only_carrier_token"]
    }
  },
  "message": "Success",
  "statusCode": 200
}

GET /vendor/settings/admin/:group / GET /vendor/settings/store/:group

Returns VendorGroupSettingsResponse for one group.

PATCH /vendor/settings/admin / PATCH /vendor/settings/store

Bulk update. Same body shape as the platform-admin endpoints. forAdmin: true keys reject with 403 even when the rest of the payload is valid — the service rejects the whole call rather than silently partial-applying. Use the platform-admin override surface (below) to write those.

Errors

StatusCodeWhen
400VALIDATION_ERRORUnknown group/key, value fails registry zod, or empty body
403FORBIDDENNo active vendor, or payload includes a forAdmin: true key

Platform-admin override — any vendor

Base path: /admin/vendors/:vendorId/settings. Required permission: platformVendorSetting:read / :update. Lets platform staff read and write any vendor's settings, including keys flagged forAdmin: true.

Distinct permission resource from the regular admin settings so it can be granted to platform support staff without also granting it via the adminSetting/storeSetting resources (and vice versa).

Method + pathNotes
GET /admin/vendors/:vendorId/settings/adminSame shape as the vendor self-service endpoint, scoped to target vendor
GET /admin/vendors/:vendorId/settings/admin/:groupSingle group
PATCH /admin/vendors/:vendorId/settings/adminBulk update. Bypasses the forAdmin write-guard
GET /admin/vendors/:vendorId/settings/storeStore scope
GET /admin/vendors/:vendorId/settings/store/:group
PATCH /admin/vendors/:vendorId/settings/storeBulk update. Bypasses the forAdmin write-guard

Registry (programmatic API)

Settings are declared with defineSetting() / defineVendorSetting(). New consumer modules don't add HTTP endpoints — they extend the registry array and call SettingsService / VendorSettingsService.

// api-modules/settings/src/registry/admin/reviews.ts
export const adminReviewsSettings = [
  defineSetting({
    scope: "admin",
    group: "reviews",
    key: "allow_vendor_approve",
    zod: z.boolean(),
    defaultValue: false,
    // public: false (default) — not exposed on /store/settings
    // forAdmin: false (default) — vendor self-service can read but ... wait, this is platform-scope
  }),
];

// api-modules/settings/src/registry/vendor/admin/shipping.ts
export const adminShippingVendorSettings = [
  defineVendorSetting({
    scope: "admin",
    group: "shipping",
    key: "flat_rate_subunit",
    zod: z.number().int().min(0),
    defaultValue: 0,
  }),
  defineVendorSetting({
    scope: "admin",
    group: "shipping",
    key: "admin_only_carrier_token",
    zod: z.string(),
    defaultValue: "",
    forAdmin: true,    // vendor reads but cannot write — surfaces in readOnlyKeys
  }),
];

Known platform groups

ScopeGroupOwner / purpose
adminreviewsReview moderation flags consumed by @sc/reviews
adminpaymentEnabled payment provider list per platform
adminpayment.razorpayRazorpay credentials (key_id, key_secret, webhook_secret)
adminshippingPlatform-wide shipping defaults / allow-lists
adminnotificationsChannel toggles, default templates
store(registered by storefront-facing features)Branding, contact details — typically public: true

Known vendor groups

ScopeGroupOwner / purpose
adminshippingflat_rate_subunit, free_above_subunit, enabled_providers, provider configs (clickpost.*)
admintaxTax provider id + provider config
adminnotificationsVendor-side notification preferences
storebranding, contact, etc.Vendor's storefront customization

  • admin-rbac — provides the platformVendorSetting, adminSetting, storeSetting permissions. See admin-rbac.md.
  • shipping — reads vendor.admin.shipping.*; provides a typed UI over the underlying settings. See shipping.md.
  • payment-razorpay — reads admin.payment.razorpay.* credentials from this module. See payment-razorpay.md.
  • reviews — reads admin.reviews.allow_vendor_* flags to gate vendor-side moderation. See reviews.md.
  • notifications — reads channel toggles from admin.notifications.*. See notifications.md.

On this page