Newsletter

Managing orders end-to-end

  • Home
  • Managing orders end-to-end

Introduction

Order management often relies on context, whether it’s shipping, order line management, or target sales channel. In this guide, we walk you through it step by step so you can be sure you’re not missing anything and can run your activity as smoothly as possible.

To do that, we put a particular focus on the end-to-end order workflow — from the moment an order reaches you, all the way to financial settlement. Before diving in, keep two structuring concepts in mind, as they condition almost everything that follows:

  • supplyMode (Seller vs Fulfillment) — determines who drives the statuses and who ships the order.

  • Order line management — some sales channels let you act at the line level (partial shipment or refusal), others only at the whole-order level.

order_lifecycle.webp

1. Retrieve and follow your orders

In this section we’ll see how to retrieve your orders and keep track of them.

Goal: make sure you collect every order without missing a single one.

  • 1.1 Count orders — GET /orders/count: estimate a volume by status without pulling the details (e.g. how many InPreparation today). Useful for building a front-office dashboard or managing your indicators.

  • 1.2 List orders with filters — GET /orders: the central use case. It supports rich filtering, pagination and sorting.

    • Identity / channel: reference, salesChannelId, businessOrder.

    • Status & logistics: status, supplyMode, shippingCountry.

    • Date ranges: createdAtMin/Max, updatedAtMin/Max, shippedAtMin/Max.

    • Pagination & sorting: pageIndex, pageSize (max 100) — GET /orders uses page-based pagination (no cursor). sort takes one or more date fields (createdAt, updatedAt, shippedAtMax); desc takes the field name(s) to sort in descending order (not a boolean) — e.g. sort=updatedAt&desc=updatedAt.

    • Poll intelligently. Octopia doesn’t offer webhooks, callbacks or push notifications: you retrieve orders by polling GET /orders regularly. To do it without missing data or overloading the API, leverage the filters above: use updatedAtMin to fetch only what changed since your last poll, paginate fully, poll at reasonable intervals (e.g. every 5–10 minutes for orders), and run a periodic full snapshot alongside incremental polling to catch any missed update. See Best Practices (#webhooks-and-polling) for the full strategy. Concrete examples:

      • To retrieve the orders from the last few days, use updatedAtMin / updatedAtMax so you’re sure to catch everything updated in that window.

      • To retrieve the orders you need to ship, filter on status=InPreparation and supplyMode=Seller.

    • Example call: With this call you retrieve the orders from the last few days on Cdiscount that you need to ship to the customer, sorted from the most recent to the oldest.

/orders?salesChannelId=CDISFR&status=InPreparation&supplyMode=Seller&updatedAtMin=2026-09-01T00:00:00Z&sort=updatedAt&desc=updatedAt&pageIndex=1&pageSize=100

  • 1.3 Retrieve an order by its ID — GET /orders/{orderId}: for back-office detail display, issue resolution, delivery tracking.

Important note:

  • Only orders with supplyMode = Seller are to be shipped by the seller. Orders in Fulfillment are handled by the warehouse.

  • Best practice: GET /orders returns as much info as GET /orders/{id} → favor the list, avoid unitary calls.

Links: Orders Management

You are now set to process the orders that require action.

2. Validate / accept an order

In this section we’ll see how to confirm or refuse an order on Cdiscount (other sales channels are all in automatic validation).

Goal: ensure orders that require your validation are accepted in time so they can move on to preparation — and avoid letting them expire.

  • 2.1 Understand when validation is required — this concerns Cdiscount only, for orders in WaitingAcceptance. If the seller doesn’t validate, the order may expire.

  • 2.2 Accept or refuse — POST /orders/{orderId}/approval-status (Accepted / Refused). Accepting does not mean ship. From here the order will follow these steps: Accepted → marketplace controls → InPreparation (eligible for shipment). Example payload (accept):

  {
    "approval_status": "Accepted"
  }

Important note: after acceptance, order cancellation changes (some cancellation-reasons become restricted).

Links: Orders Management

3. Understanding Shipping Address and Delivery modes

In this section we’ll see where and how an order must be delivered — including when the shipping address becomes available and which delivery modes apply.

Goal: know exactly when the shipping address becomes available and which delivery mode applies, so you prepare and ship each order correctly.

  • 3.1 Shipping address availability

Note — upcoming change.

The shipping address is currently returned by the API at every order status. In an upcoming release, address availability will be restricted so that the line’s address is only returned at the statuses that actually require it — for order preparation and shipping. Plan your integration so it doesn’t rely on the address being present at statuses where it isn’t needed (e.g. Refused, WaitingAcceptance).

Target behaviour (once the change is live):

In Seller: the line’s address will be available once the line is in InPreparation.

In Fulfillment: the address will be available after shipment by the Fulfillment service.

  • 3.2 Delivery modes — the base grid (from Best Practices / delivery-logistics):

    • Small parcels (<30kg): TrackedHomeDelivery (mandatory everywhere), SignedHomeDelivery, ExpressHomeDelivery, NotTrackedHomeDelivery.

    • Large parcels (P30): FixedDateHomeDelivery, SpecificRoomHomeDelivery, WithSetupHomeDelivery.

    • Pickup points: Mondial Relay, La Poste, etc. (expose pickupId / pickupName).

    • Availability varies per sales channel (some modes are Cdiscount only).

Important note: delivery mode values are case sensitive. Also, Standard and Express will be removed in the next major version.

Links: Best Practices

You are now ready to ship orders

4. Shipping an order

In this section we’ll see how to declare the shipment of an order, including the multiline, multi-parcel and large-parcel cases.

Goal: confirm your shipments correctly and on time so downstream processes (customer notification, invoicing, commission) run smoothly.

  • 4.1 Shipment prerequisites — order in InPreparation, at least one line in InPreparation (or CancelRequest, see 6), supplyMode = Seller (not Fulfillment). Call must be made before carrier pickup and can only be made once; it cannot be updated afterwards.

  • 4.2 Declare a shipment — POST /orders/{orderId}/shipments: provide carrier + parcel number(s), tracking URL (optional but recommended). Triggers customer notification, invoicing, commission calculation. Example payload (single parcel):

  [
    {
      "parcelNumber": "PARCEL-001",
      "carrierName": "Chronopost",
      "trackingUrl": "https://track.example.com/PARCEL-001"
    }
  ]

  • 4.3 Multiline & partial shipments — when the sales channel allows line-level management: ship one line today, another tomorrow; ability to refuse a single line. If not allowed: full-order shipment only. Full shipment (all order lines in one parcel):

  [
    {
      "parcelNumber": "PARCEL-001",
      "carrierName": "Chronopost",
      "trackingUrl": "https://track.example.com/PARCEL-001",
      "orderLineIds": ["LINE1", "LINE2", "LINE3"]
    }
  ]

Partial shipment (some order lines only):

  [
    {
      "parcelNumber": "PARCEL-002",
      "carrierName": "Colissimo",
      "trackingUrl": "https://track.example.com/PARCEL-002",
      "orderLineIds": ["LINE1"]
    }
  ]

Multiple parcels for a single order line — you can associate the same orderLineId with several parcels, if the item is shipped in parts or through different carriers:

  [
    {
      "carrierName": "LaPoste",
      "orderLineIds": ["AYGC", "BFCD"],
      "parcelNumber": "PARCEL1",
      "trackingUrl": "http://tracking.example.com/PARCEL1"
    },
    {
      "carrierName": "DPD",
      "orderLineIds": ["BFCD"],
      "parcelNumber": "PARCEL2",
      "trackingUrl": "http://tracking.example.com/PARCEL2"
    },
    {
      "carrierName": "Chronopost",
      "orderLineIds": ["BFCD"],
      "parcelNumber": "PARCEL3",
      "trackingUrl": "http://tracking.example.com/PARCEL3"
    }
  ]

In this example, line AYGC ships in 1 parcel (PARCEL1) and line BFCD ships across 3 (PARCEL1, PARCEL2, PARCEL3), each with its own carrier and tracking URL. Useful for oversized products, separate warehouse routing, or multi-part packaging.

4.4 Track the shipment — once the order (or line) moves to Shipped, the parcel data is populated on each order line under lines[].parcels[], readable via GET /orders (or GET /orders/{orderId}).

Each entry holds parcelNumber, carrierName, trackingUrl and trackingId — the tracking URL you (or the Fulfillment service) supplied at shipment.

Note this is declarative shipment data, distinct from the detailed carrier tracking timeline exposed by GET /parcel-trackings (a separate Fulfillment route with per-event statuses like InTransit/Delivered, refreshed every ~8h).

Important note: you can only ship order lines in InPreparation status with supplyMode = Seller and must ship all avaible lines at once when line management is not enabled

Links: Orders Management

5. Handling order cancellations

In this section we’ll see how to cancel or refund an order, and how to handle a customer-initiated cancellation request on a line (including the CancelRequest case).

Goal: process cancellations correctly, and know when you can still ship a line despite a pending cancellation request.

  • 5.1 Retrieve eligible reasons — `GET /cancellation-reasons`: the available reasons depend on the order status and the user type. Call it before any cancellation request (parameters `salesChannel`, `userType`, `orderStatus`).

  • 5.2 Full cancellation — POST /order-cancellation-requests: cancels the whole order. Not allowed if one or more lines are already cancelled; for Fulfillment lines, only when they are Shipped or Delivered. Once accepted, the order moves to Cancelled and the refund is triggered. You can choose whether or not to refund the shipping costs (shippingCostRefund). Example payload:

      {
        "orderSellerId": "SELLZZ210525142759UT8E",
        "reason": "customer-claim",
        "shippingCostRefund": true
      }

  • 5.3 Partial cancellation — POST /order-partial-cancellation-requests: cancels one or more specific lines, each with its own reason (retrieved via GET /cancellation-reasons). Example payload:

      {
        "orderSellerId": "cmd_FFM_001",
        "lines": [
          {
            "lineId": "1",
            "offerId": "f546",
            "reason": "reference-mistake",
            "shippingCostRefund": true
          }
        ]
      }

  • 5.4 CancelRequest line state and global Order state — order line status CancelRequest: a cancellation request initiated by the customer. The header stays InPreparation.

  • 5.5 Accept or refuse the request — either you honor it (the line moves to `Cancelled`), or you ship anyway, which amounts to refusing the customer’s withdrawal.

  • 5.6 Cancellation sources — Cancellation can come from multiple sources: `Customer`, `Seller`, `SalesChannel`, `Fulfillment`.

Important note: shipping a line in CancelRequest is only allowed on Cdiscount, with supplyMode = Seller, when the cancellation was initiated by the customer.

Links: Orders Management

If cancellation is not relevant :

6. Make a commercial gesture

In this section we’ll see how to grant a commercial gesture to a customer who had an issue with their order — this is different from cancelling an order: a commercial gesture does not cancel the order, it compensates the customer while the order stands.

Goal: compensate a customer for an inconvenience on a shipped or delivered order, within the limits your sales channel allows.

  • 6.1 Check the remaining allowance — `GET /orders/{orderId}/commercial-gestures-available-amounts`: returns the maximum amount still applicable (the max allowance minus gestures already granted, with cancelled lines deducted) and the total of gestures already made. Call it before creating a request to avoid a rejection.

  • 6.2 Check eligibility & limits — GET /sales-channel-commercial-gesture-configurations/{salesChannelId}: confirm the option is enabled for the sales channel and read the maximum gesture amount available for the order.

  • 6.3 Request a commercial gesture — POST /order-commercial-gesture-requests, created on a post-shipment order (Shipped, Delivered). You can submit several gestures with the same or different reason. If reason = Other, reasonDetails becomes mandatory. Example payload:

  {
    "orderId": "01210525142759UT8E",
    "reason": "MissingProduct",
    "reasonDetails": "The product is missing",
    "amount": 12.51
  }

  • 6.4 Track the request — processing is asynchronous: use GET /order-commercial-gesture-requests to check the status. Requests that don’t meet the functional rules are rejected.

Important note: the maximum total amount of gestures on an order depends on the sales channel configuration (a max amount and a max rate; the limit is the higher of the two computations). A Cancelled order line’s price is not counted in the order amount used for that calculation.

Links: Technical documentation

7. Customer relationship management – discussions on orders

In this section we’ll see how to communicate about an order: open a thread, attach files, reply, and manage your message queue.

Goal: handle customer communication efficiently and identify request participants and which messages to prioritize.

  • 7.1 Check whether you can initiate a discussion — GET /sales-channel-configurations/{salesChannelId}: isEnabled + allowedReceivers. On some channels, only the customer can initiate a conversation.

    This is not a per-seller configuration or an api restriction. It exclusively depends of a marketplace operator’s choice.

  • 7.2 Retrieve valid (sub)typologies — GET /typologies: combination of salesChannel + typologyCode + orderStatus. Only offer the available subtypologies for the corresponding use case, see state:true

  • 7.3 Open a discussion — POST /discussions: subject (5–50 chars), body (13–5000 chars), subtypology, receiver. Example payload:

      {
        "subject": "Broken product",
        "orderId": "SALESC123456789",
        "salesChannel": "SALESC",
        "subtypologyCode": "broken-product",
        "productId": "pm5972a",
        "message": {
          "body": "product received is broken",
          "receiver": "Customer"
        }
      }

  • 7.4 Add an attachment — max 3 attachments per message, 4 MB total, formats PDF/JPG/PNG/DOC/XLS… (upload). Note: heic/heif readable on retrieval on CDISFR but not yet accepted on upload. Attachments are passed in the attachments array of a discussion or a message; each carries the file as base64. Example fragment (inside a POST /discussions or POST /messages body):

      "attachments": [
        {
          "content": "UmVhZG1lIAp0aGlzIGlzIGp1c3QgYW4gZXhhbXBsZSBmb3IgYmFzZTY0IGZpbGUu",
          "name": "invoice",
          "fileFormat": "pdf"
        }
      ]
  • 7.5 Reply in a discussion — POST /messages: adds a new message to an existing discussion. Required fields: body, discussionId, receivers, salesChannel. Functional rules: the body must be 12–1500 characters (note: this differs from the 13–5000 range at discussion creation), max 3 attachments per message and 4 MB total, and the receiver’s userId is optional (it is resolved from the discussion context when omitted). Example payload:

      {
        "discussionId": "507f1f77bcf86cd799439011",
        "salesChannel": "SELLZZ",
        "body": "Here is the tracking link for your replacement parcel.",
        "receivers": [
          { "userType": "Customer" }
        ],
        "attachments": [
          {
            "content": "UmVhZG1lIAp0aGlzIGlzIGp1c3QgYW4gZXhhbXBsZSBmb3IgYmFzZTY0IGZpbGUu",
            "name": "tracking",
            "fileFormat": "pdf"
          }
        ]
      }

  • 7.6 Know who should reply / prioritize — use:

    • receiver / userType (Customer, Seller, GrcOperator) to know who a message is addressed to.

    • hasRead for read/unread indicators.

    • graduation (Discussion, Claim, Level_1) to prioritize (a Claim/Level_1 escalation = possible GRC intervention).

    • processStatus (ToTreat / Treated) and GET /discussions/count to manage the queue (e.g. how many open claims to handle).

  • 7.7 Mark as read / close — PATCH /messages/{id} (hasRead), PATCH /discussions/{id} (isOpen: false). Both take a patch array with the replace operation only. Mark a message as read:

      [
        { "opt": "replace", "path": "/hasRead", "value": "true" }
      ]

    Close a discussion:

      [
        { "opt": "replace", "path": "/isOpen", "value": "false" }
      ]

Important note: only one discussion is allowed per (order, subtypology, product) — a duplicate returns 409 with the location of the existing discussion.

Links: Discussions Management

8. Generate and submit an invoice

In this section we’ll see how to build a customer invoice from order data and attach it to an order — single upload or bulk from an ERP.

Goal: produce compliant customer invoices and attach them reliably, whether one at a time or in bulk from your ERP.

  • 8.1 Generate the invoice file — the API does not generate invoices. Build the PDF or UBL 2.1 XML from GET /orders (billing identity billingAddress, lines[], prices, taxes, currencyCode) and verify amounts with GET /operations.

    • Clearly separate customer invoice data from seller settlement data (commissions, fees) which may not appear on the customer invoice.

  • 8.2 Submit an invoice (single order) — POST /orders/{orderId}/invoice-documents (multipart/form-data). Ideal for a one-off correction: a new upload replaces the existing invoice. Example request (multipart, one or more files):

  POST {{baseUrl}}/orders/SCID123456789/invoice-documents
  Content-Type: multipart/form-data

  file1=@SCID123456789.pdf

  • 8.3 Submit in bulk — POST /order-invoice-imports: batch from ERP. The order is identified by the file name (= order identifier); ZIP accepted (unpacked, each file processed individually). Example request (multipart, each file named after its order — or a single ZIP):

  POST {{baseUrl}}/order-invoice-imports
  Content-Type: multipart/form-data

  file1=@SCID123456789.pdf
  file2=@SCID987654321.pdf

The response returns an importId — poll GET /order-invoice-imports/{importId} for the per-file result.

  • 8.4 Track processing — asynchronous upload: the importId lets you track status (Running/Success/PartialSuccess/Failure) and integrationStatus (OrderCheck, FileCheck, FileExtract, FileUpload, Done, Failure).

  • 8.5 Correct / replace an invoice — a new upload for the same order automatically replaces the previous invoice.

Important note: don’t consider the upload successful on the 201 alone — it confirms acceptance, not final processing. Check the returned status. Also, this feature is partially available depending on sales channels / order types.

Links: Orders Invoices

9. Retrieve your financial data

In this section we’ll see how to retrieve the financial data tied to your orders and payments.

Goal: reconcile your accounting reliably by retrieving sales, commissions, refunds and their payment states.

  • 9.1 Query operations

GET /operations is the entry point for everything money-related on an order: the sale itself, its VAT breakdown, the marketplace commission, refunds, service fees and guarantee-reserve movements.

  • Required filter — at least one of: orderReference (one order), invoiceId (one invoice), or a full date range (paidAtMin + paidAtMax, or changedAtMin + changedAtMax).

  • Dates for reconciliation — each operation carries createdAt, acceptedAt, shippedAt, paidAt, refundedAt, releasePaymentDate.

  • Also returned — paymentState, currency, the related invoiceId, and the eventTypes it covers.

  • Cadence — once per payout to reconcile; daily if you want continuous monitoring.

  • 9.2 Understand operation types

The eventTypes array tells you what a line represents:

  • Sales cycle — Sale, Commission, Refund, CommissionRestitution (commission given back when an order is refunded).

  • Services & logistics — Subscription, CdiscountServices, LogisticFee, SecurityDeposit, FfmOrdersReturns, FfmStorage, FfmShippingCost, plus SplittedPaymentFees, TradeMarketingFees, AgecLawFees.

Filter on eventTypes to isolate a family (e.g. eventTypes=Refund,Commission to audit refunded orders), then read the matching sub-objects on each operation: sale, refund, commission, commissionsDetail, serviceFinancialFlow.

  • 9.3 Track payment state

states reflects where the money is in the payout cycle:

  • NotPayable — past due but not paid.

  • Estimated — planned for an upcoming payout (typically the 1st, 11th or 21st).

  • InProcess — being paid during the current day.

  • Paid — settled.

  • Blocked — held because the seller’s payments are blocked.

Filter on states to build dashboards — e.g. states=Estimated for expected incoming cash, states=Blocked to surface what needs attention.

  • 9.4 Reconcile

Call GET /operations (and GET /payments) on each payout to match what Octopia paid against your own accounting.

  • Amounts are gross (VAT included) for sales, refunds, commissions and service financial flows — the VAT portion is broken out separately (see the workflow below) so you can rebuild the net amounts.

  • GET /payments — payout-level view, grouped by date, sales channel and payment status.

  • GET /invoices/{invoiceId}/details — line-by-line content of a specific invoice.

Practical example — “How do I gather the data to build my invoice?”

The API doesn’t generate the customer invoice for you (see section 8): you build it from order data, then confirm the figures against the financial operations.

  1. Get the order content and billing identity — GET /orders/{orderId} (or GET /orders): read billingAddress (name, company, companyVatNumber, companyRegistrationNumber, country), the lines[] (each with offer.productTitle, quantity, sellingPrice.unitSalesPrice, sellingPrice.shippingCost), the per-line taxes[] (code, amount, rate), the order currencyCode and totalPrice. This is what goes on the customer invoice.

  2. Confirm the money actually settled — GET /operations?orderReference={reference}: cross-check the sale sub-object (productAmount, shippingFeesAmount, processingFeesAmount, total) and its VAT breakdown in vatSale. If the order was refunded, do the same with refund / vatRefund.

  3. Separate customer-facing vs seller-settlement figures — commission (+ commissionsDetail), creditOnCommission and serviceFinancialFlow are what Octopia charges you; they generally do not appear on the customer invoice. Keep them for your own accounting.

VAT — France vs outside France. Amounts are gross (VAT included), with VAT broken out (vatSale, vatRefund) so you can derive the net. Per line, taxes[] gives code, rate, amount; currencyCode, billingAddress.countryCode and companyVatNumber flag the case (domestic FR, intra-EU B2B, export). The API gives the figures and flags — it doesn’t decide the fiscal treatment (rate, reverse-charge, invoice wording): confirm those with your finance team. Not tax advice.

Commissions & fees. The commission is gross in commission, itemised in commissionsDetail[] (product commission, commission4x, interetBca…), and refunded via creditOnCommission / a CommissionRestitution event. Logistics and service costs come as their own eventTypes (LogisticFee, FfmStorage, FfmShippingCost, SecurityDeposit, …), not inside the sale. For the invoices Octopia issues to you, use GET /invoices/{invoiceId}/details and GET /invoices/{invoiceId}/documents (PDF).

Links: Financial Management