Skip to content

Manual Prebid Controls — Frontend Integration Guide

This guide is the complete, frontend-facing contract for manually opening and closing the prebid window from the company console and the superadmin console, plus the new rule that the live auction cannot start while prebid is open.

Both consoles share exactly the same contract. The only differences are the REST mount (/v2/companies vs /v2/admin) and the role token used. Manual prebid controls are REST-only — unlike the live-auction controls (start / pause / resume / end), which are WebSocket staff commands.

Audience: frontend engineers integrating the auction console.

Related docs:


1. TL;DR

Action Transport Endpoint Success event Action log
Open prebid REST POST /v2/companies\|admin/auctions/:auctionId/prebid/open auction.state.changed transition: "prebid_opened" MANUAL_OPEN_PREBID
Close prebid REST POST /v2/companies\|admin/auctions/:auctionId/prebid/close auction.state.changed transition: "prebid_closed" MANUAL_CLOSE_PREBID
Read state REST GET /v2/companies\|admin/auctions/:auctionId console overview payload —
Audit REST GET /v2/companies\|admin/auctions/:auctionId/action-logs action log list —

Roles: COMPANY and SUPERADMIN. Company tokens are scoped to auctions owned by their company; superadmin tokens can act on any auction.

The rule that matters most: the live auction may only start once the prebid window is closed. Any manual start while prebid is open is rejected server-side with BAD_REQUEST ("Close the prebid window before starting the live auction."). Drive your Start button off the closed state, not just the scheduled status.


2. Where manual controls fit in the prebid lifecycle

The prebid window can open and close in two ways:

Path Trigger prebidPhase
Automatic (policy) autoOpenPrebid opens the window when the auction is armed; it closes by its deadline (prebidEndAt or end of the cycle day) OPEN → CLOSED
Manual (staff) POST …/prebid/open and POST …/prebid/close same phases, driven by staff action

Phase flow around the controls:

OPEN ──(manual close OR deadline passes)──▶ CLOSED
  ▲                                             │
  │                                             │ start path (see §6)
  └───────── (reopen is NOT allowed)            ▼
                                    approve (only when approvalRequired)
                                            │
                                            ▼
                                    Start (manual START or auto-start)

Key semantics:

  • Open materializes/records the window explicitly (prebidStartAt = now, prebidEndAt = existing deadline or endOfDay(cycleEndDate)). It is only accepted while the window is still naturally open (prebidPhase === 'OPEN'); a window that has already closed cannot be reopened.
  • Close pins prebidEndAt to now, immediately blocking new subscriber prebid submissions. Existing prebids keep their prebidMutabilityPolicy behavior (e.g. IMMUTABLE_CANCEL_ONLY still allows cancel-only, no edits).
  • Both actions are idempotent: repeating an open on an already-open window, or a close on an already-closed window, returns 200 with the prebidOpened / prebidClosed flag set to false.
  • A manual close also clears any pending automatic open job, so the auto-policy cannot re-open the window afterwards.

3. Endpoints and payloads

3.1 Open the prebid window

POST /v2/companies/auctions/:auctionId/prebid/open
POST /v2/admin/auctions/:auctionId/prebid/open

Request body — reason is optional:

{
  "reason": "Opening the prebid window early"
}

Successful 200 response (data):

{
  "auctionId": "auc_01H…",
  "cycleId": "cyc_01H…",
  "stateVersion": 9,
  "prebidStartAt": "2026-08-17T09:00:00.000Z",
  "prebidEndAt": "2026-08-19T23:59:59.999Z",
  "prebidOpened": true
}
Field Type Description
auctionId string Auction id.
cycleId string Cycle id.
stateVersion number New auction state version (incremented on a real open).
prebidStartAt string Window open time (now on first open, existing value on no-op).
prebidEndAt string Window deadline (existing or endOfDay(cycleEndDate)).
prebidOpened boolean true when the window was opened by this call, false if it was already open (idempotent no-op).

3.2 Close the prebid window

POST /v2/companies/auctions/:auctionId/prebid/close
POST /v2/admin/auctions/:auctionId/prebid/close

Request body — reason is optional:

{
  "reason": "Closing the window before the live auction"
}

Successful 200 response (data):

{
  "auctionId": "auc_01H…",
  "cycleId": "cyc_01H…",
  "stateVersion": 10,
  "prebidEndAt": "2026-08-17T09:30:00.000Z",
  "prebidClosed": true
}
Field Type Description
auctionId string Auction id.
cycleId string Cycle id.
stateVersion number New auction state version (incremented on a real close).
prebidEndAt string Pinned to the close time on first close; existing value on no-op.
prebidClosed boolean true when the window was closed by this call, false if it was already closed (idempotent no-op).
prebidStartAt (absent) Not returned by the close endpoint.

3.3 Response envelope

Both endpoints use the standard success envelope:

{
  "status": "success",
  "message": "Prebid window opened successfully.",
  "data": {}
}

("Prebid window closed successfully." for close.) Errors use the standard { status: "error", code, message } envelope with requestId.


4. Validation and business rules

Applies to both open and close (unless noted):

Rule Failure (400)
Program bid type is auction-based "Auction is not enabled for this program."
Cycle is ACTIVE "Auction operations are only allowed for active cycles."
Prebid is enabled for the auction "Prebid is not enabled for this auction."
Auction is SCHEDULED "Prebid can only be opened/closed while the auction is scheduled."
Company owns the auction (company token) 403 "You do not have access to this auction."
Window is still naturally open (prebidPhase === 'OPEN') close: idempotent no-op; open: 400 "Prebid window has already closed and cannot be reopened manually."
A deadline is derivable (cycleEndDate or prebidEndAt) open only: 400 "Prebid window cannot be opened without a cycle end date."
Window not already open (prebidStartAt unset) open only: idempotent no-op (prebidOpened: false).
Window not already closed by time close only: idempotent no-op (prebidClosed: false).

Actions that become available after a close:

  • prebidPhase → CLOSED (derived; there is no prebid review state anymore — the sealed prebids are not touched until the live auction ends).
  • The start path opens: when approvalRequired === true, POST .../approve moves the auction to READY, then Start; when approvalRequired === false (default) no approval is needed and the auction starts directly from SCHEDULED — see section 6.

5. Reading state and auditing

Poll or reconcile the console after each mutation:

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

Relevant fields in the overview payload:

Field Meaning
prebidPhase OPEN → CLOSED after a close (derived from auctionStatus + window state).
prebidStartAt Set after a manual (or auto) open; null until then.
prebidEndAt Original deadline; pinned to the close moment after a manual close.
activePrebidCount Number of active prebids received before the window closed.
stateVersion Bumped on every open/close.

Audit trail — both actions are recorded:

GET /v2/companies/auctions/:auctionId/action-logs
GET /v2/admin/auctions/:auctionId/action-logs
action actorId metadata
MANUAL_OPEN_PREBID staff id prebidStartAt, prebidEndAt (ISO-8601)
MANUAL_CLOSE_PREBID staff id prebidEndAt (ISO-8601)

6. The live start gate

The live auction can only start once the prebid window is closed. This is enforced server-side in three layers (state machine, guard helper, and the start command):

Attempt Result
Manual start (WS auction.status.update START) while prebidPhase === 'OPEN' error frame, code: "BAD_REQUEST", message "Close the prebid window before starting the live auction."
Manual start while prebidPhase === 'CLOSED' but approvalRequired and auction not READY error frame, code: "BAD_REQUEST", message "Approve this auction before starting the live auction."
Manual start from auctionStatus === 'READY' (approved) Allowed.
Manual start while prebidPhase === 'CLOSED' and approvalRequired === false (default) Allowed — the auction starts directly from SCHEDULED.
Auto-scheduled start Same gate: only fires once prebidPhase === 'CLOSED' (and the auction is READY when approvalRequired).
Button Visible / enabled when
Open prebid auctionStatus === 'SCHEDULED', settings.hasPrebid === true, prebidPhase === 'OPEN', and prebidStartAt is null.
Close prebid auctionStatus === 'SCHEDULED', settings.hasPrebid === true, prebidPhase === 'OPEN'.
Start (live) prebidPhase === 'CLOSED' and (approvalRequired === false or auctionStatus === 'READY'). Never show Start while prebid is open.

This replaces the previous behavior where Start was offered while prebid was still open. Keep the Close button prominent between "prebid open" and "prebid closed" so operators close the window before starting.


7. Realtime events

Both mutations publish an auction.state.changed event to the auction:<auctionId> room (staff and subscribers). The example abbreviates the complete payload.state to the prebid fields used by this screen:

{
  "type": "auction.state.changed",
  "id": "evt_01H…",
  "version": "1.0",
  "source": "auction-service",
  "timestamp": "2026-08-17T09:30:00.000Z",
  "sequence": 12,
  "data": {
    "auctionId": "auc_01H…",
    "cycleId": "cyc_01H…",
    "stateVersion": 10,
    "payload": {
      "transition": "prebid_closed",
      "state": {
        "status": "SCHEDULED",
        "stateVersion": 10,
        "currentPhase": "PREBID_CLOSED",
        "prebidPhase": "CLOSED",
        "prebidStartAt": "2026-08-17T08:00:00.000Z",
        "prebidEndAt": "2026-08-17T09:30:00.000Z"
      }
    }
  }
}
payload.transition Fired by Frontend handling
prebid_opened Manual open (and auto-policy open) Replace lifecycle state, start the prebid countdown, enable prebid submission UI.
prebid_closed Manual close Replace lifecycle state, stop submissions, and enable the Start path.

Subscriber impact of prebid_closed: their REST prebid calls are rejected ("Prebid window is not open.") and their currentUser.canPrebid flips to false on the next snapshot/detail fetch. Render the form as locked on the event, do not wait for a poll.

Note: subscribers who are not subscribed to the room still see the change the next time they load the auction — the REST overview is always the source of truth for cold starts.


8. Sequence diagram

sequenceDiagram
    participant Staff as Staff console
    participant API as REST API
    participant Svc as Auction service
    participant Room as Auction room (WS)
    participant Sub as Subscriber app

    Note over Staff,Svc: Open (optional — records the window early)
    Staff->>API: POST .../auctions/:id/prebid/open {reason}
    API->>Svc: openPrebid command
    Svc-->>API: 200 {prebidStartAt, prebidEndAt, prebidOpened: true}
    Svc-->>Room: auction.state.changed {transition: prebid_opened}

    Note over Staff,Svc: Close — required before the live auction starts
    Staff->>API: POST .../auctions/:id/prebid/close {reason}
    API->>Svc: closePrebid command
    Svc->>Svc: prebidEndAt = now, clear auto-open job
    Svc-->>API: 200 {prebidEndAt, prebidClosed: true}
    Svc-->>Room: auction.state.changed {transition: prebid_closed}
    Room-->>Sub: canPrebid = false, form locked

    Note over Staff,Svc: Approve (only when approvalRequired; else start directly)
    Staff->>API: POST .../auctions/:id/approve {reason}
    API->>Svc: approveAuction command
    Svc-->>API: 200 {auctionStatus: READY}

    Note over Staff,Svc: Live start is now possible
    Staff->>Svc: auction.status.update {status: START}
    Svc-->>Room: auction.state.changed {transition: started}

1. Fetch GET .../auctions/:auctionId → seed prebidStartAt / prebidEndAt /
   prebidPhase / auctionStatus / settings.hasPrebid / approvalRequired.
2. Subscribe to the auction room (staff: auction.subscribe with auctionId (fetch via REST)).
3. Render Open/Close buttons per the visibility table in section 6.
4. On Open tap → POST .../prebid/open → update prebidStartAt / prebidEndAt.
5. On Close tap → POST .../prebid/close → set prebidEndAt = now, prebidPhase to
   CLOSED, enable Start.
6. Reconcile with auction.state.changed (prebid_opened / prebid_closed) so all
   consoles stay in sync.
7. On reconnect → resubscribe with lastSequence, re-fetch the overview.

Event handling sketch

function handleFrame(frame: any) {
  if (frame.type === 'auction.state.changed') {
    const t = frame.data?.payload?.transition;
    if (t === 'prebid_opened') {
      setPrebidWindow({ open: true });
    }
    if (t === 'prebid_closed') {
      setPrebidWindow({ open: false });
      setPrebidPhase('CLOSED');
    }
    if (t === 'started') setStatus('LIVE');
  }
  if (frame.type === 'error') {
    // code in { BAD_REQUEST, FORBIDDEN, NOT_FOUND, CONFLICT, INTERNAL }
    showToast(frame.data.message);
  }
}

10. Error reference

Code Message (abridged) When
404 Auction not found Unknown auction id.
403 You do not have access to this auction Company token on another company's auction.
400 Auction is not enabled for this program Program bid type is not auction-based.
400 Auction operations are only allowed for active cycles Cycle not ACTIVE.
400 Prebid is not enabled for this auction settings.hasPrebid === false.
400 Prebid can only be opened while the auction is scheduled Auction already LIVE/PAUSED/ENDED.
400 Prebid can only be closed while the auction is scheduled Same, close variant.
400 Prebid window has already closed and cannot be reopened manually Open after the window closed by time.
400 Prebid window cannot be opened without a cycle end date No cycleEndDate and no explicit prebidEndAt to derive a deadline from.
200 no-op prebidOpened: false / prebidClosed: false Repeating an already-applied action (idempotent).
500 Server error Unexpected failure.