Strict Auction Lifecycle — Frontend Integration Guide¶
This guide is the frontend-facing contract for the strict auction lifecycle: prebids are sealed, no winner is selected before the live auction ends, and the pre-auction "review" stage is a pure go-live gate. It replaces the old "prebid review / live bid review" model and the prebid winner-selection features that were removed.
Both consoles (company and superadmin) share exactly the same contract; the only differences are the REST mount (/v2/companies vs /v2/admin) and the role token. Winner selection happens over WebSocket; everything else is REST.
Related docs:
1. The three rules that everything below follows¶
- No winner selection before the live auction ends. Staff can never declare a winner, run a lot, or otherwise settle the cycle from prebids while the auction is still
SCHEDULED/READY. Winner selection happens only afterauctionStatus === ENDED. - A closed prebid window is a pure go-live gate. Once the prebid window closes (
prebidPhase === CLOSED, deadline or manual close), the only staff path forward is starting the live round — and, when the policy requires it, approving it first. There is no amount review, no candidate list, no winner actions at that stage. - Prebid amounts are sealed. Staff see prebid amounts only after the live auction ends; subscribers see them only after staff explicitly reveal them (
POST .../prebids/reveal, also only after end). The live auction opens with no prebid-derived base/leading amount —leadingBidis set only by live bids.
2. State model¶
A single persistent auctionStatus drives the lifecycle. The prebid window is derived from timestamps as a prebidPhase, and the approval gate is a resolved policy flag (approvalRequired). Frontends drive UI off auctionStatus + prebidPhase + approvalRequired.
auctionStatus (persisted)¶
| Status | Meaning |
|---|---|
PENDING | Auction row created; not yet scheduled |
SCHEDULED | Start time configured; prebid window active or awaiting close |
READY | Approval-required auction approved by staff — ready to start |
LIVE | Live auction running |
PAUSED | Live auction paused |
ENDED | Live auction finished — winner selection allowed here |
COMPLETE | Every division has a declared winner — terminal |
CANCELLED | Cancelled by staff |
READY is used only when the effective policy sets approvalRequired (resolved from the auction's config snapshot). With approvalRequired disabled (the default), auctions start directly from SCHEDULED and READY never appears. Approving an auction without approvalRequired is rejected.
prebidPhase (derived, not persisted)¶
| Phase | Meaning |
|---|---|
NONE | Program has no prebid |
OPEN | Window open (now < prebidEndAt or end-of-cycle-day fallback) |
CLOSED | Window closed (deadline passed or manual close) |
The phase is orthogonal to auctionStatus; whether it matters depends on the caller's policy (e.g. a start is gated on CLOSED, canPrebid on OPEN).
3. Lifecycle walkthrough (console responsibilities)¶
PENDING
│ POST /schedule
▼
SCHEDULED (prebid window opens/closes as `prebidPhase`)
│
├── approvalRequired === false ───────────────┐
│ │
├── approvalRequired === true │
│ └── POST /approve ──▶ READY ──────────────┤
│ │
└─────────── start path (either status) ───────┘
│
▼
LIVE ──(pause/resume)──▶ PAUSED
│
└── auction ends (WS auction.end OR delayed lifecycle job)
▼
ENDED ◄── winner selection ONLY here
│ live bids exist → auction.winner.declare from a bid
│ no live bids received → sealed prebids are the fallback pool:
│ auction.winner.declare {prebidId} or
│ auction.winner.record_lot (tie at lowest)
│
├── POST /prebids/reveal (optional, subscriber disclosure)
▼
COMPLETE (when every division has a declared winner)
Stage-by-stage console guidance¶
| Stage | Console shows | Console actions enabled |
|---|---|---|
SCHEDULED / prebidPhase OPEN | Prebid rows with amounts hidden (staff); participant list | Open prebid (if window not materialized), Close prebid, schedule |
SCHEDULED / prebidPhase CLOSED | Prebid rows, amounts still hidden; no candidates/winner UI | Approve (only when approvalRequired), then Start live. Start is disabled while the window is open. |
READY | — | Start live (auction.status.update START, WS) |
LIVE / PAUSED | Live bids, participants, realtime stats | Pause / resume / end, auction.bid.mark moderation |
ENDED | Live bids and prebid rows now show amounts to staff; winner candidates | auction.winner.declare (from a live bid, or from a prebid when no live bids), auction.winner.record_lot, disqualify (then re-declare) winner |
COMPLETE | Winners list | Reveal prebids (if not yet revealed) |
4. Prebid amount visibility matrix¶
| Viewer | Before auction ends (ENDED) | After ENDED, before reveal | After POST /prebids/reveal |
|---|---|---|---|
| Staff (company + superadmin console) | Prebid row amounts null; leadingBid.amount null (leading bid is live-bid-only); winner amount null | Prebid amounts visible (fallback review needs them) | Visible |
| Subscriber (own prebid) | Their own activePrebid / latestPrebid amount visible | Own prebid only | All amounts visible |
| Subscriber (others) | Hidden | Hidden | Visible |
The console overview exposes prebidAmountsRevealed: boolean and prebidAmountsRevealedAt so the UI can mirror this matrix exactly. When amounts are hidden, the API returns amount: null (fields are not omitted).
5. REST API reference¶
Base path: POST|GET|PATCH|DELETE /v2/companies/auctions/:auctionId/... (superadmin: /v2/admin/auctions/:auctionId/...).
Auth: Bearer company or superadmin token. Company tokens are scoped to auctions their company owns; superadmin tokens can act on any auction. Responses use the shared { success, message, data } envelope.
5.1 Console overview¶
Query params (all optional): bidPageNumber, bidPageSize, prebidPageNumber, prebidPageSize, participantPageNumber, participantPageSize (default 1 / 10).
Key data fields for the strict lifecycle:
| Field | Type | Notes |
|---|---|---|
auctionStatus | enum | SCHEDULED | READY | LIVE | PAUSED | ENDED | COMPLETE | CANCELLED |
prebidPhase | enum | NONE | OPEN | CLOSED — derived, see §2 |
approvalRequired | boolean | resolved policy gate — whether READY is required before start |
prebidStartAt / prebidEndAt | ISO | null | window bounds |
prebidAmountsRevealed | boolean | drives the visibility matrix |
prebidAmountsRevealedAt | ISO | null | |
leadingBid | object | null | { id, subscriberId, enrolledSubscriberId, amount, createdAt }. Always a live bid — never seeded from prebids. |
auctionBaseAmount | number | null | null until the first live bid |
prebids.items[].amount | number | null | null until staff-visible at ENDED (or revealed) |
winners[].amount | number | null | null for prebid-sourced winners until revealed |
winners[].selectionSource | enum | null | PREBID_REVIEW | LIVE_BID_REVIEW | MANUAL_SELECTION | LOT_SELECTION |
declaredWinnerCount / remainingWinnerSlots | number | |
liveBids.items[] | array | live bid history (amounts always visible) |
The old pre-auction decision flags and the old reviewStatus / liveStartMode / availabilityPhase fields no longer exist in the response.
5.2 Approve auction (only when approvalRequired)¶
Body (optional):
- When:
auctionStatus === SCHEDULEDand the resolvedapprovalRequiredpolicy istrue. Approving an auction whose policy does not require approval is rejected (400). - Effect:
auctionStatus→READY; the lifecycle timestamp is recalculated for the approved status; action logAPPROVEis written. - Response
data:{ auctionId, cycleId, status: "READY", approvalRequired, auctionStartAt }. - Errors:
400"This auction does not require approval. Approving an auction without the approval-required policy is not allowed."400"Illegal auction transition APPROVE from ... by STAFF." if the auction is notSCHEDULED.- Approval publishes durable
auction.state.changedwithpayload.transition: "approved"and the complete post-transitionpayload.state.
5.3 Manual prebid open / close¶
POST /v2/companies|admin/auctions/:auctionId/prebid/open
POST /v2/companies|admin/auctions/:auctionId/prebid/close
Body: { "reason": "..." } (optional). See the dedicated manual-prebid-controls.md doc for the full contract. Summary:
- open materializes the window (
prebidStartAt = now,prebidEndAt= deadline or end-of-cycle-day). Idempotent; rejected with400if the window already closed. - close pins
prebidEndAt = now, immediately blocking new prebid submissions. Idempotent. After closeprebidPhasederives toCLOSED. - Response
datacarriesprebidOpened/prebidClosedbooleans and publishesauction.state.changedwithtransition: "prebid_opened"/"prebid_closed".
5.4 Reveal prebid amounts (subscriber disclosure)¶
Body: { "reason": "..." } (optional).
- When:
auctionStatus === ENDEDonly —400"Prebid amounts can be revealed only after the auction ends." otherwise. - Stamps
prebidAmountsRevealedAt, publishesauction.state.changed { transition: "prebids_revealed" }. Subscribers' views unlock at this point; staff already had access atENDED.
5.5 Action logs (audit trail)¶
data.actionLogs[] with action ∈ AUCTION_ACTION_TYPES (e.g. START, AUTO_START, APPROVE, CANCEL, MANUAL_OPEN_PREBID, MANUAL_CLOSE_PREBID, DECLARE_WINNER, REVEAL_PREBIDS, ...). The removed APPROVE_LIVE_AUCTION, DISQUALIFY_PREBID_CANDIDATE, and DISQUALIFY_LIVE_BID_CANDIDATE values are no longer in the vocabulary.
5.6 Eligible winner candidates¶
Returns eligible subscribers (enrolledSubscriberId, subscriberId, name, avatar, email, mobileNumber) — used to seed the manual-declare picker after the auction ends. It does not return prebid amounts.
5.7 Schedule / reschedule¶
Body: { auctionStartAt, auctionDurationSeconds, reason, ...settings }. Response data includes prebidStartAt, prebidEndAt, auctionStartAt, prebidPhase — use it to confirm the derived window phase. Only valid from SCHEDULED.
5.8 Replace / disqualify winner (post-end)¶
There is no PATCH /winner endpoint. To replace a declared winner, disqualify the current one and then declare the next candidate via the WebSocket auction.winner.declare command (mode CANDIDATE with a different bidId / prebidId).
Disqualify:
POST /winner/disqualifybody:{ winnerId, disqualificationReasonCode, disqualificationNote?, reason? }. Reason codes:KYC_ISSUE,PAYMENT_ISSUE,ELIGIBILITY_ISSUE,RULE_VIOLATION,OTHER.
5.9 Delete prebid¶
Deletes a specific prebid. No body.
6. WebSocket staff commands¶
Endpoint GET /ws, auth via sec-websocket-protocol header (websocket.v1, bearer.<token>), all commands require auction:staff permission and an auctionId.
6.1 Live controls¶
| Type | Payload | When legal |
|---|---|---|
auction.status.update | { auctionId, status: "START" } | SCHEDULED (no approval) or READY (approval) |
auction.pause | { auctionId, reason } | LIVE |
auction.resume | { auctionId, reason } | PAUSED |
auction.end | { auctionId, reason } | LIVE / PAUSED |
Start gate (important): START is rejected with error { code: "BAD_REQUEST" } if the prebid window is open — message "Close the prebid window before starting the live auction." — or if the auction requires approval and has not been approved — message "Approve this auction before starting the live auction." When approvalRequired is enabled, the auction must be READY to start; there is no implicit start from SCHEDULED.
6.2 Bid moderation¶
| Type | Payload |
|---|---|
auction.bid.mark | { auctionId, action: "DELETE_BID" \| "DISQUALIFY_BIDDER", bidId?, enrolledSubscriberId?, reason, reasonCode? } |
DELETE_BID→ requiresbidId;reasonCodeoptional.DISQUALIFY_BIDDER→ requiresenrolledSubscriberId+reasonCode.
reasonCode ∈ KYC_ISSUE | PAYMENT_ISSUE | ELIGIBILITY_ISSUE | RULE_VIOLATION | OTHER. The old DISQUALIFY_CANDIDATE_AND_DECLARE_NEXT action has been removed.
6.3 Winner selection (only after the auction ends)¶
| Type | Payload | Behavior |
|---|---|---|
auction.winner.declare | { auctionId, mode: "CANDIDATE", bidId? \| prebidId?, reason? } | Declares from an existing live bid or (only when the auction ended with no live bids) from a sealed prebid |
auction.winner.declare | { auctionId, mode: "MANUAL", enrolledSubscriberId, amount, reason? } | Manual declaration of an eligible subscriber (amount required) |
auction.winner.record_lot | { auctionId, winnerEnrolledSubscriberId, candidateEnrolledSubscriberIds: [≥2], reason? } | Resolves a tie at the lowest amount by lot; legal from live bids or from the prebid fallback pool |
Both commands are rejected with 400 before the auction ends — there is no winner action while the window is open or while SCHEDULED/READY. Declaring a winner moves the auction to COMPLETE when the last division is filled; a prebid-fallback winner on an auction with remaining divisions reopens it to SCHEDULED for the next division.
6.4 Announcements / presence¶
| Type | Payload |
|---|---|
auction.announcement.create | { auctionId, message, tone: "INFO" \| "WARNING" } |
auction.presence.floor.update | { auctionId, enrolledSubscriberId, present: boolean } |
7. Realtime events¶
The room broadcasts auction.state.changed with payload.transition one of:
| Transition | Trigger |
|---|---|
schedule_changed | Schedule or reschedule |
started | Live auction started (manual or auto) |
paused / resumed | Pause / resume |
extended / call_started | Closing deadline/phase changed |
closed | Auction ended (ENDED) |
prebid_opened / prebid_closed | Manual prebid window controls |
prebids_revealed | POST .../prebids/reveal |
approved | Auction approved into READY |
winner_selected | A winner was declared |
winner_disqualified | A declared winner was disqualified |
cancelled | Auction cancelled |
Use stateVersion (monotonic) to order events and replace lifecycle state from the complete payload.state. Staff also receive durable auction.winner.changed events for detailed winner reconciliation.
8. Error catalog (frontend-facing messages)¶
| Scenario | Code | Message |
|---|---|---|
| Start while prebid open | 400 | "Close the prebid window before starting the live auction." |
Start from SCHEDULED when approval needed | 400 | "Approve this auction before starting the live auction." |
| Approve without approval policy | 400 | "This auction does not require approval." |
| Winner action before auction ends | 400 | Illegal transition / "no winner selection" guard |
| Reveal before end | 400 | "Prebid amounts can be revealed only after the auction ends." |
| Reopen a closed prebid window | 400 | "Prebid window has already closed and cannot be reopened manually." |
| All divisions declared | 400 | "All division winners are already declared for this cycle." |
| Company token on foreign auction | 403 | "You do not have access to this auction." |
9. Console button-state cheat sheet¶
| Button | Show when | Enabled when |
|---|---|---|
| Open prebid | hasPrebid + SCHEDULED | prebidPhase === 'OPEN' and prebidStartAt === null |
| Close prebid | hasPrebid + SCHEDULED | prebidPhase === 'OPEN' |
| Approve | SCHEDULED | approvalRequired === true (hide entirely when false) |
| Start live | SCHEDULED/READY | prebidPhase === 'CLOSED' and (approvalRequired === false or auctionStatus === 'READY') |
| Pause / Resume / End | live | LIVE / PAUSED / LIVE-or-PAUSED |
| Declare winner / Lot / Replace | ENDED | auctionStatus === 'ENDED' (and remainingWinnerSlots > 0 for declare) |
| Reveal prebids | ENDED | prebidAmountsRevealed === false |
10. What changed (frontend migration notes)¶
- Single status axis:
reviewStatus+availabilityPhase+liveStartModeare gone. Drive UI offauctionStatus(+ derivedprebidPhase+ resolvedapprovalRequired). - Approval is a gate, not a mode: the
live-decisionendpoint (and itsAPPROVE_LIVE_AUCTIONaction log) is removed. UsePOST .../approve→READYwhenapprovalRequiredis enabled; otherwise start directly fromSCHEDULED. - Removed endpoint:
prebid/disqualify-and-declare-next(and theDISQUALIFY_CANDIDATE_AND_DECLARE_NEXTauction.bid.markaction) are gone. - Removed response fields: the four decision booleans (
canDirectDeclareFromPrebid,canRunPrebidLot,canProceedToLiveAuction,liveBiddingDecisionPending),reviewStatus,liveStartMode, andavailabilityPhaseno longer exist. - No implicit manual start: the Start command requires the prebid window closed, and (when
approvalRequired) a prior approval — drive the Start button offauctionStatus+approvalRequired, not a review status. - Leading bid is live-bid-only: the auction opens with
leadingBid: null; a prebid never seeds the opening/base amount. - Staff prebid visibility: prebid amounts unlock for staff at
ENDED(not at window close); subscribers unlock only after reveal.