Shopee is the largest marketplace in Southeast Asia by traffic and order volume, operating in Singapore, Malaysia, Indonesia, Thailand, Vietnam, the Philippines and Taiwan, with later expansions into Brazil, Mexico, Colombia, Chile, Poland and Spain. It is owned by Sea Limited. Shopee Open Platform is the public developer programme, currently on API version 2, and it is unusually complete for a marketplace: orders with full item and package breakdown, the catalogue down to variant level with per-warehouse stock, logistics tracking events, returns with buyer evidence, and an escrow endpoint that reconciles a single order down to every fee Shopee charged. A seller who sells across several Shopee markets holds a separate shop in each one, with a separate shop_id and a separate token, but a single application serves all of them.
At a glance
open.shopee.com serves its documentation from a JavaScript application, so a plain fetch returns an empty page. Everything below was read from a mirror of the official reference data published at lazykern/ecommerce-api-docs, which carries Shopee's own request and response parameter definitions and the official response samples verbatim. Paths, field names and sample values are Shopee's.
What it is
Shopee launched in 2015 as a mobile-first marketplace and is the consumer commerce arm of Sea Limited, alongside Garena and SeaMoney. It is a true marketplace: buyers browse a shared catalogue, sellers compete on price and shipping, and Shopee runs the payment escrow, the vouchers and, through Shopee Xpress (SPX), a large share of the delivery.
Three seller shapes matter for a connector. A local seller holds one shop in one market. A cross border seller holds a merchant entity that owns shops in several markets, and only cross border sellers use the Merchant API surface. A SIP seller (Shopee International Platform) holds a primary shop whose listings are mirrored into affiliate shops in other markets; authorising the primary shop automatically authorises the affiliates, but affiliate shops have narrower API permissions.
Fulfilment is mostly seller fulfilled with a Shopee-arranged carrier: the seller calls an endpoint to arrange pickup or dropoff, and Shopee supplies the tracking number and the shipping label. Fulfilled by Shopee (FBS) and Shopee Bulky exist in some markets with their own endpoints. Money does not move directly: the buyer pays Shopee, Shopee holds the funds in escrow, and releases a payout after delivery, which is why v2.payment.get_escrow_detail rather than the order object is the source of truth for what the seller actually earned.
API access
Registration is self-serve and free.
- Sign up. From open.shopee.com, Log In, then Sign up, with an email address. The account email cannot be changed after registration. Verify with the emailed code and set a password.
- Submit profile details for approval. Company information goes to Shopee for review. This is the gate before an app can go live.
- Create an app. The console issues a
partner_idand apartner_key. Test and live credentials are separate: a testpartner_idworks only against sandbox domains and a livepartner_idonly against production. - Pick an app type. The type constrains which pushes and which API permission groups the app can hold. The documented types are Original, ERP System, Seller In-house System, Product Management, Order Management, Accounting and Finance, Marketing, and Customer Service. Seller In-house System apps can only be authorised against the developer's own shop, which makes them the right choice for a single-seller integration and the wrong choice for a multi-tenant connector.
- Get shop authorisation. Each seller opens an authorisation link and confirms. This is per shop, or per main account if the seller authorises several shops at once.
Versions: the current surface is v2 and every path is literally prefixed /api/v2/. There is no dated version scheme and no announced sunset for v2 at the time of reading, and fields are added in place, so parse permissively. Shopee does not publish first-party SDKs on a package registry; the guides ship code samples in Python, Go, Java and PHP instead, and the signing scheme is simple enough that a connector usually implements it directly.
Authentication
Two things must be right on every call: the signature, and, for anything shop specific, the access token.
Domains
The three production hosts are network points of presence for the same platform, not different datasets: pick the one nearest your servers.
The three API types
Every endpoint is one of three types, and the type determines both the required common parameters and the signature base string.
- Shop API:
partner_id,timestamp,sign,access_token,shop_id - Merchant API:
partner_id,timestamp,sign,access_token,merchant_id - Public API:
partner_id,timestamp,sign
Public APIs need no token. Only cross border sellers use the Merchant API surface; a local seller never needs merchant_id.
Signature
- Concatenate, with no separators and in this exact order:
partner_id, the API path including/api/v2but without the host,timestamp, thenaccess_tokenandshop_id(Shop API) oraccess_tokenandmerchant_id(Merchant API). A Public API stops after the timestamp. - HMAC-SHA256 the base string with
partner_keyas the key. - Hex encode, lowercase. That is
sign.
Shopee's own worked example, useful as a fixture:
partner_id 2001887, path /api/v2/shop/get_shop_info, timestamp 1655714431, access_token 59777174636562737266615546704c6d, shop_id 14701711 base string 2001887/api/v2/shop/get_shop_info165571443159777174636562737266615546704c6d14701711
The timestamp is Unix seconds and is only valid for five minutes, so the signature cannot be cached.
Seller authorisation
- Build an authorisation link. This is a Public API signature over
partner_id,/api/v2/shop/auth_partnerandtimestamp.
GET https://partner.shopeemobile.com/api/v2/shop/auth_partner ?partner_id=10090 ×tamp=1594897040 &sign=90c12d3932f3826f0c72242e1ec6492eec9a1298658f41f7a9469664801c4e5a &redirect=https://connectors.example.com/shopee/callback
The link expires with the timestamp, five minutes after it is generated, so generate it on demand rather than storing it.
The seller signs in and confirms. A shop account authorises one shop; a main account can authorise several shops and, for cross border sellers, the merchant entity as well, if the seller ticks Auth Merchant. Sub-accounts cannot authorise.
Shopee redirects back. A shop account authorisation returns
codeandshop_id; a main account authorisation returnscodeandmain_account_id. The code is single use and expires after 10 minutes.Exchange the code at
/api/v2/auth/token/get, a POST Public API. The body carriescode,partner_idand exactly one ofshop_idormain_account_id.
curl -X POST \
"https://partner.shopeemobile.com/api/v2/auth/token/get?partner_id=10090×tamp=1758412800&sign=<sign>" \
-H "Content-Type: application/json" \
-d '{"code":"abcdef0123456789","shop_id":14701711,"partner_id":10090}'
{
"request_id": "b937c04e554847789cbf3fe33a0ad5f1",
"error": "",
"access_token": "5977717463656273726661554670",
"refresh_token": "4a6d64526f4a6b4b4c4d4e4f5051",
"expire_in": 14400,
"merchant_id_list": [],
"shop_id_list": []
}
When the input was main_account_id, shop_id_list and merchant_id_list come back populated with everything the seller just authorised.
- Refresh at
/api/v2/auth/access_token/getbefore expiry, passingrefresh_tokenwithshop_idormerchant_id. Each call returns a new access token and a new refresh token, and the new refresh token must be used next time.
Token lifetimes, and a trap
access_token: 4 hours.refresh_token: 30 days.- Shop authorisation itself: up to 365 days, set by the seller, after which the seller must authorise again. Push code 12 warns 7 days in advance.
The trap is in main account authorisation. The first GetAccessToken call returns one token pair that is valid for every shop and merchant authorised in that batch. The first time you call RefreshAccessToken, that shared pair is replaced by independent pairs, one per shop_id and one per merchant_id. Shopee's own example: seven shops and three merchants share one pair initially, then become ten separate pairs after the first refresh. A credential store that assumes one row per shop from the start, and writes the shared token into every row, survives this cleanly. One that keys on the main account does not.
Response envelope
Every response carries request_id, error and message. error is an empty string on success and an error code such as common.error_auth otherwise. The business content is under response. Some endpoints also return warning. Note that HTTP 200 is returned even for business errors, so check error and never the status code alone.
Objects we can read
Orders
POST /api/v2/order/get_order_list returns order identifiers only. It is cursor paginated: pass cursor empty on the first call, then next_cursor while more is true.
Parameters: time_range_field (create_time or update_time), time_from and time_to as Unix seconds, page_size, cursor, and optionally a single order_status from UNPAID, READY_TO_SHIP, PROCESSED, SHIPPED, COMPLETED, IN_CANCEL, CANCELLED, INVOICE_PENDING. Pass request_order_status_pending=true to also see PENDING. The date range is capped; Shopee documents a maximum window per call, so a backfill must walk the range in slices.
{
"error": "",
"request_id": "b937c04e554847789cbf3fe33a0ad5f1",
"response": {
"more": true,
"next_cursor": "20",
"order_list": [{ "order_sn": "201218V2Y6E59M" }, { "order_sn": "201218V2W2SG1E" }]
}
}
POST /api/v2/order/get_order_detail then fetches the real records, up to 50 order_sn values per call as a comma separated order_sn_list. Many fields are opt-in: pass response_optional_fields to get them. This is the endpoint that costs a connector most of its call budget, so batch it at 50.
{
"error": "",
"message": "",
"request_id": "023c50ace933ba38473a5fb2a7dc8821",
"response": {
"order_list": [
{
"order_sn": "2404098R48U37H",
"order_status": "COMPLETED",
"region": "VN",
"currency": "VND",
"cod": true,
"total_amount": 32119,
"estimated_shipping_fee": 5000,
"actual_shipping_fee_confirmed": true,
"payment_method": "Cash on Delivery",
"fulfillment_flag": "fulfilled_by_local_seller",
"shipping_carrier": "Giao Hang Nhanh",
"create_time": 1712601591,
"pay_time": 1712817766,
"ship_by_date": 1712671200,
"pickup_done_time": 1712726577,
"update_time": 1713139948,
"buyer_user_id": 1170319091,
"buyer_username": "xt4fdsf96j",
"cancel_by": "",
"cancel_reason": "",
"recipient_address": {
"name": "P******n", "phone": "******64", "full_address": "Ap******",
"district": "Xa Phong Thanh Tay B", "city": "Huyen Phuoc Long",
"state": "Bac Lieu", "region": "VN", "zipcode": ""
},
"item_list": [
{
"item_id": 23620853561,
"item_name": "Kem no nguc SADOER 60g",
"item_sku": "",
"model_id": 221404189791,
"model_name": "60g (Papaya)",
"model_sku": "QAZ-SADOER-05",
"model_quantity_purchased": 1,
"model_original_price": 300000,
"model_discounted_price": 48000,
"order_item_id": 23620853561,
"product_location_id": ["VN10XX2UZ"],
"promotion_type": "flash_sale"
}
],
"package_list": [
{
"package_number": "OFG166300791210964",
"logistics_status": "LOGISTICS_DELIVERY_DONE",
"shipping_carrier": "5-Day Delivery (SPX)",
"logistics_channel_id": 18080,
"parcel_chargeable_weight_gram": 10,
"item_list": [{ "item_id": 23620853561, "model_id": 221404189791, "model_quantity": 1 }]
}
]
}
]
}
}
recipient_address is PII and Shopee masks it by default: the sample above is Shopee's own, with name, phone and full_address partially starred, while the administrative fields come through in full, enough for geographic analysis but not for delivery. buyer_username is also masked for some non-integrated orders. Note the structure: item_list is the commercial view and package_list the logistics view, and a split order has several packages each with their own subset of items and their own logistics status. Build both.
Order items
There is no separate order item endpoint. Items arrive inside get_order_detail as item_list. The identifiers to keep are item_id (the listing), model_id (the variant, zero when the item has no variants), order_item_id and the seller's own item_sku and model_sku. model_quantity_purchased is the real quantity, so unlike Lazada a line is a line and not a unit. model_original_price and model_discounted_price are per unit.
POST /api/v2/order/get_package_detail returns package level item detail where the package view is needed on its own.
Products and listings
Two calls, same pattern as orders. POST /api/v2/product/get_item_list returns identifiers with offset and page_size up to 100, filtered by item_status (an array of NORMAL, BANNED, UNLIST, REVIEWING, SELLER_DELETE, SHOPEE_DELETE) and optionally an update_time_from and update_time_to window.
{
"response": {
"item": [{ "item_id": 2500139861, "item_status": "NORMAL", "update_time": 1608128470 }],
"total_count": 19,
"has_next_page": true,
"next_offset": 10
}
}
POST /api/v2/product/get_item_base_info then fetches up to 50 items by id.
{
"response": {
"item_list": [
{
"item_id": 34002,
"category_id": 14646,
"item_name": "seller discount",
"item_sku": "",
"create_time": 1600572637,
"update_time": 1600572640,
"item_status": "NORMAL",
"has_model": true,
"condition": "NEW",
"weight": "10.02",
"dimension": { "package_length": 11, "package_width": 12, "package_height": 13 },
"price_info": [
{
"currency": "SGD",
"original_price": 122.02,
"current_price": 122.02,
"inflated_price_of_original_price": 222.02,
"inflated_price_of_current_price": 111.02
}
],
"attribute_list": [
{ "attribute_id": 4811, "original_attribute_name": "Brand", "is_mandatory": true }
],
"image": { "image_url_list": ["https://cf.shopee.sg/file/abc"] },
"pre_order": { "is_pre_order": true, "days_to_ship": 3 }
}
]
}
}
The critical branch: if has_model is true, price_info is not returned on the item at all. Price and stock then live on the variants, fetched with POST /api/v2/product/get_model_list, one item_id per call.
{
"request_id": "75c2b01e50764cec8cfdc61e75c1f26d",
"response": {
"tier_variation": [{ "name": "Colour", "option_list": [{ "option": "blue" }] }],
"model": [
{
"model_id": 2000458802,
"model_sku": "blue bag",
"model_status": "MODEL_NORMAL",
"gtin_code": "",
"price_info": [{ "currency": "TWD", "original_price": 100, "current_price": 100 }],
"stock_info_v2": {
"summary_info": { "total_reserved_stock": 0, "total_available_stock": 0 },
"seller_stock": [{ "location_id": "IDZ", "stock": 0, "if_saleable": true }],
"shopee_stock": [{ "location_id": "IDZ", "stock": 0 }]
}
}
]
}
}
original_price is the list price and current_price what the buyer sees now, equal to original_price when no promotion is running. inflated_price_of_* is the tax-inclusive price in Indonesia, Colombia and Poland and equal to the plain price elsewhere. SIP shops also return sip_item_price.
Inventory
Stock is on the model, in stock_info_v2, as a three way split a connector must keep separate. seller_stock[] is the quantity the seller holds, broken down by location_id, one row per warehouse, and is the only part a write can change. shopee_stock[] is quantity Shopee holds for fulfilled-by-Shopee arrangements. summary_info.total_available_stock and total_reserved_stock are the rollup, where reserved is locked by an ongoing or upcoming promotion.
POST /api/v2/shop/get_warehouse_detail returns the seller's warehouses and their location_id values, but only for shops on the multi-warehouse allowlist; other shops get warehouse.error_not_in_whitelist, which is documented behaviour rather than a failure.
{
"request_id": "16488d76e337c606s5504f26",
"response": [
{ "warehouse_id": 6, "warehouse_name": "warehouse1", "warehouse_type": 1,
"location_id": "IDZ", "region": "ID", "city": "KAB. ACEH UTARA", "zipcode": "24379" }
]
}
Shipments and tracking
POST /api/v2/logistics/get_tracking_info takes order_sn and optionally package_number and returns the event list.
{
"error": "",
"request_id": "e3e3e7f33c4d75f91fb48ecb41aa2a01",
"response": {
"order_sn": "2409177JCSRTEU",
"logistics_status": "LOGISTICS_DELIVERY_FAILED",
"tracking_info": [
{ "logistics_status": "ORDER_CREATED", "description": "Seller will ship after confirming", "update_time": 1726561320 },
{ "logistics_status": "PICKED_UP", "description": "Parcel handed to carrier", "update_time": 1726561320 },
{ "logistics_status": "FAILED_DELIVERED", "description": "Parcel returning to hub", "update_time": 1726561500 }
],
"reversed_tracking_number": "SG246907296501CU",
"reversed_courier_name": "SPX Express"
}
}
description comes back in the shop's local language, which is why logistics_status and not the text is the field to map, and reversed_* only appears for the cross border leg of failed deliveries returning to the seller. POST /api/v2/logistics/get_tracking_number returns the carrier's air waybill after shipment has been arranged; Shopee warns that it can legitimately return an empty tracking number while the carrier catches up and that polling at five minute intervals is acceptable, though push code 4 removes the need entirely. POST /api/v2/order/get_shipment_list cursor pages through orders sitting in READY_TO_SHIP or RETRY_SHIP, which is the work queue for a fulfilment integration.
Returns and cancellations
POST /api/v2/returns/get_return_list is page numbered with page_no and page_size, filtered by create_time_from and create_time_to or update_time_from and update_time_to, plus status, negotiation_status, seller_proof_status and seller_compensation_status.
{
"request_id": "33e099960cdf420393ca5d5c35016f6d",
"response": {
"more": true,
"return": [
{
"return_sn": "200203171852695",
"order_sn": "200203C6W0AR27",
"status": "CANCELLED",
"reason": "PHYSICAL_DMG",
"text_reason": "return reason",
"refund_amount": 1409,
"amount_before_discount": 1409,
"currency": "SGD",
"create_time": 1580721513,
"update_time": 1580729377,
"return_ship_due_date": 1655438336,
"tracking_number": "RNSHS00177569",
"needs_logistics": true,
"negotiation_status": "PENDING_RESPOND",
"seller_proof_status": "PENDING",
"seller_compensation_status": "PENDING_REQUEST",
"image": ["https://cf.shopee.sg/file/166f23cbfb31bd.jpg"],
"user": { "username": "abcdefg", "email": "***********r1@shopee.com" },
"item": [
{ "item_id": 2147533133, "model_id": 0, "item_sku": "USB", "variation_sku": "RED", "amount": 1, "item_price": 1409.9 }
]
}
]
}
}
POST /api/v2/returns/get_return_detail returns one return by return_sn and /api/v2/returns/get_reverse_tracking_info the return leg tracking; buyer email arrives masked. Cancellations before shipping are not returns: they appear as order_status of IN_CANCEL or CANCELLED with cancel_by and cancel_reason on the order.
Payments and settlements
This is where Shopee is genuinely better than most marketplaces. POST /api/v2/payment/get_escrow_detail takes one order_sn and returns the complete accounting for that order, split into what the buyer paid and what the seller receives.
{
"error": "",
"request_id": "b3adb9c441e3b32f6a46286390ef4b00",
"response": {
"order_sn": "220914R9U7D3C6",
"buyer_user_name": "fatpotatomamashop",
"return_order_sn_list": [],
"buyer_payment_info": {
"buyer_payment_method": "Credit Card/Debit Card",
"buyer_total_amount": 7.07,
"merchant_subtotal": 7.98,
"shipping_fee": 0,
"shopee_voucher": -0.67,
"shopee_coins_redeemed": -0.24
},
"order_income": {
"escrow_amount": 6.06,
"escrow_amount_after_adjustment": 6.06,
"buyer_total_amount": 7.07,
"cost_of_goods_sold": 7.98,
"commission_fee": 1.02,
"credit_card_transaction_fee": 0.24,
"campaign_fee": 0,
"coins": 0.24,
"final_escrow_product_gst": 0.66,
"items": []
}
}
}
escrow_amount is the payout for the order and every deduction is named, so a fee ledger can be built without guessing, while buyer_payment_info is the buyer-side mirror for reconciling gross merchandise value.
POST /api/v2/payment/get_escrow_list gives the payout index: release_time_from and release_time_to are mandatory, page_size maxes at 100, and each row is order_sn, payout_amount and escrow_release_time. Walk that list daily and fetch detail for anything new. get_escrow_detail_batch accepts several order numbers in one call. Beyond per order accounting there is get_payout_detail and get_payout_info for the bank payout level, get_wallet_transaction_list for the seller wallet ledger, and generate_income_statement with get_income_statement for period statements.
Customers
No customer object. The order carries buyer_user_id and a masked buyer_username, and the return carries a masked email. buyer_user_id is stable within a shop, so repeat purchase analysis per shop is possible, but there is no cross-shop identity and no usable contact detail.
Locations
POST /api/v2/shop/get_shop_info is the call to make at onboarding: it confirms the token, names the shop, gives the region and, importantly, returns expire_time, the moment the seller's authorisation lapses.
{
"error": "",
"request_id": "e3e3e7f3314a5c09fbf56b4649990b01",
"shop_name": "mysipuat", "region": "MY", "status": "NORMAL",
"auth_time": 1741944925, "expire_time": 1773503999,
"is_cb": false, "is_sip": true, "is_main_shop": true, "merchant_id": null
}
Warehouses come from get_warehouse_detail as above; POST /api/v2/logistics/get_address_list returns the seller's pickup and return addresses.
Writing back: listings, price and stock
All writes are synchronous and all of them are partial: the response carries a success_list and a failure_list with a failed_reason per model. Never treat a 200 as success for the whole batch.
Price
POST /api/v2/product/update_price takes one item_id per call and between 1 and 50 entries in price_list, each with a model_id (use 0 for an item with no variants) and an original_price. To update several items, call it several times. Check the allowed range first with v2.product.get_item_limit, which returns a price_limit.
{ "item_id": 1000, "price_list": [{ "model_id": 2000458802, "original_price": 129.0 }] }
{
"response": {
"success_list": [{ "model_id": 2000458802, "original_price": 129.0 }],
"failure_list": [{ "model_id": 2000458803, "failed_reason": "fail" }]
}
}
Note that update_price sets original_price only. Promotional prices come from the campaign and discount endpoints, and current_price on the read side reflects them.
Stock
POST /api/v2/product/update_stock also takes one item_id and a stock_list of 1 to 50 models. Each entry carries a seller_stock array, and each element of that array is a location_id and a stock. A seller with no warehouses omits location_id.
{ "item_id": 1000, "stock_list": [{ "model_id": 2000458802, "seller_stock": [{ "location_id": "IDZ", "stock": 40 }] }] }
Three documented constraints: the API updates seller_stock only and never shopee_stock; when a promotion is running or upcoming, total stock must stay at or above the live reserved_stock for it, checked with v2.product.get_item_promotion; and deleted items cannot have their stock changed.
Listing content
v2.product.add_item creates a listing and v2.product.update_item edits one. Variants are their own lifecycle: init_tier_variation establishes the variant axes on a new item, add_model, update_model and delete_model manage individual variants, and update_tier_variation changes the axes. Images go through the Media Space endpoints first and are attached by id. v2.product.unlist_item takes up to 50 item and unlist pairs and takes a listing off sale without deleting it; delete_item is permanent.
Fulfilment
POST /api/v2/logistics/get_shipping_parameter first, for a given order_sn, tells you what that order needs: an info_needed block naming pickup, dropoff or non_integrated, with the valid address ids and time slots. POST /api/v2/logistics/ship_order then arranges it with exactly the block that was asked for, and Shopee recommends waiting about an hour after the order is placed. batch_ship_order and mass_ship_order handle volume. Labels come from create_shipping_document, then get_shipping_document_result, then download_shipping_document, which is an asynchronous three step because the label is generated by the carrier. v2.order.cancel_order, handle_buyer_cancellation, split_order and set_note round out the order-side writes.
Webhooks and notifications
Shopee calls webhooks Push Mechanism. Subscription is in the console: pick the app, Set Push, enter the callback URL, click Verify (Shopee sends a test POST and shows the failure reason inline), then tick the push codes. It can also be managed through v2.push.set_app_push_config.
The push codes, from the official list:
Codes 1, 2 and 12 matter more than they look: when a seller revokes several shops from a main account the redirect only returns the main account id, so those pushes are the only way to learn which shops were affected.
Verification. The Authorization request header holds a lowercase hex HMAC-SHA256. The base string is the full callback URL, then a literal |, then the raw response body exactly as received. The key is partner_key. Shopee's guide explicitly warns against parsing the JSON and re-serialising it before computing the signature.
base_string = url + "|" + raw_request_body Authorization = hex(hmac_sha256(base_string, partner_key))
Semantics. Pushes are change notifications, not data transfer: they tell you an order status changed and you then call get_order_detail for the record. Shopee recommends using both push and polling. POST /api/v2/public/get_shopee_ip_ranges returns Shopee's outbound ranges per environment for teams whose ingress is restricted. If a push is missed, v2.push.get_lost_push_message retrieves the backlog and v2.push.confirm_consumed_lost_push_message acknowledges it, which is the recovery path after an outage and removes the need for an aggressive catch-up poll.
Rate limits and pagination
Shopee's API reference carries a rate_limit field on every endpoint, and across the whole v2 reference read on 2026-09-21 it is either empty or [0, 0, 0]. No per-endpoint rate limit is published. Do not infer a number from that zero: it means unset in the documentation, not unlimited. Build the connector with a configurable concurrency cap and back off on error, and ask Shopee for the real figures through a support ticket before running at volume.
What is published and does constrain design: timestamp is valid for 5 minutes, so signatures cannot be precomputed far in advance; get_order_detail takes 50 order numbers per call and get_item_base_info 50 item ids; get_item_list and get_escrow_list cap page_size at 100, the latter defaulting to 40; update_price and update_stock take one item_id and 1 to 50 models per call; unlist_item takes 50 items; get_model_list takes one item_id, which makes a full variant-level catalogue sync the most expensive read a connector performs; and get_order_list and the returns list both cap the date range per call, so backfills must slice the window.
Pagination is inconsistent by design and the connector needs all three styles: cursor (cursor, next_cursor, more) for orders and shipments, offset (offset, next_offset, has_next_page) for items, and page number (page_no, page_size, more) for returns and escrow.
A workable cadence per shop: subscribe to push codes 3, 4, 1, 2 and 12; poll get_order_list by update_time every 15 minutes as a safety net; walk get_item_list by update_time hourly and fetch get_model_list only for items whose update_time moved; poll get_escrow_list daily by release_time and fetch detail for new rows; call get_tracking_info only for packages not yet in a terminal state.
Mapping to the unified model
orders
order_items
listings
inventory
shipments
returns and settlements
returns: return_id is return_sn and order_id is order_sn. items[] comes from item[] with item_id, model_id and amount as the quantity. reason is the enumerated reason with text_reason as the buyer's own words. status is status, with negotiation_status, seller_proof_status and seller_compensation_status as parallel tracks for the dispute, the evidence and the compensation claim. refund_amount maps directly and initiated_at is create_time. received_at is not published; derive it from get_reverse_tracking_info.
settlements: Shopee is per order rather than per statement, so settlement_id is best set to order_sn and period_start and period_end collapse to escrow_release_time from get_escrow_list. gross_amount is buyer_total_amount, net_amount is escrow_amount_after_adjustment, paid_at is escrow_release_time, and fees[] is built by naming every non-zero key inside order_income: commission_fee, campaign_fee, credit_card_transaction_fee, actual_shipping_fee, the final_escrow_* tax fields and the rest. For the bank payout level rather than the order level, use get_payout_detail.
products, customers and locations: products comes from get_item_base_info, with gtin from the model's gtin_code and attributes from attribute_list. customers cannot be populated meaningfully. locations comes from get_warehouse_detail, with channel_location_id set to location_id.
Gaps and open questions
- No published rate limits. The reference's
rate_limitfield is empty or zeroed on every endpoint read. This is the single biggest unknown before building, since the design of a catalogue sync depends entirely on how manyget_model_listcalls per minute are tolerated. Raise a support ticket. get_model_listis one item per call and has no batch equivalent that was found. For a shop with tens of thousands of variant items, a full price and stock refresh is tens of thousands of calls. Verify whether a bulk variant read exists under a permission group not visible in the public reference.- Date range caps are stated but not quantified for
get_order_listand the returns list. Confirm the exact maximum window before writing the backfill slicer. - PII masking extent. The official samples show
recipient_address.name,phoneandfull_addressmasked and the administrative fields clear. Whether an approved app in a given market sees more is not documented in the guides read, and Taiwan non-integrated orders are called out as masked further. Settle it with a live token per market. - Merchant API scope. Only cross border sellers need it, but which endpoints are merchant scoped rather than shop scoped is spread across the reference rather than listed. Enumerate it when a cross border seller is first onboarded.
- Escrow timing.
get_escrow_detailis documented as changing before the order completes, so refetch after the order reachesCOMPLETEDand after any return closes. The exact settlement point is not stated. - Mirror freshness. All of this came from a mirror rather than a live render of
open.shopee.com, so re-verify against the live portal once an app key exists.
Sources
- Shopee Open Platform, the official portal, which renders its documentation client side, so the pages below were read through the mirror that follows.
- lazykern/ecommerce-api-docs, a mirror of the Shopee Open Platform developer guides and v2 API reference: Introduction, Developer account registration, App management, API calls, Authorization and authentication, Push mechanism notifications, Sandbox testing v2, Stock and price management, Order management, Return refund management, and the reference data for the order, product, logistics, payment, returns, shop, push and public API modules including their official response samples.