Auction — Frontend Integration Guide¶
For the current realtime payload and replay migration, see Auction current changes — complete frontend integration.
This guide documents the complete frontend-facing contract for the Dichit auction feature (v2 API). It covers the REST endpoints used by the subscriber app and the company/admin auction console, plus the WebSocket protocol for the live auction room.
Audiences:
- Subscriber app (web/mobile) — browse auctions, submit/cancel prebids, join the live room, place live bids.
- Company console — run prebid review, schedule/start/pause/resume/end the live auction, review bids, declare and disqualify winners, reveal prebids.
- Superadmin console — same console surface as the company console, with cross-company access.
Related docs:
- Subscriber auction detail (REST + WebSocket) — complete contract for
GET /v2/subscribers/me/auctions/:auctionIdand the realtime events that keep the auction screen in sync - Auction lifecycle control — start / stop / pause / resume for the company & admin consoles
- Auction participant presence and counts — durable participants, live online presence, modal reconciliation, and reconnects
- WebSocket frontend integration
- WebSocket auction events
- Auction & prebid contract
- Auction lifecycle configuration
- Starting an auction
1. Authentication¶
REST¶
All auction endpoints require an authenticated user with the appropriate role:
Route-level role guards:
| Role | Scope |
|---|---|
| SUBSCRIBER | GET /v2/subscribers/me/** routes |
| COMPANY | GET/PATCH/POST/DELETE /v2/companies/** console routes |
| SUPERADMIN | GET/PATCH/POST/DELETE /v2/admin/** console routes |
WebSocket¶
The realtime room uses a single endpoint for every role:
Native/mobile clients send the access token as an HTTP header:
Browser clients cannot set custom WebSocket headers, so send the token through Sec-WebSocket-Protocol:
Invalid or missing tokens close the socket with close code 1008.
2. Common REST Envelope¶
Every REST response uses the same envelope:
{
"status": "success",
"code": 200,
"data": {},
"message": "Auctions fetched successfully.",
"error": null
}
List collections follow the standard pagination shape:
{
"count": 42,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 5,
"hasPreviousPage": false,
"hasNextPage": true
}
Query parameters for pagination are pageNumber (min 1) and pageSize (min 1, max 100).
3. Auction Lifecycle and Phases¶
Auction status (auctionStatus)¶
PENDING — auction created, not yet scheduled
SCHEDULED — prebid window open/closed, live auction not yet started
READY — scheduled and approved (only when approval is required)
LIVE — live bidding in progress
PAUSED — live auction paused by staff
ENDED — auction closed (winner selection / reveal may still happen)
COMPLETE — auction fully settled
CANCELLED — auction cancelled
Prebid phase (prebidPhase)¶
Derived server-side from the prebid window state. Use it to drive prebid-related UI; live / paused / ended state comes from auctionStatus:
NONE — no prebid configured for this program
OPEN — subscribers can submit/cancel prebids
CLOSED — prebid window closed; prebids locked
prebidPhase replaces the old availabilityPhase. PREBID_OPEN is now OPEN; PREBID_CLOSED_WAITING_AUCTION / AWAITING_AUCTION are now CLOSED; an auction with no prebid is NONE. LIVE / PAUSED / ENDED are auctionStatus values now, not phase values.
Approval (approvalRequired)¶
Resolved from the company/platform policy (see §5.13). When approvalRequired is true, the auction must be approved (§5.6) before it can start — approval moves it from SCHEDULED to READY. The default is false, which skips the approval step.
Review status is removed¶
The old console-only review-status field is gone. Its states now map onto the status/phase model above: a pending-start auction is SCHEDULED (or READY when approval is required), a closed prebid window is prebidPhase === 'CLOSED', live bidding is auctionStatus === 'LIVE', and post-auction winner review is auctionStatus === 'ENDED'.
Settings (settings)¶
Shared response shape across subscriber and console payloads:
| Field | Type | Notes |
|---|---|---|
hasPrebid | boolean | Prebid enabled for this program |
auctionBidMode | string | DISCOUNT_INCREASING | TOTAL_VALUE_DECREASING |
auctionClosingMode | string | DURATION_MODE | CALL_MODE |
auctionDurationSeconds | number? | Default duration when scheduling |
auctionExtensionSeconds | number | Auto-extension after a late bid |
auctionFirstCallSeconds | number | Closing-call timing (call mode) |
auctionSecondCallSeconds | number | Closing-call timing (call mode) |
auctionThirdCallSeconds | number | Closing-call timing (call mode) |
subscriberJoinGraceSeconds | number | Join grace after live start |
subscriberJoinLeadSeconds | number | Join-window lead time before auction start |
allowStaffOfflineBids | boolean | Staff may bid on behalf of floor users |
prebidMutabilityPolicy | string | IMMUTABLE_CANCEL_ONLY | MUTABLE_UNTIL_CLOSE |
auctionJoiningRule | string | BEFORE_START | GRACE_PERIOD | ANYTIME |
4. Subscriber REST API¶
All routes below require a SUBSCRIBER token.
4.1 List subscriber auctions¶
Query:
| Param | Type | Default | Notes |
|---|---|---|---|
pageNumber | number | 1 | |
pageSize | number | 10 | max 100 |
search | string | — | optional free-text search |
Response data:
{
"count": 2,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 1,
"hasPreviousPage": false,
"hasNextPage": false,
"auctions": [
{
"auctionId": "auction-id",
"cycleId": "cycle-id",
"enrolledSubscriberId": "enrolled-id",
"programId": "program-id",
"programName": "Monthly Chit",
"chitId": "chit-id",
"companyId": "company-id",
"companyName": "Dichit",
"companyAvatar": "https://...",
"companyBranch": "Kochi",
"cycleNumber": 3,
"cycleStatus": "ACTIVE",
"auctionStatus": "SCHEDULED",
"prebidPhase": "OPEN",
"prebidEndAt": "2026-08-10T18:30:00.000Z",
"auctionStartAt": "2026-08-11T10:00:00.000Z",
"auctionDurationSeconds": 1800,
"auctionEndAt": "2026-08-11T10:30:00.000Z",
"minimumBidAmount": 25000,
"totalAmount": 1000000,
"leadingBidAmount": null,
"invoiceStatus": null
}
]
}
leadingBidAmount is live-bid-only: it is null before the live auction starts, and when the auction has a prebid it stays null until the auction ends / prebid amounts are revealed — the auction opens with no prebid-derived base amount (see §7 Amount visibility).
The cycle lists on GET /v2/subscribers/me/programs/:programId/cycles and GET /v2/companies/programs/:programId/cycles now include auctionId on each cycle item, so you can deep-link from a cycle to its auction.
4.2 Get subscriber auction detail¶
Query:
| Param | Type | Default | Notes |
|---|---|---|---|
participantPageNumber | number | 1 | pagination for the participants list |
participantPageSize | number | 10 | max 100, pagination for participants |
Response data:
{
"auctionId": "auction-id",
"cycleId": "cycle-id",
"cycleNumber": 3,
"cycleStatus": "ACTIVE",
"programId": "program-id",
"programName": "Monthly Chit",
"chitId": "chit-id",
"companyId": "company-id",
"companyName": "Dichit",
"companyAvatar": "https://...",
"companyBranch": "Kochi",
"totalAmount": 1000000,
"auctionStatus": "SCHEDULED",
"prebidPhase": "OPEN",
"prebidEndAt": "2026-08-10T18:30:00.000Z",
"auctionStartAt": "2026-08-11T10:00:00.000Z",
"auctionEndAt": "2026-08-11T10:30:00.000Z",
"auctionDurationSeconds": 1800,
"prebidAmountsRevealed": false,
"prebidAmountsRevealedAt": null,
"leadingBidAmount": null,
"minimumBidAmount": 25000,
"settings": {
"hasPrebid": true,
"auctionBidMode": "TOTAL_VALUE_DECREASING",
"auctionClosingMode": "DURATION_MODE",
"auctionDurationSeconds": 1800,
"auctionExtensionSeconds": 120,
"auctionFirstCallSeconds": 30,
"auctionSecondCallSeconds": 20,
"auctionThirdCallSeconds": 10,
"subscriberJoinGraceSeconds": 0,
"subscriberJoinLeadSeconds": 600,
"allowStaffOfflineBids": true,
"prebidMutabilityPolicy": "MUTABLE_UNTIL_CLOSE",
"auctionJoiningRule": "BEFORE_START"
},
"liveBids": [
{
"id": "bid-id",
"enrolledSubscriberId": "enrolled-id",
"subscriberName": "Subscriber One",
"subscriberAvatar": "https://...",
"amount": 25000,
"createdAt": "2026-08-11T10:05:00.000Z"
}
],
"announcements": [
{
"id": "announcement-id",
"auctionId": "auction-id",
"cycleId": "cycle-id",
"actorId": null,
"message": "Auction is live",
"tone": "INFO",
"createdAt": "2026-08-11T10:00:00.000Z"
}
],
"audience": {
"viewerCount": 4,
"joinedParticipantCount": 12,
"presentParticipantCount": 8,
"bidderCount": 3,
"participants": {
"count": 12,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 2,
"hasPreviousPage": false,
"hasNextPage": true,
"items": [
{
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrolled-id",
"name": "Subscriber One",
"avatar": "https://...",
"joinSource": "ONLINE",
"presenceStatus": "PRESENT"
}
]
}
},
"enrollments": [
{
"enrolledSubscriberId": "enrolled-id",
"invoiceStatus": null,
"currentUser": {
"canViewDetails": true,
"canSubscribeRealtime": true,
"isPrebidDisqualified": false,
"isLiveBidDisqualified": false,
"canPrebid": false,
"prebidBlockedReason": "AUCTION_NOT_ACCEPTING_PREBIDS",
"canBid": true,
"hasAlreadyWonProgram": false,
"isPaymentCompletedForCycle": true
},
"liveBids": [
{
"id": "bid-id",
"amount": 25000,
"status": "LEADING",
"createdAt": "2026-08-11T10:05:00.000Z"
}
],
"prebids": [
{
"id": "prebid-id",
"amount": 25000,
"status": "ACTIVE",
"createdAt": "2026-08-10T12:00:00.000Z"
}
]
}
],
"winners": [
{
"id": "winner-id",
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrolled-id",
"subscriberName": "Subscriber One",
"subscriberAvatar": "https://...",
"amount": 25000,
"division": 1,
"status": "DECLARED",
"type": "AUCTION",
"selectionSource": "PREBID_REVIEW",
"auctionBidId": null,
"auctionPrebidId": "prebid-id",
"selectedAt": "2026-08-11T10:05:00.000Z"
}
]
}
Notes:
participantsis paginated — it is an object withitems, not a plain array.sourceisONLINEorFLOOR(AuctionParticipationSource);isOnlinereflects live presence.liveBidslists live bids the subscriber may see. Prebid entries are excluded until reveal.enrollments[].liveBidsare the subscriber's own live bids (renamed frombids);enrollments[].prebidsare the subscriber's own prebids with their currentstatus.winnersis populated only onceauctionStatus === 'ENDED'and lists the cycle's declared winners (disqualified winners are excluded). Prebid-sourced winner amounts arenulluntil prebid amounts are revealed.enrollments[].currentUserdrives detail, realtime, prebid, join, and live-bid controls. Use the server booleans and their blocked reasons directly.
4.3 Get subscriber auction overview (per cycle enrollment)¶
This is the primary screen payload for the auction room screen. Response data:
{
"auctionId": "auction-id",
"cycleId": "cycle-id",
"programId": "program-id",
"companyId": "company-id",
"stateVersion": 7,
"cycleNumber": 3,
"cycleStatus": "ACTIVE",
"auctionStatus": "LIVE",
"programBidType": "AUCTION",
"minimumBidAmount": 25000,
"totalAmount": 1000000,
"leadingBid": {
"id": "bid-id",
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrolled-id",
"amount": 25000,
"createdAt": "2026-08-11T10:05:00.000Z"
},
"bidCount": 3,
"activePrebidCount": 5,
"prebidAmountsRevealed": false,
"prebidAmountsRevealedAt": null,
"prebidStartAt": "2026-08-05T10:00:00.000Z",
"prebidEndAt": "2026-08-10T18:30:00.000Z",
"auctionStartAt": "2026-08-11T10:00:00.000Z",
"auctionEndAt": "2026-08-11T10:30:00.000Z",
"minimumBidReachedAt": null,
"startedAt": "2026-08-11T10:00:00.000Z",
"pausedAt": null,
"endedAt": null,
"auctionDurationSeconds": 1800,
"prebidPhase": "CLOSED",
"settings": {},
"auctionBaseAmount": 1000000,
"liveBids": [
{
"id": "bid-id",
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrolled-id",
"subscriberName": "Subscriber One",
"subscriberAvatar": "https://...",
"amount": 25000,
"type": "LIVE",
"createdAt": "2026-08-11T10:05:00.000Z"
}
],
"currentUser": {
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrolled-id",
"isLeadingBidder": false,
"isPaymentCompletedForCycle": true,
"hasAlreadyWonProgram": false,
"canPrebid": false,
"prebidBlockedReason": "ACTIVE_PREBID_EXISTS",
"isPrebidDisqualified": false,
"isLiveBidDisqualified": false,
"activePrebid": {
"id": "prebid-id",
"amount": 30000,
"createdAt": "2026-08-05T10:30:00.000Z",
"updatedAt": "2026-08-06T09:00:00.000Z",
"documentSubmissionId": "doc-id",
"documentSubmissionStatus": "GENERATED",
"generatedPdfUrl": "https://..."
},
"latestBid": {
"id": "bid-id",
"amount": 25000,
"createdAt": "2026-08-11T10:05:00.000Z",
"disqualifiedAt": null,
"disqualificationReasonCode": null,
"deletionReason": null,
"deletedAt": null
},
"latestPrebid": {
"id": "prebid-id",
"amount": 30000,
"status": "ACTIVE",
"createdAt": "2026-08-05T10:30:00.000Z",
"updatedAt": "2026-08-06T09:00:00.000Z",
"documentSubmissionId": "doc-id",
"documentSubmissionStatus": "GENERATED",
"generatedPdfUrl": "https://...",
"disqualifiedAt": null,
"disqualificationReasonCode": null,
"deletionReason": null,
"deletedAt": null
},
"disqualifications": []
}
}
programBidType values: AUCTION / AUCTION_AND_LOT / FIXED_PRICE / LOT_ONLY. leadingBid.amount is present once the auction has a leading live bid; prebid-origin amounts are only exposed via prebids / winners after prebid amounts are revealed.
4.4 Prebid form configuration¶
The dedicated form endpoint was removed together with the legacy cycle-keyed API. Form configuration now comes from the auction detail response (§4.3):
prebidDocumentRequiredandprebidDocumentTemplateVersionId— whether a prebid document is needed and which template version to render.settings.prebidMutabilityPolicy— whether the submitted prebid can be edited (MUTABLE_UNTIL_CLOSE) or only cancelled (IMMUTABLE_CANCEL_ONLY).- Resolve the actual template fields via §4.9 (get template version by id) when a template version is configured.
fieldType values: TEXT, CURRENCY_AMOUNT, SIGNATURE_IMAGE.
4.5 Prebid state (current prebid)¶
There is no dedicated GET for the subscriber's prebid; read it from the auction detail response (§4.3): latestPrebid (current user's own prebid) and enrollments[].prebids[] (per enrollment). latestPrebid is null when the subscriber has no prebid. status values: ACTIVE, CANCELLED, DISQUALIFIED, APPLIED, SUPERSEDED.
4.6 Place prebid¶
{
"enrolledSubscriberId": "enrolled-subscriber-id",
"amount": 30000,
"signatureAssetId": "asset-id"
}
enrolledSubscriberId is required and selects the enrollment the prebid is placed for. signatureAssetId is required only when the prebid form requires a signature document. Creating a prebid also creates/queues the prebid document submission.
Response:
{
"auctionId": "auction-id",
"cycleId": "cycle-id",
"prebidId": "prebid-id",
"enrolledSubscriberId": "enrolled-subscriber-id",
"status": "ACTIVE",
"documentSubmissionId": "doc-id"
}
Placement allowed only while prebidPhase === 'OPEN'. Only one active prebid is allowed per enrollment — placing again while one is active is rejected; cancel first.
4.7 Edit prebid¶
Body is the same as place (enrolledSubscriberId, amount, optional signatureAssetId). Allowed only while the prebid window is open and settings.prebidMutabilityPolicy === 'MUTABLE_UNTIL_CLOSE'. When the policy is IMMUTABLE_CANCEL_ONLY, updating the amount after creation is rejected; the subscriber must cancel and re-submit.
Response mirrors the place response with status: "ACTIVE".
4.8 Cancel prebid¶
DELETE /v2/subscribers/me/auctions/:auctionId/prebid/:prebidId?enrolledSubscriberId=enrolled-subscriber-id
Response mirrors the place response with status: "CANCELLED". Cancellation is allowed under both mutability policies while the prebid window is open.
4.9 Get a published template version by id¶
Available to any authenticated role (SUBSCRIBER / COMPANY / SUPERADMIN). Resolves a published, active template version from its version id and returns the same template shape used by the prebid form (§4.4) — useful when the app only holds a prebidDocumentTemplateVersionId (e.g. from the auction detail) and needs to render the form without the cycle/program context:
{
"templateId": "template-id",
"templateKey": "chit-prebid",
"templateName": "Prebid Form",
"templateDescription": null,
"templateVersionId": "version-id",
"templateVersion": 1,
"fileName": "prebid.pdf",
"fields": [
{
"fieldKey": "amount",
"label": "Bid Amount",
"fieldType": "CURRENCY_AMOUNT",
"required": true,
"maxLength": null,
"sortOrder": 1
}
]
}
Returns 404 when the version does not exist or is not a published version of an active template (drafts are never exposed).
5. Company / Admin Console REST API¶
Company and superadmin consoles share the same route handlers under two mounts:
GET|PATCH|POST|DELETE /v2/companies/auctions/:auctionId/...(COMPANY role)GET|PATCH|POST|DELETE /v2/admin/auctions/:auctionId/...(SUPERADMIN role)
Company users can only access auctions owned by their company; the server enforces ownership on every call.
5.1 Get auction console overview¶
Query — three independent pagination groups:
| Param | Default | Notes |
|---|---|---|
bidPageNumber | 1 | pagination for liveBids |
bidPageSize | 10 | max 100 |
prebidPageNumber | 1 | pagination for prebids |
prebidPageSize | 10 | max 100 |
participantPageNumber | 1 | pagination for participants |
participantPageSize | 10 | max 100 |
Response data (highlights; full field set in AdminAuctionResponseSchema):
{
"auctionId": "auction-id",
"cycleId": "cycle-id",
"stateVersion": 7,
"cycleNumber": 3,
"cycleStatus": "ACTIVE",
"auctionStatus": "SCHEDULED",
"programId": "program-id",
"companyId": "company-id",
"programBidType": "AUCTION",
"auctionBaseAmount": null,
"minimumBidAmount": 25000,
"totalAmount": 1000000,
"leadingBid": null,
"bidCount": 0,
"activePrebidCount": 5,
"prebidAmountsRevealed": false,
"prebidAmountsRevealedAt": null,
"settings": {},
"prebidStartAt": "2026-08-05T10:00:00.000Z",
"prebidEndAt": "2026-08-10T18:30:00.000Z",
"auctionStartAt": "2026-08-11T10:00:00.000Z",
"auctionDurationSeconds": 1800,
"auctionEndAt": "2026-08-11T10:30:00.000Z",
"prebidPhase": "CLOSED",
"programDivisions": 1,
"declaredWinnerCount": 0,
"remainingWinnerSlots": 1,
"minimumBidReachedAt": null,
"startedAt": null,
"pausedAt": null,
"endedAt": null,
"winnerId": null,
"winners": [],
"participants": {
"count": 12,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 2,
"hasPreviousPage": false,
"hasNextPage": true,
"items": [
{
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrolled-id",
"name": "Subscriber One",
"avatar": "https://...",
"mobileNumber": "+919000002001",
"countryCode": "+91",
"invoiceStatus": null,
"isPaymentCompletedForCycle": true,
"isOnline": false,
"source": "FLOOR",
"hasBid": false,
"hasPrebid": true,
"latestBidAmount": null,
"isDisqualified": false,
"hasWonCycle": false,
"wonCycleNumber": null
}
]
},
"announcements": [],
"realtimeStats": {
"bidVelocityPerMinute": 0,
"averageBidIntervalSeconds": null,
"uniqueBidders": 0,
"totalBids": 0
},
"liveBids": {
"count": 0,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 0,
"hasPreviousPage": false,
"hasNextPage": false,
"items": []
},
"prebids": {
"count": 5,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 1,
"hasPreviousPage": false,
"hasNextPage": false,
"items": [
{
"id": "prebid-id",
"auctionId": "auction-id",
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrolled-id",
"documentSubmissionId": "doc-id",
"documentSubmissionStatus": "GENERATED",
"generatedPdfUrl": "https://...",
"subscriberName": "Subscriber One",
"subscriberAvatar": null,
"amount": null,
"status": "ACTIVE",
"createdAt": "2026-08-05T10:30:00.000Z",
"updatedAt": "2026-08-06T09:00:00.000Z"
}
]
}
}
Console-specific notes:
winnersis only populated whenauctionStatus === 'ENDED'(orCOMPLETE); it is an empty array at every other status. This field replaces the oldcurrentWinner/declaredWinnerspair.realtimeStatsisnullbefore the auction reachesLIVE/PAUSED/ENDEDand is otherwise an object withbidVelocityPerMinute,averageBidIntervalSeconds,uniqueBidders,totalBids.participants,liveBids, andprebidsare all paginated objects withitems.- Hidden prebid amounts come back as
nulluntil reveal (see §7 Amount visibility).
5.2 Eligible winner candidates¶
GET /v2/companies/auctions/:auctionId/eligible-winner-candidates
GET /v2/admin/auctions/:auctionId/eligible-winner-candidates
Query: pageNumber, pageSize.
Response data:
{
"cycleId": "cycle-id",
"programId": "program-id",
"eligibleWinnerCandidates": {
"count": 3,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 1,
"hasPreviousPage": false,
"hasNextPage": false,
"items": [
{
"enrolledSubscriberId": "enrolled-id",
"subscriberId": "subscriber-id",
"programId": "program-id",
"name": "Subscriber One",
"avatar": null,
"email": "sub@example.com",
"mobileNumber": "+919000002001"
}
]
}
}
5.3 Auction action logs¶
Response data:
{
"auctionId": "auction-id",
"cycleId": "cycle-id",
"actionLogs": [
{
"id": "log-id",
"auctionId": "auction-id",
"cycleId": "cycle-id",
"actorId": "user-id",
"action": "SCHEDULE",
"reason": null,
"metadata": {},
"createdAt": "2026-08-05T10:00:00.000Z"
}
]
}
action values: SCHEDULE, RESCHEDULE, START, AUTO_START, PAUSE, RESUME, END, AUTO_END, DECLARE_WINNER, DISQUALIFY_WINNER, REPLACE_DISQUALIFIED_WINNER, RECORD_LOT, APPROVE, CANCEL, DELETE_BID, DELETE_PREBID, DISQUALIFY_BIDDER, REVEAL_PREBIDS, UPDATE_SETTINGS, STAFF_OFFLINE_BID.
5.4 Auction audience audit¶
GET /v2/companies/auctions/:auctionId/audience-sessions
GET /v2/admin/auctions/:auctionId/audience-sessions
Query:
| Param | Type | Notes |
|---|---|---|
enrolledSubscriberId | string | optional enrollment filter |
accessMode | string | VIEWER | PARTICIPANT |
activeOnly | boolean | only sessions without a disconnect |
pageNumber / pageSize | number | standard pagination |
Response data:
{
"auctionId": "auction-id",
"cycleId": "cycle-id",
"count": 2,
"participationCount": 1,
"pageNumber": 1,
"pageSize": 10,
"totalPages": 1,
"hasPreviousPage": false,
"hasNextPage": false,
"participations": [
{
"id": "participant-id",
"auctionId": "auction-id",
"cycleId": "cycle-id",
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrolled-id",
"source": "ONLINE",
"joinWindowVersion": 1,
"joinedAt": "2026-08-11T10:00:05.000Z"
}
],
"sessions": [
{
"id": "session-id",
"connectionId": "conn-id",
"initialAccessMode": "VIEWER",
"currentAccessMode": "PARTICIPANT",
"connectedAt": "2026-08-11T10:00:00.000Z",
"lastSeenAt": "2026-08-11T10:05:00.000Z",
"becameParticipantAt": "2026-08-11T10:00:05.000Z",
"disconnectedAt": null,
"disconnectReason": null
}
]
}
Disconnect reasons are UNSUBSCRIBE, SOCKET_CLOSE, HEARTBEAT_EXPIRED, AUCTION_ENDED, and SERVER_SHUTDOWN. Company responses omit IP address and user agent; those fields are available only to super-admin audit access.
5.5 Schedule auction¶
{
"auctionStartAt": "2026-08-11T10:00:00.000Z",
"auctionDurationSeconds": 1800,
"reason": "Schedule for cycle 3",
"auctionBidMode": "TOTAL_VALUE_DECREASING",
"auctionClosingMode": "DURATION_MODE",
"auctionExtensionSeconds": 120,
"auctionFirstCallSeconds": 30,
"auctionSecondCallSeconds": 20,
"auctionThirdCallSeconds": 10,
"subscriberJoinGraceSeconds": 0,
"allowStaffOfflineBids": true,
"prebidMutabilityPolicy": "MUTABLE_UNTIL_CLOSE",
"auctionJoiningRule": "BEFORE_START"
}
auctionStartAt and auctionDurationSeconds are required; all settings are optional and override the program defaults for this cycle. The start time must be in the future. For immediate start, send the WebSocket staff command auction.status.update with { "status": "START" }.
Response data: auctionId, cycleId, stateVersion, status, prebidStartAt, prebidEndAt, auctionStartAt, auctionDurationSeconds, auctionEndAt, prebidPhase.
5.6 Approve auction (approval-required policy only)¶
Only auctions whose resolved settings enable approvalRequired need this step. The request is rejected with 400 when approvalRequired is disabled ("This auction does not require approval. Approving an auction without the approval-required policy is not allowed.") or when the auction is not SCHEDULED. When enabled, approving sets auctionStatus to READY and writes an APPROVE action log entry. Response data: { auctionId, cycleId, status: "READY", approvalRequired, auctionStartAt }.
An auction whose window is closed does not require amount review — prebids are sealed and proceed to the live round directly. When approvalRequired is disabled (the default), no approval is needed: the auction starts directly from SCHEDULED via the WebSocket start command (or its scheduled auto-start).
5.7 Reveal prebid amounts¶
POST /v2/companies/auctions/:auctionId/prebids/reveal
POST /v2/admin/auctions/:auctionId/prebids/reveal
Only allowed when the auction is ENDED and hasPrebid is true. Idempotent — repeating the call after a reveal is a no-op. After reveal the server sets prebidAmountsRevealed: true, prebidAmountsRevealedAt, logs REVEAL_PREBIDS, and publishes auction.state.changed with payload.transition: "prebids_revealed".
The removed cycle-scoped reveal URLs under .../cycles/:cycleId/auction/prebids/reveal should not be used by new clients.
5.8 Replace winner¶
There is no PATCH /winner endpoint. To replace a declared winner, disqualify the current one via POST .../winner/disqualify (§5.9) and then declare the next candidate via the WebSocket auction.winner.declare command (mode CANDIDATE with a different bidId / prebidId).
5.9 Disqualify winner¶
POST /v2/companies/auctions/:auctionId/winner/disqualify
POST /v2/admin/auctions/:auctionId/winner/disqualify
{
"winnerId": "winner-id",
"disqualificationReasonCode": "PAYMENT_ISSUE",
"disqualificationNote": "Payment not received",
"reason": "Disqualify winner"
}
disqualificationReasonCode values: KYC_ISSUE, PAYMENT_ISSUE, ELIGIBILITY_ISSUE, RULE_VIOLATION, OTHER.
5.10 Delete prebid¶
DELETE /v2/companies/auctions/:auctionId/prebids/:prebidId
DELETE /v2/admin/auctions/:auctionId/prebids/:prebidId
Deletes a specific prebid (disqualification/review workflow). No body.
5.11 Manual prebid window open/close¶
Opens or closes the prebid window before the live auction. The live auction can only start once the window is closed.
POST /v2/companies/auctions/:auctionId/prebid/open
POST /v2/admin/auctions/:auctionId/prebid/open
POST /v2/companies/auctions/:auctionId/prebid/close
POST /v2/admin/auctions/:auctionId/prebid/close
Responses carry prebidStartAt / prebidEndAt and a boolean prebidOpened / prebidClosed (false when the action was an idempotent no-op). Opening requires the window to still be naturally open; closing pins prebidEndAt to now (deriving prebidPhase to CLOSED) and clears any pending auto-open job.
5.12 Mutation response envelope¶
POST/PATCH console mutations return a shared data envelope; fields are null unless relevant to the action:
{
"auctionId": "auction-id",
"cycleId": "cycle-id",
"stateVersion": 8,
"status": "LIVE",
"approvalRequired": false,
"leadingBidId": "bid-id",
"leadingBidAmount": 25000,
"prebidAmountsRevealed": false,
"prebidAmountsRevealedAt": null,
"prebidAmountsRevealedById": null,
"prebidStartAt": "2026-08-05T10:00:00.000Z",
"prebidEndAt": "2026-08-10T18:30:00.000Z",
"prebidOpened": null,
"prebidClosed": null,
"bidId": null,
"prebidId": null,
"disqualificationId": null,
"enrolledSubscriberId": null,
"phase": null,
"winnerId": null,
"winningBidId": null,
"division": null
}
Use stateVersion to detect stale console state; the realtime stream is the authoritative source for live updates.
5.13 Get resolved company auction policy¶
Reads the effective auction settings a new program created by this company would inherit before any program exists, folding default < platform < company. Company overrides win, a key the company never set falls back to the platform policy, and anything still unset falls back to the server default. Use it to prefill the auction setup step of program creation.
| Role | Endpoint |
|---|---|
COMPANY | GET /v2/companies/company-auction-policy/resolved |
SUPERADMIN | GET /v2/admin/companies/:companyId/company-auction-policy/resolved |
Company callers are scoped to their own tenant; the admin mount targets any company by path. A missing company returns 404.
Response data:
{
"companyId": "company-id",
"settings": {
"hasPrebid": true,
"auctionBidMode": "TOTAL_VALUE_DECREASING",
"auctionClosingMode": "DURATION_MODE",
"auctionDurationSeconds": 1800,
"auctionExtensionSeconds": 120,
"auctionFirstCallSeconds": 15,
"auctionSecondCallSeconds": 15,
"auctionThirdCallSeconds": 15,
"subscriberJoinGraceSeconds": 0,
"allowStaffOfflineBids": true,
"prebidMutabilityPolicy": "MUTABLE_UNTIL_CLOSE",
"auctionJoiningRule": "BEFORE_START"
},
"allowedSecurities": ["GOLD_ORNAMENTS"],
"triggerPolicy": {
"autoStart": false,
"approvalRequired": false,
"autoOpenPrebid": false,
"autoRevealPrebids": false,
"autoDeclareWinner": false
},
"provenance": {
"hasPrebid": "company",
"auctionBidMode": "default",
"auctionClosingMode": "default",
"auctionDurationSeconds": "company",
"auctionExtensionSeconds": "platform",
"auctionFirstCallSeconds": "default",
"auctionSecondCallSeconds": "default",
"auctionThirdCallSeconds": "default",
"subscriberJoinGraceSeconds": "default",
"allowStaffOfflineBids": "default",
"prebidMutabilityPolicy": "default",
"auctionJoiningRule": "default",
"allowedSecurities": "company",
"autoStart": "default",
"approvalRequired": "default",
"autoOpenPrebid": "default",
"autoRevealPrebids": "default",
"autoDeclareWinner": "default"
}
}
provenance maps every resolved key to its source (default, platform, or company) — use it to tell staff where a prefill value comes from. When no program/draft exists yet, this is the correct endpoint; once a draft program has an id, prefer 5.13's program-scoped sibling GET /v2/companies/programs/:programId/settings, which additionally folds the preset and program layers and surfaces programName plus the prebid document template when the program requires one.
6. Staff WebSocket Bid / Winner Commands¶
The live auction console acts over WebSocket (auction:staff:<auctionId> room) for fast actions. The same actions are available as HTTP console mutations above; the WebSocket variants are documented in §8.
7. Prebid Amount Visibility¶
Server-side logic always uses real prebid amounts, but client-facing responses redact them until the completed auction is revealed.
Before reveal:
- Console
prebidsrows and winner candidates returnamount: nullwhen the amount is prebid-derived. - Subscribers can see their own
activePrebid/latestPrebidamount. leadingBidsourced from a prebid returnsamount: nulluntil a live bid replaces it.- Realtime
auction.prebid.createdis staff-only. Itsamountisnullwhile prebids are sealed; clients must still treat the field as nullable after reveal. - Subscriber-visible bid history omits prebid entries.
After reveal (prebidAmountsRevealed: true):
- All auction payloads may include prebid-derived amounts.
- The console reveals the amounts in the
prebidslist and winner payloads.
When hasPrebid is false, normal live-bid amount behavior is unchanged.
8. WebSocket Protocol¶
8.1 Connection¶
Authenticate per §1 Authentication. Both subscriber and staff roles share this endpoint; authorization happens per message.
8.2 Message envelope¶
Client → server:
{
"id": "request-id",
"type": "auction.subscribe",
"version": "1.0",
"source": "web-client",
"timestamp": "2026-08-11T10:00:00.000Z",
"data": {},
"meta": { "traceId": "client-trace-id" }
}
Rules:
typeis required and must be a registered event.datais validated per type.idis optional but recommended for correlating acks/errors.version,source,timestamp,metaare informational.
Server → client frames add:
version— always"1.0".sequence— monotonic counter per connection; a gap means frames were dropped → recover via snapshot + replay.meta.traceId— echoes the client trace id when sent.
8.3 Query vs command¶
- Sync query (e.g.
auction.subscribe,auction.unsubscribe) returns a singleackframe with the handler result. - Async command (e.g.
auction.bid.place, all staff commands) returns an immediatecommand.acknowledged, then business results arrive as pushed auction events or command result frames.
8.4 Subscribe (subscriber)¶
{
"id": "subscribe-001",
"type": "auction.subscribe",
"data": {
"auctionId": "auction-id",
"enrolledSubscriberId": "enrolled-id"
}
}
The server authorizes an active enrollment for viewer access, joins the auction:<auctionId> room, opens a per-connection audience session, and then sends, in order. Payment and an open join window are not required to watch.
Breaking change:
auctionIdis now required (the auction row is guaranteed beforeREADY/LIVE). FetchauctionIdvia REST (GET /v2/.../auctions/by-cycle/:cycleIdor cycle overview) before subscribing.
ack frame:
{
"id": "subscribe-001",
"type": "ack",
"version": "1.0",
"source": "dichit-backend",
"timestamp": "2026-08-11T10:00:01.000Z",
"sequence": 1,
"data": {
"eventType": "auction.subscribe",
"auctionId": "auction-id",
"cycleId": "cycle-id",
"room": "auction:auction-id"
},
"meta": { "traceId": "client-trace-id" }
}
8.5 Subscribe (company / admin)¶
Staff omit enrolledSubscriberId:
Staff are additionally joined to auction:staff:<auctionId>, and the ack data includes staffRoom. Staff snapshots include the full room (all subscribers, bids, action logs, announcements).
8.6 Resume with replay¶
{
"id": "subscribe-002",
"type": "auction.subscribe",
"data": {
"auctionId": "auction-id",
"enrolledSubscriberId": "enrolled-id",
"lastSequence": 42
}
}
lastSequence is the auction-level high-water cursor from the last processed durable data.serverSequence (bids, lifecycle, join-window, participation, prebid, and winner events as permitted for the caller). Replay is role-, scope-, and target-filtered, so visible sequence values can be sparse. When replay data is available it is merged into the auction.state.snapshot payload under a replay key; advance to replay.lastSequence even when no visible event uses that exact sequence.
8.7 Initial events¶
auction.connected:
{
"type": "auction.connected",
"source": "auction-service",
"timestamp": "2026-08-11T10:00:01.000Z",
"data": {
"auctionId": "auction-id",
"cycleId": "cycle-id",
"stateVersion": 7,
"payload": {
"connectionId": "conn-id",
"heartbeatIntervalMs": 30000
}
}
}
auction.state.snapshot — the payload is the full auction state:
- Subscriber:
{ auction, currentUser, viewerCount, joinedParticipantCount, onlineParticipantCount, bidderCount, recentBids, lastSequence } - Staff:
{ auction, allSubscribers, onlineSubscribers, viewerCount, joinedParticipantCount, onlineParticipantCount, bidderCount, recentBids, actionLogs, announcements, lastSequence }
auction.audience.snapshot:
{
"type": "auction.audience.snapshot",
"data": {
"auctionId": "auction-id",
"cycleId": "cycle-id",
"payload": {
"viewerCount": 4,
"joinedParticipantCount": 3,
"onlineParticipantCount": 2,
"bidderCount": 1
}
}
}
8.8 Client → server commands¶
Subscriber commands:
Join an auction after payment while the join window is open:
{
"id": "join-001",
"type": "auction.participation.join",
"data": {
"auctionId": "auction-id",
"enrolledSubscriberId": "enrolled-id"
}
}
The frame id is required and is the idempotency key. A successful terminal ack returns accepted or replayed, participantId, joinedAt, and joinWindowVersion. Once admitted, the subscriber may bid until the auction ends, including after the join window closes and after reconnecting.
Place a bid after admission:
{
"id": "bid-001",
"type": "auction.bid.place",
"data": {
"auctionId": "auction-id",
"enrolledSubscriberId": "enrolled-id",
"amountMinor": "2500000"
}
}
amountMinoris required and is always a positive integer string in minor currency units. Never send floating-point money.- The WebSocket frame
idis the bid command id. Reuse the sameidwhen retrying the same bid; retries return the original result and do not create duplicate bids. - The payload is strict. Do not send client timestamps, auction versions, current bid, previous bid, or ordering state.
- The response is terminal:
ack.data.statusisacceptedorreplayed. Rejections use the correlatederrorenvelope. - Rate limit: 100 messages / 60 s for
auction.bid.place. - Subscriber self-bids become
origin: "ONLINE". Staff/floor bids use the sameauction.bid.placecommand with a targetenrolledSubscriberId; the server derivesorigin: "FLOOR"from the authenticated actor and persists the actual staffplacedByUserId.
ack data: { eventType: "auction.unsubscribe", auctionId, unsubscribed: true }. Unsubscribing removes the room membership and closes that connection's audience session with disconnectReason: "UNSUBSCRIBE". Durable participation remains.
Staff commands (all permissions: ["auction:staff"], all async):
| Type | Payload |
|---|---|
auction.status.update | { auctionId, status: "START" } |
auction.pause | { auctionId, reason } |
auction.resume | { auctionId, reason } |
auction.end | { auctionId, reason } |
auction.bid.mark | { auctionId, action: "DELETE_BID" \| "DISQUALIFY_BIDDER", bidId?, enrolledSubscriberId?, reason, reasonCode? } |
auction.winner.declare | { auctionId, mode: "CANDATE" \| "MANUAL", bidId?, prebidId?, enrolledSubscriberId?, amount?, reason? } |
auction.winner.record_lot | { auctionId, winnerEnrolledSubscriberId, candidateEnrolledSubscriberIds: [≥2], reason? } |
auction.announcement.create | { auctionId, message, tone: "INFO" \| "WARNING" } |
auction.presence.floor.update | { auctionId, enrolledSubscriberId, present: boolean } |
auction.bid.mark action mapping:
DELETE_BID→ requiresbidId.DISQUALIFY_BIDDER→ requiresenrolledSubscriberId+reasonCode.
auction.winner.declare mapping:
CANDIDATE→ declare from an existing bid/prebid (bidId/prebidId).MANUAL→ requiresenrolledSubscriberId+amount.
8.9 Bid terminal responses¶
auction.bid.place returns a terminal ack after commit (accepted or replayed) or a correlated error when rejected. Staff commands remain async and continue to use command.acknowledged plus domain events.
Bid rejection codes are machine-readable: AUCTION_NOT_LIVE, AUCTION_ENDED, INVALID_AMOUNT, BID_NOT_IMPROVING, DUPLICATE_COMMAND, and SUBSCRIBER_NOT_ELIGIBLE.
Successful bid broadcasts are authoritative for the shared feed. Reconcile local state by feed serverSequence; do not order bids by client timestamps or WebSocket receipt order.
8.10 Realtime events (server → client)¶
Events are delivered to the subscribed connection as:
{
"id": "evt_01HQ5BY3Y7Z2V6Y0W5X0W5X0W5X",
"type": "auction.bid.created",
"version": "1.0",
"source": "auction-service",
"timestamp": "2026-08-11T10:05:00.000Z",
"sequence": 7,
"data": {
"auctionId": "auction-id",
"cycleId": "cycle-id",
"stateVersion": 8,
"serverSequence": 12,
"payload": {}
},
"meta": { "traceId": "server-trace-id" }
}
Event catalog:
| Event | Payload / notes |
|---|---|
auction.state.changed | Durable canonical lifecycle delta. payload is { transition, state, winner?, declaredWinnerCount?, remainingWinnerSlots?, replacementRequired? }; replace the lifecycle slice from the complete state object. |
auction.settings.updated | Settings changed |
auction.bid.created | payload is the bid activity item: { id, auctionId, cycleId, subscriberProgramId, subscriberId, bidderName, bidderAvatarUrl, bidderCode, bidAmount, bidRank, isWinningBid, status, createdAt, serverSequence, origin } where status is ACCEPTED | OUTBID and origin is ONLINE | FLOOR | AUTO |
auction.bid.updated | same bid activity item shape (previous leading bid marked OUTBID) |
auction.bid.deleted | staff deleted a bid |
auction.prebid.created | Staff-only durable prebid projection: { id, subscriberId, enrolledSubscriberId, bidderName, amount, createdAt, status, documentSubmissionId }; amount is null while sealed. |
auction.prebid.deleted | prebid removed/cancelled |
auction.announcement.created | { id, message, tone, actorId, createdAt } |
auction.announcement.updated | — |
auction.announcement.deleted | — |
auction.bidder.disqualified | bidder disqualified (prebid or live phase) |
auction.audience.changed | Ephemeral public and staff copies contain aggregate audience counts plus the affected participant projection. |
auction.join_window.opened | Durable payload { stateVersion, joinWindow }; joinWindow contains status OPEN, version, rule, scheduled/actual opening, close, and null closedAt. |
auction.join_window.closed | Durable payload { stateVersion, joinWindow, reason }; joinWindow contains status CLOSED and all timestamps. |
auction.participation.joined | Staff-only durable event for an admitted online or floor participant. |
auction.winner.changed | Staff-only durable { action, winner, suggestedReplacementCandidate? } event for winner selection/disqualification. |
Presence participant payload:
{
"subscriberId": "subscriber-id",
"enrolledSubscriberId": "enrolled-id",
"name": "Subscriber One",
"avatar": null,
"lastSeenAt": "2026-08-11T10:05:01.000Z",
"connectionCount": 2,
"source": "ONLINE"
}
Subscriber connections only receive events for their own room plus their own targeted events; staff connections receive the full room stream.
Subscriber audience events never expose identities. Durable participants come from AuctionParticipant; online/floor state is an ephemeral overlay. Multiple tabs create separate audit sessions but count once per enrollment.
8.11 Error handling¶
Dispatch, validation, or authorization failures return an error frame:
{
"id": "bid-001",
"type": "error",
"version": "1.0",
"source": "dichit-backend",
"timestamp": "2026-08-11T10:05:00.000Z",
"sequence": 6,
"data": {
"code": "FORBIDDEN",
"message": "Only subscribers can place live auction bids."
},
"meta": { "traceId": "client-trace-id" }
}
Error codes: BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, RATE_LIMITED, INTERNAL.
8.12 Rate limits¶
- Default: 120 messages / 60 s per user + IP per event type.
auction.bid.place: 100 messages / 60 s.
Rate-limited messages receive an error frame with code: "RATE_LIMITED".
8.13 Reconnection¶
On socket close:
- Open a new
/wsconnection with a fresh access token. - Re-send
auction.subscribewith the last processeddata.serverSequence. - Rebuild UI from the latest
auction.state.snapshot, apply visible replay events in ascending sequence, then advance toreplay.lastSequence.
Do not treat serverSequence > lastSequence + 1 as proof of loss. Staff-only and target-user events consume sequence values that a subscriber is not allowed to receive.
9. Frontend Screen Mapping¶
| Screen | Data source |
|---|---|
| My auctions list | GET /v2/subscribers/me/auctions |
| Auction detail / lobby | GET /v2/subscribers/me/auctions/:auctionId |
| Prebid form | Auction detail (§4.3) + GET /v2/common/templates/versions/:templateVersionId |
| Prebid form by template version | GET /v2/common/templates/versions/:templateVersionId |
| Place / edit / cancel prebid | POST / PUT / DELETE /v2/subscribers/me/auctions/:auctionId/prebid… |
| Live room (subscriber) | GET /v2/subscribers/me/cycles/:cycleId/auction/:enrolledSubscriberId + WebSocket |
| Live room (company/admin console) | GET /v2/companies\|admin/auctions/:auctionId + WebSocket |
| Winner / review screens | console endpoints in §5 |
| Reveal prebids | POST .../auctions/:auctionId/prebids/reveal |
Recommended realtime flow:
- Open
/wswith a valid access token. auction.subscribefor the active cycle.- Render
auction.state.snapshot+auction.audience.snapshot. - If
currentUser.canJoin, sendauction.participation.joinafter the user chooses to join. - Send
auction.bid.placeonly whencurrentUser.canBid; staff send staff commands. - Apply pushed events and deduplicate durable events by event ID and
serverSequence. auction.unsubscribewhen leaving the screen.- On network loss, reconnect and resubscribe with
lastSequence.
10. Migration Notes (from earlier versions)¶
- Subscriber auction detail now returns
audience: nulluntil the join window opens. Afterward, counts are visible to enrolled subscribers and the paginated participant roster is visible only to subscribers who joined. - Staff auction
participantschanged from a plain array to a paginated object{ count, pageNumber, pageSize, totalPages, hasPreviousPage, hasNextPage, items }. - Console winner payloads: the old
currentWinner+declaredWinnersfields were replaced by a singlewinnersarray that is only populated when the auction has ended. realtimeStatsisnullbefore the live phase (previously an all-zeros object).- Prebid reveal endpoints moved from
/cycles/:cycleId/auction/prebids/revealto/auctions/:auctionId/prebids/reveal(company and admin mounts). - Cycle lists (
subscriberandcompany) now includeauctionIdper cycle. - Settings now include
prebidMutabilityPolicy(IMMUTABLE_CANCEL_ONLY|MUTABLE_UNTIL_CLOSE).