Guide

Sales channels and returns

Which marketplaces connect, what the API can and cannot do with them, and how returns are filtered and paginated differently from everything else.

·7 min read ·Updated 3 Sep 2026

Stockpilot sits between a seller’s channels and their stock. The API exposes what is connected and lets you trigger a sync, but connecting a channel is not something you can do through it.

What is connected

GET /sales-channels
{
  "channels": [
    {
      "id": 1,
      "name": "My Shopify Store",
      "handle": "shopify",
      "is_active": true,
      "logo": "https://…/shopify-icon.png",
      "channel_is_forwarded": false,
      "is_inventory_source": true,
      "is_synchronized": true,
      "created_at": "2023-01-15T10:30:00Z"
    }
  ]
}

Supported handles today:

shopify · woocommerce · amazon · bol · etsy · kaufland · mirakl · b2b-portal

Four fields decide how a channel behaves, and they are worth reading rather than assuming:

FieldMeaning
is_activeThe channel is switched on
is_inventory_sourceStock held for this channel feeds the offer. Compare with sums_onto_offered in the inventory model
is_synchronizedListings are being kept in step
channel_is_forwardedOrders from this channel are handled elsewhere, see forwarding

Note the response is an object with a channels key, not a bare array. It pages with page and page_size like everything else.

Triggering a sync

POST /sales-channels/sync-listings
curl -s -X POST https://api.stockpilot.dev/sales-channels/sync-listings \
  -H "X-CLIENT-ID: $SP_CLIENT_ID" \
  -H "X-CLIENT-SECRET: $SP_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"channel": "shopify", "channel_id": 1}'

channel is the handle exactly as returned above, and channel_id is the id. Both come from the same row, so read them together rather than hardcoding a handle.

Listings are pulled in the background. The call returns as soon as the job is queued, and does not wait for the sync to finish. It sits in the heavy rate limit bucket at 20 per minute.

What the API cannot do

Be clear with your users about the boundary:

  • You cannot connect a channel. OAuth handshakes, marketplace credentials and channel configuration are UI only.
  • You cannot change channel settings. is_inventory_source and the rest are read only here.
  • There is no per-channel listing endpoint. You read stock through inventory and orders through orders, both of which carry the channel on the record.

So an integration built on this API reads channel state and reacts to it. It does not administer channels.

Returns

Returns are channel shaped, which is why they live in this guide.

GET /returns

Each return carries an uppercase status:

StatusMeaning
REQUESTEDNothing handled yet, needs action
PARTLY_ACCEPTEDSome lines handled, some not
RETURN_ACCEPTEDFully handled

The filter values are lowercase and one of them does not match its status. Filter with requested, partly_accepted and accepted, where accepted maps to RETURN_ACCEPTED. Sending the uppercase status as a filter will not work.

Filtering by channel

handle and channel_id must be supplied together. Sending only one is rejected with a 400 rather than silently returning unfiltered results, which is the right call but will surprise you once.

curl -s "https://api.stockpilot.dev/returns?status=requested&handle=bol&channel_id=7" \
  -H "X-CLIENT-ID: $SP_CLIENT_ID" -H "X-CLIENT-SECRET: $SP_CLIENT_SECRET"

Pagination is different here

next and previous are booleans, not URLs. Page by incrementing page and reading current_page and total_pages. Code written against the pagination shape used by the rest of the API will silently read page one forever.

page_size maxes out at 100 on this endpoint, not the 1000 allowed elsewhere.

Unmatched lines

product_id and sku are null when a returned line could not be matched to a product. Fall back to title, and do not assume a SKU is present:

for line in ret["items"]:
    label = line.get("sku") or line["title"]
    handled = line["is_handled"]

Returns not linked to an order are excluded from the list entirely.

Reacting in near real time

There is no returns webhook today. The two events that exist are orders.completed and inventory.stock_changed, both covered in the webhook delivery contract. For returns, poll ?status=requested on a schedule and dedupe on the return id.

A Stockpilot engineer talking through an integration with a developer

Stuck on something

Ask a person, not a search box

The API surface is wide, and some of it only makes sense once someone explains why it works that way. If a payload is not doing what you expect, or you are weighing two approaches, say so and we will look at it with you.