UltraEdge API›Docs›Index
Stuck?Troubleshooting and edge casesv0.1.0 · api.ultraedge.ai · MCP

How the API fails, what each failure means, and what to do. Every error body is application/problem+json with a stable code. Try things safely in your sandbox from Get started; probe the platform with GET /v1/monitoring/health.

Reading an error · Tokens · Listings · Bids and counters · Deals, escrow and fees · Signals and live streams · Spreadsheet import · Sandboxes and the demo

Reading an error

Every error is application/problem+json: {type, title, status, detail, code, instance}. Branch on code, never on the English in detail. instance is the request id to quote when asking for help.

Symptom or codeWhat it meansWhat to do
400 (validation)Body or query failed the schema; detail lists field: message pairsFix the field named. Money is integer cents; enums are lower-case; ids are UUIDs; dates are ISO 8601 with a Z
401 unauthorizedNo token, expired token, token from another environment (staging vs production issuer), or the user's token_version moved (logout everywhere, suspension, sandbox reset)Mint or refresh a token for this API base. After a sandbox reset every old token is dead by design: mint again
403 forbiddenRight token, wrong role: a buyer calling a vendor route, a vendor touching another company's listing, a company-admin calling admin routesCheck GET /v1/me: user.role and company.id. The per-role documents list exactly what each role may call
403 read_onlyThe token came from a shared, read-only sandbox linkViewers can look but not change. Open your own sandbox, or ask the owner for an editing session
404 not_foundWrong id, or an object you are not allowed to see (private listings, other companies' deals and invoices return 404, not 403, so ids leak nothing)Search again with your own token; listing ids come from GET /v1/listings, bid ids from GET /v1/bids
409 concurrentSomeone changed the object between your read and your write (deals carry a version; counters and bids check status)Re-read and retry once. Never loop on 409
412 etag_mismatch / 428 if_match_requiredSettings writes need the current ETagGET /v1/admin/settings, copy the ETag header into If-Match, send the PUT
413 import_too_largeMore rows than listings.import_max_rowsSplit the sheet; the limit is in the settings schema
5xxRare: a dependency failed. GET /v1/monitoring/health (admin or service) says whichRetry with backoff; the request id in instance finds the log line

Tokens

Symptom or codeWhat it meansWhat to do
Access token stops working after an hourAccess tokens live one hourCall POST /v1/auth/refresh with the refresh token; you get a new pair. Store the new refresh token: the old one is spent
Refresh returns 401 although it is not expiredRefresh tokens are single-use and belong to a family. Reusing a spent one is treated as theft and revokes the whole familyLog in again. Make sure only one process holds a refresh token; two workers sharing one will keep revoking each other
Everything 401 after all_devices logout, suspension or sandbox resetThese bump the user's token_version; access tokens carry the old valueLog in or mint again. This is the intended kill switch
Token works on staging, not productionTokens carry the issuer of the API that minted themEach API base has its own tokens; the demo site's tokens are for the sandbox API only

Listings

Symptom or codeWhat it meansWhat to do
floor_on_fixedA fixed-price listing cannot carry a private floorSend pricing_mode: negotiable, or drop floor_cents
floor_above_ask · moq_above_quantity · quantity_below_reservedArithmetic rules checked on the merged result of a PATCH, not just the fields you sentRead the listing first; a PATCH that lowers quantity below what deals already reserve is refused
used_details_requiredUsed inventory needs the used block (sale format, functional status, cosmetic grade)Send it on create; on import set condition to A, B or C and it is filled in
Published, but status is reviewlistings.review_required is first_listing by default: a company's first listing waits for the operatorAn admin approves with POST /v1/admin/listings/{id}/approve; later listings go straight to active. The ops queue lists what is waiting
My listing is not in searchSearch shows only active, unexpired listings in the caller's sandbox. Anonymous calls see the shared data unless ?sandbox= names oneUse ?mine=true with your token to see every status; check expires_at
visible_band_disabled · visible_band_too_wideMarketplace settings forbid visible bands, or cap themSee negotiation.floor_mode and listings.max_discount_visible_band_pct

Bids and counters

Symptom or codeWhat it meansWhat to do
Bid returns 201 but status: auto_declinedPrice under the seller's private floor. The seller is never told; the buyer sees the reasonBid higher. The floor is never revealed; price history shows where deals closed
listing_not_active · listing_expired · quantity_unavailable · quantity_below_moqThe listing changed since you read it, or the quantity breaks its rulesRe-read the listing: quantity_available excludes units reserved by open deals; moq is the minimum
bid_not_openThe bid is already accepted, declined, withdrawn or expiredRead GET /v1/bids/{id}; only open and countered bids take actions
not_your_turnCounters alternate: whoever made the last counter must waitAccept, decline or let it lapse; or wait for the other side
counter_expired · bid expiredEach bid and counter carries expires_at; the 15-minute sweep expires them and signals both sidesPlace a new bid from the listing; expiry does not count as a round
Counter refused as out of rangeVendor counters must sit between the buyer's last price and the ask; buyer counters between their last price and the vendor'sThe thread shows both bounds; max_rounds on the bid is a snapshot of the setting at bid time
Duplicate bids on retryRetries without Idempotency-Key create duplicates until the per-listing open-bid limit refuses themAlways send Idempotency-Key (a UUID). A replay with the same key and body returns the original; a different body with the same key is 409
429 or a limit error on bidsbidders.rate_limit_per_hour, bidders.max_open_bids_per_listing and bidders.max_auto_declined_per_listing_per_day are enforced per companyValues are in the settings schema; auto-declined bids count toward the hourly limit
party_suspendedOne party is suspended (strikes, or an operator decision)Nothing moves until reinstated; the ops queue shows recent suspensions

Deals, escrow and fees

Symptom or codeWhat it meansWhat to do
illegal_transitionThe state machine is linear: accepted → payment_pending → payment_confirmed → ready_released → shipped_picked_up → received → completed, with disputed and cancelled from most statesdetail lists the allowed next statuses. Only the party named by the rules may make each move (buyer confirms receipt, vendor ships)
escrow_requiredEscrow is required by escrow.mode or by escrow.required_above_centsPOST /v1/deals/{id}/escrow first, then payment_pending
Deal cancelled by itself, buyer got a strikeUnpaid past reserved_until (deals.reservation_hours); the sweep cancels, releases stock and strikes the buyer; enough strikes suspendMove to payment_pending inside the hold; signals warn before expiry
Disputed and stuckOnly the operator can leave disputedPOST /v1/ops/deals/{id}/resolve with continue or cancel and a note; both parties are signalled
No fee entry after completionFees accrue at fee.trigger_state (default completed) and only if fee_cents > 0 on the dealCheck the deal's fee_trigger_state and the ledger with ?deal_id=; a 0% fee or fee.min_cents=0 yields nothing
Fee invoiced to the wrong partyfee.charged_to at deal time decides (vendor, buyer, split)The invoice shows fee.payers; the setting is frozen per deal

Signals and live streams

Symptom or codeWhat it meansWhat to do
426 on /v1/signals/streamNot a WebSocket upgrade, or the ticket is missingPOST /v1/signals/stream-token, then open wss://<api>/v1/signals/stream?t=<token> within 60 seconds
Stream opens, then nothingThe first frame is {type:'hello', items} with the hot list; new rows arrive as {type:'signals'}Idle sockets are fine: send the text ping and expect pong. After a reconnect send {"type":"recent"} for the hot page
{type:'reset'} arrivedThe sandbox was reset; the hot list is gone and your token is about to be invalidClose the socket, mint a new token, reload state
Unread count never dropsUnread is everything newer than the seen cursorPOST /v1/signals/seen with no body marks everything, or pass up_to
Market feed looks empty or wrongThe market feed is per sandbox; anonymous calls read the shared data unless ?sandbox=Signed-in callers always get their own sandbox's market
The same signal twiceDedupe keys stop repeats from sweeps and retries; two genuinely different events can look alikeUse id as the identity, never title and body

Spreadsheet import

Symptom or codeWhat it meansWhat to do
403 import_disabledlistings.spreadsheet_import_enabled is offAn admin turns it on
Row says import_missing_fieldsA row whose mpn matches nothing needs title, category, quantity and ask to create a listingRows that match an existing part number only need the fields you want changed
Row says duplicate mpn in this sheetThe same part number twice in one uploadOne row per part number; quantities are absolute, not additive
Everything created as fixedNo floor on the row means fixed priceAdd a floor column (dollars) or pricing_mode
Good rows did not land because of bad onesThey do land: the report is per rowRead report[].action; fix the error rows and re-upload only those (matched by mpn they become updates)

Sandboxes and the demo

Symptom or codeWhat it meansWhat to do
I see different data than a colleagueEvery person has their own sandbox; the market, search and feeds are scopedShare a read-only link, or compare the sandbox_id the token exchange returned
Reset wiped my tokensBy design: reset bumps token versions so stale scripts cannot keep writingMint again after a reset
Postman collection has empty variablesThe collection is generated before a token existsMint first, then download; or fill access_token and base_url by hand
CORS error in a browser appOnly listed origins may call from a browser (CORS_ORIGINS)Server-side calls are unaffected; ask for your origin to be added

Mirrored in the API design document, section 12. Request ids (instance) find the log line; include one when reporting a problem to bill@samas.tech.