Skip to content

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

  1. 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 after auctionStatus === ENDED.
  2. 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.
  3. 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 — leadingBid is 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

GET /v2/companies|admin/auctions/:auctionId

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)

POST /v2/companies|admin/auctions/:auctionId/approve

Body (optional):

{
  "reason": "Prebids closed, proceeding to live round"
}
  • When: auctionStatus === SCHEDULED and the resolved approvalRequired policy is true. 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 log APPROVE is 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 not SCHEDULED.
  • Approval publishes durable auction.state.changed with payload.transition: "approved" and the complete post-transition payload.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 with 400 if the window already closed.
  • close pins prebidEndAt = now, immediately blocking new prebid submissions. Idempotent. After close prebidPhase derives to CLOSED.
  • Response data carries prebidOpened / prebidClosed booleans and publishes auction.state.changed with transition: "prebid_opened" / "prebid_closed".

5.4 Reveal prebid amounts (subscriber disclosure)

POST /v2/companies|admin/auctions/:auctionId/prebids/reveal

Body: { "reason": "..." } (optional).

  • When: auctionStatus === ENDED only — 400 "Prebid amounts can be revealed only after the auction ends." otherwise.
  • Stamps prebidAmountsRevealedAt, publishes auction.state.changed { transition: "prebids_revealed" }. Subscribers' views unlock at this point; staff already had access at ENDED.

5.5 Action logs (audit trail)

GET /v2/companies|admin/auctions/:auctionId/action-logs

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

GET /v2/companies|admin/auctions/:auctionId/eligible-winner-candidates?pageNumber=1&pageSize=10

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

PATCH /v2/companies|admin/auctions/:auctionId/schedule

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 /v2/companies|admin/auctions/:auctionId/winner/disqualify
  • POST /winner/disqualify body: { winnerId, disqualificationReasonCode, disqualificationNote?, reason? }. Reason codes: KYC_ISSUE, PAYMENT_ISSUE, ELIGIBILITY_ISSUE, RULE_VIOLATION, OTHER.

5.9 Delete prebid

DELETE /v2/companies|admin/auctions/:auctionId/prebids/:prebidId

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 → requires bidId; reasonCode optional.
  • DISQUALIFY_BIDDER → requires enrolledSubscriberId + 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 + liveStartMode are gone. Drive UI off auctionStatus (+ derived prebidPhase + resolved approvalRequired).
  • Approval is a gate, not a mode: the live-decision endpoint (and its APPROVE_LIVE_AUCTION action log) is removed. Use POST .../approve → READY when approvalRequired is enabled; otherwise start directly from SCHEDULED.
  • Removed endpoint: prebid/disqualify-and-declare-next (and the DISQUALIFY_CANDIDATE_AND_DECLARE_NEXT auction.bid.mark action) are gone.
  • Removed response fields: the four decision booleans (canDirectDeclareFromPrebid, canRunPrebidLot, canProceedToLiveAuction, liveBiddingDecisionPending), reviewStatus, liveStartMode, and availabilityPhase no longer exist.
  • No implicit manual start: the Start command requires the prebid window closed, and (when approvalRequired) a prior approval — drive the Start button off auctionStatus + 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.