{"openapi":"3.0.0","paths":{"/":{"get":{"operationId":"RootController_root","parameters":[],"responses":{"200":{"description":""}},"summary":"Service info","tags":["root"]}},"/health":{"get":{"operationId":"HealthController_live","parameters":[],"responses":{"200":{"description":""}},"summary":"Liveness probe","tags":["health"]}},"/health/ready":{"get":{"operationId":"HealthController_ready","parameters":[],"responses":{"200":{"description":""}},"summary":"Readiness probe (MongoDB)","tags":["health"]}},"/api/v1/vendors":{"post":{"description":"Public endpoint. Returns a new API key once — store it securely.","operationId":"VendorController_createVendor","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVendorDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCreatedResponseDto"}}}}},"summary":"Register a vendor","tags":["vendors"]}},"/api/v1/vendors/rules":{"post":{"operationId":"VendorController_updateRules","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRulesDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorRulesUpdatedResponseDto"}}}}},"security":[{"api-key":[]}],"summary":"Update pricing rules for the authenticated vendor","tags":["vendors"]}},"/api/v1/slots/sync":{"post":{"operationId":"SlotController_syncSlots","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncSlotsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncSummaryResponseDto"}}}}},"security":[{"api-key":[]}],"summary":"Upsert inventory slots for the authenticated vendor","tags":["slots"]}},"/api/v1/slots":{"get":{"description":"Offset pagination by `page` and `limit`. Ordered by `startTime` descending (newest window first).","operationId":"SlotController_listSlots","parameters":[{"name":"page","required":false,"in":"query","description":"1-based page index","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedSlotsResponseDto"}}}}},"security":[{"api-key":[]}],"summary":"List all slots for the authenticated vendor","tags":["slots"]}},"/api/v1/slots/{slotId}/pricing":{"get":{"operationId":"SlotController_getPricing","parameters":[{"name":"slotId","required":true,"in":"path","description":"External slot identifier","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingResultResponseDto"}}}},"400":{"description":"Slot has expired"},"404":{"description":"Slot not found"},"409":{"description":"Slot booked or locked"}},"security":[{"api-key":[]}],"summary":"Get current discounted price for a slot","tags":["slots"]}},"/api/v1/slots/{slotId}/lock":{"post":{"operationId":"SlotController_lockSlot","parameters":[{"name":"slotId","required":true,"in":"path","description":"External slot identifier","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LockIdResponseDto"}}}},"400":{"description":"Slot has expired"},"404":{"description":"Slot not found"},"409":{"description":"Slot booked, locked, or lock could not be acquired"}},"security":[{"api-key":[]}],"summary":"Acquire a short-lived lock before confirming a booking","tags":["slots"]}},"/api/v1/slots/{slotId}/confirm":{"post":{"operationId":"SlotController_confirmSlot","parameters":[{"name":"slotId","required":true,"in":"path","description":"External slot identifier","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmSlotDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmSlotResponseDto"}}}},"400":{"description":"Invalid state or lock id"},"404":{"description":"Slot not found"},"409":{"description":"Already booked with a different lock"}},"security":[{"api-key":[]}],"summary":"Confirm booking using the lock id from the lock step","tags":["slots"]}},"/api/v1/analytics/summary":{"get":{"operationId":"AnalyticsController_getSummary","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsSummaryResponseDto"}}}}},"security":[{"api-key":[]}],"summary":"Aggregated slot and revenue metrics for the vendor","tags":["analytics"]}}},"info":{"title":"VacantSlot API","description":"Rule-based dynamic discounting for time-based inventory. Authenticated routes require the `x-api-key` header (except vendor registration, which returns an API key).","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"securitySchemes":{"api-key":{"type":"apiKey","in":"header","name":"x-api-key","description":"Vendor API key"}},"schemas":{"DiscountBucketDto":{"type":"object","properties":{"hoursBefore":{"type":"number","minimum":1,"example":24,"description":"Apply this discount when at most this many hours remain before slot start"},"discountPercent":{"type":"number","minimum":0,"maximum":100,"example":10,"description":"Discount percent for this bucket"}},"required":["hoursBefore","discountPercent"]},"CreateVendorDto":{"type":"object","properties":{"name":{"type":"string","example":"Acme Billboards"},"vacancyThresholdHours":{"type":"number","minimum":1,"example":48,"description":"Hours before start below which discounts may apply"},"discountBuckets":{"type":"array","items":{"$ref":"#/components/schemas/DiscountBucketDto"}},"maxDiscountPercent":{"type":"number","minimum":0,"maximum":100,"example":50,"description":"Cap on discount across buckets"},"minPrice":{"type":"number","minimum":0,"example":10},"lockTtlMinutes":{"type":"number","minimum":1,"example":15,"description":"How long a lock stays valid (minutes)"}},"required":["name"]},"VendorCreatedResponseDto":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"apiKey":{"type":"string","description":"Save this key; it is only returned on create"},"vacancyThresholdHours":{"type":"number"},"discountBuckets":{"type":"array","items":{"type":"object"}},"maxDiscountPercent":{"type":"number"},"minPrice":{"type":"number"},"lockTtlMinutes":{"type":"number"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["_id","name","apiKey","vacancyThresholdHours","discountBuckets","maxDiscountPercent","minPrice","lockTtlMinutes","createdAt","updatedAt"]},"UpdateRulesDto":{"type":"object","properties":{"vacancyThresholdHours":{"type":"number","minimum":1,"example":48},"discountBuckets":{"type":"array","items":{"$ref":"#/components/schemas/DiscountBucketDto"}},"maxDiscountPercent":{"type":"number","minimum":0,"maximum":100,"example":50},"minPrice":{"type":"number","minimum":0,"example":10},"lockTtlMinutes":{"type":"number","minimum":1,"example":15}}},"VendorRulesUpdatedResponseDto":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"vacancyThresholdHours":{"type":"number"},"discountBuckets":{"type":"array","items":{"type":"object"}},"maxDiscountPercent":{"type":"number"},"minPrice":{"type":"number"},"lockTtlMinutes":{"type":"number"},"updatedAt":{"type":"string","format":"date-time"}},"required":["_id","name","vacancyThresholdHours","discountBuckets","maxDiscountPercent","minPrice","lockTtlMinutes","updatedAt"]},"SyncSlotItemDto":{"type":"object","properties":{"slotId":{"type":"string","example":"slot-abc-123"},"startTime":{"type":"string","format":"date-time","example":"2026-04-01T10:00:00.000Z"},"endTime":{"type":"string","format":"date-time","example":"2026-04-01T11:00:00.000Z"},"basePrice":{"type":"number","minimum":0,"example":99.99}},"required":["slotId","startTime","endTime","basePrice"]},"SyncSlotsDto":{"type":"object","properties":{"slots":{"minItems":1,"type":"array","items":{"$ref":"#/components/schemas/SyncSlotItemDto"}}},"required":["slots"]},"SyncSummaryResponseDto":{"type":"object","properties":{"created":{"type":"number","description":"New slots inserted"},"updated":{"type":"number","description":"Existing non-booked slots updated"},"skipped":{"type":"number","description":"Skipped because slot was already booked"}},"required":["created","updated","skipped"]},"SlotListItemResponseDto":{"type":"object","properties":{"_id":{"type":"string","description":"Mongo document id"},"slotId":{"type":"string","description":"External slot id"},"startTime":{"type":"string","format":"date-time"},"endTime":{"type":"string","format":"date-time"},"basePrice":{"type":"number"},"status":{"type":"string","enum":["available","locked","booked","expired"]},"lockId":{"type":"string","nullable":true,"format":"uuid"},"lockedUntil":{"type":"string","nullable":true,"format":"date-time"},"bookedAt":{"type":"string","nullable":true,"format":"date-time"},"discountApplied":{"type":"object","nullable":true,"description":"Discount percent at booking"},"finalPrice":{"type":"object","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["_id","slotId","startTime","endTime","basePrice","status","lockId","lockedUntil","bookedAt","discountApplied","finalPrice","createdAt","updatedAt"]},"PaginatedSlotsResponseDto":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/SlotListItemResponseDto"}},"total":{"type":"number","description":"Total slots for this vendor (all pages)"},"page":{"type":"number"},"limit":{"type":"number"},"totalPages":{"type":"number","description":"Ceiling of total / limit"}},"required":["items","total","page","limit","totalPages"]},"PricingResultResponseDto":{"type":"object","properties":{"originalPrice":{"type":"number"},"finalPrice":{"type":"number"},"discountPercent":{"type":"number","description":"Discount percentage applied (0–100)"},"validUntil":{"type":"string","format":"date-time","description":"Price is valid until this instant"}},"required":["originalPrice","finalPrice","discountPercent","validUntil"]},"LockIdResponseDto":{"type":"object","properties":{"lockId":{"type":"string","format":"uuid","description":"Use this id when confirming the booking"}},"required":["lockId"]},"ConfirmSlotDto":{"type":"object","properties":{"lockId":{"type":"string","format":"uuid","description":"Lock id returned from POST .../lock"}},"required":["lockId"]},"ConfirmSlotResponseDto":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]},"AnalyticsSummaryResponseDto":{"type":"object","properties":{"totalSlots":{"type":"number"},"bookedSlots":{"type":"number"},"recoveredSlots":{"type":"number","description":"Bookings where a discount was applied"},"recoveredRevenue":{"type":"number","description":"Sum of finalPrice for discounted bookings"}},"required":["totalSlots","bookedSlots","recoveredSlots","recoveredRevenue"]}}}}