Domain model
Carriyo's domain model spans the commerce lifecycle, from the order placed at checkout, through warehouse work, to the shipment booked with a carrier and any return that follows. This page introduces the core objects, how they reference each other, and the master-data entities (Product, Inventory, Location, Carrier account, Merchant, Tenant) that surround them.
An Order is what the customer wants. A fulfillment order is the plan for who picks and packs it. Pick and Pack are the warehouse-floor work that produces a parcel. A Shipment is the parcel handed to a carrier; a Collection is the parcel staged for in-store pickup. A Return is what comes back. Surrounding them, Products, Inventory, Locations, Carrier accounts, Merchants, and the Tenant are the master data each transactional object references.
The sections below walk through the lifecycle in order. The Order arrives, fulfillment orders allocate work to locations, the warehouse picks and packs, a Shipment is booked with a carrier (or a Collection is staged for in-store pickup), and a Return may come back. Each object is a first-class entity in the platform with its own API, its own webhooks, and its own lifecycle.
Order
The Order is the top-level object: the customer's intent. It captures what was purchased, who the customer is, where it should go, and what the merchant has agreed to deliver. Orders typically arrive from an upstream e-commerce platform (Shopify, Magento, SFCC, or your own checkout) via the Orders API or a connector.
An Order is identified by order_id (Carriyo-issued) and by
your own partner_order_reference. An Order doesn't directly
know how it will be fulfilled. That decision is made one level
down.
Fulfillment order
A fulfillment order (FO) is an allocation of an Order's line items to a specific Location. It answers the question: "who is going to pick, pack, and dispatch which items?"
An Order can be split into multiple FOs. Common reasons: items held in different warehouses, immediate-ship items separated from a backorder portion, or click-and-collect items routed to a store. Fulfillment orders can also be merged or re-routed before fulfillment begins.
FOs are identified by fulfillment_order_id. Each FO carries its
own location_id, delivery address, and line items.
Pick
A Pick represents one picker's task list: walking the
warehouse floor pulling items off shelves for one or more
fulfillment orders. Picks are created when a fulfillment order
is ready for the warehouse, and progress through
start → complete as the picker works.
Picks expose item-level actions: pick (off the shelf), restock (put back), and mispick (record a damaged or wrong-item event). The result is a set of physical items ready to be packed.
Picks run through the Fulfillment App and appear as derived status on the parent FO in the Orders API.
Pack
A Pack turns picked items into parcels at a packing station. The packer scans items into one or more packages, applies dimensions and weight, and confirms the pack. Completing a Pack triggers the next step: either a Shipment is booked (for delivery-type FOs) or a Collection is created (for click-and-collect FOs).
A Pack references its originating Pick. The lineage Pick → Pack → (Shipment or Collection) is preserved on every Pack.
Shipment
A Shipment is a physical parcel (or set of parcels) handed to a carrier for delivery. A Shipment carries the carrier booking, the label, the tracking number, the manifest reference, and the live status updates.
Shipping is the more common of the two terminal paths: most fulfillment orders end with a carrier booking, not an in-store pickup. Carrier integration is where most of Carriyo's depth concentrates: many carriers and multiple integration shapes (booking, label generation, manifest handover, tracking, cancellation, returns), all normalized into one consistent set of statuses.
A fulfillment order may produce one Shipment (the typical case) or multiple Shipments (when fulfillment is split across parcels or carriers). Shipments are also the unit of integration with carrier networks: every event the carrier emits attaches to a Shipment.
A Shipment is identified by shipment_id and by your
partner_shipment_reference. It carries references to its
parent context: order_id and fulfillment_order_id are both
available directly on the Shipment, so you can pivot from a
tracking event back to the originating Order in a single read.
Collection
A Collection is the click-and-collect alternative to a Shipment. When the customer is picking up in-store rather than having Carriyo ship to them, the parcel is staged for collection and a Collection record tracks the handover.
When a Pack completes for a click-and-collect fulfillment order, Carriyo creates a Collection. The customer later arrives at the collection point, identity is verified via OTP, and the items are handed over. Compared to a Shipment, the lifecycle is simpler: no carrier, no external tracking, no manifest. The work is on-premise.
Collections are identified by collection_id and reference their
Pack via pack_id. They're also queryable by order_id, fulfillment_order_id,
pack_id, and shipment_id. The shipment_id applies to remote
collection, where a Shipment moves the parcel to the collection
point. You can look up a Collection by any of these identifiers.
Return
A Return is a request to reverse part or all of an Order. It links back to the original Order and the Shipment(s) being returned, captures the reason and resolution (refund, exchange, store credit), and orchestrates any reverse-logistics shipment needed.
A Return is identified by return_request_id and carries a
shipments array listing the original outbound Shipments being
reversed. Any reverse Shipment Carriyo books to bring goods back
carries return_request_id pointing to its Return.
The full return-request workflow is part of Carriyo's Returns module. Reverse shipments can also be booked directly without raising a request, for clients running their own returns workflow.
How they relate
Order ──┬──▶ FulfillmentOrder ─┬──▶ Pick ──▶ Pack ──┬──▶ Shipment ──▶ Return
│ (location A) │ │
│ └────────────────────┴──▶ Collection
│ (click-and-collect)
└──▶ FulfillmentOrder ──▶ ...
(location B)
A single Order can fan out into many fulfillment orders, one per participating location. Each FO produces one or more Picks and Packs. Each Pack produces either a Shipment (delivery) or a Collection (click-and-collect). Shipments can produce Returns. The relationships always point upward: one Order per FO, one FO per Pick, one Pick per Pack, one Pack per Shipment-or-Collection.
Walked example
Take a shopper buying two SKUs (A and B) from a multi-location merchant. Stock for SKU-A is at warehouse DXB-1; SKU-B is at DXB-2. The shopper later returns SKU-A.
- Order created.
Order { order_id: O1, partner_order_reference: SHOP-7788, line_items: [A, B] }. - Allocation splits the line items into two FOs under the
Order:
FulfillmentOrder { fulfillment_order_id: FO1, location_id: DXB-1, line_items: [A] }FulfillmentOrder { fulfillment_order_id: FO2, location_id: DXB-2, line_items: [B] }
- Warehouse work at DXB-1 produces a Pick (P1), then a Pack (PK1) referencing P1. DXB-2 produces P2 / PK2 independently.
- Shipments booked. Each Pack completes and a Shipment is
created:
Shipment { shipment_id: S1, order_id: O1, fulfillment_order_id: FO1 }Shipment { shipment_id: S2, order_id: O1, fulfillment_order_id: FO2 }
- Return raised. The shopper requests a return of SKU-A.
ReturnRequest { return_request_id: R1, partner_order_reference: SHOP-7788, shipments: [S1] }. Carriyo books a reverse Shipment:Shipment { shipment_id: S3, return_request_id: R1, order_id: O1 }.
How the IDs link together
Every object carries one stable identifier and a set of references to its parent context.
| Object | Own ID | Refers to |
|---|---|---|
| Order | order_id | (top-level) |
| FulfillmentOrder | fulfillment_order_id | order_id |
| Pick | pick_id | fulfillment_order_id |
| Pack | pack_id | pick_id, fulfillment_order_id |
| Collection | collection_id | pack_id, fulfillment_order_id, optionally shipment_id |
| Shipment | shipment_id | order_id, fulfillment_order_id, optionally return_request_id |
| Return | return_request_id | shipments[] (the originals being returned) |
A common look-up pattern: from a webhook event you receive a
Shipment payload with both order_id and fulfillment_order_id
on it. Read either to get the parent context without a second
hop.
Partner references: your IDs alongside ours
Every top-level object accepts a partner reference: an ID you assign. Carriyo indexes it so you can read objects back by your own identifiers without storing Carriyo IDs on your side.
partner_order_referenceon Order, ReturnRequest, and Shipmentpartner_fulfillment_order_referenceon FulfillmentOrderpartner_shipment_referenceon Shipment (unique per tenant; re-sending the same value returns the existing Shipment, not a duplicate)
Use partner references in webhook handlers, reconciliation jobs, and any code path where your platform's order number is the natural key. Use Carriyo IDs in carrier-facing flows where Carriyo's identifier is what the carrier knows.
Consolidated shipments
A Shipment can be the parent of a multi-parcel consolidated booking. When Carriyo merges multiple confirmed Shipments into a single carrier handover, a parent Shipment is created and the originals become children. The relationship appears as two fields:
consolidated_parent_shipmenton children pointing to the parent.consolidated_child_shipments: string[]on the parent listing the children.
See Consolidated shipments for the full operational treatment.
Master data
The transactional objects above don't exist in isolation. They reference a set of master-data entities that describe who the operation runs for, what is being shipped, and where it ships from. These are configured once and read many times:
- Tenant. The Carriyo customer account itself. Every object in this model belongs to exactly one tenant; nothing crosses the tenant boundary.
- Merchant. A sub-brand inside a tenant. Each Order, Shipment, and Return belongs to exactly one merchant; master data like Carrier accounts and Locations can be shared across merchants or scoped to one.
- Location. The physical sites the tenant operates from. A single Location can act as a warehouse, an inventory point, a click-and-collect counter, a shipping origin, or a delivery destination. Referenced by FOs, Shipments, Picks, Inventory, and Collections.
- Carrier account. The carriers a tenant ships through, with their credentials and profiles. Each Shipment is bound to a Carrier account at booking time.
- Product. The merchant's catalog. Line items on Orders and Shipments reference Products by SKU.
- Inventory. Per-location stock positions. Allocation, click-and-collect eligibility, and reporting all read from Inventory.
Lifecycle at a glance
Each transactional object has its own status field and progresses through its own state machine:
| Object | Status field | What drives transitions |
|---|---|---|
| Order | status | Rolled up from its fulfillment orders |
| FulfillmentOrder | status | Rolled up from its line items |
| Pick | status | Picker actions in the Fulfillment App |
| Pack | status | Packer actions in the Fulfillment App |
| Collection | status | OTP verification and handover |
| Shipment | status | Carrier callbacks and operator actions |
| Return | status | Item-receipt events and operator actions |
They move independently. An Order can be confirmed long before
any Shipment is delivered, and a Return can be pending while
its outbound Shipment is still in_transit. For the rules that
govern transitions and the events they emit, see the
Event model. For the canonical Shipment
state machine, see the
Shipment status codes reference.
Reading patterns
When you read an Order, Carriyo returns its fulfillment orders inline. Both arrive in a single API call. Shipment, Pick, Pack, Collection, and Return are accessed through their own endpoints. Each is a first-class object with its own read, list, and update operations, and each produces its own webhook events when its state changes.
The practical implication for integrators:
- An update to a fulfillment order is an update to its parent Order: one event, one webhook on the Order.
- A Shipment status change is independent. It fires its own Shipment webhook regardless of the Order.
- Subscribe at the entity granularity that matches the state-change you care about (Shipment for carrier events, Order for fulfillment roll-ups, Return for reverse-flow events).