Files
goodgo-platform/docs/explorations/API_ENDPOINTS_REFERENCE.md
Ho Ngoc Hai 08b96f9c2d docs: consolidate exploration & audit reports under docs/ (TEC-3094)
- Move 8 stray .md (+5 .txt) from ~/Desktop into docs/explorations/from-desktop/
- Reorganize 27 .md/.txt at workspace root:
  - audit reports -> docs/audits/
  - exploration reports -> docs/explorations/
  - design system -> docs/design-system/
- Keep only README/CHANGELOG/CONTRIBUTING/CLAUDE at repo root
- Refresh docs/README.md as canonical index with links to all groups
- Note: pre-existing docs/audits/AUDIT_INDEX.md and AUDIT_SUMMARY.md were
  overwritten by the newer root-level versions during the move

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-21 16:29:24 +07:00

28 KiB
Raw Blame History

GoodGo Platform — API Endpoints & Data Fields Reference

For UI Component Mapping

Generated: April 2026


Table of Contents

  1. Analytics Module
  2. Listings Module
  3. Agents Module
  4. Search Module
  5. Prisma Schema Key Models

Analytics Module

Base Info

  • Namespace: GET/POST /analytics
  • Auth: JWT Bearer Token required (most endpoints)
  • Rate Limiting: Varies by endpoint
  • Caching: Yes (30min-1hr TTL)

Market Snapshot

Endpoint: GET /analytics/market-report

Query Parameters:

{
  city: string;           // e.g., "HCMC", "Hanoi"
  period?: string;        // time period identifier
  propertyType?: PropertyType;  // APARTMENT | VILLA | TOWNHOUSE | LAND | OFFICE | SHOPHOUSE
}

Response (MarketReportDto):

{
  // Array of per-district market data
  [
    {
      district: string;
      city: string;
      propertyType: PropertyType;
      period: string;
      medianPrice: string;        // formatted price (e.g., "2500000000")
      avgPriceM2: number;         // average price per m² (VND)
      totalListings: number;      // active listings count
      daysOnMarket: number;       // average time to sell (days)
      inventoryLevel: number;     // cumulative inventory metric
      absorptionRate: number | null;  // listings sold per month (ratio)
      yoyChange: number | null;   // year-over-year price change (%)
    }
  ]
}

Market Snapshot (Quick View)

Endpoint: GET /analytics/market-snapshot

Query Parameters:

{
  city: string;                   // e.g., "HCMC"
  propertyType?: PropertyType;    // optional filter
}

Response (MarketSnapshotDto):

{
  city: string;
  propertyType?: PropertyType;
  activeCount: number;            // total active listings
  avgPrice: number;               // average listing price (VND)
  medianPrice: number;            // median listing price (VND)
  priceChangePct: {
    d1: number;                   // % change in last 1 day
    d7: number;                   // % change in last 7 days
    d30: number;                  // % change in last 30 days
  };
  avgPricePerM2: number;          // average price per m² (VND)
  daysOnMarket: number;           // average days on market
  newListings24h: number;         // new listings in last 24 hours
  cachedAt: string | null;        // ISO timestamp of cache
  nextRefreshAt: string | null;   // ISO timestamp of next refresh
}

Cache: 30 minutes


Endpoint: GET /analytics/price-trend

Query Parameters:

{
  district: string;               // e.g., "Quận 1"
  city: string;                   // e.g., "Hồ Chí Minh"
  propertyType: PropertyType;     // required
  periods?: string[];             // comma-separated time periods
}

Response (PriceTrendDto):

{
  district: string;
  city: string;
  propertyType: string;
  trend: [
    {
      period: string;             // e.g., "2026-Q1"
      medianPrice: string;        // formatted price (VND)
      avgPriceM2: number;
      totalListings: number;
    }
  ]
}

Use Case: Line chart for price evolution over time
Cache: 1 hour


Heatmap

Endpoint: GET /analytics/heatmap

Query Parameters:

{
  city: string;                   // e.g., "HCMC"
  period: string;                 // e.g., "2026-04"
}

Response (HeatmapDto):

{
  city: string;
  period: string;
  dataPoints: [
    {
      district: string;
      city: string;
      avgPriceM2: number;         // price intensity for heatmap color
      totalListings: number;
      medianPrice: string;        // formatted price (VND)
    }
  ]
}

UI Mapping: Use avgPriceM2 for color intensity, district names for overlay
Cache: 1 hour


District Stats

Endpoint: GET /analytics/district-stats

Query Parameters:

{
  city: string;                   // e.g., "HCMC"
  period: string;                 // e.g., "2026-04"
}

Response (DistrictStatsDto):

{
  city: string;
  period: string;
  districts: [
    {
      district: string;
      city: string;
      propertyType: PropertyType;
      medianPrice: string;        // formatted price (VND)
      avgPriceM2: number;
      totalListings: number;
      daysOnMarket: number;
      inventoryLevel: number;     // supply metric
      absorptionRate: number | null;  // velocity metric
      yoyChange: number | null;   // year-over-year % change
    }
  ]
}

UI Mapping: Table or grid showing stats per district
Cache: 6 hours


Endpoint: GET /analytics/trending-areas

Query Parameters:

{
  period?: number;                // days (default: 30)
  limit?: number;                 // max areas to return (default: 10)
  level?: string;                 // "district" | "ward" (default: "district")
}

Response (TrendingAreasDto):

{
  period: number;                 // query period in days
  level: string;                  // "district" | "ward"
  limit: number;
  areas: [
    {
      districtId: string;         // district identifier
      name: string;               // display name (e.g., "Quận 1")
      listings: number;           // new listings in period
      inquiries: number;          // buyer inquiries
      views: number;              // total view count
      priceChangePct: number | null;  // YoY price change (%)
      scoreRank: number;          // rank (1 = hottest)
    }
  ]
}

Scoring Formula: score = inquiries × 0.6 + views × 0.3 + listings × 0.1
Cache: 30 minutes


Valuations (AVM)

Get Valuation (by Property ID or Coordinates)

Endpoint: GET /analytics/valuation

Query Parameters:

{
  propertyId?: string;            // if querying existing property
  latitude?: number;              // if querying coordinates
  longitude?: number;
  areaM2?: number;                // area required if no propertyId
  propertyType?: PropertyType;
}

Response (ValuationDto):

{
  estimatedPrice: string;         // formatted price (e.g., "2500000000" VND)
  confidence: number;             // 0.0-1.0 confidence score
  pricePerM2: number;             // estimated price per m²
  comparables: [
    {
      propertyId: string;
      address: string;
      district: string;
      priceVND: string;           // comparable listing price
      pricePerM2: number;
      areaM2: number;
      propertyType: PropertyType;
      distanceMeters: number;     // distance from query point
      soldAt: string;             // ISO timestamp
    }
  ];
  modelVersion: string;           // "v1" | "v2" | "ensemble"
  confidenceExplanation?: string; // explanation of confidence
}

Cache: 24 hours
Rate Limit: 10 req/min per user (on POST endpoint)


Predict Valuation (Manual Input)

Endpoint: POST /analytics/valuation

Request Body (PredictValuationDto):

{
  // ── Core Fields (v1 & v2) ──
  propertyType: PropertyType;     // required
  area: number;                   // m² (required, min: 1)
  district: string;               // required (e.g., "Quận 1")
  city: string;                   // required (e.g., "Hồ Chí Minh")
  
  // ── Optional Details ──
  bedrooms?: number;              // 0-20
  bathrooms?: number;             // 0-20
  floors?: number;                // 0-200
  frontage?: number;              // meters (m)
  roadWidth?: number;             // meters (m)
  yearBuilt?: number;             // 1900-2100
  hasLegalPaper?: boolean;
  latitude?: number;              // if available
  longitude?: number;
  projectId?: string;
  imageUrl?: string;
  description?: string;           // max 2000 chars
  deepAnalysis?: boolean;         // triggers Claude analysis
  
  // ── AVM v2 Features ──
  useV2?: boolean;                // enable enhanced model
  distanceToHospitalKm?: number;
  distanceToParkKm?: number;
  distanceToMallKm?: number;
  floodZoneRisk?: 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH';
  hasElevator?: boolean;
  hasParking?: boolean;
  hasPool?: boolean;
}

Response (ValuationDto - same as above)

Rate Limit: 10 req/min per user


Batch Valuation

Endpoint: POST /analytics/valuation/batch

Request Body:

{
  propertyIds: string[];          // 1-50 property IDs
}

Response:

{
  results: [
    {
      propertyId: string;
      valuation: ValuationDto | null;
      error?: string;             // error message if failed
    }
  ]
}

Rate Limit: 10 req/min per user


Valuation History (Chart Data)

Endpoint: GET /analytics/valuation/history/:propertyId

Query Parameters:

{
  limit?: number;                 // default: 50, max: 365
}

Response:

{
  propertyId: string;
  history: [
    {
      estimatedPrice: string;
      confidence: number;
      pricePerM2: number;
      modelVersion: string;
      valuedAt: string;           // ISO timestamp
    }
  ]
}

UI Mapping: Time-series chart of valuation estimates
Cache: 24 hours


Compare Valuations

Endpoint: POST /analytics/valuation/compare

Request Body:

{
  propertyIds: string[];          // 2-5 property IDs
}

Response:

{
  comparisons: [
    {
      propertyId: string;
      address: string;
      district: string;
      areaM2: number;
      propertyType: PropertyType;
      valuation: ValuationDto | null;
    }
  ]
}

UI Mapping: Side-by-side comparison table
Rate Limit: 10 req/min per user


Neighborhood Score

Endpoint: GET /analytics/neighborhoods/:district/score

Query Parameters:

{
  city?: string;                  // default: "Hồ Chí Minh"
}

Response (NeighborhoodScoreResult):

{
  district: string;
  city: string;
  educationScore: number;         // 0-100
  healthcareScore: number;        // 0-100
  transportScore: number;         // 0-100
  shoppingScore: number;          // 0-100
  greeneryScore: number;          // 0-100
  safetyScore: number;            // 0-100
  totalScore: number;             // weighted average 0-100
  poiCounts: {
    schools: number;
    hospitals: number;
    transit: number;
    shopping: number;
    parks: number;
    restaurants: number;
    [key: string]: number;
  };
  calculatedAt: Date;             // ISO timestamp
}

UI Mapping: Score gauge + POI count breakdown
Cache: 24 hours
Public: Yes (no auth required)


Nearby POIs

Endpoint: GET /analytics/pois/nearby (Public)

Query Parameters:

{
  lat: number;                    // latitude (required)
  lng: number;                    // longitude (required)
  radius?: number;                // meters (default: 2000)
  limit?: number;                 // max results (default: 30)
}

Response (NearbyPOIsResultDto):

{
  pois: [
    {
      id: string;
      name: string;
      type: POIType;              // SCHOOL | HOSPITAL | METRO_STATION | MALL | PARK | RESTAURANT | etc.
      category: POICategory;      // 'school' | 'hospital' | 'transit' | 'shopping' | 'restaurant' | 'park'
      lat: number;
      lng: number;
      distance: number;           // meters from query point
      address: string | null;
    }
  ];
  center: {
    lat: number;
    lng: number;
  };
}

UI Mapping: Map markers grouped by category + list view
Cache: 1 hour
Public: Yes


Listings Module

Listing Entity Fields

Entity: ListingEntity

{
  id: string;                         // unique listing ID
  propertyId: string;                 // reference to Property
  agentId: string | null;             // assigned agent
  sellerId: string;                   // seller/owner User ID
  transactionType: TransactionType;   // SALE | RENT
  status: ListingStatus;              // DRAFT | PENDING_REVIEW | ACTIVE | RESERVED | SOLD | RENTED | EXPIRED | REJECTED
  
  // Pricing
  price: Price;                       // Price VO (amount in VND)
  pricePerM2: number | null;          // calculated price per m²
  rentPriceMonthly: bigint | null;    // for rental listings
  commissionPct: number | null;       // agent commission (default: 2.0%)
  
  // AI & Moderation
  aiPriceEstimate: bigint | null;     // AVM-generated price estimate (VND)
  aiConfidence: number | null;        // AVM confidence (0.0-1.0)
  moderationScore: number | null;     // quality score (0-100)
  moderationNotes: string | null;     // moderation feedback
  
  // Engagement Metrics
  viewCount: number;                  // total views
  saveCount: number;                  // times saved by users
  inquiryCount: number;               // buyer inquiries
  
  // Dates
  featuredUntil: Date | null;         // promoted listing expiry
  expiresAt: Date | null;             // listing expiration date
  publishedAt: Date | null;           // date activated (ACTIVE status)
  createdAt: Date;
  updatedAt: Date;
}

UI Field Mapping:

  • Header: price (formatted), transactionType, status
  • Metadata: pricePerM2, aiPriceEstimate (with confidence badge)
  • Engagement: viewCount, inquiryCount, saveCount
  • Status Badge: status, moderationScore if available

Property Entity Fields

Entity: PropertyEntity

{
  id: string;                         // unique property ID
  
  // Core Info
  propertyType: PropertyType;         // APARTMENT | VILLA | TOWNHOUSE | LAND | OFFICE | SHOPHOUSE
  title: string;                      // property title
  description: string;                // detailed description
  
  // Location
  address: Address;                   // VO: { street, ward, district, city }
  location: GeoPoint;                 // VO: { lat, lng } (PostGIS geometry)
  ward: string;                       // administrative ward
  district: string;
  city: string;
  
  // Physical Attributes
  areaM2: number;                     // total area (m²)
  usableAreaM2: number | null;        // usable area (m²)
  bedrooms: number | null;            // -1 means "studio"
  bathrooms: number | null;
  floors: number | null;              // building height in floors
  floor: number | null;               // unit floor level
  totalFloors: number | null;         // total building floors
  direction: Direction | null;        // NORTH | SOUTH | EAST | WEST | NORTHEAST | etc.
  yearBuilt: number | null;
  legalStatus: string | null;         // e.g., "Sở hữu lâu dài"
  
  // Infrastructure & Amenities
  amenities: unknown;                 // JSON array of amenity names
  nearbyPOIs: unknown;                // JSON array of nearby POI references
  metroDistanceM: number | null;      // distance to nearest metro (meters)
  projectName: string | null;         // if part of a development project
  projectDevelopmentId: string | null;
  
  // Enhanced Fields (Phase B)
  furnishing: Furnishing | null;      // FULLY_FURNISHED | BASIC_FURNISHED | UNFURNISHED
  propertyCondition: PropertyCondition | null;  // NEW | LIKE_NEW | RENOVATED | USED
  balconyDirection: Direction | null;
  maintenanceFeeVND: bigint | null;   // monthly/annual fee
  parkingSlots: number | null;
  viewType: string[];                 // e.g., ["street", "park", "water"]
  petFriendly: boolean | null;
  suitableFor: string[];              // e.g., ["young_couples", "families", "investors"]
  whyThisLocation: string | null;     // narrative about location
  
  createdAt: Date;
  updatedAt: Date;
}

UI Field Mapping:

  • Hero Section: propertyType, title, bedrooms/bathrooms, areaM2
  • Location Badge: district, ward, city
  • Details Grid: yearBuilt, floors, direction, furnishing, propertyCondition
  • Amenities: amenities[], nearbyPOIs[]
  • Lifestyle: suitableFor[], whyThisLocation, petFriendly

Agents Module

Agent Entity Fields

Entity: AgentEntity

{
  id: string;                         // agent profile ID (different from userId)
  userId: string;                     // reference to User (role=AGENT)
  
  // License & Credentials
  licenseNumber: string | null;
  agency: string | null;              // brokerage/agency name
  isVerified: boolean;                // license verified by admin
  
  // Performance
  qualityScore: QualityScore;         // VO: { value: number, breakdown: {...} }
  totalDeals: number;                 // lifetime transactions
  responseTimeAvg: number | null;     // seconds
  
  // Profile
  bio: string | null;                 // agent biography/pitch
  serviceAreas: string[];             // list of district IDs (e.g., ["quan-1", "quan-7"])
  
  createdAt: Date;
  updatedAt: Date;
}

QualityScore VO:

{
  value: number;                      // 0.0-100.0 composite score
  // Potentially includes breakdown by metric
}

UI Field Mapping:

  • Agent Card: name (from User), agency, qualityScore.value (star rating)
  • Badges: isVerified, licenseNumber (if available)
  • Stats: totalDeals, responseTimeAvg (e.g., "avg 2h response")
  • Service Areas: serviceAreas[] (comma-separated or chip list)
  • Bio: bio (short preview)

Search Module

Search Result Format

Endpoint: Search is handled by Meilisearch (typesense-compatible)

Search Result Item (ListingDocument):

{
  id: string;                         // document ID in search index
  listingId: string;                  // reference to Listing
  propertyId: string;                 // reference to Property
  
  // Display
  title: string;
  description: string;
  
  // Classification
  propertyType: string;               // "APARTMENT", "VILLA", etc.
  transactionType: string;            // "SALE", "RENT"
  
  // Price
  priceVND: number;                   // for sorting/filtering
  pricePerM2: number | null;
  
  // Property Details
  areaM2: number;
  bedrooms: number | null;
  bathrooms: number | null;
  floors: number | null;
  direction: string | null;
  
  // Location
  address: string;                    // full address string
  ward: string;
  district: string;
  city: string;
  location: [number, number];         // [lat, lng]
  
  // Relations
  agentId: string | null;
  sellerId: string;
  
  // Metadata
  status: string;                     // "ACTIVE", etc.
  publishedAt: number;                // unix timestamp
  projectName: string | null;
  
  // Engagement
  viewCount: number;
  saveCount: number;
  
  // Amenities & Features
  amenities: string[];                // e.g., ["elevator", "pool", "gym"]
  isFeatured: number;                 // 1 if featured, 0 otherwise
}

Search Response (SearchResult):

{
  hits: ListingDocument[];            // matched listings
  totalFound: number;                 // total matching results
  page: number;                       // current page
  perPage: number;                    // items per page
  totalPages: number;
  searchTimeMs: number;               // query time in milliseconds
}

Supported Filters:

  • propertyType: APARTMENT, VILLA, TOWNHOUSE, LAND, OFFICE, SHOPHOUSE
  • transactionType: SALE, RENT
  • priceVND: range [min..max] or >= or <=
  • areaM2: range [min..max] or >= or <=
  • bedrooms: >= operator
  • district, city: equality
  • status: fixed to ACTIVE in search
  • isFeatured: 1 or 0

Prisma Schema Key Models

Property Model

model Property {
  id                   String    @id @default(cuid())
  propertyType         PropertyType
  title                String
  description          String    @db.Text
  address              String
  ward                 String
  district             String
  city                 String
  addressNormalized    String?   // for duplicate detection
  location             geometry(Point, 4326)  // PostGIS
  areaM2               Float
  usableAreaM2         Float?
  bedrooms             Int?
  bathrooms            Int?
  floors               Int?
  floor                Int?
  totalFloors          Int?
  direction            Direction?
  yearBuilt            Int?
  legalStatus          String?
  amenities            Json?
  nearbyPOIs           Json?
  metroDistanceM       Float?
  projectName          String?
  projectDevelopmentId String?
  furnishing           Furnishing?
  propertyCondition    PropertyCondition?
  balconyDirection     Direction?
  maintenanceFeeVND    BigInt?   // CHECK >= 0
  parkingSlots         Int?
  viewType             String[]  @default([])
  petFriendly          Boolean?
  suitableFor          String[]  @default([])
  whyThisLocation      String?   @db.Text
  createdAt            DateTime  @default(now())
  updatedAt            DateTime  @updatedAt
  
  listings   Listing[]
  valuations Valuation[]
  media      PropertyMedia[]
  
  // Indexes for queries
  @@index([propertyType])
  @@index([district, city])
  @@index([location], type: Gist)
  @@index([district, propertyType])
  @@index([district, city, propertyType])
}

Listing Model

model Listing {
  id               String    @id @default(cuid())
  propertyId       String
  agentId          String?
  sellerId         String
  transactionType  TransactionType
  status           ListingStatus @default(DRAFT)
  priceVND         BigInt        // CHECK > 0
  pricePerM2       Float?
  rentPriceMonthly BigInt?       // CHECK > 0
  commissionPct    Float?        @default(2.0)
  aiPriceEstimate  BigInt?       // CHECK > 0
  aiConfidence     Float?
  moderationScore  Float?
  moderationNotes  String?
  viewCount        Int           @default(0)
  saveCount        Int           @default(0)
  inquiryCount     Int           @default(0)
  featuredUntil    DateTime?
  expiresAt        DateTime?
  publishedAt      DateTime?
  createdAt        DateTime      @default(now())
  updatedAt        DateTime      @updatedAt
  
  transactions   Transaction[]
  inquiries      Inquiry[]
  orders         Order[]
  priceHistories PriceHistory[]
  savedByUsers   SavedListing[]
  
  @@index([status])
  @@index([transactionType])
  @@index([sellerId, status, publishedAt(sort: Desc)])
  @@index([agentId, status])
  @@index([status, publishedAt(sort: Desc)])
}

Agent Model

model Agent {
  id              String    @id @default(cuid())
  userId          String    @unique
  user            User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  licenseNumber   String?
  agency          String?
  qualityScore    Float     @default(0)
  totalDeals      Int       @default(0)
  responseTimeAvg Int?      // seconds
  bio             String?
  serviceAreas    Json      // ["quan-1", "quan-7", "thu-duc"]
  isVerified      Boolean   @default(false)
  createdAt       DateTime  @default(now())
  updatedAt       DateTime  @updatedAt
  
  listings Listing[]
  leads    Lead[]
  
  @@index([qualityScore])
  @@index([isVerified])
}

Valuation Model (Analytics)

model Valuation {
  id             String    @id @default(cuid())
  propertyId     String
  property       Property  @relation(fields: [propertyId], references: [id], onDelete: Cascade)
  estimatedPrice BigInt
  confidence     Float     // 0.0-1.0
  pricePerM2     Float
  comparables    Json      // array of {propertyId, address, priceVND, distance, ...}
  features       Json      // model input features used
  modelVersion   String    // "v1" | "v2" | "ensemble"
  createdAt      DateTime  @default(now())
  
  @@index([propertyId, createdAt(sort: Desc)])
}

MarketIndex Model (Analytics)

model MarketIndex {
  id             String       @id @default(cuid())
  district       String
  city           String
  propertyType   PropertyType
  period         String       // e.g., "2026-Q1" or "2026-04"
  medianPrice    BigInt
  avgPriceM2     Float
  totalListings  Int
  daysOnMarket   Float
  inventoryLevel Int
  absorptionRate Float?       // listings sold per month
  yoyChange      Float?       // year-over-year % change
  createdAt      DateTime     @default(now())
  
  @@unique([district, city, propertyType, period])
  @@index([city, period])
}

User Model (Auth)

model User {
  id              String    @id @default(cuid())
  email           String?
  phone           String
  passwordHash    String?
  fullName        String
  avatarUrl       String?
  role            UserRole  @default(BUYER)  // BUYER | SELLER | AGENT | DEVELOPER | PARK_OPERATOR | ADMIN
  kycStatus       KYCStatus @default(NONE)  // NONE | PENDING | VERIFIED | REJECTED
  isActive        Boolean   @default(true)
  createdAt       DateTime  @default(now())
  updatedAt       DateTime  @updatedAt
  
  agent           Agent?
  listings        Listing[]
  subscription    Subscription?
  payments        Payment[]
  
  @@index([role])
  @@index([kycStatus])
  @@index([isActive])
}

Enums

PropertyType

APARTMENT, VILLA, TOWNHOUSE, LAND, OFFICE, SHOPHOUSE

TransactionType

SALE, RENT

ListingStatus

DRAFT, PENDING_REVIEW, ACTIVE, RESERVED, SOLD, RENTED, EXPIRED, REJECTED

Direction

NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST

Furnishing

FULLY_FURNISHED, BASIC_FURNISHED, UNFURNISHED

PropertyCondition

NEW, LIKE_NEW, RENOVATED, USED

Common Patterns

Price Formatting

All prices in VND are returned as:

  • strings in DTOs (to preserve precision with large numbers)
  • BigInt in database and some service responses
  • number in JavaScript/TS objects (caution: precision loss above 2⁵³)

Display Pattern: Format with Intl.NumberFormat or custom VND formatter

Pagination

Search results include:

{
  page: number;
  perPage: number;
  totalPages: number;
  totalFound: number;
}

Caching Headers

Responses include cache metadata:

{
  cachedAt: string | null;      // ISO timestamp of cache retrieval
  nextRefreshAt: string | null; // ISO timestamp of next refresh
}

Confidence Scores

  • Valuation confidence: 0.0 - 1.0 (higher = more reliable)
  • Moderation score: 0-100 (higher = better quality)
  • Quality score: 0.0-100.0 (agent performance)

Rate Limits

  • Analytics queries: 100 req/min per subscription tier
  • Valuation POST: 10 req/min per user
  • Search: 200 req/min per user
  • Batch operations: Limited to 50 items per request

Error Responses

All endpoints return consistent error format:

{
  statusCode: number;
  message: string;
  error?: string;  // error type
  details?: Record<string, any>;  // field-level errors
  timestamp: string;  // ISO timestamp
  path: string;  // request path
}

Common Codes:

  • 400: Invalid parameters (missing required fields, validation errors)
  • 401: Unauthorized (no/invalid JWT token)
  • 403: Forbidden (quota exceeded, permission denied)
  • 404: Not found (property/listing/agent not found)
  • 429: Rate limit exceeded
  • 500: Server error
  • 503: Service unavailable (e.g., AI service down)

References

Generated from:

  • apps/api/src/modules/analytics/ — Market data, valuations, neighborhood scores
  • apps/api/src/modules/listings/ — Property and listing entities
  • apps/api/src/modules/agents/ — Agent profiles and quality scores
  • apps/api/src/modules/search/ — Search indexing and retrieval
  • prisma/schema.prisma — Database models and relationships

Last Updated: April 2026