Supercommerce API Docs
Vendor API

Shipping ClickPost Module — Vendor surface

Vendor-facing HTTP surface for per-vendor ClickPost integration — credentials, pickup address, courier map, parcel defaults, webhook secret, and the courier allow-list. ClickPost is one shipping provider…

Vendor-facing HTTP surface for per-vendor ClickPost integration — credentials, pickup address, courier partner map, parcel defaults, webhook secret, and the courier allow-list. ClickPost is one shipping provider plugin (see shipping.md for the provider-agnostic surface and order.md for the fulfillment dispatch).

Source: api-modules/shipping-clickpost/src/controllers/vendor-clickpost-config.controller.ts.


Conventions

Authentication

Both endpoints require a Better-Auth bearer session with an active vendor.

Authorization: Bearer <session-token>

The active vendor is resolved via resolveActiveVendorId(session). Sessions missing an active vendor are rejected with 403 Forbidden. There is no platform RBAC permission — vendor users are not platform staff.

Tenant scoping

Every read and write scopes to the active vendor's id. Config is persisted via VendorSettingsService under vendor.admin.shipping.clickpost.*, so writes are also subject to the registry's forAdmin: true flag — but the controller calls setMany(..., { allowAdminOnly: false }), so any key marked admin-only in the registry would reject with 403 (same rule as vendor/settings).

Response envelope

{
  "data": <payload>,
  "message": "Success",
  "statusCode": 200,
  "metadata": { /* optional */ }
}

Error envelope

statusCodeerrorCode examples
400BAD_REQUEST, VALIDATION_ERROR
401UNAUTHORIZED
403FORBIDDEN (no active vendor, or payload includes a forAdmin: true key)
500INTERNAL_SERVER_ERROR

Domain types

ClickPostCourierEntry

Each entry in the courierMap records the ClickPost-side integer courier partner id and the account code for that courier. ClickPost's v3 API uses integer courier_partner_id values rather than string codes, and these values are per-account — obtain them from your ClickPost dashboard or account manager.

type ClickPostCourierEntry = {
  cpId: number;          // ClickPost integer courier_partner_id (from your ClickPost dashboard)
  accountCode: string;   // Account code for this courier (from ClickPost dashboard)
};

ClickPostConfigResponse

type ClickPostConfigResponse = {
  apiKey: string;
  username: string;
  webhookSecret: string;
  pickupPincode: string;                   // Indian 6-digit
  enabledCouriers: string[];               // courier string codes in the allow-list

  /** Maps each enabled courier code to its ClickPost cpId + accountCode.
   *  Required for v3 fulfillment — a courier in enabledCouriers with no
   *  entry here will fail at ship-time with a clear error. */
  courierMap: Record<string, ClickPostCourierEntry>;

  /** Full pickup address used on the ClickPost create-order payload. */
  pickupName: string;
  pickupAddress: string;
  pickupCity: string;
  pickupState: string;
  pickupPhone: string;
  pickupEmail: string;
  pickupTin: string;   // TIN / GSTIN

  /** Parcel dimensions. Weight in grams; length/breadth/height in
   *  centimetres. L/B/H are sent on every shipment (the v3 payload has
   *  no per-item dimensions); only weight falls back, used when the
   *  order's lines supply no weight. Null when not yet configured. */
  defaultParcel: {
    weight: number;    // grams
    length: number;    // centimetres
    breadth: number;   // centimetres
    height: number;    // centimetres
  } | null;

  /** Fully-qualified URL the vendor should configure in ClickPost's
   *  webhook settings. Computed from PUBLIC_API_BASE_URL + the per-vendor
   *  route. Null when PUBLIC_API_BASE_URL isn't set on the API process —
   *  in that case vendor docs should fall back to manual instructions. */
  webhookUrl: string | null;
};

ClickPost config

Base path: /vendor/shipping/clickpost/config.

GET /vendor/shipping/clickpost/config — Get config

Returns the active vendor's ClickPost credentials, pickup address, courier map, parcel defaults, and the resolved webhook URL.

Response 200ClickPostConfigResponse.

{
  "data": {
    "apiKey": "cp_live_...",
    "username": "acme-bakery",
    "webhookSecret": "whsec_...",
    "pickupPincode": "560001",
    "enabledCouriers": ["bluedart", "delhivery"],
    "courierMap": {
      "bluedart": { "cpId": 12, "accountCode": "BD_ACME_001" },
      "delhivery": { "cpId": 4, "accountCode": "DL_ACME_002" }
    },
    "pickupName": "Acme Bakery Warehouse",
    "pickupAddress": "123 Industrial Area",
    "pickupCity": "Bengaluru",
    "pickupState": "Karnataka",
    "pickupPhone": "9876543210",
    "pickupEmail": "dispatch@acme-bakery.com",
    "pickupTin": "29ABCDE1234F1Z5",
    "defaultParcel": {
      "weight": 500,
      "length": 20,
      "breadth": 15,
      "height": 10
    },
    "webhookUrl": "https://api.example.com/webhooks/shipping/clickpost/01J9..."
  },
  "message": "Success",
  "statusCode": 200
}

Errors

StatusCodeWhen
403FORBIDDENNo active vendor on session

PATCH /vendor/shipping/clickpost/config — Update config

Partial update — fields not in the body are left untouched. The body is .strict() so unknown keys are rejected at the zod layer. The controller maps each field to a setting under vendor.admin.shipping.clickpost.* and writes via VendorSettingsService.setMany (audit row per changed key).

Body

{
  "apiKey": "cp_live_...",
  "username": "acme-bakery",
  "webhookSecret": "whsec_...",
  "pickupPincode": "560001",
  "enabledCouriers": ["bluedart", "delhivery"],
  "courierMap": {
    "bluedart": { "cpId": 12, "accountCode": "BD_ACME_001" },
    "delhivery": { "cpId": 4, "accountCode": "DL_ACME_002" }
  },
  "pickupName": "Acme Bakery Warehouse",
  "pickupAddress": "123 Industrial Area",
  "pickupCity": "Bengaluru",
  "pickupState": "Karnataka",
  "pickupPhone": "9876543210",
  "pickupEmail": "dispatch@acme-bakery.com",
  "pickupTin": "29ABCDE1234F1Z5",
  "defaultParcel": {
    "weight": 500,
    "length": 20,
    "breadth": 15,
    "height": 10
  }
}

Credentials and courier list

FieldTypeConstraintsSetting key
apiKeystring?Trimmed; 1..500 charsclickpost.api_key
usernamestring?Trimmed; 1..200 charsclickpost.username
webhookSecretstring?Trimmed; 32..500 charsclickpost.webhook_secret
pickupPincodestring?Trimmed; exactly 6 digits (/^\d{6}$/)clickpost.pickup_pincode
enabledCouriersstring[]?Each entry non-emptyclickpost.enabled_couriers

enabledCouriers gates nothing on the server. It drives the config UI's courier picker only; no code path validates a shipment against it. resolveCourier and methodsForVendor both key off courierMap, so a courier is usable once it has a courierMap entry — with or without an enabledCouriers entry.

Courier map — required for v3 fulfillment

FieldTypeNotesSetting key
courierMapRecord<string, { cpId: number; accountCode: string }>?One entry per enabled courier; cpId and accountCode are per-account values from your ClickPost dashboardclickpost.courier_map

Where to find cpId and accountCode: Log in to your ClickPost dashboard, go to Couriers (or contact your ClickPost account manager). The integer cpId (courier_partner_id) and accountCode are specific to your ClickPost account — they cannot be inferred from the courier name.

Pickup address — required for v3 fulfillment

FieldTypeConstraintsSetting key
pickupNamestring?Trimmed; 1..120 charsclickpost.pickup_name
pickupAddressstring?Trimmed; 1..500 charsclickpost.pickup_address
pickupCitystring?Trimmed; 1..120 charsclickpost.pickup_city
pickupStatestring?Trimmed; 1..120 charsclickpost.pickup_state
pickupPhonestring?Trimmed; 6..20 charsclickpost.pickup_phone
pickupEmailstring?Valid email, max 200 charsclickpost.pickup_email
pickupTinstring?Trimmed; 1..40 chars (TIN / GSTIN)clickpost.pickup_tin

Default parcel dimensions — required for v3 fulfillment

FieldTypeConstraintsSetting key
defaultParcel{ weight, length, breadth, height }?Nested object; all four sub-fields must be positive integers. weight in grams; length, breadth, height in centimetres.clickpost.default_parcel (stored as a JSON object under this single key)

Not optional in practice, and not purely a fallback. getCredentials rejects a config without it. length / breadth / height are sent on every shipment — the ClickPost v3 payload carries no per-item dimensions, so this is the only source. Only weight behaves as a fallback (line.weight ?? defaultParcel.weight).

Response 200 — updated ClickPostConfigResponse (re-fetched after the write).

Errors

StatusCodeWhen
400VALIDATION_ERRORBody fails zod (unknown key, short secret, bad pincode)
403FORBIDDENNo active vendor on session, or payload maps to a forAdmin: true key

Fulfillment flow (ClickPost v3)

When a vendor marks a sub-order fulfilled with providerId="clickpost" and a courier method, the system:

  1. Validates that all required config keys are present — api_key, username, pickup_* fields, and a courier_map entry for the chosen courier. Missing keys are listed in the error response.
  2. Builds the ClickPost v3 create-order payload:
    • pickup_info — from the vendor's pickup_* config fields.
    • drop_info — from the order's shipping address.
    • shipment_details — one item per order line carrying sku, description, quantity, price, and weight (from order_line.weightAtOrder, snapshotted at place-order; falls back to defaultParcel.weight when null). Total weight is the sum of per-line weights, falling back to defaultParcel.weight when every line is null. Parcel length / breadth / height come from defaultParcel unconditionally — the v3 item shape has no dimension fields, so nothing per-line or per-variant contributes.
    • cod_value — set to the sub-order total (in rupees; converted from internal integer subunits) when payment_method === "cod". Set to 0 for prepaid orders.
    • courier_partner_id — the integer cpId from the vendor's courier_map for the chosen courier.
  3. Calls POST https://www.clickpost.in/api/v3/create-order/?username=<u>&key=<k> with Content-Type: application/json.
  4. Handles the response:
    • ClickPost always returns HTTP 200, even on errors. Success is signaled by meta.success being true and meta.status being 200, 102, or 202.
    • Status 200 — synchronous success. AWB is at result.waybill; label URL at result.label. Both are stamped onto the sub-order immediately.
    • Status 102 / 202async accepted. ClickPost has accepted the order but the AWB is not yet assigned. The AWB will arrive later via a ClickPost tracking webhook (see the webhooks doc). The sub-order is marked fulfilled, and the AWB/label will be populated when the webhook is received.
    • meta.status 400 (or meta.success === false) — business error. The error message from ClickPost is surfaced to the vendor.
  5. Stamps the AWB and label URL on order_vendor (immediately on sync; via webhook on async).

Product variant fields used at fulfillment

Of the fields set in the product editor under Variants → Shipping/Customs, exactly one reaches ClickPost:

Variant fieldTypeReaches ClickPost?
weightintegerYes — grams. Snapshotted onto order_line.weightAtOrder at place-order for stable manifests, then sent as each item's weight and summed into the shipment total.
length / breadth / heightintegerNo — centimetres, but the v3 payload takes dimensions only from defaultParcel.
countryOfOriginstringNo
midCodestringNo
hsnCodestringNo — snapshotted onto order_line at place-order and used for GST invoicing, not shipping.

CreateShipmentInput.items (built by OrderLineRepository.listForManifest) carries only sku, description, quantity, unitPriceSubunits, and weight, so the customs fields have no path into the create-order call today. They remain meaningful for invoicing and for any future provider that accepts them — but setting them will not change a ClickPost manifest.

Set defaultParcel to realistic values for your typical parcel: its length / breadth / height are what every shipment declares, and its weight is the fallback when a line has no snapshotted weight.


Webhook receiver

The ClickPost webhook receiver itself is not on the vendor surface — it's an unauthenticated public endpoint that verifies the per-vendor webhookSecret from the request signature and lands rows on shipping_event. See the webhooks documentation under separated/webhooks/ for that surface.


  • shipping — provider-agnostic config (enabledProviders, flat rate, tracking events). clickpost must appear in vendor.admin.shipping.enabled_providers for these credentials to be used at fulfill time. See shipping.md.
  • settingsvendor.admin.shipping.clickpost.* keys live in the vendor settings registry; this controller is a typed wrapper over vendor-settings. See settings.md.
  • orderPOST /vendor/orders/:id/fulfilled with providerId="clickpost" dispatches to ClickPostProvider.createShipment() which reads these credentials.

On this page