API Reference (9 files updated): - Marketplace API: corrected offer endpoints (scoped under /purchase-requests/:id/offers), marked phantom /search /stats /seller/:sellerId /withdraw routes as NOT IMPLEMENTED, documented PUT→PATCH mismatches, removed invalid SellerOffer 'active' status - Dispute API: corrected resolve schema (action enum), categories (no 'fraud'), removed 'under_review' status, added security callouts (3 unguarded endpoints), route shadowing documented, all socket events marked as TODO stubs - Notification API: corrected mark-all-read method+path, fixed broken GET /:id, added unread-count-update event, 90-day TTL documented - Payment API: /create→/save, removed 10+ phantom endpoints, fixed release/refund paths (no /shkeeper/ segment), added 3 unauthenticated endpoint security warnings, stats undercounting documented, export privilege gap documented - Authentication API: 8-digit→6-digit code, no-complexity warning on reset-with-code, rate limiter counts all attempts, passkey stub claims removed, deleteAccount bug noted - Admin API: PUT→PATCH bug documented, wrong status values documented, hard vs soft delete clarified, scanner no-auth security bug, 3 NOT IMPLEMENTED endpoints - Chat API: file upload wrong endpoint bug, archive PUT→PATCH bug, rate limits added - Points API: corrected redeem schema, referral triggers on 'completed' only, leaderboard period ignored, removed 'refund' PointTransaction type - Socket Events: removed request-cancelled, notification-read; added unread-count-update; dispute events all stubs; referral-signup is auth-domain not points-domain Data Models (3 files updated): - SellerOffer: removed 'active' from status enum, withdrawOffer() is dead code - PurchaseRequest: added pending_payment/active statuses, added 'urgent' urgency, corrected description minimum (5 chars), removed finalized/archived - Dispute: corrected action enum, categories (no fraud), removed under_review, security callout on unguarded status/resolve endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
10 KiB
title, tags, aliases
| title | tags | aliases | |||||
|---|---|---|---|---|---|---|---|
| PurchaseRequest |
|
|
PurchaseRequest
Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)
The central buyer-side document. A PurchaseRequest captures what a buyer wants to acquire (physical product, digital product, service, or consultation), the budget envelope, urgency, delivery details, and the entire lifecycle from creation through payment, delivery, and completion. Sellers respond by attaching SellerOffer documents; the buyer accepts one, a Payment is opened, and delivery is verified by a 6-digit code.
[!note] Source
backend/src/models/PurchaseRequest.ts:95— schema definitionbackend/src/models/PurchaseRequest.ts:387— model export
Schema
| Field | Type | Required | Default | Validation | Index | Description |
|---|---|---|---|---|---|---|
buyerId |
ObjectId → User | yes | — | — | yes | Buyer that owns the request. |
title |
String | yes | — | trim, maxlength 200 | — | Short headline. |
description |
String | yes | — | trim, minlength 5 (frontend), maxlength 2000 | — | Long form description. Frontend enforces a 5-character minimum; the field is optional in the raw schema but the form will reject shorter values. |
categoryId |
ObjectId → Category | yes | — | — | yes | Category the request belongs to. |
productType |
String | no | physical_product |
enum: physical_product / digital_product / service / consultation |
yes | What kind of fulfilment is expected. |
productLink |
String | no | — | trim, must match /^https?:\/\/.+/ |
— | Reference URL for the desired product. |
size |
String | no | — | trim, maxlength 100 | — | Product size. |
color |
String | no | — | trim, maxlength 100 | — | Product color. |
brand |
String | no | — | trim, maxlength 100 | — | Brand preference. |
preferredSellerIds[] |
ObjectId → User | no | [] |
— | — | Targeted sellers for a private request. |
quantity |
Number | no | 1 |
min 1 | — | Unit count. |
budget.min |
Number | no | — | min 0 | — | Lower bound. |
budget.max |
Number | no | — | min 0 | — | Upper bound. |
budget.currency |
String | no | USD |
enum: USD / EUR / IRR |
— | Budget currency. |
urgency |
String | no | medium |
enum: low / medium / high / urgent |
yes | Buyer urgency. |
status |
String | no | pending |
enum (13 values — see State Transitions below) | yes | Lifecycle state. |
isPublic |
Boolean | no | true |
— | — | Public marketplace listing vs. private request. |
tags[] |
String[] | no | — | trim | — | Free-form tags. |
specifications[].key |
String | yes | — | trim | — | Spec key. |
specifications[].value |
String | yes | — | trim | — | Spec value. |
specifications[].label |
String | no | — | trim | — | Human label. |
deliveryInfo.deliveryType |
String | yes | physical |
enum: physical / online |
— | Delivery channel. |
deliveryInfo.address |
String | no | — | — | — | Physical address. |
deliveryInfo.preferredDate |
Date | no | — | — | — | Buyer's target date. |
deliveryInfo.notes |
String | no | — | — | — | Free-form notes. |
deliveryInfo.deliveryAddress.name |
String | no | — | — | — | Recipient name. |
deliveryInfo.deliveryAddress.phoneNumber |
String | no | — | — | — | Recipient phone. |
deliveryInfo.deliveryAddress.fullAddress |
String | no | — | — | — | Full address string. |
deliveryInfo.deliveryAddress.addressType |
String | no | — | — | — | e.g. Home / Office. |
deliveryInfo.email |
String | no | — | email regex | — | For digital delivery. |
deliveryInfo.sellerDeliveryInfo.estimatedDeliveryDate |
Date | no | — | — | — | Seller's ETA date. |
deliveryInfo.sellerDeliveryInfo.estimatedDeliveryTime |
String | no | — | — | — | Seller's ETA time. |
deliveryInfo.sellerDeliveryInfo.trackingNumber |
String | no | — | — | — | Carrier tracking. |
deliveryInfo.sellerDeliveryInfo.deliveryNotes |
String | no | — | — | — | Notes from seller. |
deliveryInfo.sellerDeliveryInfo.shippingMethod |
String | no | — | — | — | Method label. |
deliveryInfo.sellerDeliveryInfo.downloadLink |
String | no | — | — | — | Download URL for digital products. |
deliveryInfo.sellerDeliveryInfo.digitalFiles[] |
String[] | no | — | — | — | Digital file URLs. |
deliveryInfo.deliveryDateTime |
Date | no | — | — | — | Confirmed delivery datetime. |
deliveryInfo.deliveryDate |
Date | no | — | — | — | Confirmed delivery date. |
deliveryInfo.shippedAt |
Date | no | — | — | — | Timestamp of shipment. |
deliveryInfo.deliveryCode |
String | no | — | trim, length 6 | — | 6-digit handoff code. |
deliveryInfo.deliveryCodeGeneratedAt |
Date | no | — | — | — | When code was issued. |
deliveryInfo.deliveryCodeExpiresAt |
Date | no | — | — | — | When code expires. |
deliveryInfo.deliveryCodeUsed |
Boolean | no | false |
— | — | Whether the code has been redeemed. |
deliveryInfo.deliveryCodeUsedAt |
Date | no | — | — | — | When it was redeemed. |
deliveryInfo.deliveryCodeUsedBy |
ObjectId → User | no | — | — | — | Seller that redeemed. |
deliveryInfo.deliveredAt |
Date | no | — | — | — | Final delivery timestamp. |
deliveryInfo.deliveryAttempts[].sellerId |
ObjectId → User | yes | — | — | — | Seller making the attempt. |
deliveryInfo.deliveryAttempts[].attemptedAt |
Date | no | Date.now |
— | — | When attempted. |
deliveryInfo.deliveryAttempts[].success |
Boolean | yes | — | — | — | Whether it succeeded. |
deliveryInfo.deliveryAttempts[].code |
String | no | — | — | — | Code entered (only stored on success). |
serviceInfo.duration |
Number | no | — | min 0.5 | — | Hours, only for service/consultation. |
serviceInfo.sessionType |
String | no | — | enum: online / in_person / hybrid |
— | Service session type. |
serviceInfo.location |
String | no | — | trim, maxlength 200 | — | Service location. |
serviceInfo.requirements[] |
String[] | no | — | trim | — | Pre-requisites. |
attachments[] |
String[] | no | — | — | — | Attached file URLs. |
offers[] |
ObjectId → SellerOffer | no | — | — | — | Offers received. |
selectedOfferId |
ObjectId → SellerOffer | no | null |
— | — | Accepted offer. |
rating |
Number | no | null |
min 1, max 5 | — | Buyer's post-delivery rating. |
feedback |
String | no | null |
maxlength 1000 | — | Buyer's feedback text. |
deliveryConfirmed |
Boolean | no | false |
— | — | Buyer confirmation flag. |
deliveryConfirmedAt |
Date | no | null |
— | — | Confirmation timestamp. |
metadata.source |
String | no | manual |
enum: manual / template / api |
— | Where the request came from. |
metadata.templateId |
String | no | — | trim | — | Originating RequestTemplate id. |
metadata.version |
String | no | — | trim | — | Schema version. |
createdAt |
Date | auto | — | — | yes (desc) | Mongoose timestamp. |
updatedAt |
Date | auto | — | — | — | Mongoose timestamp. |
Status enum — all valid values
pending_payment · pending · active · received_offers · in_negotiation · payment · processing · delivery · delivered · confirming · completed · seller_paid · cancelled
Note: finalized and archived are not valid status values and do not appear in the IPurchaseRequest frontend type or the Mongoose schema enum. Using either would cause a validation error.
Virtuals
None defined.
Indexes
Single-field — backend/src/models/PurchaseRequest.ts:376-381:
{ buyerId: 1 }{ categoryId: 1 }{ productType: 1 }{ status: 1 }{ createdAt: -1 }{ urgency: 1 }
Compound — backend/src/models/PurchaseRequest.ts:384-385:
{ productType: 1, status: 1 }{ categoryId: 1, productType: 1 }
Pre/Post Hooks
None declared at the schema level.
Instance Methods
None defined.
Static Methods
None defined.
Relationships
- References: User (
buyerId,preferredSellerIds[],deliveryInfo.deliveryCodeUsedBy,deliveryInfo.deliveryAttempts[].sellerId), Category (categoryId), SellerOffer (offers[],selectedOfferId). - Referenced by: SellerOffer (
purchaseRequestId), Payment (purchaseRequestId), Dispute (purchaseRequestId), Chat (relatedTo.idwhenrelatedTo.type === 'PurchaseRequest'), Review (purchaseRequestId).
State Transitions
stateDiagram-v2
[*] --> pending_payment
[*] --> pending
pending_payment --> pending : payment confirmed
pending --> active : published
active --> received_offers : first offer
received_offers --> in_negotiation : buyer engages
in_negotiation --> payment : offer accepted
payment --> processing : payment captured
processing --> delivery : shipped
delivery --> delivered : handed over
delivered --> confirming : code redeemed
confirming --> completed : buyer confirms
completed --> seller_paid : payout released
pending --> cancelled
active --> cancelled
received_offers --> cancelled
in_negotiation --> cancelled
completed --> [*]
seller_paid --> [*]
cancelled --> [*]
Common Queries
// Buyer's open requests
PurchaseRequest.find({ buyerId, status: { $in: ['pending', 'active', 'received_offers'] } });
// Public marketplace feed
PurchaseRequest.find({ isPublic: true, status: 'active' }).sort({ createdAt: -1 });
// Sellers' eligible queue
PurchaseRequest.find({ productType, status: 'active', categoryId });
// Populate offers
PurchaseRequest.findById(id).populate('offers').populate('selectedOfferId');
// Redeem delivery code
PurchaseRequest.findOneAndUpdate(
{ _id: id, 'deliveryInfo.deliveryCode': code, 'deliveryInfo.deliveryCodeUsed': false },
{ $set: { 'deliveryInfo.deliveryCodeUsed': true, 'deliveryInfo.deliveryCodeUsedAt': new Date() } }
);
Related: SellerOffer, Payment, Chat, Dispute, Review, RequestTemplate, Category.