Skip to content

Auction Lifecycle Control — Frontend Integration Guide

This guide is the complete, frontend-facing contract for starting, stopping (ending), pausing and resuming a live auction from the company console and the superadmin console.

Both consoles share exactly the same contract. The only difference is the REST mount and the role token used. The live actions themselves (start / pause / resume / end) are WebSocket staff commands — there is no REST endpoint that starts, pauses, resumes or ends an auction.

Audience: frontend engineers integrating the auction console.

Related docs:


1. TL;DR

Action Transport Command / endpoint Success event
Start WebSocket auction.status.update → { status: "START" } auction.state.changed transition: "started"
Pause WebSocket auction.pause → { reason } auction.state.changed transition: "paused"
Resume WebSocket auction.resume → { reason } auction.state.changed transition: "resumed"
End WebSocket auction.end → { reason } auction.state.changed transition: "closed"
Read REST GET /v2/companies\|admin/auctions/:auctionId console overview payload
Audit REST GET /v2/companies\|admin/auctions/:auctionId/action-logs action log list
Arm REST PATCH /v2/companies\|admin/auctions/:auctionId/schedule schedule payload
Approve REST POST /v2/companies\|admin/auctions/:auctionId/approve mutation envelope — only when approvalRequired is true

Roles: COMPANY and SUPERADMIN. Both are "staff" in the auction room and require the auction:staff permission.


2. Lifecycle model

2.1 Status machine (auctionStatus)

SCHEDULED ──(approve)──▶ READY ──start──▶ LIVE ──pause──▶ PAUSED ──resume──▶ LIVE
                                               │                              │
                                               └──────────end─────────────────┴──▶ ENDED

(approve) applies only when the resolved policy sets approvalRequired: true; otherwise the auction starts directly from SCHEDULED.

Current auctionStatus Start Pause Resume End
SCHEDULED ✅ ✕ ✕ ✕
READY ✅ ✕ ✕ ✕
LIVE ✕ ✅ ✕ ✅
PAUSED ✕ ✕ ✅ ✅
ENDED ✕ ✕ ✕ ✕
CANCELLED ✕ ✕ ✕ ✕

End is allowed from both LIVE and PAUSED. A failed command returns an error frame with code: "CONFLICT" (or BAD_REQUEST) and a safe message — drive your button states from the current status so invalid actions are unreachable.

2.2 auctionStatus and prebidPhase

The console overview (GET .../auctions/:auctionId) exposes the status fields that drive the control buttons:

  • auctionStatus — PENDING / SCHEDULED / READY / LIVE / PAUSED / ENDED / COMPLETE / CANCELLED.
  • prebidPhase — NONE / OPEN / CLOSED. LIVE / PAUSED / ENDED are auctionStatus values, not prebidPhase values.
  • Migration note: the old derived "review status" and "availability phase" fields are removed, as is the "live decision" approval endpoint. Drive control logic from auctionStatus plus prebidPhase only, and use POST .../approve for approval.

Recommended button visibility:

Button Visible when
Start auctionStatus === 'SCHEDULED' (READY when approvalRequired is true) and prebidPhase === 'CLOSED' — i.e. after the prebid window closed and, if approval is required, after POST .../approve; never while prebid is OPEN (see Manual prebid controls)
Pause auctionStatus === 'LIVE'
Resume auctionStatus === 'PAUSED'
End auctionStatus === 'LIVE' or auctionStatus === 'PAUSED'

3. Setup: connect the console WebSocket

All four live actions travel over one socket. Open wss://api.example.com/ws (see WebSocket frontend integration for auth details — browsers send the token via Sec-WebSocket-Protocol).

Example (browser):

const token = '<access-token>';
const ws = new WebSocket(`wss://api.example.com/ws`, `websocket.v1, bearer.${token}`);

ws.onopen = () => {
  ws.send(
    JSON.stringify({
      id: 'subscribe-001',
      type: 'auction.subscribe',
      data: { auctionId }
    })
  );
};

ws.onmessage = event => {
  const frame = JSON.parse(event.data);
  handleFrame(frame);
};

Staff members omit enrolledSubscriberId on subscribe; the server joins them to both auction:<auctionId> and auction:staff:<auctionId>. Fetch auctionId via REST before subscribing..


4. Start the auction

4.0 Approve the auction (only when approval required)

Approval is needed only when the resolved policy sets approvalRequired: true and auctionStatus === 'SCHEDULED'. When approvalRequired is false (the default) no approval is needed — start directly from SCHEDULED.

POST /v2/companies/auctions/:auctionId/approve   (COMPANY token)
POST /v2/admin/auctions/:auctionId/approve       (SUPERADMIN token)

Body: { reason? } (optional). On success the server sets auctionStatus = READY and writes an APPROVE action log. Response:

{
  "data": {
    "auctionId": "...",
    "cycleId": "...",
    "status": "READY",
    "approvalRequired": true,
    "auctionStartAt": "..."
  }
}

The endpoint is valid only when approvalRequired === true AND auctionStatus === 'SCHEDULED'. Approving without the approval-required policy is rejected with 400: "This auction does not require approval. Approving an auction without the approval-required policy is not allowed."

4.1 Command

{
  "id": "start-001",
  "type": "auction.status.update",
  "data": {
    "auctionId": "auction-id",
    "status": "START"
  }
}

4.2 What happens

  • Immediate frame: command.acknowledged (no business data).
  • On success, the authoritative event arrives: auction.state.changed with data.payload.transition: "started".
  • The server sets auctionStatus = LIVE, persists the automatic end timestamp, writes a START action log, and enqueues AUCTION_STARTED notifications.
  • A start is only accepted when prebidPhase === 'CLOSED' — starting while the prebid window is open is rejected ("Close the prebid window before starting the live auction."). When approvalRequired is true the auction must already be READY — starting from SCHEDULED is rejected ("Approve this auction before starting the live auction."). When approvalRequired is false (the default) no approval is needed and the auction starts directly from SCHEDULED.

4.3 UI guidance

  • Show a loading state from send until transition: "started" (or an error frame).
  • Never flip the console to "live" on the command.acknowledged frame — the ack only means the command was accepted.

5. Pause the auction

{
  "id": "pause-001",
  "type": "auction.pause",
  "data": {
    "auctionId": "auction-id",
    "reason": "network issue"
  }
}
  • reason is required, trimmed, 1–500 characters.
  • Requires auctionStatus === 'LIVE'.
  • Success event: auction.state.changed with transition: "paused"; read pausedAt, auctionEndAt, closingPhase, and closingPhaseEndsAt from the complete payload.state object.
  • Bids submitted while paused are rejected (AUCTION_NOT_LIVE); subscribers see the auction as paused.

6. Resume the auction

{
  "id": "resume-001",
  "type": "auction.resume",
  "data": {
    "auctionId": "auction-id",
    "reason": "resolved"
  }
}
  • reason is required, trimmed, 1–500 characters.
  • Requires auctionStatus === 'PAUSED'.
  • Success event: auction.state.changed with transition: "resumed"; the complete payload.state has pausedAt: null and the shifted auctionEndAt / closingPhaseEndsAt deadline. The outer event timestamp is the occurrence time; there is no root payload.resumedAt field.
  • Bidding is re-enabled immediately on the server side; subscribers receive the same event.

7. End (stop) the auction

{
  "id": "end-001",
  "type": "auction.end",
  "data": {
    "auctionId": "auction-id",
    "reason": "auction complete"
  }
}
  • reason is required, trimmed, 1–500 characters.
  • Requires auctionStatus === 'LIVE' or auctionStatus === 'PAUSED'.
  • Success event: auction.state.changed with transition: "closed".
  • Server sets auctionStatus = ENDED, recalculates the next lifecycle transition, writes an END action log, and enqueues AUCTION_ENDED notifications.

8. Reading state and auditing

Poll or reconcile the console with REST after each transition:

GET /v2/companies/auctions/:auctionId     (COMPANY token)
GET /v2/admin/auctions/:auctionId         (SUPERADMIN token)

The overview carries auctionStatus, prebidPhase, stateVersion, startedAt, pausedAt, endedAt, and the current leadingBid/bidCount/prebids/participants.

Audit trail — the same four actions are recorded here:

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

Relevant action values: APPROVE, START, AUTO_START, PAUSE, RESUME, END, AUTO_END.


1. Open /ws with an access token.
2. auction.subscribe { auctionId } (fetch via REST; row guaranteed before `READY`/`LIVE`)
3. Fetch GET .../auctions/:auctionId → seed control state.
4. On user action → send the WS command above.
5. On auction.state.changed → replace lifecycle fields from payload.state and
   use payload.transition for transition-specific UI effects.
6. On error frame → surface the message, keep previous state.
7. On network loss → reconnect, resubscribe with lastSequence, re-fetch overview.

Event handling sketch

function handleFrame(frame: AuctionSocketFrame) {
  if (frame.type === 'auction.state.changed') {
    const t = frame.data?.payload?.transition;
    const state = frame.data?.payload?.state;
    if (state) replaceAuctionLifecycleState(state);
    if (t === 'started') showAuctionStartedEffect();
    if (t === 'closed') showAuctionEndedEffect();
  }
  if (frame.type === 'error') {
    // frame.data.code in { BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, RATE_LIMITED, INTERNAL }
    showToast(frame.data.message);
  }
}

Reconnection

ws.onclose = () => {
  const lastSequence = lastProcessedAuctionSequence; // from data.serverSequence
  reconnect().then(() =>
    ws.send(
      JSON.stringify({
        id: 'subscribe-002',
        type: 'auction.subscribe',
        data: { auctionId, lastSequence }
      })
    )
  );
};

10. Error reference

Code Typical cause
FORBIDDEN Non-staff caller, or company does not own the auction.
NOT_FOUND Cycle/auction does not exist.
CONFLICT Invalid transition for current status (e.g. pause while not live).
BAD_REQUEST Missing/invalid fields (e.g. empty reason, missing auctionId).
RATE_LIMITED More than the per-event-type message limit.
INTERNAL Server error.

Company users are scoped to auctions owned by their company; the server enforces ownership on every command. Superadmin tokens bypass the company scope.