May 18, 2025·8 min

A Minimal API Contract for Sales and Warehouse: Entities and Events

A minimal API contract for sales and warehouse: the set of objects and events that speeds up connecting new services without rewrites or data loss.

A Minimal API Contract for Sales and Warehouse: Entities and Events

Why a minimal contract is needed and what it solves

Integrations between sales and warehouse systems often slow down not because of the API itself, but because of mismatched meanings. One system calls it a “client”, another - a “counterparty”. In one place the status “Shipped” means “left the warehouse”, elsewhere it means “handed to the carrier”. Add different identifiers: one service keeps its own order number, another generates a new one, and after a few weeks it’s unclear what corresponds to what.

“Fast” integrations without a shared contract look cheaper at first, but later become sources of losses: duplicates, incorrect stock levels, double decrements, manual reconciliations and emergency fixes with every new service. It’s especially painful when a new sales channel or a new WMS is added: old point-to-point agreements start to conflict.

A minimal contract is a common set of objects and events that all systems need to understand the same operation in the same way. It doesn’t try to describe every nuance of ERP, CRM and WMS. It fixes the basic common parts: unified identifiers, key statuses and required fields.

Typically the contract covers a simple chain: sale (order creation and updates), reservation (what is reserved and why), shipment (what physically left the warehouse), receiving (what returned or arrived at the warehouse), and adjustments (inventory, write-offs, mispicks).

A simple example: a new delivery service receives an "Shipment created" event and doesn’t have to guess what the status means, where to get the address or how to match line items. It reads one format — with the same IDs and rules as other integration participants.

Principles: boundaries, data owners and identifiers

For a sales-and-warehouse contract to actually accelerate integrations, you must agree not only on a list of fields but on rules. These rules answer four questions: where the domain boundary is, who owns the data, how identifiers look, and what is a reference (master) vs a transaction.

Domain boundaries: don’t mix responsibilities

Sales, warehouse, finance and delivery often use the same words ("status", "shipment", "payment") but mean different things by them. If responsibilities are mixed, any change becomes a chain reaction.

A simple guideline: each domain stores and changes only what it owns. Sales manage the order and its lifecycle; warehouse manages movements and reservations; finance handles payments and reconciliations; delivery handles routing and tracking.

One source of truth per entity

For each entity pick one master: the system that creates and mutates the object. Others receive a copy and may add their attributes, but must not overwrite the owner’s part.

For example, a product card (name, barcode, unit) belongs to PIM or ERP. The warehouse shouldn’t “fix” the product name, and sales shouldn’t change the base packaging, even if that’s convenient in a UI.

Identifiers: immutability and mapping

Design two types of IDs from the start: an internal ID (in the owning system) and an external ID (for integration). The external ID must be stable and must not change during migrations, merges or transfers.

A practical rule: every service accepts requests with the external ID and returns both IDs. This makes mapping easier and helps resolve mismatches without manual tables.

Reference data vs transactions: different lifecycles

References describe “what it is” (product, warehouse, counterparty, currency) and change rarely. Transactions describe “what happened” (order, shipment, return, receipt, movement) and are recorded as facts.

If in doubt, ask: can this object be renamed without consequences, or should every change be visible as an event? If the latter, treat it as a transaction and store it as a separate entity with date, author and reason.

Basic reference entities: what should always exist

If you want a new service to connect without long negotiations, start with a common vocabulary. Contracts for sales and warehouse usually fail not on orders and balances, but on disagreements about who sells, where the item is stored and what counts as a single line.

A good base of references isn’t “full master data” but a short set of entities that provide context for any operation and allow documents to be matched across systems.

A minimal set that is almost always needed:

  • Organization and branch: who owns the operation (legal entity) and where it happened (branch/location). Often company_id, branch_id, name and active status are enough.
  • Warehouse and storage location: the warehouse is relevant to almost everyone; zone or bin is necessary when precision matters (e.g., addressable storage). Even if bins aren’t used yet, reserve a location_id field.
  • Product and SKU (variant): product = “what we sell” (model), SKU = “in which form” (color, configuration, serial). Add unit of measure and conversion factors (piece, box, kg) to avoid disputes about conversions.
  • Counterparty: a minimal card for customer or supplier for documents. Don’t pull in all details at once, but keep a stable counterparty_id, type (customer/supplier), name and, if needed, tax identifier.
  • Actor (user or service): who created, changed or confirmed. This helps audits and incident investigations.

Practical example: an order was placed in an online store for branch A, the shipment is performed from warehouse B, and picking is done from a specific zone. If there are no common branch, warehouse and location references, two documents cannot be reliably joined even if numbers match.

Keep these entities small, with clear identifiers and an archive flag. Everything else can be extended later without changing the foundation.

Warehouse entities: balances, reservations and movements

Warehouses break integrations not because of complex logic but because of different interpretations of simple words: "balance", "reservation", "movement". The contract should define these entities so any new service understands how much is available, what is already promised to customers, and why numbers changed.

Stock balance - a snapshot of “how much is available now” in a specific place. It should be broken down at least by warehouse, location and SKU. It’s important to record the status of quantities: for example, "available", "quarantine/damaged", "in transit", so services don’t consume items that can’t be sold.

Reservation answers "what is already allocated". A reservation always references a reason (usually an order or order line), has an author (system/user), an expiration and an explicit cancellation. This avoids eternal reservations when an order is canceled but the warehouse never freed the stock.

Stock movement - the source of truth about why a balance changed: receipt, consumption or transfer. When movements exist, the dispute "an item disappeared" quickly turns into "here’s the operation and the reason".

Minimal fields to standardize

Agree on a small, common set of fields that every service uses:

  • warehouse_id, location_id, sku_id (where and what)
  • quantity and unit of measure (how much)
  • stock_status (available, damaged, in_transit)
  • reservation_id or reference (what the reservation/decrement refers to)
  • movement_type and reason_code (what happened and why)

Inventory counts add a fact: how much was found, the difference vs accounting, and who confirmed the result.

Batches/serials/expiration dates should be added only if they affect sales or write-offs (e.g., pharmaceuticals, food, warranty equipment). Those attributes must appear the same in balances, reservations and movements. If they exist only in one place, accounting will quickly diverge.

Sales: order, shipment, return and payments

To connect a new service to sales quickly, it usually needs four entities: order, shipment, return and payment.

Order: what the buyer requested

An order records intent, not the fact of shipment. It should be readable without calling other services: calculate totals, understand composition and deadlines.

A minimal set that prevents rework:

  • Identifiers: order_id, order number, creation timestamp, customer (customer_id)
  • Lines: item_id/sku, quantity, price, discount, tax rate/amount, line total
  • Money: currency, amounts before/after taxes, rounding
  • Addresses and contact: delivery and billing (at least country/city/address line)
  • Dates: promised ship/delivery dates, condition notes

Order statuses should be simple and unambiguous. A working minimum: draft, confirmed, canceled, closed. More important than names is the rule: status changes only by a clear event (confirmed, canceled, fully shipped and closed).

Shipment and return: what actually happened

A shipment (shipment/fulfillment) describes the fact: exactly what left, when and from where. It must be linked to the order and include the shipment composition by lines, because partial shipments are normal.

A return (return) must reference a specific shipment or its lines so there’s no dispute about what was returned. Add a reason for the return and the condition of the item (e.g., new/opened/damaged).

Payment can be kept minimal: payment_id, order_id, amount, currency, method, status (created, authorized, paid, canceled, refunded), operation date. If the order is confirmed but the payment is only authorized, delivery and support services should not treat it as "paid" by default.

Integration events: which ones and why

Risk-free integration pilot
We will run a pilot on one warehouse and one sales channel without changing your core.
Order a pilot

Integration events let sales and warehouse services learn about facts that have already happened and react without direct calls to each other. One service publishes a fact; others subscribe and act.

It’s important to distinguish an event from a command. A command is a request "please do this" (e.g., reserve stock). An event is a notification "this has already happened" (e.g., reservation created). Commands usually expect a reply and can be rejected. Events don’t ask permission and must be truthful.

For sales, a minimal set of events often reduces to OrderCreated, OrderConfirmed, OrderCanceled. That’s enough to plug in anti-fraud, loyalty, notifications, document printing and analytics.

For warehouse you usually need facts about reservations and balance changes: StockReserved, StockReleased, StockMoved, StockAdjusted (adjustment from inventory/damage). These let systems rely on history rather than guess the current state.

Logistics adds the shipment and return lifecycle: ShipmentCreated, ShipmentShipped, ShipmentDelivered, ReturnReceived. Then delivery, CRM and support see the same status without manual reconciliation.

To make events useful and safe, include a mandatory minimum in each message:

  • eventId
  • type
  • occurredAt
  • source
  • version

If actions happen in a chain (for example, an order created a reservation, then a shipment was created), add a correlationId. It ties events into one story and greatly simplifies problem tracing in integrations.

Contract rules: statuses, versions, idempotency

To prevent the contract from falling apart after a month, you need rules around entities and events. They make integrations predictable even when services evolve independently.

Every object should have common base fields so it can be stored, updated and reconciled across systems:

  • id (stable object identifier)
  • createdAt and updatedAt (when it appeared and when it changed)
  • status (current state)
  • source or originSystem (who created the record, if relevant for investigations)

Be boring with statuses: a short list and clear transition rules. The main rule is to forbid unexpected jumps. If an order is already shipped, it shouldn’t suddenly become draft. Use separate events for rollbacks (for example, return) rather than rewriting history.

Idempotency is needed in two places: commands and events.

  • For commands (create order, confirm reservation) use an idempotencyKey: on retries the server must return the same result, not create duplicates.
  • For events assume retries are normal: consumers must handle the same event twice without side effects, using eventId and storing a processing fact.

Versioning schemas should be simple: backward compatibility is mandatory. New fields can be added; old fields must not be renamed or redefined. If a field is deprecated, mark it deprecated and set a removal timeline.

Errors should be helpful: a code, a clear message and field-level details. On validation failure return a general code (for example, VALIDATION_ERROR) and a list of problematic fields with reasons — this lets integrators fix requests without lengthy correspondence.

How to build the contract in 1–2 weeks: a step-by-step plan

Sales and warehouse integrations audit
We will find places where duplicates and stock mismatches come from differing meanings.
Request an audit

To fit it into 1–2 weeks, don’t try to describe the whole business at once. Start with the minimum that delivers value: sell, ship, receive goods and reconcile stock. These scenarios quickly reveal which objects and events are missing.

Work plan (no unnecessary bureaucracy)

Gather 3–4 minimal scenarios and write them as short stories: who does what and what the expected outcome is (for example, order created, stock reserved, shipment confirmed).

Then draw entities and relationships on one sheet: product, warehouse, counterparty, order, shipment, receipt, movement, balance, reservation. At this step agree on identifiers: which system is master, which is the foreign key, what counts as "the same object".

Next lock down statuses and transitions for order and shipment. You don’t need many: 4–6 statuses with clear rules about what can change and what is final.

After that define events and their payloads. For each event answer three questions: what happened, which key fields does the recipient need, and how to know the event was already processed (via eventId, version and deduplication rules).

Also agree the “little things” that break integrations: timezones and timestamp formats, rounding, currency, units of measure, conversion rules (pieces, boxes, kg).

After agreement prepare test data and canonical message examples. This saves days of discussion: everyone looks at the same sample.

What should be ready by the end

  • One entity-relationship schema that is readable without explanations.
  • A table of statuses and transitions for order and shipment.
  • A list of events with brief descriptions and example payloads.
  • A set of test objects and 5–10 canonical messages for checks in different systems.

Common mistakes and traps in design

A frequent issue is storing both reference data and transaction data in the same object. For example, storing last sale price, balance and last shipment date in the “Product” object. The reference starts changing constantly, caches break, and new services don’t know the true source of data.

Another trap is mutable identifiers and weak links between documents. If an order number can be “renamed” and a shipment doesn’t store a link to the order, after a month no one can confidently say what was shipped and what was refunded.

Even worse is when reservations are missing or “hidden” inside the balance. Sales see “balance = 10” and sell, while the warehouse has already reserved 8 for another order. Disputes and manual reconciliations begin.

Integration events are often created without a version or correlationId. Then tracing mismatches becomes guessing: which event came first, where did a confirmation get lost, which order does a movement relate to.

To make the contract helpful rather than scary, don’t overload the model on day one. Usually it’s enough to close the basic scenarios and leave room for extension.

Common omissions:

  • partial shipments (one order shipped in 2–3 vehicles)
  • partial returns (1 of 5 items returned)
  • separate statuses for "created", "confirmed", "canceled" for each document
  • idempotency for events and commands

Example: a delivery service sent ShipmentCreated and then ShipmentUpdated without a version. The warehouse counted the movement twice and balances "drifted". Event version and correlationId would quickly show where the repetition occurred.

Quick check: minimal contract checklist

Before handing the contract to new teams and external services, do a short verification. The goal is simple: the integration shouldn’t rely on guesses and must survive event retries.

Run through these items and honestly answer yes/no:

  • Unified identifiers: order, shipment, sku, warehouse, location have stable IDs that don’t change over time and don’t depend on the source system.
  • Statuses and transitions: statuses and allowed transitions are defined for order and shipment. It’s clear what happens on cancellation, partial shipment and return.
  • Warehouse as a separate model: there are entities for reservation and stock movement, not just a numeric balance. You can explain where the balance came from and why it changed.
  • Events are not empty: each event carries eventId, time, source, version and correlationId to link the chain “order -> shipment -> payment” and diagnose incidents.
  • Retries and determinism: idempotency and retry rules are defined (what counts as a duplicate, how deduplication works). Units, rounding and timezone are agreed.

If you answered “no” to two or more items, surprises are almost guaranteed. A typical case: the delivery service sends a repeated ShipmentCreated, and without idempotency you get double stock decrement or a second receipt printed. This check takes 10 minutes but saves days of production troubleshooting.

Example scenario: connecting a new service without changing the core

Reliable operations for integrations
We will help set up monitoring, alerts and logs so incidents are resolved quickly.
Order implementation

An online store grows and decides to do two things at once: add a new delivery service (for ETA and tracking) and roll out a new WMS. Previously many things relied on manual reconciliation: a manager compared "what’s in the order", "what was picked" and "what shipped", and stock levels often didn’t match.

A minimal contract fixes the main pain: all systems speak the same objects and events. The sales core doesn’t need to know WMS and delivery internals; it publishes and consumes standard facts: order confirmed, reservation created, shipment sent.

A typical flow looks like: OrderConfirmed -> StockReserved -> ShipmentShipped -> StockMoved. Because of this, the new WMS can be attached as a handler of warehouse events, and the delivery service can subscribe to shipment events without breaking existing processes.

Exceptions also become clear because they have a place in the contract:

  • order cancellation: OrderCanceled frees reservations and stops picking
  • shortage: StockReservationFailed or StockAdjusted records the fact and reason
  • partial shipment: ShipmentPartiallyShipped and proper reserve decrement
  • return: ReturnRegistered -> StockMoved (to a quarantine zone or main stock)
  • duplicate errors: idempotency prevents creating duplicate reservations or movements

To evaluate results agree on metrics up front: how many days the integration took, how many incidents occurred in the first 2 weeks, and how often sales and warehouse balances differ. With a proper contract these numbers drop noticeably without heroic firefighting.

Next steps: how to roll out and where help is useful

Start with an inventory. List objects and events already present in your systems (ERP, WMS, POS, marketplaces, CRM) and map them to what you want to lock in as the minimum. You’ll quickly find where different names represent the same thing, where identifiers are missing, and where statuses exist only in the team’s heads.

Next agree on data owners. Without that integrations turn into "whose truth is it" disputes. Define responsibilities for key areas: who maintains products and references, which source is authoritative for balances and reservations, where orders originate and who confirms shipments and returns, which service reports payments and under what rules.

Plan a pilot to deliver value without drowning in details. A good option is one warehouse and one sales channel (for instance, the online store) plus a clear set of scenarios: order, reservation, shipment, return. After the pilot scale to other warehouses and channels without changing the contract, only adding new sources.

Think through infrastructure early: a queue or event bus, unified logs, alerts and delivery metrics, retry and deduplication policies. Simple rule: if an event can’t be located and explained within a week, it’s not helping the business.

If you need integration and infrastructure help, GSE.kz can act as a system integrator: help design the architecture, set up monitoring and resilience, and choose and deploy the server platform for the bus and services (for example, on S200 servers) with 24/7 support.

A Minimal API Contract for Sales and Warehouse: Entities and Events | GSE