Build1 distinct publisher3 min readUpdated
Ozon and Wildberries disagree about identifiers, batch semantics and how failure is signalled. In one Go codebase, the only shared machinery on show is the transport.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A team building a self-hosted seller store in Go published its integration notes for Ozon and Wildberries, and the finding is negative: the obvious four-method `Marketplace` interface, `ListOffers` / `SetStock` / `SetPrice` / `Orders`, fell apart on every one of the four [2] [3]. That is worth reading if you have ever sketched that interface because "the actions are the same across platforms" [1], because the breakage is in the identifiers and the response shapes, not in the verbs.
Start with keys. On Ozon a product is an `offer_id`, your own article number uploaded to the cabinet, and both stock and price are set by it, so the link table is one row with one text column [5]. On Wildberries a card is an `nmID`, a card has sizes, and a size has barcodes: stock is set per barcode, price per card [6]. Both identifiers have to be stored, with a unique index on barcode and a non-unique one on `nmID`, because several rows sharing an `nmID` are just the sizes of one card, while two products sharing a barcode would push two different levels into the same slot on every pass [7] [8]. One key on one platform, two on the other [1]: `SetStock(offerID string, n int64)` has nowhere to put the second, and which key you pass depends on the operation [9].
Then the return values. According to the write-up, Ozon answers a stock push with a per-item result list carrying `OfferID`, `Updated` and errors, and the team treats an item the platform said nothing about as not pushed, on the reasoning that remembering a level the platform never received is worse than sending it twice [10] [11]. Wildberries answers `PUT /api/v3/stocks/{warehouseId}` with 204 and an empty body, crediting the entire batch at once, and names offenders only in a 409, only the ones that failed [12]. So one client returns `[]ItemResult` and the other `map[string]string`; force them into a common type and half its fields are always empty [13] [14]. The client also refuses to credit a batch when the error is not a 409 API error, on the grounds that the transport died [15].
Failure detection is worse than the shape mismatch. Some Wildberries methods return 200 with `{"error": true, "errorText": "..."}` in the body, which Ozon does not do; miss the check and the push is recorded as successful and never retried [16]. That check therefore sits in the transport rather than in each method, and the same choke point strips the token out of platform text before it reaches a log or the database, on the theory that one line is cheaper than establishing whether the API echoes requests back [17] [18].
Price is where the abstraction stops being salvageable. Ozon gives a verdict per item immediately, so a row's state is one number, the last value pushed, guarded by SQL that rounds up to whole roubles inside the comparison [19] [20]. The platform accepts whole roubles, so `price_pushed` holds the rounded value, and comparing raw kopecks against it marks every row as changed on every pass [20]; in integer arithmetic `(price + 99) / 100 * 100` turns 12301 kopecks into 12400 [2]. That rounding rule is platform policy leaking into your database schema, which no interface method signature can absorb.
Two things to watch. The post says one thing did turn out to be genuinely shared, and the excerpt available breaks off before naming it; the only common machinery visible in the code shown is the transport layer, doing envelope checks, error classification and token scrubbing [1] [17] [18]. And the unique barcode index [7] is a bet on seller discipline: it will be the first thing to fail in a catalogue where the same barcode was pasted onto two products.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The task is framed as a textbook polymorphism example: several platforms, the same actions, list the cards, push the stock, push the price, pull the orders. The author states that at the end one thing turned out to be genuinely shared, and not the one he would have bet on.
The proposed Go interface is: type Marketplace interface { ListOffers() ([]Offer, error); SetStock(offerID string, n int64) error; SetPrice(offerID string, price int64) error; Orders(since time.Time) ([]Order, error) }.
The team wrote Ozon, then Wildberries, and that interface fell apart on every one of its four methods.
On Ozon a product is an offer_id, the seller's own article number uploaded to the cabinet; both stock and price are set by it. The link table is one row: CREATE TABLE ozon_links (product_id INTEGER PRIMARY KEY, offer_id TEXT NOT NULL, ...).
On Wildberries a card is an nmID, a card has sizes, and a size has barcodes. Stock is set per barcode, price per card. Those are two identifiers at different levels of nesting and both have to be stored: wb_links (product_id INTEGER PRIMARY KEY, nm_id INTEGER NOT NULL, barcode TEXT NOT NULL, ...).
The schema has a unique index on wb_links(barcode) and a non-unique index on wb_links(nm_id).
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Specific and checkable, but entirely first-party
The post supplies unusually concrete artefacts — the interface definition, ozon_links and wb_links DDL with index choices, the ItemResult struct, endpoint paths and status codes, the SetStocks control flow, the errorEnvelope check and the literal SQL guard string — which makes the technical claims falsifiable against the platforms' APIs. It is nonetheless one author reporting on one codebase, with no vendor documentation, second implementation or external test to corroborate the described platform behaviours, and the market-size framing is unsourced.
No adoption signal beyond one self-described codebase
The only adoption-adjacent fact supplied is the author's own description of the product the code lives in (self-hosted, one Go binary, SQLite, one installation per shop, open code). There are no install counts, downloads, stars, customer references, benchmarks or third-party deployments, so adoption cannot be scored without inventing facts.
Slightly understated relative to the code shown
The framing is restrained: the dek hedges to 'the only shared machinery on show', and the body argues from concrete schemas, status codes and guard predicates rather than promise. The narrow overstatement is the categorical headline verdict that a shared Marketplace interface is the wrong abstraction, generalised from two implementations without weighing alternative designs, plus one unsourced market-size aside; those pull the score back close to aligned rather than clearly negative.
Disclosed first-party product interest, mild promotion
The author writes as a member of the team building the product whose code is being shown, and the piece points readers to the open repository, so there is a visible interest in the per-platform approach looking like the sensible one. The interest is disclosed up front in the context paragraph, the claims are technical rather than comparative against competitors, and nothing is gated or sold, which keeps the incentive pressure moderate rather than high.
Moderate: detailed but single-source and unaudited
Confidence is lifted by the density of checkable artefacts and internal consistency between the schema, response handling and guard logic, and held down by the cluster having one publisher, one first-party author with a product interest, no corroborating documentation, and no adoption evidence at all. The engineering claims are credible as testimony; the generalised design verdict and the market framing are not independently established.
build
Your meter now runs on someone else's machine: signed receipts, fsync, and failing open1 distinct publisher
build
Once the question needs a cube, you own the parser1 distinct publisher
build
Zero-Instrumentation Go Tracing Works, But You Are Now Maintaining ABI Offsets1 distinct publisher
build
A 12MB Go binary bets agent cost control is cache stickiness, not a dashboard1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 16, 2026