Supercommerce API Docs
Admin API

Serviceability Module — Admin

Admin surface for the delivery deny-list — the pincodes the store cannot ship to, whether each one warns or blocks at checkout, and the message the shopper sees. Bulk add, bulk edit, bulk remove.

Admin surface for the delivery deny-list — the pincodes the store cannot ship to, whether each one warns or blocks at checkout, and the message the shopper sees. Everything is bulk-capable: one endpoint adds a single pincode or a pasted batch.

Source: api-modules/serviceability/src/controllers/admin-serviceability.controller.ts.

This is an optional plugin — removing ServiceabilityModule.forRoot() from app.module.ts removes these routes along with the storefront check, drops the cart advisory, and makes every pincode deliverable again. The customer-facing side is documented in Serviceability — Storefront.


Model

The table is a deny-list. A pincode with no row is deliverable; there is no allow-list to maintain and an empty table means the store ships anywhere. Each row carries a mode:

ModeCheckout behaviour
informThe message renders as a warning. The shopper may still place the order.
blockThe message renders as an error and order placement is refused with PINCODE_NOT_SERVICEABLE.

pincode is unique. Submitting a pincode that already has a rule overwrites its mode and message rather than erroring — re-pasting an overlapping batch is the normal way an operator corrects one.


Conventions

Authentication

All endpoints: BetterAuthGuard + PermissionsGuard.

EndpointPermission
GET /admin/serviceability/pincodesserviceability: read
GET /admin/serviceability/pincodes/:idserviceability: read
POST /admin/serviceability/pincodesserviceability: create
PATCH /admin/serviceability/pincodes/:idserviceability: update
PATCH /admin/serviceability/pincodes/bulkserviceability: update
DELETE /admin/serviceability/pincodes/:idserviceability: delete
POST /admin/serviceability/pincodes/bulk-deleteserviceability: delete

Admin UI

Storefront → Serviceability (@sc/admin-serviceability), gated on serviceability: read. The Restrict pincodes button is gated on serviceability: create, the row-level and bulk edit actions on serviceability: update, and both remove actions on serviceability: delete. Removing the module's line from apps/admin/modules/registry.ts drops the nav entry and its route.

It is a single list route — both the batch-add form and the single-row editor open in a dialog over it. Clicking a row opens the editor pre-filled from the row already in the table, so editing costs no extra request. Selecting rows reveals a bulk bar with Edit restriction (mode and/or message) and Remove.

Response envelope

{ "data": <payload>, "message": "Success", "statusCode": <code> }

The list uses the standard metadata: { total, limit, offset, hasMore } shape.


Endpoints

GET /admin/serviceability/pincodes — List restricted pincodes

Standard limit / offset pagination, newest first, plus:

QueryTypeNotes
mode"inform" | "block"Filter by restriction
pincodestringPrefix match — 56 narrows to 56xxxx

Response 200 — paginated rows:

type ServiceabilityPincode = {
  id: string;
  pincode: string;
  mode: "inform" | "block";
  message: string;
  createdAt: string;  // ISO
  updatedAt: string;  // ISO
};

GET /admin/serviceability/pincodes/:id — Read one

Response 200 — one ServiceabilityPincode. 404 when the id is unknown.

POST /admin/serviceability/pincodes — Add or overwrite

The single and bulk path are the same endpoint. pincodes accepts either a delimited string or an array, so the admin form needs one text box either way.

type UpsertServiceabilityPincodes = {
  pincodes: string | string[];   // comma / space / semicolon / newline separated
  mode: "inform" | "block";
  message?: string;              // up to 500 chars; blank or omitteddefault
};

message is optional. Omitting it — or sending a blank/whitespace-only string — stores the default copy, "We don't deliver to this pincode.", so a shopper always sees a reason and the operator only writes copy when the generic line isn't good enough.

Entries are trimmed, de-duplicated, and each must match ^[1-9]\d{5}$. At most 5000 pincodes per request; a single invalid entry fails the whole request with VALIDATION_ERROR rather than silently dropping it.

Response 201:

type UpsertServiceabilityResult = {
  created: number;     // pincodes that had no rule before
  updated: number;     // pincodes whose rule was overwritten
  affected: number;    // created + updated
  pincodes: string[];  // the normalised, de-duplicated batch
};

PATCH /admin/serviceability/pincodes/:id — Edit one

type UpdateServiceabilityPincode = {
  pincode?: string;
  mode?: "inform" | "block";
  message?: string;
};

At least one field is required. Changing pincode to one that already has a rule is 409. Omitting message leaves it alone; sending it blank resets it to the default. Response 200 — the updated ServiceabilityPincode.

PATCH /admin/serviceability/pincodes/bulk — Edit many

type BulkUpdateServiceabilityPincodes = {
  ids: string[];                  // 11000, de-duplicated
  mode?: "inform" | "block";
  message?: string;
};

At least one of mode / message is required. pincode is deliberately not bulk-editable — it identifies the row. A blank message resets the selection to the default copy. Unknown ids are ignored, so affected may be lower than ids.length.

Response 200{ created: 0, updated, affected }.

DELETE /admin/serviceability/pincodes/:id — Lift one restriction

Response 204. 404 when the id is unknown. The pincode becomes deliverable immediately.

POST /admin/serviceability/pincodes/bulk-delete — Lift many

POST rather than DELETE because the id list travels in the body, which DELETE does not carry portably across proxies.

type BulkDeleteServiceabilityPincodes = { ids: string[] };  // 11000

Response 200{ created: 0, updated: 0, affected }.


Enforcement

The deny-list is consulted through the neutral SERVICEABILITY_PORT (@sc/core/ports), never by importing this module, so every consumer degrades cleanly when the plugin is unmounted:

ConsumerBehaviour
Cart reads (GET /store/cart, POST /store/cart/prepare-checkout)Attach a serviceability advisory for the picked delivery address. Advisory only — a cart read never fails.
Order placement (POST /store/orders)Re-checks the resolved shipping address and rejects block with 400 PINCODE_NOT_SERVICEABLE. inform passes.
Razorpay Magic serviceability callbackReports serviceable: false for a blocked pincode, so Magic stops the shopper before payment is captured.

Placement re-checks rather than trusting the cart advisory: the storefront may ignore it, and the operator may have added the pincode between the shopper reading the cart and pressing pay.

For orders that arrive already paid (the externally-paid webhook path), a blocked pincode is logged as a warning and accepted — refusing would strand captured money. The provider's own pre-payment callback is the gate there.

On this page