Section 4: The Answer You Get Back Immediately - API Design
Executive Summary: Section 3 showed that bid writes peak at a modest 5,000 per second, so this contract was never about throughput. Its job is to tell a bidder the truth in one round trip. Five endpoints carry the product: create an auction, place a bid, read state, read history, and subscribe to a stream, plus a watch toggle. The one that matters is POST /auctions/{id}/bids: it accepts a maximum in minor units, is guarded by an Idempotency-Key, optionally carries the price the bidder was looking at, and returns a verdict of HIGH_BIDDER, OUTBID or REJECTED synchronously. Five pushbacks test each of those choices, an error table pins down what every failure means, and additive versioning keeps the oldest clients in the world, sniping tools, working.
The interviewer puts the marker down. "Show me the API." The reflex answer is a resource list: auctions, bids, watchers, users. Resist it, because one endpoint carries everything Section 1 established. A bidder does not submit a price. A bidder submits the most they are willing to pay, and the system owes them an immediate, final answer about where that leaves them.
Five Endpoints, and What Each One Refuses to Say
| Endpoint | Who | Returns | Deliberately absent |
|---|---|---|---|
POST /auctions |
Seller | auction_id, end_time, status: SCHEDULED for a future start_time, else OPEN |
Listing content, images, shipping. The Listing service owns those |
POST /auctions/{id}/bids |
Bidder | A verdict, synchronously | Anyone else's maximum |
GET /auctions/{id} |
Anyone | current_price, high bidder alias, bid_count, end_time, version, server_time (countdowns must run on the server's clock, Section 8's rule) |
high_max_amount, always |
GET /auctions/{id}/bids |
Anyone | History: price_after and a masked alias per accepted bid |
Maxes, bidder identities |
GET /auctions/{id}/stream |
Viewers | SSE or WebSocket state updates, coalesced to at most 2 per second | The bidder's own verdict, which travels on the response |
PUT / DELETE /auctions/{id}/watch |
Signed-in user | 204 |
Nothing interesting, which is the point |
Here is Elena's Thursday bid on #7731. The page showed $4,350; she decides she will go to $9,500.
POST /auctions/7731/bids
Idempotency-Key: bid_7f3a9c
{ "max_amount": 950000, "currency": "USD", "expected_price": 435000 }
200 OK
{ "outcome": "OUTBID",
"current_price": 955000, "high_bidder_is_you": false,
"bid_count": 61, "end_time": "2026-10-25T19:00:00.000-07:00",
"version": 61 }
Three details are load-bearing. Amounts are integers in minor units (950000 is $9,500.00): no float ever touches money, and an eBay-style increment table that steps in cents stays exact. The Idempotency-Key is minted by the client when the intent forms, as the Confirm button renders, and reused for every retry of that attempt. And the response is a verdict, not a receipt: Elena learns in the same round trip that a standing maximum beat her and that the price is now $9,550 because of her. What she never learns is that Marcus's maximum is $12,000.
The second tap never reaches the auction row. It also never meets the rate limiter, because the replay check runs first: a retry is a retry, not a second bid, and punishing it with a 429 would teach clients to invent fresh keys.
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson