--- title: Marketplace API tags: [api, marketplace, reference] --- # Marketplace API > **Last updated:** 2026-05-31 — request-template delivery mode and payment rail validation updated. All marketplace endpoints live under `/api/marketplace/*`. The router is composed of several files mounted from `app.ts`: - New controller-pattern routes: [`backend/src/services/marketplace/controllerRoutes.ts`](../../backend/src/services/marketplace/controllerRoutes.ts) (`marketplaceControllerRouter`) - Legacy + delivery-code routes: [`backend/src/services/marketplace/routes.ts`](../../backend/src/services/marketplace/routes.ts) (`marketplaceRouter`) - Request templates: [`requestTemplateRoutes.ts`](../../backend/src/services/marketplace/requestTemplateRoutes.ts) (`/api/marketplace/request-templates/*`) - Shop settings: [`shopSettingsRoutes.ts`](../../backend/src/services/marketplace/shopSettingsRoutes.ts) (`/api/marketplace/shop/*`) - Reviews: [`reviewRoutes.ts`](../../backend/src/services/marketplace/reviewRoutes.ts) (`/api/marketplace/reviews/*`) Core models: [[PurchaseRequest]], [[SellerOffer]], [[Category]], [[RequestTemplate]], [[ShopSettings]], [[Review]]. Real-time updates are emitted on the `request-`, `seller-`, `buyer-`, and `sellers`/`buyers` rooms — see [[Socket Events]]. ## Categories ### GET /api/marketplace/categories **Description:** Flat list of all categories. **Auth required:** No **Response 200:** `{ success: true, data: [Category, ...] }` ### GET /api/marketplace/categories/tree **Description:** Nested tree based on `parentId`. **Auth required:** No ### GET /api/marketplace/categories/:id **Description:** Single category by id. **Auth required:** No **Errors:** `404` not found. ## Sellers ### GET /api/marketplace/sellers **Description:** Public seller directory (users with `role: 'seller'`). Includes aggregated stats (completed orders, ratings). **Auth required:** No ### GET /api/marketplace/request-templates/sellers **Description:** Sellers who currently expose at least one active [[RequestTemplate]] (used by the shop discovery page). **Auth required:** No ### GET /api/marketplace/request-templates/sellers/:sellerId **Description:** Public shop profile for a seller, including their templates and shop settings. **Auth required:** No ## Purchase Requests The buyer-facing CRUD plus seller-side workflow endpoints. Model: [[PurchaseRequest]]. ### POST /api/marketplace/purchase-requests **Description:** Create a purchase request (new controller path). **Auth required:** Bearer JWT (buyer) **Request body:** ```ts { title: string; description: string; categoryId: string; productLink?: string; size?: string; color?: string; quantity?: number; // default 1 budget?: { min?: number; max?: number; currency: "USD" | "EUR" | "IRR" | "USDT" | "USDC" }; urgency?: "low" | "medium" | "high" | "urgent"; deliveryInfo?: { deliveryType: "physical" | "online"; addressId?: string; // when physical email?: string; // when online }; preferredSellerIds?: string[]; attachments?: string[]; // URLs from [[File API]] } ``` **Response 201:** `{ success, data: { purchaseRequest } }` **Side effects:** Emits `new-purchase-request` to the `sellers` room (broadcast). **Source:** `marketplaceController.createPurchaseRequest` ### POST /api/marketplace/purchase-requests/bulk **Description:** Bulk creation (used by the template checkout flow). **Auth required:** Bearer JWT **Request body:** `{ items: Array }` ### GET /api/marketplace/purchase-requests **Description:** List requests with filters. Buyers see their own; sellers see ones routed to them; admins see all. **Auth required:** Bearer JWT **Query params:** `status`, `categoryId`, `urgency`, `search`, `page`, `limit`, `sortBy`, `sortOrder` > **Note:** Use query params on this endpoint for filtering/searching. The separate search and stats endpoints documented in earlier versions do not exist — see below. ### ⚠️ NOT IMPLEMENTED: GET /api/marketplace/purchase-requests/search This endpoint does not exist. Use query params (`search`, `status`, `categoryId`, etc.) on `GET /api/marketplace/purchase-requests` instead. ### ⚠️ NOT IMPLEMENTED: GET /api/marketplace/purchase-requests/stats This endpoint does not exist in the backend. ### GET /api/marketplace/purchase-requests/my **Description:** Shortcut for the caller's own purchase requests. **Auth required:** Bearer JWT ### GET /api/marketplace/purchase-requests/:id **Description:** Single request with populated category and selected offer. **Auth required:** No (public read for shareable links) **Errors:** `404` not found. ### PATCH /api/marketplace/purchase-requests/:id **Description:** Buyer edits draft / pending request fields. **Auth required:** Bearer JWT (owner) > ⚠️ **KNOWN BUG:** The frontend sends `PUT` but the backend registers `PATCH`. Requests from clients using `PUT` will receive `404`. Use `PATCH`. ### PATCH /api/marketplace/purchase-requests/:id/status **Description:** Transition the request status (`draft` → `pending` → `payment` → `processing` → `delivery` → `delivered` → `seller_paid` → `completed`, or `cancelled`). **Auth required:** Bearer JWT (owner or admin) **Request body:** `{ status: string }` **Side effects:** Emits `purchase-request-update` to `request-`. ### DELETE /api/marketplace/purchase-requests/:id **Description:** Cancel/delete a request that has no committed payment. **Auth required:** Bearer JWT (owner) ### GET /api/marketplace/purchase-requests/:id/workflow-steps **Description:** Returns the ordered workflow steps + current pointer used by the frontend stepper. **Auth required:** No ### GET /api/marketplace/purchase-requests/:id/payment-status **Description:** Returns the latest payment + escrow state for the request. **Auth required:** Bearer JWT ### POST /api/marketplace/purchase-requests/:id/sync-payment-status **Description:** Force a re-check against the payment provider ([[Payment API]]). **Auth required:** Bearer JWT ### POST /api/marketplace/purchase-requests/:id/confirm-payment **Description:** Legacy buyer-confirmation endpoint kept for old clients. **Auth required:** Bearer JWT ### POST /api/marketplace/purchase-requests/:id/release-payment **Description:** Triggers admin escrow release (mirror of `POST /api/payment/shkeeper/:id/release`). **Auth required:** Bearer JWT (admin) ### PUT /api/marketplace/purchase-requests/:id/delivery (controller route) **Description:** Seller submits shipping details (carrier, tracking number, expected date). **Auth required:** Bearer JWT (selected seller) ### POST /api/marketplace/purchase-requests/:id/update-delivery (legacy) **Description:** Older equivalent retained for compatibility. ### POST /api/marketplace/requests/:id/start-delivery **Description:** Marks the request as `delivery` and notifies the buyer. **Auth required:** Bearer JWT (seller) **Side effects:** Emits `purchase-request-update` event. ### PATCH /api/marketplace/purchase-requests/:id/confirm-delivery **Description:** Buyer confirms goods received (transitions to `delivered`). **Auth required:** Bearer JWT (buyer) ### POST /api/marketplace/purchase-requests/:id/final-approval **Description:** Buyer's final approval that releases escrow to the seller. **Auth required:** Bearer JWT (buyer) ### GET /api/marketplace/buyers/:buyerId/purchase-requests **Description:** Admin/seller view of a buyer's request history. **Auth required:** Bearer JWT ## Delivery codes Six-digit codes the buyer hands to the seller at handover. Backed by `deliveryService`. ### POST /api/marketplace/purchase-requests/:id/delivery-code/generate **Description:** Buyer generates a delivery code. Request must be in `delivery` status. **Auth required:** Bearer JWT (buyer) **Response 200:** `{ success: true, data: { deliveryCode: "123456" } }` **Errors:** `400` wrong status, `403` not buyer, `404` request not found. **Side effects:** Emits `delivery-code-generated` on `request-`. ### POST /api/marketplace/purchase-requests/:id/delivery-code/verify **Description:** Seller verifies the code. On success the status moves to `delivered`. **Auth required:** Bearer JWT (selected seller) **Request body:** `{ code: string }` (exactly 6 chars) **Errors:** `400` bad code, `403` not the selected seller. **Side effects:** Emits `delivery-confirmed` and `buyer-confirmed-delivery`. ### GET /api/marketplace/purchase-requests/:id/delivery-code **Description:** Buyer or seller fetches the current code metadata. **Auth required:** Bearer JWT ### GET /api/marketplace/purchase-requests/:id/delivery-code/status **Description:** Returns `{ isValid, hasDeliveryCode, ... }` so the UI can decide whether to show "regenerate". **Auth required:** Bearer JWT ## Seller Offers Model: [[SellerOffer]]. Valid `status` values: `pending | accepted | rejected | withdrawn` > **Note:** The status value `active` does not exist on SellerOffer. Earlier docs were incorrect. ### POST /api/marketplace/purchase-requests/:id/offers **Description:** Submit an offer against the purchase request identified by `:id` in the path. The purchase request must be in `pending`, `received_offers`, or `active` status. **Auth required:** Bearer JWT (seller) **Path param:** `:id` — the `purchaseRequestId` (not a body field) **Request body:** ```ts { price: { amount: number; currency: "USDT" }; // USDT only for escrow MVP deliveryEstimate: { days: number; note?: string }; notes?: string; attachments?: string[]; } ``` **Response 201:** `{ success, data: { offer } }` **Side effects:** Emits `new-offer` to `buyer-` and `seller-offer-update` to `seller-`. > **Note:** Currency is locked to `USDT` for the escrow MVP (commit 3aaa2fe). The frontend `CURRENCY_SYMBOLS` map in `src/sections/request/constants.ts` exposes only `USDT`. ### PUT /api/marketplace/purchase-requests/:id/offers (legacy) **Description:** Older offer-update endpoint kept for compatibility. ### GET /api/marketplace/purchase-requests/:id/offers **Description:** List all offers for a request. **Auth required:** No (buyers and prospective sellers compare offers) ### GET /api/marketplace/purchase-requests/:id/has-offer **Description:** Returns `{ hasOffer: boolean }` for the caller (seller-side helper). **Auth required:** Bearer JWT (seller) ### GET /api/marketplace/purchase-requests/:id/offers/:sellerId **Description:** Fetch a specific seller's offer on a request. **Auth required:** No ### ⚠️ NOT IMPLEMENTED: GET /api/marketplace/offers/request/:requestId This endpoint does not exist. Use `GET /api/marketplace/purchase-requests/:id/offers` instead. ### GET /api/marketplace/offers/seller/:sellerId **Description:** Returns all offers submitted by the given seller, across all purchase requests. Used by the Offer Management dashboard page (`/dashboard/seller/marketplace/offers`). **Auth required:** Bearer JWT (seller, own `:sellerId` only) **Response 200:** `{ data: [SellerOffer, ...] }` **Frontend action:** `getSellerOffers(sellerId)` in `src/actions/marketplace.ts` (added commit 240a668) ### PATCH /api/marketplace/offers/:id **Description:** Seller edits their pending offer (price, delivery estimate, notes). **Auth required:** Bearer JWT (offer owner) > ✅ **Fixed (commit 240a668):** The frontend `updateOffer` and `acceptOffer` actions now correctly send `PATCH`. ### DELETE /api/marketplace/offers/:id **Description:** Seller withdraws their offer. **Auth required:** Bearer JWT (offer owner) ### PUT /api/marketplace/offers/:id/status **Description:** Direct status mutation (admin override / counter-offer states). This is also the correct way to withdraw an offer programmatically — send `{ status: 'withdrawn' }`. **Auth required:** Bearer JWT **Request body:** `{ status: "pending" | "accepted" | "rejected" | "withdrawn" }` ### POST /api/marketplace/offers/:id/withdraw **Description:** Seller withdraws their offer. Sets offer status to `withdrawn` using `sellerOfferService.withdrawOffer()`. Only the offer owner may call this. **Auth required:** Bearer JWT (offer owner) **Response 200:** `{ success: true, data: { /* updated offer */ } }` **Errors:** `403` not the offer owner, `404` offer not found. > **Note:** This endpoint was previously documented as NOT IMPLEMENTED. It was added to `backend/src/services/marketplace/routes.ts` (commit `3e47713`). ### POST /api/marketplace/purchase-requests/:id/select-offer **Description:** Buyer selects/accepts an offer; this triggers payment intent creation in [[Payment API]] and rejects all other offers automatically once payment lands. **Auth required:** Bearer JWT (buyer) **Request body:** `{ offerId: string }` **Side effects:** - Persists `selectedOfferId` on [[PurchaseRequest]] (commit `023255f` — previously this field was not saved, causing it to be lost). Status moves toward `payment`. - Rejects all **losing** offers (sets their status to `rejected`) when payment is confirmed (commit `023255f`). - Emits `seller-offer-update` to all sellers for the request. ### POST /api/marketplace/offers/:id/accept (legacy) **Description:** Older synonym retained for backward compatibility with old clients. ### DELETE /api/marketplace/offers/:id (controller route) **Description:** Controller-pattern delete that also notifies the buyer. ## Request Templates A [[RequestTemplate]] is a re-usable "shop product" a seller can publish. Buyers convert templates into actual purchase requests via the shareable link, individually or in bulk (cart checkout). ### POST /api/marketplace/request-templates **Description:** Create a new template. **Auth required:** Bearer JWT (seller) **Request body:** ```ts { title: string; // 1-200 chars description: string; // 1-2000 categoryId: string; // MongoId productLink?: string; // valid URL size?: string; // <=100 color?: string; // <=100 quantity?: number; // 1-10000 budget?: { min?: number; max?: number; currency: "USD" | "EUR" | "IRR" | "USDT" | "USDC" }; urgency?: "low" | "medium" | "high" | "urgent"; deliveryInfo?: { deliveryType: "physical" | "online"; // seller-selected; buyer cannot override at checkout notes?: string; email?: string; // optional legacy field; empty string is accepted }; paymentConfig?: { useShopDefault: boolean; // false = template override, true = shop defaults allowedChains: number[]; // at least one positive chain id when paymentConfig is sent allowedTokens: string[]; // at least one non-empty token symbol when paymentConfig is sent }; maxUsage?: number | null; // 0/null = unlimited expiresAt?: string | null; // ISO date images?: string[]; // URLs from [[File API]] } ``` **Response 201:** `{ data: { template } }` with a generated `shareableLink`. **Validation:** If `paymentConfig` is present, both `allowedChains` and `allowedTokens` must be non-empty. The UI now defaults new templates to explicit template rails, so a seller must choose at least one chain and one token before publishing. ### GET /api/marketplace/request-templates **Description:** Paginated list of the caller's templates. **Auth required:** Bearer JWT (seller) **Query params:** `page`, `limit` (1-100), `isActive`, `categoryId`, `search` ### GET /api/marketplace/request-templates/stats **Description:** Aggregate counts of the caller's templates (active, expired, usage). **Auth required:** Bearer JWT (seller) ### GET /api/marketplace/request-templates/:id **Description:** Full template by id (owner view). **Auth required:** Bearer JWT (owner) ### PUT /api/marketplace/request-templates/:id **Description:** Update the template. Same body as create. **Auth required:** Bearer JWT (owner) ### DELETE /api/marketplace/request-templates/:id **Description:** Delete a template. **Auth required:** Bearer JWT (owner) ### PATCH /api/marketplace/request-templates/:id/toggle-status **Description:** Toggle `isActive`. **Auth required:** Bearer JWT (owner) ### GET /api/marketplace/request-templates/public/:shareableLink **Description:** Public read of a template via its shareable slug. Used by the shop preview page. **Auth required:** No **Errors:** `404` link not found or template inactive/expired. ### POST /api/marketplace/request-templates/:shareableLink/convert **Description:** Buyer converts the template into a real [[PurchaseRequest]]. **Auth required:** Bearer JWT (buyer) **Request body:** Overrides (quantity, address, etc.) **Response 201:** `{ data: { purchaseRequest } }` ### POST /api/marketplace/request-templates/batch-convert **Description:** Convert several templates at once (cart checkout). The seller's template delivery mode is preserved; buyer-supplied checkout details are only overlaid where that mode requires them. **Auth required:** Bearer JWT (buyer) **Request body:** ```ts { items: Array<{ shareableLink: string; quantity: number; // 1-100 sellerId: string; // MongoId }>; status?: "pending" | "pending_payment" | "active"; paymentConfirmed?: boolean; paymentData?: Record; deliveryInfo?: { email?: string; // copied to generated online requests billing?: { name?: string; phoneNumber?: string; address?: string; city?: string; state?: string; country?: string; zipCode?: string; addressType?: string; fullAddress?: string; // copied to generated physical requests }; }; } ``` **Delivery mapping:** `online` templates use `deliveryInfo.email`; `physical` templates use `deliveryInfo.billing` to fill `deliveryInfo.address` and `deliveryInfo.deliveryAddress` on the generated [[PurchaseRequest]]. ### POST /api/marketplace/request-templates/complete-payment **Description:** Marks the requests created by `batch-convert` as paid (called after a successful checkout). **Auth required:** Bearer JWT **Request body:** ```ts { requestIds: string[]; // 1+ MongoIds newStatus?: "pending" | "active" | "processing"; paymentData?: Record; } ``` ## Shop Settings Per-seller storefront preferences. Model: [[ShopSettings]]. ### GET /api/marketplace/shop/settings/:sellerId **Description:** Public shop settings for the given seller (used by the shop landing page). **Auth required:** No **Response 200:** `{ data: ShopSettings }` ### GET /api/marketplace/shop/settings **Description:** The authenticated seller's own settings. **Auth required:** Bearer JWT (seller) ### PUT /api/marketplace/shop/settings **Description:** Update shop settings (banner, bio, policies, `allowSellerReviews`, `allowTemplateReviews`, ...). **Auth required:** Bearer JWT (seller) ## Reviews Model: [[Review]]. Reviews can target a seller or a template. Subject must be `seller` or `template`. ### GET /api/marketplace/reviews/:subjectType/:subjectId **Description:** Published reviews + aggregate stats. Honours `allowSellerReviews` / `allowTemplateReviews` from [[ShopSettings]]. **Auth required:** No **Query params:** `page` (default 1), `limit` (default 10) **Response 200:** ```json { "data": [Review, ...], "pagination": { "page": 1, "limit": 10, "total": 42, "pages": 5 }, "stats": { "count": 42, "avg": 4.6, "one": 1, "two": 0, "three": 3, "four": 10, "five": 28 } } ``` **Errors:** `400` bad subjectType / invalid id, `403` reviews disabled by seller. ### GET /api/marketplace/reviews/:subjectType/:subjectId/summary **Description:** Stats only (no review list). **Auth required:** No ### POST /api/marketplace/reviews **Description:** Submit a review. The server computes `isVerifiedBuyer` when `purchaseRequestId` is given and the request is in a terminal state (`delivery`, `delivered`, `seller_paid`, `completed`). **Auth required:** Bearer JWT **Request body:** ```ts { subjectType: "seller" | "template"; subjectId: string; rating: 1 | 2 | 3 | 4 | 5; comment?: string; purchaseRequestId?: string; } ``` **Errors:** `400` validation, `403` reviews disabled, `404` template not found, `409` duplicate review. **Response 201:** `{ data: Review, stats: { ... } }` ## Payments (legacy under marketplace) These routes are duplicates of the main [[Payment API]] kept under `/api/marketplace/payments/*` for backward compatibility with the early frontend. Prefer the canonical endpoints. ### POST /api/marketplace/payments ### GET /api/marketplace/payments ### GET /api/marketplace/payments/:paymentId ### PATCH /api/marketplace/payments/:paymentId See [[Payment API]] for the canonical descriptions. ## Verify Web3 payment (legacy) ### POST /api/marketplace/payments/verify **Description:** Legacy Web3 verification endpoint that records a transaction and moves the purchase request to `processing`. Modern flows use `POST /api/payment/shkeeper/confirm-transaction` instead. **Auth required:** Bearer JWT **Request body:** ```ts { purchaseRequestId: string; sellerOfferId: string; buyerId: string; sellerId: string; amount: number; currency: "USDT" | string; paymentHash: string; paymentMethod?: string; token?: string; network?: string; escrowWalletAddress?: string; metadata?: Record; } ``` **Side effects:** Creates a [[Payment]] record, updates [[PurchaseRequest]] status, emits `payment-received` to `user-`. ## Real-time events Most marketplace mutations fan out via `global.io` to the rooms below — see [[Socket Events]] for payloads: - `purchase-request-update` → `request-` - `new-purchase-request` → `sellers` - `new-offer` → `buyer-` - `seller-offer-update` → `seller-` (and global on payment confirm) - `delivery-code-generated` / `delivery-confirmed` / `delivery-update` → `request-` - `request-cancelled` → `user-`, `user-` - `transaction-completed` → `user-`, `user-` ## Related - [[Purchase Request Flow]] - [[Seller Offer Flow]] - [[Template Checkout Flow]] - [[Delivery Code Flow]]