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

1056 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GoodGo Platform — API Endpoints & Data Fields Reference
**For UI Component Mapping**
Generated: April 2026
---
## Table of Contents
1. [Analytics Module](#analytics-module)
- [Market Snapshot](#market-snapshot)
- [Price Trends](#price-trends)
- [Heatmap](#heatmap)
- [District Stats](#district-stats)
- [Trending Areas](#trending-areas)
- [Valuations (AVM)](#valuations-avm)
- [Neighborhood Score](#neighborhood-score)
- [Nearby POIs](#nearby-pois)
2. [Listings Module](#listings-module)
- [Listing Entity](#listing-entity)
- [Property Entity](#property-entity)
3. [Agents Module](#agents-module)
4. [Search Module](#search-module)
5. [Prisma Schema Key Models](#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**:
```typescript
{
city: string; // e.g., "HCMC", "Hanoi"
period?: string; // time period identifier
propertyType?: PropertyType; // APARTMENT | VILLA | TOWNHOUSE | LAND | OFFICE | SHOPHOUSE
}
```
**Response** (`MarketReportDto`):
```typescript
{
// 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**:
```typescript
{
city: string; // e.g., "HCMC"
propertyType?: PropertyType; // optional filter
}
```
**Response** (`MarketSnapshotDto`):
```typescript
{
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
---
### Price Trends
**Endpoint**: `GET /analytics/price-trend`
**Query Parameters**:
```typescript
{
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`):
```typescript
{
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**:
```typescript
{
city: string; // e.g., "HCMC"
period: string; // e.g., "2026-04"
}
```
**Response** (`HeatmapDto`):
```typescript
{
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**:
```typescript
{
city: string; // e.g., "HCMC"
period: string; // e.g., "2026-04"
}
```
**Response** (`DistrictStatsDto`):
```typescript
{
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
---
### Trending Areas
**Endpoint**: `GET /analytics/trending-areas`
**Query Parameters**:
```typescript
{
period?: number; // days (default: 30)
limit?: number; // max areas to return (default: 10)
level?: string; // "district" | "ward" (default: "district")
}
```
**Response** (`TrendingAreasDto`):
```typescript
{
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**:
```typescript
{
propertyId?: string; // if querying existing property
latitude?: number; // if querying coordinates
longitude?: number;
areaM2?: number; // area required if no propertyId
propertyType?: PropertyType;
}
```
**Response** (`ValuationDto`):
```typescript
{
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`):
```typescript
{
// ── 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**:
```typescript
{
propertyIds: string[]; // 1-50 property IDs
}
```
**Response**:
```typescript
{
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**:
```typescript
{
limit?: number; // default: 50, max: 365
}
```
**Response**:
```typescript
{
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**:
```typescript
{
propertyIds: string[]; // 2-5 property IDs
}
```
**Response**:
```typescript
{
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**:
```typescript
{
city?: string; // default: "Hồ Chí Minh"
}
```
**Response** (`NeighborhoodScoreResult`):
```typescript
{
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**:
```typescript
{
lat: number; // latitude (required)
lng: number; // longitude (required)
radius?: number; // meters (default: 2000)
limit?: number; // max results (default: 30)
}
```
**Response** (`NearbyPOIsResultDto`):
```typescript
{
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`
```typescript
{
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`
```typescript
{
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`
```typescript
{
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**:
```typescript
{
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`):
```typescript
{
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`):
```typescript
{
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
```prisma
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
```prisma
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
```prisma
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)
```prisma
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)
```prisma
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)
```prisma
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:
```typescript
{
page: number;
perPage: number;
totalPages: number;
totalFound: number;
}
```
### Caching Headers
Responses include cache metadata:
```typescript
{
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:
```typescript
{
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