Lazada

Lazada Open Platform: app key and secret, HMAC-SHA256 signed REST calls, reads orders, products and finance, writes price and stock.

Lazada is the Alibaba-owned marketplace group operating six Southeast Asian ventures: Singapore, Malaysia, Thailand, Vietnam, the Philippines and Indonesia. Each venture is a separate storefront with its own domain, its own currency and, crucially for a connector, its own API gateway host. A seller may be local to one venture or a cross border seller authorised across all six at once. Lazada Open Platform, usually shortened to "lazop", is the public developer programme: anyone can register, create an application, request API permission groups and, after a seller authorises the app through OAuth, read and write that seller's catalogue, orders, logistics and finance data. The signing scheme, parameter conventions and error envelope are the Alibaba Top gateway style, so anyone who has integrated AliExpress, Taobao or Daraz will recognise the shape immediately. Daraz, covered separately at /connectors/daraz, is the same codebase applied to South Asia.

At a glance

Note

open.lazada.com renders its documentation client side, so a plain HTTP fetch returns an empty shell. The content below was read from a complete mirror of the official portal published at lazykern/ecommerce-api-docs, cross checked against the endpoint definitions in an open source SDK. Field names, sample values and limits are Lazada's own, not reconstructed.

What it is

Lazada Group was founded in 2012 by Rocket Internet and has been majority owned by Alibaba Group since 2016. It is a marketplace, not a D2C platform: sellers list onto a shared storefront and compete on a shared catalogue, in the same way as Amazon or Flipkart. The six ventures are operationally distinct, with separate seller centres, separate category trees and separate fee schedules, but a single Open Platform developer account and a single application serve all of them.

Three fulfilment models matter for data mapping. Dropshipping is seller fulfilled, where the seller packs and hands over to a Lazada-appointed or self-selected carrier. Fulfilment by Lazada (FBL) holds seller stock in Lazada warehouses. Multi-warehouse sellers hold stock in several named warehouses and the inventory APIs take a WarehouseCode per line. Choice, the cross border programme, and Global Plus have their own API groups that are out of scope here. For a unified commerce database, Lazada is one of the richer marketplace connections available: order, order item, logistics event and settlement line data are all readable, and stock and price writes are synchronous rather than feed based.

API access

Getting credentials is self-serve and free, but there are gates.

  1. Register a developer account. Go to open.lazada.com, click Create Account, verify by email, set a password and accept the Lazada Open Platform Developer Agreement. The documentation warns that an exclamation mark in the password can cause errors at registration.
  2. Complete the profile. Under User ID, then Profile, fill in company information and upload the requested documents. The Lazada operations team reviews this. An app key cannot be created until the profile is approved, and an approved profile is required before an application can be deployed to production.
  3. Register an application. Choose an app category. The official guide on creating a test order recommends choosing "seller in house" or "ERP" as the app type, because those categories carry the broadest default API permissions and avoid a second round of permission applications later.
  4. Request API permission groups. A permission group is a named bundle of endpoints. Some are granted with the app, others show as Inactive and must be applied for individually with a written business justification and an optional supporting document. Calls to an endpoint outside a granted group are rejected at the gateway.
  5. Pass a security review for sensitive data. Buyer identifying fields are masked unless the application goes through the documented security review, which includes a security questionnaire, a penetration test and Lazada's DataMoat requirements. Assume masked addresses and phone numbers on day one.
  6. Meet the release bar. The official testing guide states that to release an application you need more than 1,000 calls per day at a success rate of at least 95 percent, sustained for two weeks.

Optional hardening: the app console has an IP whitelist page that binds API calls to specific source addresses or CIDR ranges, in formats such as 10.10.10.* and 10.10.10.0/24; left empty, the feature is off.

Official SDKs are downloadable archives hosted on Alibaba Cloud OSS, covering Java, PHP, Python, .NET and Node. The Java archive linked from the order reference on 2026-09-21 was lazop-sdk-java-20240813. There is no package registry release, so most teams vendor the archive or use a community SDK, and there is no dated API version scheme: endpoints are added and fields deprecated in place, so a connector should tolerate unknown fields rather than pin a version.

Authentication

Two independent mechanisms apply to every authenticated call and both are required. The OAuth exchange produces a per-seller access_token; the signature proves the request came from your application and has not been tampered with.

Endpoints

Seller authorisation sequence

  1. Build the authorisation URL and send the seller to it in a browser. client_id is the app key and redirect_uri must exactly match the callback URL registered on the application.
GET https://auth.lazada.com/oauth/authorize
  ?response_type=code
  &force_auth=true
  &redirect_uri=https://connectors.example.com/lazada/callback
  &client_id={app_key}
  &state={nonce}

force_auth=true refreshes the browser cookie so a new authorisation session starts rather than silently reusing a previous login. state is echoed back unchanged. The uuid parameter is documented as currently unavailable and will error if sent.

  1. The seller picks a country, signs in with seller centre credentials and approves the listed permissions. A cross border seller selects "Crossborder" in the country dropdown and authorises with the Malaysia site account; the resulting token then works for all six ventures.

  2. Lazada redirects to redirect_uri with a code parameter, valid for thirty minutes and single use.

  3. Exchange the code at /auth/token/create on the auth host. This call is itself signed but carries no access_token.

curl -X POST "https://auth.lazada.com/rest/auth/token/create?app_key=123456&code=0_TryzT8Vd9T1pwS7VWZ2qlMOS5&sign_method=sha256&timestamp=1758412800000&sign=4190D32361CFB9581350222F345CB77F3B19F0E31D162316848A2C1FFD5FAB4A"

Response, as published in the official sample:

{
  "access_token": "50000601c30atpedfgu3LVvik87Ixlsvle3mSoB7701ceb156fPunYZ43GBg",
  "refresh_token": "500016000300bwa2WteaQyfwBMnPxurcA0mXGhQdTt18356663CfcDTYpWoi",
  "country": "cb",
  "refresh_expires_in": 259200,
  "expires_in": 259200,
  "account_platform": "seller_center",
  "account": "xxx@126.com",
  "account_id": "7063844",
  "country_user_info": [
    { "country": "sg", "seller_id": "1001", "user_id": "1001", "short_code": "SG1001" }
  ]
}

country_user_info is the key array for multi-account storage. A local seller returns one element; a cross border seller returns one per venture, each with its own seller_id and short_code. Store the token once, index it by every (country, seller_id) pair it covers, then route each call to that country's gateway host with the same token.

  1. Refresh before expiry with /auth/token/refresh on the auth host, passing refresh_token. Both expires_in and refresh_expires_in are in seconds; the published sample shows 259200, three days, so a refresh job must run at least daily. Treat the values as authoritative per token rather than hardcoding three days.

Request signature

The signature covers the API path and every parameter, so it is recomputed per request.

  1. Collect all system parameters (app_key, access_token, timestamp, sign_method) and all business parameters. sign_method is sha256. timestamp is milliseconds since epoch and must be within 7200 seconds of UTC.
  2. Sort the parameter names in ASCII order, excluding sign itself and any byte array parameters.
  3. Concatenate name and value pairs with no separators: bar2foo1foo_bar3foobar4.
  4. Prefix the API path: /order/getbar2foo1....
  5. If the request carries a body, append the body string to the end.
  6. Compute HMAC-SHA256 over the UTF-8 bytes using the app secret as the key, hex encode it and uppercase it.

Lazada's own worked example, which is a useful unit test fixture:

path /order/get, secret "helloworld", params app_key=123456 access_token=test
order_id=1234 sign_method=sha256 timestamp=1517820392000

base /order/getaccess_tokentestapp_key123456order_id1234sign_methodsha256timestamp1517820392000
sign 4190D32361CFB9581350222F345CB77F3B19F0E31D162316848A2C1FFD5FAB4A

An authenticated read then looks like this, and note that even POST endpoints expect their parameters in the query string.

curl "https://api.lazada.sg/rest/order/get?app_key=123456&access_token=test&timestamp=1517820392000&sign_method=sha256&order_id=1234&sign=4190D32361CFB9581350222F345CB77F3B19F0E31D162316848A2C1FFD5FAB4A"

Response envelope

Success carries code of "0" alongside request_id and data. Anything else is an error, with type set to SYSTEM for gateway and permission problems, ISV for business rule violations and ISP for backend service failures. request_id is the support handle.

{
  "request_id": "0bb1f74015178205",
  "code": "IllegalAccessToken",
  "type": "ISV",
  "message": "The access token is invalid"
}

Objects we can read

Orders

GET /orders/get returns the order headers for a date window. Either created_after or update_after is mandatory. Filters: created_before, update_before, status, sort_by (created_at or updated_at), sort_direction (ASC or DESC), offset and limit with a documented maximum of 100.

Pagination is offset based, and the response head carries countTotal for the current filter set plus count for the page. Because offsets drift while orders are being created, sync by a moving update_after window ordered by updated_at ascending rather than by deep offsets. status accepts, per the error code documentation: unpaid, pending, packed, canceled, ready_to_ship, delivered, returned, shipped, failed, topack, toship, lost, lost_by_3pl, damaged_by_3pl, failed_delivery, shipped_back, shipped_back_success, shipped_back_failed, package_scrapped.

{
  "code": "0",
  "data": {
    "countTotal": 500,
    "count": 10,
    "orders": [
      {
        "order_id": "491253082180001",
        "order_number": "491253082180001",
        "created_at": "2018-02-09T22:44:30+08:00",
        "updated_at": "2018-02-09T22:44:30+08:00",
        "statuses": ["pending"],
        "items_count": 2,
        "price": "106.00",
        "payment_method": "COD",
        "shipping_fee": "0.54",
        "shipping_fee_original": "0.00",
        "shipping_fee_discount_seller": "0.00",
        "shipping_fee_discount_platform": "0.00",
        "voucher": "0.00",
        "voucher_platform": "0.00",
        "voucher_seller": "0.00",
        "warehouse_code": "dropshipping",
        "customer_first_name": "Ha Hung",
        "customer_last_name": "last_name",
        "tax_code": "562562",
        "address_shipping": {
          "first_name": "Ha Hung",
          "last_name": "last_name",
          "phone": "6****67",
          "address1": "1 CHANGI VILLAGE ROAD, 11",
          "address2": "address2",
          "city": "Singapore-Singapore-500001",
          "post_code": "500001",
          "country": "Singapore"
        },
        "address_billing": { "first_name": "Ha Hung", "phone": "61****7" }
      }
    ]
  }
}

Both address blocks and the customer name fields are PII, and the asterisks in the sample phone values are Lazada's own masking, which is what an unreviewed application receives. The address1 through address5 ladder is the local address format, not a fixed street or district split, so parse it as opaque lines. GET /order/get takes a single order_id and returns one order in the same shape.

Order items

GET /order/items/get takes order_id and returns the item lines. GET /orders/items/get takes order_ids as a stringified JSON array, documented at a maximum list size of 1000, and is the endpoint to use for bulk sync.

{
  "code": "0",
  "data": [
    {
      "order_item_id": 98108,
      "order_id": "31202",
      "sku": "BRSD#02",
      "shop_sku": "BE494HLAAUE3SGAMZ-39898",
      "sku_id": "666",
      "product_id": "12345",
      "name": "Bean Rester Dooby Red",
      "variation": "1",
      "currency": "SGD",
      "item_price": "99.00",
      "paid_price": "99.00",
      "tax_amount": "6.48",
      "voucher_amount": "0.00",
      "voucher_seller": "0.00",
      "status": "canceled",
      "return_status": "1",
      "cancel_return_initiator": "cancellation-customer",
      "shipping_type": "Dropshipping",
      "shipment_provider": "LEL",
      "tracking_code": "456",
      "package_id": "345",
      "warehouse_code": "WH-01",
      "is_fbl": "0",
      "buyer_id": "1001",
      "created_at": "2014-10-15 19:12:15 +0800",
      "updated_at": "2014-10-15 19:12:15 +0800",
      "reverse_order_id": 704947701379284
    }
  ]
}

Two things to notice. Status lives on the item, not the header, which is why orders/get returns a statuses list: an order with two items can have one delivered and one returned. And there are three SKU identities per line: sku is the seller SKU, shop_sku the marketplace listing identifier and sku_id the numeric variant id used by the write APIs. GET /order/document/get returns invoices, shipping labels and carrier manifests for up to 100 order_item_ids, selected by doc_type.

Products and listings

GET /products/get is the catalogue read. filter is mandatory and accepts all, live, inactive, deleted, pending, rejected and sold-out; the SDK also lists image-missing. limit maxes at 50. offset is documented as deprecated with a maximum of 10000, and the documentation explicitly recommends scrolling by date instead, using create_after, create_before, update_after and update_before. sku_seller_list takes a JSON array of exact seller SKUs. options=1 adds the extended stock fields: ReservedStock, RtsStock, PendingStock, RealTimeStock and FulfillmentBySellable.

{
  "code": "0",
  "data": {
    "total_products": 10,
    "products": [
      {
        "item_id": 180226526,
        "primary_category": 10000211,
        "status": "Active",
        "subStatus": "Lock",
        "updated_time": "1611554725000",
        "attributes": { "name": "asd", "brand": "Asante" },
        "images": ["https://my-live.slatic.net/p/540bc796d1ead.jpg"],
        "skus": [
          {
            "SkuId": 314525867,
            "SellerSku": "39817:01:01",
            "ShopSku": "BU565ELAX8AGSGAMZ-1104491",
            "Status": "active",
            "price": 32,
            "special_price": 9,
            "special_from_time": "2015-07-3100:00",
            "special_to_time": "2020-02-0300:00",
            "quantity": 0,
            "Available": 0,
            "Url": "https://alice.lazada.sg/asd-1083832.html",
            "package_weight": "0.04"
          }
        ],
        "rejectReason": [{ "violationDetail": "Price Not Reasonable" }]
      }
    ]
  }
}

The status enumerations, verbatim from the field description, are Active, InActive, Pending QC, Suspended and Deleted for status, and Lock, Reject, Live Reject and Admin for subStatus. price is the regular price and special_price with its date window is the promotional price. GET /product/item/get fetches a single product by item_id or seller SKU. GET /category/tree/get, GET /category/attributes/get and GET /brands/get supply the category and brand reference data needed before any create call.

Inventory

There is no standalone inventory read endpoint. Stock lives on the SKU inside /products/get: quantity is the total and Available is the sellable figure, with the extended fields appearing when options=1 is passed. For multi-warehouse sellers, the per-warehouse split is written through the update APIs by WarehouseCode, and GET /rc/warehouse/get lists the seller's warehouses. Lazada's inventory calculation guide distinguishes total from sellable inventory: sellable is total minus withheld, occupied and campaign-locked quantities, and an update that would take total below the sum of those locks is rejected.

Shipments and tracking

GET /logistic/order/trace, also callable as POST, takes order_id, optional locale and an optional ofcPackageIdList. The documentation states it is only available after the order has reached ready to ship.

{
  "code": "0",
  "result": {
    "module": {
      "package_detail_info_list": [
        {
          "ofc_package_id": "FP032211046428116",
          "tracking_number": "NLXSG20300914",
          "logistic_detail_info_list": [
            {
              "status_code": "1200",
              "detail_type": "ready_to",
              "title": "Packed by seller / warehouse",
              "description": "Your parcel has been packed and ready to be handed over.",
              "event_time": 1625987646597
            }
          ]
        }
      ]
    }
  }
}

GET /shipment/providers/get lists the active carriers for the venture and is the source list for the shipment_provider value required when packing.

Returns and cancellations

Returns are modelled as reverse orders with their own identifier space. GET/POST /reverse/getreverseordersforseller is the list endpoint. It is genuinely page based, with page_no and page_size both mandatory and page_size defaulting to 10, and it filters on reverse_order_id, trade_order_id, request_type_list, reverse_status_list, ofc_status_list, return_to_type (RTM back to the seller or RTW to a Lazada warehouse), dispute_in_progress, QC_Decision and four millisecond timestamp ranges over trade order line creation, reverse order line creation and reverse order line modification.

{
  "code": "0",
  "result": {
    "page_no": 1,
    "page_size": 10,
    "total": 50,
    "items": [
      {
        "reverse_order_id": 0,
        "trade_order_id": 0,
        "request_type": "CANCEL",
        "is_rtm": "true",
        "reverse_order_lines": [
          {
            "reverse_order_line_id": 0,
            "trade_order_line_id": 0,
            "reverse_status": "REQUEST_INITIATE",
            "ofc_status": "RETURN_CANCELED",
            "reason_code": 123,
            "reason_text": "Out of stock",
            "refund_amount": 0,
            "whqc_decision": "scrap",
            "seller_sku_id": "th-1000",
            "platform_sku_id": "th-1001",
            "tracking_number": "TH2404B29P6D",
            "is_dispute": "true",
            "return_order_line_gmt_create": 0,
            "product": { "product_id": 0, "product_sku": "0" }
          }
        ]
      }
    ]
  }
}

The published sample uses zeros as placeholder identifiers. GET/POST /reverse/getreverseorderdetail returns a single reverse order, /reverse/getreverseorderhistorylist its state history and /reverse/getreverseorderreasonlist the reason code reference. Cancellation also surfaces on the order item through return_status, cancel_return_initiator and reverse_order_id.

Payments and settlements

Two endpoints answer different questions. GET /finance/transaction/details/get returns the individual ledger lines. start_time and end_time are mandatory, limit maxes at 500 and offset pages through. It can be narrowed by trans_type, trade_order_id or trade_order_line_id, which makes it the way to attribute fees back to a specific order line.

{
  "code": "0",
  "data": [
    {
      "transaction_number": "SG103EF-1P9VK1A",
      "transaction_date": "17 May 2016",
      "transaction_type": "Payment Fee",
      "fee_type": "13",
      "amount": "-0.62",
      "WHT_amount": "0.0112",
      "statement": "11 May 2016 - 17 May 2016",
      "paid_status": "Not paid",
      "order_no": "123445666666",
      "orderItem_no": "1666666",
      "seller_sku": "sellerSKU"
    }
  ]
}

GET /finance/payout/status/get takes a single mandatory created_after and returns the statement headers, which is the payout level view.

{
  "code": "0",
  "data": [
    {
      "statement_number": "EG100RT-20141228",
      "created_at": "2018-01-04 00:23:04",
      "updated_at": "2018-01-04 00:23:04",
      "opening_balance": "0.00",
      "closing_balance": "3962.41",
      "payout": "3962.41 EUR",
      "paid": "0",
      "item_revenue": "0",
      "shipment_fee": "51.20",
      "fees_total": "51.20",
      "refunds": "0"
    }
  ]
}

payout is a string with the currency appended, not a number. Two further endpoints exist: /finance/account/transactions/get for account level movements and /finance/logistics/fee/detail/query for the logistics fee breakdown. Lazada publishes a separate reference page enumerating the fee_type values, worth mirroring into a lookup table.

Customers

There is no customer endpoint. Buyer identity is only what appears on the order and order item: buyer_id, customer_first_name, customer_last_name, address_shipping and address_billing, all PII. Phone numbers and often address lines arrive masked, and unmasking requires the sensitive data access review. Treat the buyer as a per-order pseudonymous entity keyed on buyer_id.

Locations

GET/POST /rc/warehouse/get lists warehouses and /rc/warehouse/detail/query returns the detail. GET /seller/get returns the shop record, which a connector should call at onboarding to confirm the token and capture the seller identity. GET /seller/pickup/store/list returns pickup stores, which also appear on order items as pick_up_store_info for click and collect orders.

{
  "code": "0",
  "data": {
    "seller_id": 10,
    "name": "abc shop",
    "name_company": "alibaba group",
    "short_code": "SG1015W",
    "email": "Beanbagmart.sg@gmail.com",
    "status": "ACTIVE",
    "cb": "false",
    "marketplaceEaseMode": "true"
  }
}

Writing back: listings, price and stock

Writes are synchronous. There is no feed queue and no processing report to poll: the call returns success or a per-SKU error in the same response.

Price and stock

POST /product/price_quantity/update is the workhorse. It carries one payload parameter containing an XML document and updates the total inventory of the warehouse, plus price fields where supplied. Maximum 50 SKUs per request, with 20 recommended, and 50 calls per second per seller. Single warehouse form:

<Request>
  <Product>
    <Skus>
      <Sku>
        <ItemId>6718170187</ItemId>
        <SkuId>12761620980</SkuId>
        <Quantity>200</Quantity>
      </Sku>
    </Skus>
  </Product>
</Request>

Multi warehouse form, which single warehouse sellers must not use. The <Quantity> element is replaced by a <MultiWarehouseInventories> block inside the same <Sku>:

<MultiWarehouseInventories>
  <MultiWarehouseInventory>
    <WarehouseCode>Dropshipping</WarehouseCode>
    <Quantity>20</Quantity>
  </MultiWarehouseInventory>
  <MultiWarehouseInventory>
    <WarehouseCode>warehouseTest2</WarehouseCode>
    <Quantity>30</Quantity>
  </MultiWarehouseInventory>
</MultiWarehouseInventories>

Quantity cannot be negative, and the new value must be at least the sum of withheld, occupied and campaign-locked inventory or the call is rejected.

GET/POST /product/stock/sellable/update sets sellable quantity in an overriding manner, using <SellableQuantity> in place of <Quantity> in the same document shapes, and GET/POST /product/stock/sellable/adjust applies a delta instead, positive to increase and negative to decrease. Both carry the same 50 SKU and 50 calls per second limits.

Listing content

POST /product/update updates an existing product, keyed on ItemId, and accepts a JSON document as well as XML. It can also set dropshipping warehouse quantity, but only for SKUs belonging to that single product.

{
  "Request": {
    "Product": {
      "ItemId": "6718170187",
      "Attributes": {},
      "Skus": { "Sku": [{ "SkuId": "123456", "quantity": "1" }] }
    }
  }
}

POST /product/create creates a product with its category, attributes and SKU list. Before calling it, resolve the category with /category/tree/get or /category/suggestion/get and fetch the mandatory attribute list with /category/attributes/get, since attribute requirements differ per category and per venture. Images go up through /image/upload, /image/migrate or /images/migrate, which pull an external URL into Lazada's CDN at JPG or PNG up to 1 MB, and /images/set attaches the resulting Lazada URLs to a product. POST /product/deactivate and POST /product/activate toggle listing visibility. POST /product/remove deletes a product, some SKUs or all SKUs, taking seller_sku_list as a JSON array capped at 50 seller SKUs, and POST /sku/remove removes individual SKUs.

Fulfilment writes

POST /order/fulfill/pack packs pending or repacked order items; POST /order/package/rts marks packages ready to ship, with a batch limit of 20 packages. The older item level pair /order/pack and /order/rts is still documented and takes order_item_ids as a JSON array with delivery_type and shipment_provider. POST /order/cancel cancels a single order item with a reason_id from /order/failure_reason/get. POST /order/invoice_number/set writes the invoice number back. Responses carry a per-package item_err_code and msg, so a partial success is possible and every response element must be inspected.

Webhooks and notifications

Lazada Push Mechanism is the official webhook system.

Subscription. Log into open.lazada.com, open the Message Service tab, enter the callback URL and click Verify. The platform sends a test message; you confirm receipt and signature manually, then choose the message types to subscribe to and save. There is no API for managing subscriptions. The callback must be HTTPS with a CA-issued certificate at OV or EV level: self-signed and DV certificates are explicitly rejected. Lazada does not publish its source IP ranges and states that IP allowlisting is not supported by the platform.

Message shape. A trade order notification, verbatim from the official sample. A reverse order notification adds reverse_order_id and reverse_order_line_id alongside the trade identifiers. trade_order_id maps to order_id in the order APIs and trade_order_line_id to order_item_id.

{
  "seller_id": "1234567",
  "message_type": 0,
  "data": {
    "order_status": "unpaid",
    "status_update_time": 1603698638,
    "trade_order_id": "260422900198363",
    "trade_order_line_id": "260422900298363"
  },
  "timestamp": 1603766859530,
  "site": "lazada_vn"
}

Verification. The Authorization header holds a lowercase hex HMAC-SHA256. The base string is the app key concatenated with the raw request body exactly as received, and the key is the app secret. Compare in constant time, and capture the raw bytes before any JSON parse or re-serialisation, since a reformatted body will not match.

Retry policy. Acknowledge with HTTP 200 within 500 ms. A failure is retried after 30 minutes, then every 30 minutes up to 12 attempts, after which the message is dropped. That 500 ms budget rules out real work in the handler: write to a queue and return.

Documented message types. Trade order, reverse order, product update, product category update, product quality control, shallow stock, product review, seller status update, authorization token expiration alert, fulfilment order update, JIT pickup order, JIT purchase order status, promotion, short video state, and instant messaging send and session updates.

The official best practice is to treat push as an accelerator rather than a replacement: keep a low frequency poll running alongside it so nothing is lost. The recommended consumption pattern is order message, then /order/get, then /order/items/get, then business logic.

Rate limits and pagination

Lazada publishes no general rate limit table and the API reference pages carry no quota values. What is published:

  • The inventory APIs (/product/price_quantity/update, /product/stock/sellable/update, /product/stock/sellable/adjust) are limited to 50 calls per second per seller.
  • Throttling exists on read APIs and is referenced repeatedly as the reason to adopt push, but no number is given. Treat this as not published; measure against the app console.
  • The app console exposes Total successful calls, Total failed calls and Average QPS per application, plus per-seller call counts. That dashboard is the only reliable source of your actual ceiling.
  • Release requires sustaining more than 1,000 calls per day at 95 percent success over two weeks.

Batch limits that are published: 100 orders per /orders/get page, 1000 order ids per /orders/items/get, 50 products per /products/get page with offset deprecated above 10000, 50 SKUs per inventory write, 20 packages per ready to ship, 100 order items per document request, 500 transaction lines per finance page.

A workable cadence for a single seller: subscribe to trade order and reverse order push for near real time order capture; poll /orders/get by update_after every 15 minutes as a safety net; poll /products/get by update_after hourly; pull /finance/transaction/details/get daily for the previous 48 hours and /finance/payout/status/get daily; call /logistic/order/trace only for orders currently in transit rather than on a schedule.

Mapping to the unified model

orders

order_items

listings

inventory

shipments

returns and settlements

returns: return_id is reverse_order_id, a separate identifier space from orders, and order_id is trade_order_id. items[] comes from reverse_order_lines[], keyed by trade_order_line_id back to order items. reason combines reason_code and reason_text, with the reference list at /reverse/getreverseorderreasonlist. status needs both reverse_status and ofc_status, which are two parallel state machines, the request and the fulfilment. refund_amount maps directly and initiated_at is return_order_line_gmt_create in milliseconds. received_at is not published; infer it from ofc_status transitions or whqc_decision.

settlements: settlement_id is statement_number and net_amount is payout with the trailing currency code stripped. period_start and period_end have to be parsed out of the statement string on the transaction line, which is a human format such as 11 May 2016 - 17 May 2016. order_id is order_no, with orderItem_no giving line attribution, and gross_amount is item_revenue. fees[] is one row per transaction line, with type from transaction_type and fee_name and a signed amount. paid_at has no field at all: paid_status is only Paid or Not paid.

products, customers and locations

For products, take sku from SellerSku, title, brand and category from the product attributes and primary_category, and images from the product and SKU image arrays; gtin and hsn_code are category attributes where the venture requires them, not top level fields. customers cannot be populated durably: only buyer_id and a masked name and address per order. locations comes from /rc/warehouse/get plus /seller/pickup/store/list, with channel_location_id set to the warehouse code.

Gaps and open questions

  • General rate limits are not published. Only the 50 per second inventory limit is stated. The practical ceiling has to be measured through the app console once an app key exists.
  • The sandbox is not a separate environment. Test accounts are lent from the console and there is a Sandbox Test area, but the hosts are the production hosts, so any write test touches something real. Confirm the isolation boundary with Lazada before running a write test suite.
  • Sensitive data masking. The extent of masking before the security review is not enumerated field by field. The published samples show masked phone numbers but full address lines, which may not reflect an unreviewed application's view. This needs a live token to settle.
  • /products/get offset deprecation. The docs cap offset at 10000 and recommend date scrolling, but do not say what happens when more than 50 SKUs share an identical updated_time. Plan a tie-break strategy.
  • Two generations of fulfilment endpoints coexist. The item level /order/pack and /order/rts and the package level /order/fulfill/pack and /order/package/rts are both documented, with a separate "new fulfillment API announcement" page. Which pair applies depends on the venture and the seller's fulfilment setup, so resolve it per seller at onboarding.
  • paid_at for settlements has no field, only paid_status. If payout dates matter they may have to come from /finance/account/transactions/get, which was not read in detail here.
  • Portal access. Everything above came from a mirror of the official documentation rather than a live render of open.lazada.com. The mirror's most recent page timestamps sit in 2024 and 2025, so a field added in the last few months could be missing. Re-verify against the live portal once an account exists.

Sources

  • Lazada Open Platform, the official portal, which renders documentation client side, so the pages below were read through the mirror that follows.
  • lazykern/ecommerce-api-docs, a complete mirror of the Lazada Open Platform documentation and API reference: the signature algorithm guide, endpoint URL table, calling parameters, HTTP request sample, error messages, seller authorization introduction, push mechanism guide, inventory management API guide, order status flow, and the raw reference JSON for the order, product, system, finance, logistics, seller, fulfilment and return APIs.
  • Phonbopit/lazada-open-platform-sdk, a Node SDK, used to cross check gateway hosts, the signing implementation, the response envelope types and the endpoint path list.
  • laijingwu/lazop-sdk, a PHP SDK, used to confirm the request classes for authorization, orders, products and logistics.