# Storefront BulqC API Doc

48 operations across 40 paths. Generated from OpenAPI and verified against Express registrations.

See index.html for the searchable reference and openapi.json for the machine-readable contracts.

## GET /api/v2/storefront/{slug}/me/addresses

List own buyer addresses in this store



Source: bulqc-v2-backend/src/modules/buyers/buyers.routes.js

```json
{
  "summary": "List own buyer addresses in this store",
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Active addresses",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BuyerAddress"
                }
              }
            }
          }
        }
      }
    },
    "401": {
      "description": "Buyer login required"
    },
    "404": {
      "description": "Store unavailable"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## POST /api/v2/storefront/{slug}/me/addresses

Create an own-store buyer address with an idempotency key



Source: bulqc-v2-backend/src/modules/buyers/buyers.routes.js

```json
{
  "summary": "Create an own-store buyer address with an idempotency key",
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "name": "Idempotency-Key",
      "in": "header",
      "required": true,
      "schema": {
        "type": "string",
        "minLength": 16,
        "maxLength": 128
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/BuyerAddressInput"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Idempotent replay",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontCreatedAddress"
          }
        }
      }
    },
    "201": {
      "description": "Address created",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontCreatedAddress"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input"
    },
    "409": {
      "description": "Address limit or conflict"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## DELETE /api/v2/storefront/{slug}/me/addresses/{addressId}

Delete an own-store address



Source: bulqc-v2-backend/src/modules/buyers/buyers.routes.js

```json
{
  "summary": "Delete an own-store address",
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "name": "addressId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "expectedVersion"
          ],
          "properties": {
            "expectedVersion": {
              "type": "integer",
              "minimum": 0
            }
          }
        }
      }
    }
  },
  "responses": {
    "204": {
      "description": "Address deleted"
    },
    "404": {
      "description": "Address inaccessible"
    },
    "409": {
      "description": "Version or default conflict"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## PATCH /api/v2/storefront/{slug}/me/addresses/{addressId}

Update an own-store address with its expected version



Source: bulqc-v2-backend/src/modules/buyers/buyers.routes.js

```json
{
  "summary": "Update an own-store address with its expected version",
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "name": "addressId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/BuyerAddressUpdateInput"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated address",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/BuyerAddress"
          }
        }
      }
    },
    "404": {
      "description": "Address inaccessible"
    },
    "409": {
      "description": "Version or default conflict"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## POST /api/v2/auth/logout

Revoke current session

Revoke a valid session and clear the host-only cookie. Missing or invalid sessions also receive 204 with cookie clearing. Production requires an allowed canonical buyer or seller origin; wrong-origin valid sessions remain forbidden.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Revoke current session",
  "description": "Revoke a valid session and clear the host-only cookie. Missing or invalid sessions also receive 204 with cookie clearing. Production requires an allowed canonical buyer or seller origin; wrong-origin valid sessions remain forbidden.",
  "operationId": "post__api_v2_auth_logout",
  "parameters": [
    {
      "in": "header",
      "name": "Origin",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      },
      "description": "Must exactly match a configured allowed origin, including port. Required for all mutations, including OTP requests."
    }
  ],
  "responses": {
    "204": {
      "description": "Session revoked; cookie cleared"
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid authentication or challenge",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Forbidden origin or role",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Missing or inaccessible resource",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Payload too large",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Rate limited",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Internal failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Dependency unavailable",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## GET /api/v2/auth/me

Read authenticated identity



Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Read authenticated identity",
  "description": "",
  "operationId": "get__api_v2_auth_me",
  "parameters": [],
  "responses": {
    "200": {
      "description": "Own profile",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/MerchantAccountProfile"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid authentication or challenge",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Forbidden origin or role",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Missing or inaccessible resource",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Payload too large",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Rate limited",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Internal failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Dependency unavailable",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## PATCH /api/v2/auth/me

Merchant-only personal name update

Update own merchant name, optional contact email and/or a bounded profile photo. expectedVersion protects against stale updates. Photo input is null to remove, or JPG/PNG/WebP data URI, at most 220000 characters; server validates actual bytes, rejects animation and more than 16 million pixels, crops to at most 400px square, strips metadata and stores a small private WebP. Existing photo is retained when omitted. No mobile changes are accepted here.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Merchant-only personal name update",
  "parameters": [],
  "responses": {
    "200": {
      "description": "Own updated profile",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/MerchantAccountProfile"
          }
        }
      }
    },
    "400": {
      "description": "Invalid or inactive selection"
    },
    "401": {
      "description": "Authentication required"
    },
    "409": {
      "description": "Slug, version or idempotency conflict"
    }
  },
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "name": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "email": {
              "type": "string",
              "maxLength": 254
            },
            "photo": {
              "type": "string",
              "nullable": true,
              "maxLength": 220000
            },
            "expectedVersion": {
              "type": "integer",
              "minimum": 0
            }
          }
        }
      }
    }
  },
  "description": "Update own merchant name, optional contact email and/or a bounded profile photo. expectedVersion protects against stale updates. Photo input is null to remove, or JPG/PNG/WebP data URI, at most 220000 characters; server validates actual bytes, rejects animation and more than 16 million pixels, crops to at most 400px square, strips metadata and stores a small private WebP. Existing photo is retained when omitted. No mobile changes are accepted here.",
  "security": [
    {
      "session": []
    }
  ]
}
```

## POST /api/v2/auth/otp/request

Request merchant OTP

Does not return or log OTP. Maximum five phone requests and twenty source requests per fixed UTC-hour window, and rolling sixty-second resend cooldown. No real sender is enabled by default.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Request merchant OTP",
  "description": "Does not return or log OTP. Maximum five phone requests and twenty source requests per fixed UTC-hour window, and rolling sixty-second resend cooldown. No real sender is enabled by default.",
  "operationId": "post__api_v2_auth_otp_request",
  "parameters": [
    {
      "in": "header",
      "name": "Origin",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      },
      "description": "Must exactly match a configured allowed origin, including port. Required for all mutations, including OTP requests."
    }
  ],
  "responses": {
    "202": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Challenge"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid authentication or challenge",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Forbidden origin or role",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Missing or inaccessible resource",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Payload too large",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Rate limited",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Internal failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Dependency unavailable",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": [],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Phone"
        }
      }
    }
  }
}
```

## POST /api/v2/auth/otp/verify

Verify single-use OTP and establish cookie session

Five attempts per challenge, five-minute expiry, fifty verification requests/source per five-minute window. Challenge determines merchant versus buyer/store scope.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Verify single-use OTP and establish cookie session",
  "description": "Five attempts per challenge, five-minute expiry, fifty verification requests/source per five-minute window. Challenge determines merchant versus buyer/store scope.",
  "operationId": "post__api_v2_auth_otp_verify",
  "parameters": [
    {
      "in": "header",
      "name": "Origin",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      },
      "description": "Must exactly match a configured allowed origin, including port. Required for all mutations, including OTP requests."
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Session"
          }
        }
      },
      "headers": {
        "Set-Cookie": {
          "description": "Opaque eight-hour session cookie; HttpOnly; SameSite=Strict; Path=/api/v2; Secure in production. Rotation preserves absolute expiry.",
          "schema": {
            "type": "string"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid authentication or challenge",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Forbidden origin or role",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Missing or inaccessible resource",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Payload too large",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Rate limited",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Internal failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Dependency unavailable",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": [],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Proof"
        }
      }
    }
  }
}
```

## POST /api/v2/storefront/{slug}/auth/otp/request

Request store-bound buyer OTP

Store must be live or paused in an active organization. Does not accept tenant IDs. Phone limits span all stores. Optional emailCopy selects an additional verified-email copy; it never replaces SMS/WhatsApp or enrolls/links an address.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Request store-bound buyer OTP",
  "description": "Store must be live or paused in an active organization. Does not accept tenant IDs. Phone limits span all stores. Optional emailCopy selects an additional verified-email copy; it never replaces SMS/WhatsApp or enrolls/links an address.",
  "operationId": "post__api_v2_storefront_slug_auth_otp_request",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$"
      }
    },
    {
      "in": "header",
      "name": "Origin",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      },
      "description": "Must exactly match a configured allowed origin, including port. Required for all mutations, including OTP requests."
    }
  ],
  "responses": {
    "202": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Challenge"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid authentication or challenge",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Forbidden origin or role",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Missing or inaccessible resource",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Payload too large",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Rate limited",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Internal failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Dependency unavailable",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": [],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/BuyerOtpRequest"
        }
      }
    }
  }
}
```

## DELETE /api/v2/storefront/{slug}/me

Soft-delete the current store-bound buyer account

Requires the current buyer session for this slug and the normal cookie/Origin protections. No target ID is accepted. Atomically retires the identity, releases its phone for a fresh OTP signup, revokes every session and existing login/email challenges, and suppresses notifications to the retired buyer. Retains historical records and private original phone; this is not irreversible erasure. Re-registration creates a new buyer ID with no old profile, addresses, orders, approval, tier assignment, verified email, consent or sessions. Email must be enrolled and verified again. Does not delete merchant accounts, organizations or stores. Repeat calls using the retired cookie return 401 and cannot change the fresh identity. A lost response should be followed by a profile check; 401 means the cookie is no longer active, not proof by itself of deletion. Existing commercial commitments continue through their normal lifecycle. An existing active session can self-delete when its store is no longer public; signup still requires an available public store.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "operationId": "deleteBuyerAccount",
  "summary": "Soft-delete the current store-bound buyer account",
  "description": "Requires the current buyer session for this slug and the normal cookie/Origin protections. No target ID is accepted. Atomically retires the identity, releases its phone for a fresh OTP signup, revokes every session and existing login/email challenges, and suppresses notifications to the retired buyer. Retains historical records and private original phone; this is not irreversible erasure. Re-registration creates a new buyer ID with no old profile, addresses, orders, approval, tier assignment, verified email, consent or sessions. Email must be enrolled and verified again. Does not delete merchant accounts, organizations or stores. Repeat calls using the retired cookie return 401 and cannot change the fresh identity. A lost response should be followed by a profile check; 401 means the cookie is no longer active, not proof by itself of deletion. Existing commercial commitments continue through their normal lifecycle. An existing active session can self-delete when its store is no longer public; signup still requires an available public store.",
  "security": [
    {
      "session": []
    }
  ],
  "requestBody": {
    "required": false,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "properties": {}
        }
      }
    }
  },
  "responses": {
    "204": {
      "description": "Account retired; session cookie cleared"
    },
    "400": {
      "description": "Unexpected request fields or invalid slug"
    },
    "401": {
      "description": "Missing, expired or revoked session; includes repeated deletion with the old cookie"
    },
    "403": {
      "description": "Origin protection rejected the request"
    },
    "404": {
      "description": "Missing store, merchant session, or buyer session bound to another store"
    }
  },
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$"
      }
    }
  ]
}
```

## GET /api/v2/storefront/{slug}/me

Read own store-bound buyer profile



Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Read own store-bound buyer profile",
  "description": "",
  "operationId": "get__api_v2_storefront_slug_me",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Profile"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid authentication or challenge",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Forbidden origin or role",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Missing or inaccessible resource",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Payload too large",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Rate limited",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Internal failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Dependency unavailable",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## PATCH /api/v2/storefront/{slug}/me

Update own buyer contact or business name



Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Update own buyer contact or business name",
  "description": "",
  "operationId": "patch__api_v2_storefront_slug_me",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$"
      }
    },
    {
      "in": "header",
      "name": "Origin",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      },
      "description": "Must exactly match a configured allowed origin, including port. Required for all mutations, including OTP requests."
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Profile"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid authentication or challenge",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Forbidden origin or role",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Missing or inaccessible resource",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Payload too large",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Rate limited",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Internal failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Dependency unavailable",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/BuyerProfileUpdate"
        }
      }
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## DELETE /api/v2/storefront/{slug}/me/email

Remove buyer email

Clears address, verification and consent; invalidates pending proofs and delivery contexts.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Remove buyer email",
  "operationId": "removeBuyerEmail",
  "description": "Clears address, verification and consent; invalidates pending proofs and delivery contexts.",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$"
      }
    },
    {
      "in": "header",
      "name": "Origin",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      },
      "description": "Must exactly match a configured allowed origin, including port. Required for all mutations, including OTP requests."
    }
  ],
  "security": [
    {
      "session": []
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Profile"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Email enrollment unavailable or definite provider rejection",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  }
}
```

## PATCH /api/v2/storefront/{slug}/me/email/preferences

Update buyer email preferences

Requires a live store-bound buyer session and matching profile version; identity email verification is not required to opt out or opt in. Records an explicit transactional email preference for the seller customer-record email and invalidates pending delivery contexts through version change. Login OTP email copies still require verified identity email.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Update buyer email preferences",
  "operationId": "updateBuyerEmailNotificationConsent",
  "description": "Requires a live store-bound buyer session and matching profile version; identity email verification is not required to opt out or opt in. Records an explicit transactional email preference for the seller customer-record email and invalidates pending delivery contexts through version change. Login OTP email copies still require verified identity email.",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$"
      }
    },
    {
      "in": "header",
      "name": "Origin",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      },
      "description": "Must exactly match a configured allowed origin, including port. Required for all mutations, including OTP requests."
    }
  ],
  "security": [
    {
      "session": []
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Profile"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Email enrollment unavailable or definite provider rejection",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/BuyerEmailPreferenceRequest"
        }
      }
    }
  }
}
```

## POST /api/v2/storefront/{slug}/me/email/request

Request buyer email verification

Authenticated store-bound buyer and live session. Uses actual OTP lifetime, 60-second cooldown, ownership-only purpose. Does not verify or link accounts. Unknown submission can return 202; definitive rejection returns 503.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Request buyer email verification",
  "operationId": "requestBuyerEmailOwnership",
  "description": "Authenticated store-bound buyer and live session. Uses actual OTP lifetime, 60-second cooldown, ownership-only purpose. Does not verify or link accounts. Unknown submission can return 202; definitive rejection returns 503.",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$"
      }
    },
    {
      "in": "header",
      "name": "Origin",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      },
      "description": "Must exactly match a configured allowed origin, including port. Required for all mutations, including OTP requests."
    }
  ],
  "security": [
    {
      "session": []
    }
  ],
  "responses": {
    "202": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Challenge"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Email enrollment unavailable or definite provider rejection",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/BuyerEmailEnrollmentRequest"
        }
      }
    }
  }
}
```

## POST /api/v2/storefront/{slug}/me/email/verify

Verify buyer email ownership

Session/store/identity-bound ownership proof, five attempts, single-use, cannot authenticate. Successful proof updates address, verification, consent and profile version together.

Source: bulqc-v2-backend/src/modules/identity/identity.routes.js

```json
{
  "summary": "Verify buyer email ownership",
  "operationId": "verifyBuyerEmailOwnership",
  "description": "Session/store/identity-bound ownership proof, five attempts, single-use, cannot authenticate. Successful proof updates address, verification, consent and profile version together.",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$"
      }
    },
    {
      "in": "header",
      "name": "Origin",
      "required": true,
      "schema": {
        "type": "string",
        "format": "uri"
      },
      "description": "Must exactly match a configured allowed origin, including port. Required for all mutations, including OTP requests."
    }
  ],
  "security": [
    {
      "session": []
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Profile"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "429": {
      "description": "Invalid input, challenge, context, version or rate limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Email enrollment unavailable or definite provider rejection",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/Proof"
        }
      }
    }
  }
}
```

## GET /api/v2/storefront/{slug}/cart

Read or create store-bound guest or buyer cart; merge guest cart after buyer login



Source: bulqc-v2-backend/src/modules/carts/carts.routes.js

```json
{
  "summary": "Read or create store-bound guest or buyer cart; merge guest cart after buyer login",
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Current cart; checkout remains unavailable",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Cart"
          }
        }
      }
    }
  }
}
```

## GET /api/v2/storefront/{slug}/cart/context

Restore storefront identity and cart in one request

Resolves current publication, visibility and live identity once. With cart=1 (default), creates/restores a guest cart or restores a matching buyer cart and merges an eligible guest cart. With cart=0, defers cart work and returns cartDeferred. Incompatible sessions never receive buyer cart/profile. A valid anonymous publicVersion can reuse SSR catalog data; buyer pricing is always restored from current access. Independent cart/catalog/filter failures are reported separately.

Source: bulqc-v2-backend/src/modules/carts/carts.routes.js

```json
{
  "summary": "Restore storefront identity and cart in one request",
  "operationId": "getStorefrontCartContext",
  "security": [],
  "description": "Resolves current publication, visibility and live identity once. With cart=1 (default), creates/restores a guest cart or restores a matching buyer cart and merges an eligible guest cart. With cart=0, defers cart work and returns cartDeferred. Incompatible sessions never receive buyer cart/profile. A valid anonymous publicVersion can reuse SSR catalog data; buyer pricing is always restored from current access. Independent cart/catalog/filter failures are reported separately.",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$"
      }
    },
    {
      "in": "query",
      "name": "view",
      "schema": {
        "type": "string",
        "enum": [
          "catalog",
          "about",
          "terms",
          "privacy",
          "shell"
        ],
        "default": "catalog"
      }
    },
    {
      "name": "publicVersion",
      "in": "query",
      "schema": {
        "type": "integer",
        "minimum": 0
      },
      "description": "Fresh anonymous SSR configuration version; never used to grant access."
    },
    {
      "name": "cart",
      "in": "query",
      "schema": {
        "type": "string",
        "enum": [
          "0",
          "1"
        ],
        "default": "1"
      },
      "description": "Defer cart work on views that do not need it."
    }
  ],
  "responses": {
    "200": {
      "description": "Current storefront data; Cache-Control: private, no-store; Vary: Cookie",
      "headers": {
        "Cache-Control": {
          "schema": {
            "type": "string"
          }
        },
        "Server-Timing": {
          "schema": {
            "type": "string"
          }
        }
      },
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontCartContext"
          }
        }
      }
    },
    "400": {
      "description": "Invalid view/slug",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Origin rejected",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Store unavailable or host mismatch",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Dependency failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  }
}
```

## POST /api/v2/storefront/{slug}/cart/items

Add whole packs of a listed sellable



Source: bulqc-v2-backend/src/modules/carts/carts.routes.js

```json
{
  "summary": "Add whole packs of a listed sellable",
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "sellableId",
            "packs",
            "expectedVersion"
          ],
          "properties": {
            "sellableId": {
              "type": "string"
            },
            "packs": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100000
            },
            "expectedVersion": {
              "type": "integer",
              "minimum": 0
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated cart",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Cart"
          }
        }
      }
    },
    "400": {
      "description": "Invalid pack quantity"
    },
    "404": {
      "description": "Sellable unavailable"
    },
    "409": {
      "description": "Cart version conflict"
    }
  }
}
```

## DELETE /api/v2/storefront/{slug}/cart/items/{sellableId}

Remove a cart line



Source: bulqc-v2-backend/src/modules/carts/carts.routes.js

```json
{
  "summary": "Remove a cart line",
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "name": "sellableId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "expectedVersion"
          ],
          "properties": {
            "expectedVersion": {
              "type": "integer",
              "minimum": 0
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated cart",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Cart"
          }
        }
      }
    },
    "409": {
      "description": "Cart version conflict"
    }
  }
}
```

## PATCH /api/v2/storefront/{slug}/cart/items/{sellableId}

Set pack count for an existing cart line



Source: bulqc-v2-backend/src/modules/carts/carts.routes.js

```json
{
  "summary": "Set pack count for an existing cart line",
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "name": "sellableId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "packs",
            "expectedVersion"
          ],
          "properties": {
            "packs": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100000
            },
            "expectedVersion": {
              "type": "integer",
              "minimum": 0
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated cart",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Cart"
          }
        }
      }
    },
    "400": {
      "description": "Invalid pack quantity"
    },
    "409": {
      "description": "Cart version conflict"
    }
  }
}
```

## GET /api/v2/storefront/{slug}/catalog/filters

Get published category and attribute facet counts

Category facets include categoryId, count and name. Names are resolved from active categories belonging to this store's organization; unavailable names are null. Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs.

Source: bulqc-v2-backend/src/modules/storefront/storefront.routes.js

```json
{
  "summary": "Get published category and attribute facet counts",
  "description": "Category facets include categoryId, count and name. Names are resolved from active categories belonging to this store's organization; unavailable names are null. Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs.",
  "security": [],
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "categories": {
                "type": "array"
              },
              "attributes": {
                "type": "array"
              }
            }
          }
        }
      }
    },
    "403": {
      "description": "Store-specific buyer sign-in or explicit approval required to view this catalogue."
    },
    "404": {
      "description": "Unavailable",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  }
}
```

## GET /api/v2/storefront/{slug}/catalog/products

List published Phase 3 catalog products with store offers and shared stock

Search matches displayed product names or organization-scoped brand names using escaped literal matching. Name sort uses the lowercased displayed name and listing ID. Name cursors are version 2; obsolete cursors return INVALID_CURSOR. Rank cursors are unchanged.

Source: bulqc-v2-backend/src/modules/storefront/storefront.routes.js

```json
{
  "summary": "List published Phase 3 catalog products with store offers and shared stock",
  "security": [],
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "query",
      "name": "after",
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100
      }
    },
    {
      "in": "query",
      "name": "query",
      "schema": {
        "type": "string",
        "maxLength": 100
      }
    },
    {
      "in": "query",
      "name": "categoryId",
      "schema": {
        "type": "string",
        "pattern": "^[a-f0-9]{24}$"
      }
    },
    {
      "in": "query",
      "name": "attributes",
      "schema": {
        "type": "string",
        "description": "Comma-separated attributeCode:optionCode filters; all must match one selected variation."
      }
    },
    {
      "in": "query",
      "name": "sort",
      "schema": {
        "enum": [
          "rank",
          "name"
        ],
        "default": "rank"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PublicCatalogPage"
          }
        }
      }
    },
    "403": {
      "description": "Store-specific buyer sign-in or explicit approval required to view this catalogue."
    },
    "404": {
      "description": "Not found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "description": "Search matches displayed product names or organization-scoped brand names using escaped literal matching. Name sort uses the lowercased displayed name and listing ID. Name cursors are version 2; obsolete cursors return INVALID_CURSOR. Rank cursors are unchanged."
}
```

## GET /api/v2/storefront/{slug}/me/catalog/products

List buyer-specific catalogue products with tier prices

Search matches displayed product names or organization-scoped brand names using escaped literal matching. Name sort uses the lowercased displayed name and listing ID. Name cursors are version 2; obsolete cursors return INVALID_CURSOR. Rank cursors are unchanged.

Source: bulqc-v2-backend/src/modules/storefront/storefront.routes.js

```json
{
  "summary": "List buyer-specific catalogue products with tier prices",
  "security": [
    {
      "session": []
    }
  ],
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "query",
      "name": "after",
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "query",
      "name": "limit",
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100
      }
    },
    {
      "in": "query",
      "name": "query",
      "schema": {
        "type": "string",
        "maxLength": 100
      }
    },
    {
      "in": "query",
      "name": "categoryId",
      "schema": {
        "type": "string",
        "pattern": "^[a-f0-9]{24}$"
      }
    },
    {
      "in": "query",
      "name": "attributes",
      "schema": {
        "type": "string",
        "description": "Comma-separated attributeCode:optionCode filters; all must match one selected variation."
      }
    },
    {
      "in": "query",
      "name": "sort",
      "schema": {
        "enum": [
          "rank",
          "name"
        ],
        "default": "rank"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PublicCatalogPage"
          }
        }
      }
    },
    "403": {
      "description": "Store-specific buyer sign-in or explicit approval required to view this catalogue."
    },
    "404": {
      "description": "Not found",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "operationId": "ListBuyerCatalogProducts",
  "description": "Search matches displayed product names or organization-scoped brand names using escaped literal matching. Name sort uses the lowercased displayed name and listing ID. Name cursors are version 2; obsolete cursors return INVALID_CURSOR. Rank cursors are unchanged."
}
```

## GET /api/v2/storefront/{slug}/media/{assetId}

Redirect to a short-lived ready-media download

Published eligible product media redirects to the CloudFront detail URL when the feature is enabled; unpublished product originals are denied. Legacy single-key public derivatives remain supported. Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs.

Source: bulqc-v2-backend/src/modules/storefront/storefront.routes.js

```json
{
  "summary": "Redirect to a short-lived ready-media download",
  "security": [
    {
      "session": []
    }
  ],
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "path",
      "name": "assetId",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "302": {
      "description": "Signed media redirect"
    },
    "403": {
      "description": "Store-specific buyer sign-in or explicit approval required to view this catalogue."
    },
    "404": {
      "description": "Error",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Error",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "description": "Published eligible product media redirects to the CloudFront detail URL when the feature is enabled; unpublished product originals are denied. Legacy single-key public derivatives remain supported. Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs."
}
```

## GET /api/v2/storefront/{slug}/products

Browse published store products

 Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs.

Source: bulqc-v2-backend/src/modules/storefront/storefront.routes.js

```json
{
  "summary": "Browse published store products",
  "description": " Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs.",
  "operationId": "get_api_v2_storefront_slug_products",
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/ProductPage"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Authentication required",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Store-specific buyer sign-in or explicit approval required to view this catalogue."
    },
    "404": {
      "description": "Missing or inaccessible resource",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict, stale version or readiness blocker",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Payload or import exceeds size limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "422": {
      "description": "Import contains validation errors",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": [],
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "name": "after",
      "in": "query",
      "schema": {
        "type": "string",
        "pattern": "^[a-f0-9]{24}$"
      }
    },
    {
      "name": "limit",
      "in": "query",
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 100,
        "default": 25
      }
    }
  ]
}
```

## POST /api/v2/storefront/{slug}/checkout/attempts

Hold stock and create a pending buyer order



Source: bulqc-v2-backend/src/modules/checkout/checkout.routes.js

```json
{
  "summary": "Hold stock and create a pending buyer order",
  "operationId": "createCheckoutAttempt",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "header",
      "name": "Idempotency-Key",
      "required": true,
      "schema": {
        "type": "string",
        "minLength": 16
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/CheckoutAttemptInput"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Pending order and fifteen-minute stock hold",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/CheckoutAttempt"
          }
        }
      }
    },
    "409": {
      "description": "Requote, payment connection, stock, or acknowledgement required"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## GET /api/v2/storefront/{slug}/checkout/attempts/{attemptId}

Read own checkout attempt



Source: bulqc-v2-backend/src/modules/checkout/checkout.routes.js

```json
{
  "summary": "Read own checkout attempt",
  "operationId": "getCheckoutAttempt",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "path",
      "name": "attemptId",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-f0-9]{24}$"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Buyer-owned attempt",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/CheckoutAttempt"
          }
        }
      }
    },
    "404": {
      "description": "Not found or not owned"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## POST /api/v2/storefront/{slug}/checkout/attempts/{attemptId}/payment

Initiate Razorpay test-mode payment



Source: bulqc-v2-backend/src/modules/checkout/checkout.routes.js

```json
{
  "summary": "Initiate Razorpay test-mode payment",
  "operationId": "initiateCheckoutPayment",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "path",
      "name": "attemptId",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-f0-9]{24}$"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Provider order for browser checkout",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/PaymentInitiation"
          }
        }
      }
    },
    "409": {
      "description": "Expired or unknown initiation; reconcile before retry"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## POST /api/v2/storefront/{slug}/checkout/orders

Place a production manual-terms order without online payment

Default public order flow after onboarding publication. pay_later mode needs no online-payment approval, live-provider flags or payment connection, including production. Requires ready/enabled checkout, buyer session, current quote and Idempotency-Key. Checks warehouse, delivery, pricing and tax. Pay-later defaults to untracked inventory (unlimited stock capacity) unless published settings select inventoryMode=tracked. Only tracked orders check stock/backorders and commit inventory demand. paymentMethod=manual_terms, paymentStatus=unpaid and paidMinor=0. Settlement is recorded separately. Successful manual/pay-later placement clears the quoted buyer cart and increments its version atomically with order creation. Concurrent cart edits conflict instead of losing unquoted items; failed checkout leaves the cart unchanged. Same-key retries return the original order without duplicate demand or clearing subsequently added cart items. Legacy test/live modes retain their existing production gates.

Source: bulqc-v2-backend/src/modules/checkout/checkout.routes.js

```json
{
  "summary": "Place a production manual-terms order without online payment",
  "description": "Default public order flow after onboarding publication. pay_later mode needs no online-payment approval, live-provider flags or payment connection, including production. Requires ready/enabled checkout, buyer session, current quote and Idempotency-Key. Checks warehouse, delivery, pricing and tax. Pay-later defaults to untracked inventory (unlimited stock capacity) unless published settings select inventoryMode=tracked. Only tracked orders check stock/backorders and commit inventory demand. paymentMethod=manual_terms, paymentStatus=unpaid and paidMinor=0. Settlement is recorded separately. Successful manual/pay-later placement clears the quoted buyer cart and increments its version atomically with order creation. Concurrent cart edits conflict instead of losing unquoted items; failed checkout leaves the cart unchanged. Same-key retries return the original order without duplicate demand or clearing subsequently added cart items. Legacy test/live modes retain their existing production gates.",
  "operationId": "placeManualOrder",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "header",
      "name": "Idempotency-Key",
      "required": true,
      "schema": {
        "type": "string",
        "minLength": 16
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/CheckoutAttemptInput"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Unpaid pay-later order placed and demand committed",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/BuyerOrder"
          }
        }
      }
    },
    "404": {
      "description": "Store or buyer context unavailable"
    },
    "409": {
      "description": "Quote expired, stock conflict, or backorder acknowledgement required"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## POST /api/v2/storefront/{slug}/checkout/quotes

Create an expiring server-priced buyer quote

Creates a five-minute server-priced quote for a live public store with published delivery and tax settings. GST uses the explicit delivery state code as place of supply, active product HSN rates, line-level mixed-rate shipping allocation and the published rounding mode. A quote does not reserve stock.

Source: bulqc-v2-backend/src/modules/checkout/checkout.routes.js

```json
{
  "summary": "Create an expiring server-priced buyer quote",
  "operationId": "createCheckoutQuote",
  "description": "Creates a five-minute server-priced quote for a live public store with published delivery and tax settings. GST uses the explicit delivery state code as place of supply, active product HSN rates, line-level mixed-rate shipping allocation and the published rounding mode. A quote does not reserve stock.",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/CreateCheckoutQuoteInput"
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Immutable five-minute quote",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/CheckoutQuote"
          }
        }
      }
    },
    "409": {
      "description": "Stale cart, missing policy or HSN, unavailable price/stock, serviceability or delivery state code"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## GET /api/v2/storefront/{slug}/checkout/quotes/{quoteId}

Read the buyer's own quote



Source: bulqc-v2-backend/src/modules/checkout/checkout.routes.js

```json
{
  "summary": "Read the buyer's own quote",
  "operationId": "getCheckoutQuote",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "path",
      "name": "quoteId",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Buyer-owned quote",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/CheckoutQuote"
          }
        }
      }
    },
    "404": {
      "description": "Quote unavailable or owned by another buyer"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## GET /api/v2/storefront/{slug}/me/whatsapp/preferences

Read own buyer WhatsApp preferences for this store

Missing preferences default to utilityEnabled true and marketingEnabled false, with null consent timestamps and version. Existing opt-outs remain effective. The policy default does not create a consent record.

Source: bulqc-v2-backend/src/modules/whatsapp/whatsapp.routes.js

```json
{
  "summary": "Read own buyer WhatsApp preferences for this store",
  "description": "Missing preferences default to utilityEnabled true and marketingEnabled false, with null consent timestamps and version. Existing opt-outs remain effective. The policy default does not create a consent record.",
  "operationId": "getBuyerWhatsAppPreference",
  "responses": {
    "200": {
      "description": "Current preference and version",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontWhatsAppPreference"
          }
        }
      }
    },
    "404": {
      "description": "Wrong buyer or store"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## PUT /api/v2/storefront/{slug}/me/whatsapp/preferences

Set own buyer WhatsApp preferences for this store

Marketing is disabled until the buyer explicitly enables it for this store.

Source: bulqc-v2-backend/src/modules/whatsapp/whatsapp.routes.js

```json
{
  "summary": "Set own buyer WhatsApp preferences for this store",
  "description": "Marketing is disabled until the buyer explicitly enables it for this store.",
  "operationId": "setBuyerWhatsAppPreference",
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/WhatsAppPreferenceInput"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated preference",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontWhatsAppPreference"
          }
        }
      }
    },
    "409": {
      "description": "Version conflict"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## GET /api/v2/storefront/{slug}/order-links/{orderId}

Read linked order as its owning buyer or authorized store merchant

Requires a revocable session. Store scope is resolved from the persisted slug; merchants require current orders.read. Returns only order presentation fields. A URL is not authorization.

Source: bulqc-v2-backend/src/modules/orders/orders.routes.js

```json
{
  "operationId": "getStorefrontLinkedOrder",
  "summary": "Read linked order as its owning buyer or authorized store merchant",
  "description": "Requires a revocable session. Store scope is resolved from the persisted slug; merchants require current orders.read. Returns only order presentation fields. A URL is not authorization.",
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "name": "orderId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^(?:[a-f0-9]{24}|ORD-[A-F0-9]{24}|[A-Z0-9]{6})$"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Authorized order snapshot",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/LinkedOrder"
          }
        }
      }
    },
    "400": {
      "description": "Invalid route identifier"
    },
    "401": {
      "description": "Authentication required"
    },
    "404": {
      "description": "Order not found or access denied"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## GET /api/v2/storefront/{slug}/orders

List buyer orders



Source: bulqc-v2-backend/src/modules/orders/orders.routes.js

```json
{
  "summary": "List buyer orders",
  "operationId": "Listbuyerorders",
  "description": "",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/OrderPage"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## GET /api/v2/storefront/{slug}/orders/{orderId}

Read a buyer-owned historical order



Source: bulqc-v2-backend/src/modules/orders/orders.routes.js

```json
{
  "summary": "Read a buyer-owned historical order",
  "operationId": "getBuyerOrder",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "path",
      "name": "orderId",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^(?:[a-f0-9]{24}|ORD-[A-F0-9]{24}|[A-Z0-9]{6})$"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Order snapshot, including after store closure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/BuyerOrder"
          }
        }
      }
    },
    "404": {
      "description": "Not found or not owned"
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## GET /api/v2/storefront/{slug}/orders/{orderId}/invoices/{documentId}

Read own invoice



Source: bulqc-v2-backend/src/modules/orders/orders.routes.js

```json
{
  "summary": "Read own invoice",
  "operationId": "Readowninvoice",
  "description": "",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "in": "path",
      "name": "orderId",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^(?:[a-f0-9]{24}|ORD-[A-F0-9]{24}|[A-Z0-9]{6})$"
      }
    },
    {
      "in": "path",
      "name": "documentId",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-f0-9]{24}$"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/SignedDownload"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": [
    {
      "session": []
    }
  ]
}
```

## GET /api/v2/storefront/bootstrap

Resolve storefront bootstrap from its configured subdomain

Subdomain mode only. Uses Host; X-Forwarded-Host is ignored. Unknown hosts return 404. Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs.

Source: bulqc-v2-backend/src/modules/storefront/storefront.routes.js

```json
{
  "summary": "Resolve storefront bootstrap from its configured subdomain",
  "description": "Subdomain mode only. Uses Host; X-Forwarded-Host is ignored. Unknown hosts return 404. Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs.",
  "operationId": "get_api_v2_storefront_bootstrap",
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontBootstrap"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Authentication required",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Forbidden origin or role",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Missing or inaccessible resource",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Conflict, stale version or readiness blocker",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Payload or import exceeds size limit",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "422": {
      "description": "Import contains validation errors",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "security": []
}
```

## GET /api/v2/storefront/{slug}/bootstrap

Get published storefront configuration and capabilities

Published configuration and real products only. Responses use Cache-Control: no-store and no ETag. Host/slug mismatches return 404 in subdomain mode. Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs.

Source: bulqc-v2-backend/src/modules/storefront/storefront.routes.js

```json
{
  "summary": "Get published storefront configuration and capabilities",
  "security": [],
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Published storefront bootstrap",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontBootstrap"
          }
        }
      }
    },
    "404": {
      "description": "Store unavailable or not published",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  },
  "description": "Published configuration and real products only. Responses use Cache-Control: no-store and no ETag. Host/slug mismatches return 404 in subdomain mode. Published catalogue/price visibility is enforced for an optional same-store buyer session. Cache-Control: private, no-store; Vary: Cookie. Restricted catalogue denies data/media/facets; bootstrap keeps public business branding but returns no products/About product IDs. Hidden prices use null and empty quantityBreaks. Restricted authorized product media uses private signed URLs, never public CDN URLs."
}
```

## GET /api/v2/storefront/{slug}/render-data

Get storefront render-data

Always anonymous, including when Cookie is supplied. Catalog returns the first 50 rank-sorted products and facets; About includes selected products or a published fallback. Policies omit catalog work. No cart creation or Set-Cookie. Current publication and visibility checks precede internal caching.

Source: bulqc-v2-backend/src/modules/storefront/storefront.routes.js

```json
{
  "summary": "Get storefront render-data",
  "operationId": "getStorefrontRenderData",
  "security": [],
  "description": "Always anonymous, including when Cookie is supplied. Catalog returns the first 50 rank-sorted products and facets; About includes selected products or a published fallback. Policies omit catalog work. No cart creation or Set-Cookie. Current publication and visibility checks precede internal caching.",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$"
      }
    },
    {
      "in": "query",
      "name": "view",
      "schema": {
        "type": "string",
        "enum": [
          "catalog",
          "about",
          "terms",
          "privacy"
        ],
        "default": "catalog"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Current storefront data; Cache-Control: private, no-store; Vary: Cookie",
      "headers": {
        "Cache-Control": {
          "schema": {
            "type": "string"
          }
        },
        "Server-Timing": {
          "schema": {
            "type": "string"
          }
        }
      },
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontRenderData"
          }
        }
      }
    },
    "400": {
      "description": "Invalid view/slug",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Origin rejected",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Store unavailable or host mismatch",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Dependency failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  }
}
```

## GET /api/v2/storefront/{slug}/session-context

Get storefront session-context

Optional live authentication is resolved once. Matching buyer receives profile and permitted catalog/pricing. Guest or incompatible account never receives buyer catalog/profile. Rechecks approval and visibility on every request. No cart creation or merge.

Source: bulqc-v2-backend/src/modules/storefront/storefront.routes.js

```json
{
  "summary": "Get storefront session-context",
  "operationId": "getStorefrontSessionContext",
  "security": [],
  "description": "Optional live authentication is resolved once. Matching buyer receives profile and permitted catalog/pricing. Guest or incompatible account never receives buyer catalog/profile. Rechecks approval and visibility on every request. No cart creation or merge.",
  "parameters": [
    {
      "in": "path",
      "name": "slug",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$"
      }
    },
    {
      "in": "query",
      "name": "view",
      "schema": {
        "type": "string",
        "enum": [
          "catalog",
          "about",
          "terms",
          "privacy",
          "shell"
        ],
        "default": "catalog"
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Current storefront data; Cache-Control: private, no-store; Vary: Cookie",
      "headers": {
        "Cache-Control": {
          "schema": {
            "type": "string"
          }
        },
        "Server-Timing": {
          "schema": {
            "type": "string"
          }
        }
      },
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontSessionContext"
          }
        }
      }
    },
    "400": {
      "description": "Invalid view/slug",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Origin rejected",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Store unavailable or host mismatch",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "500": {
      "description": "Dependency failure",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  }
}
```

## GET /api/v2/storefront/{slug}/me/support/attachments/{attachmentId}/download

Download an attachment from a public message in own ticket



Source: bulqc-v2-backend/src/modules/support/support.routes.js

```json
{
  "operationId": "buyerSupportAttachment",
  "summary": "Download an attachment from a public message in own ticket",
  "security": [
    {
      "session": []
    }
  ],
  "responses": {
    "200": {
      "description": "Short-lived signed download URL; private, no-store",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/SupportDownload"
          }
        }
      }
    },
    "401": {
      "description": "Sign in required"
    },
    "404": {
      "description": "Attachment inaccessible to buyer"
    }
  }
}
```

## GET /api/v2/storefront/{slug}/me/support/tickets

List the signed-in buyer's tickets

Private, no-store. Store is resolved from slug and must match the active buyer session. Returns items (buyer-safe tickets), nextOffset and total; excludes staff fields, counts and internal activity.

Source: bulqc-v2-backend/src/modules/support/support.routes.js

```json
{
  "operationId": "buyerSupportTickets",
  "summary": "List the signed-in buyer's tickets",
  "description": "Private, no-store. Store is resolved from slug and must match the active buyer session. Returns items (buyer-safe tickets), nextOffset and total; excludes staff fields, counts and internal activity.",
  "security": [
    {
      "session": []
    }
  ],
  "parameters": [
    {
      "name": "offset",
      "in": "query",
      "schema": {
        "type": "integer",
        "minimum": 0,
        "maximum": 100000,
        "default": 0
      }
    },
    {
      "name": "limit",
      "in": "query",
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 50,
        "default": 20
      }
    }
  ],
  "responses": {
    "200": {
      "description": "Buyer-only ticket page",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontSupportPage"
          }
        }
      }
    },
    "401": {
      "description": "Sign in required"
    },
    "404": {
      "description": "Wrong store or buyer scope"
    }
  }
}
```

## POST /api/v2/storefront/{slug}/me/support/tickets

Create a buyer support ticket

Customer and store are derived on the server. Rejects seller-only fields. Optional orderId must belong to this buyer in this store. Same support tickets as Seller Hub; channel storefront. Requires Idempotency-Key; retries with the same body return the same ticket, conflicting reuse returns 409.

Source: bulqc-v2-backend/src/modules/support/support.routes.js

```json
{
  "operationId": "createBuyerSupportTicket",
  "summary": "Create a buyer support ticket",
  "description": "Customer and store are derived on the server. Rejects seller-only fields. Optional orderId must belong to this buyer in this store. Same support tickets as Seller Hub; channel storefront. Requires Idempotency-Key; retries with the same body return the same ticket, conflicting reuse returns 409.",
  "security": [
    {
      "session": []
    }
  ],
  "parameters": [
    {
      "name": "Idempotency-Key",
      "in": "header",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "subject",
            "body",
            "category"
          ],
          "properties": {
            "subject": {
              "type": "string",
              "minLength": 1,
              "maxLength": 240
            },
            "body": {
              "type": "string",
              "minLength": 1,
              "maxLength": 20000
            },
            "category": {
              "type": "string",
              "enum": [
                "order_issue",
                "payment",
                "return_replacement",
                "product_query",
                "other"
              ]
            },
            "orderId": {
              "type": "string",
              "nullable": true,
              "pattern": "^[a-f0-9]{24}$"
            }
          }
        }
      }
    }
  },
  "responses": {
    "201": {
      "description": "Buyer-safe created ticket",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontSupportTicket"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input"
    },
    "401": {
      "description": "Sign in required"
    },
    "404": {
      "description": "Wrong scope or order"
    },
    "409": {
      "description": "Idempotency conflict"
    }
  }
}
```

## GET /api/v2/storefront/{slug}/me/support/tickets/{ticketId}

Read own ticket conversation

Private, no-store. Returns buyer-safe ticket and public messages only, never internal notes, staff identifiers, assignment, resolution notes or activity. ticketId accepts an existing Mongo ID or the authoritative store-scoped T-… ticket number; current buyer ownership is required for both.

Source: bulqc-v2-backend/src/modules/support/support.routes.js

```json
{
  "operationId": "buyerSupportTicket",
  "summary": "Read own ticket conversation",
  "description": "Private, no-store. Returns buyer-safe ticket and public messages only, never internal notes, staff identifiers, assignment, resolution notes or activity. ticketId accepts an existing Mongo ID or the authoritative store-scoped T-\u2026 ticket number; current buyer ownership is required for both.",
  "security": [
    {
      "session": []
    }
  ],
  "responses": {
    "200": {
      "description": "Ticket and messages",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontSupportConversation"
          }
        }
      }
    },
    "401": {
      "description": "Sign in required"
    },
    "404": {
      "description": "Ticket outside buyer/store scope"
    }
  },
  "parameters": [
    {
      "name": "slug",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string"
      }
    },
    {
      "name": "ticketId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^(?:[a-f0-9]{24}|T-[1-9][0-9]{0,14})$"
      }
    }
  ]
}
```

## POST /api/v2/storefront/{slug}/me/support/tickets/{ticketId}/messages

Reply to own support ticket

Private, no-store. Optimistic version and durable idempotency required. Public buyer message only. A reply reopens resolved/waiting tickets; closed tickets reject replies.

Source: bulqc-v2-backend/src/modules/support/support.routes.js

```json
{
  "operationId": "replyToBuyerSupportTicket",
  "summary": "Reply to own support ticket",
  "description": "Private, no-store. Optimistic version and durable idempotency required. Public buyer message only. A reply reopens resolved/waiting tickets; closed tickets reject replies.",
  "security": [
    {
      "session": []
    }
  ],
  "parameters": [
    {
      "name": "Idempotency-Key",
      "in": "header",
      "required": true,
      "schema": {
        "type": "string"
      }
    }
  ],
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "type": "object",
          "additionalProperties": false,
          "required": [
            "expectedVersion",
            "body"
          ],
          "properties": {
            "expectedVersion": {
              "type": "integer",
              "minimum": 0
            },
            "body": {
              "type": "string",
              "minLength": 1,
              "maxLength": 20000
            }
          }
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Updated buyer-safe ticket",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/StorefrontSupportTicket"
          }
        }
      }
    },
    "400": {
      "description": "Invalid input"
    },
    "401": {
      "description": "Sign in required"
    },
    "404": {
      "description": "Wrong buyer scope"
    },
    "409": {
      "description": "Version/idempotency conflict or closed ticket"
    }
  }
}
```
