Errors
Every V3 endpoint returns the same error envelope on any non-2xx response:
{ "error": "<code>", "message": "<human-readable description>"}429 responses add a retryAfter integer (seconds). All other fields are
optional and stable — adding a new top-level key in a future version
won’t break existing clients.
Status codes
Section titled “Status codes”| Status | error code | Meaning | What to do |
|---|---|---|---|
400 | validation_error | Your request was malformed — bad params, unknown filter token, out-of-range value. | Read message for the specific issue. Fix the request before retrying. |
401 | unauthorized | Token missing, expired, or minted for the wrong client. | Refresh the token via the SMS flow or refresh_token exchange. |
403 | captcha_failed | Captcha token missing or invalid on a gated endpoint. | Acquire a new reCAPTCHA token before retrying. |
404 | not_found | The resource doesn’t exist or isn’t visible to the caller. | Don’t retry. Check the ID. |
409 | email_exists | Registration attempted with an email that’s already registered. | Switch to the login path (/v3/auth/email-check). |
429 | rate_limited | Per-endpoint rate limit exceeded. | Honor retryAfter (seconds). Don’t retry sooner. |
500 | server_error | Unexpected failure on our side. | Treat as transient. Exponential backoff is appropriate. |
Anatomy of an error
Section titled “Anatomy of an error”A typical 400 from the notifications endpoint:
HTTP/1.1 400 Bad RequestContent-Type: application/json{ "error": "validation_error", "message": "`filter` contains unknown type \"closing_soon\". Allowed: outbid, winning, purchase, broadcast, general."}A typical 429 from the same endpoint after a burst:
HTTP/1.1 429 Too Many RequestsContent-Type: application/jsonRetry-After: 60Access-Control-Expose-Headers: Retry-After{ "error": "rate_limited", "message": "Rate limit exceeded. Retry after 60 seconds.", "retryAfter": 60}Both the header and the body field carry retryAfter. Use whichever is
easier on your client — they always match.
How to interpret message
Section titled “How to interpret message”The message field is for humans. It’s safe to render in a UI as a
toast or alert. It tells the user what went wrong in language they can act
on:
- ✅ “Auction not found.”
- ✅ “limit must be between 0 and 100.”
- ❌ Not a stack trace.
- ❌ Not a SQL error.
- ❌ Not a token or any other secret.
The error field is for your code. Branch on it for retry logic,
custom UI, or telemetry tags. Don’t pattern-match on message — that’s
unstable across versions.
Idempotency
Section titled “Idempotency”Write endpoints in V3 are idempotent by intent rather than by idempotency-key header. Specifically:
POST /v3/item/{id}/bid— placing the same bid twice in quick succession may legitimately fail the second time (“already winning”). That’s not an idempotency failure — it’s the server protecting you from double-bidding.POST /v3/bidder/favorite/{id}— calling on an already-favorited item succeeds with no side effects.DELETE /v3/bidder/favorite/{id}— calling on a non-favorited item succeeds with no side effects.PUT /v3/bidder/notifications/read-all— calling when there’s nothing unread returns{ "count": 0 }. Always safe to call.
Treat these as the contract. If your client retries a failed-network bid and the bid actually went through, the second call will tell you the right state — not a duplicate-bid error.
Retry guidance
Section titled “Retry guidance”- 2xx: done.
- 400 / 403 / 404 / 409: do not retry. The error is in your request, not ours. Fix and resubmit.
- 401: refresh the token, then retry once. If 401 again, drop the session.
- 429: honor
retryAfter. Don’t dispatch another request to the same endpoint until that many seconds have passed. - 5xx: retry with exponential backoff (e.g., 1s, 2s, 4s, 8s, capped at 60s) up to 4 attempts. After that, surface the error to the user.
Reporting an unexpected error
Section titled “Reporting an unexpected error”If you see a 500 with a message that looks like a true bug:
- Capture the response headers (including any
x-request-id). - Note the timestamp in UTC.
- Email engineering@handbid.com with both. We can usually trace the request to the offending log line in under a minute.