Skip to content

Developer notes

Everything the add-on sends to or reads from AliExpress, with its source in the official AliExpress Open Platform documentation (openservice.aliexpress.com). The pages were read on 2026-11-08 through the documentation site’s own JSON handlers and saved in the repository (research/aliexpress-api/: <method>.json = API reference, doc<id>.json = guide pages, *.txt = readable extracts). Items that could not be verified in the text documentation are marked UNVERIFIED with the behaviour chosen.

Page Where
API endpoint URLs (doc 1369) https://openservice.aliexpress.com/doc/doc.htm#/?docId=1369
Signature algorithm (doc 1367) https://openservice.aliexpress.com/doc/doc.htm#/?docId=1367
HTTP request sample (doc 1366) https://openservice.aliexpress.com/doc/doc.htm#/?docId=1366
Calling parameters (doc 1368), Error messages (doc 1372) same site, docId 1368 / 1372
Seller authorization introduction (doc 1364), Configure seller authorization (doc 1365) same site, docId 1364 / 1365
Become a developer (1362), Register an application (1361), Retrieve App Key and App Secret (1360), Request API Permission (1359) same site
API reference, category “System Tool” (cid 3): /auth/token/create, /auth/token/refresh https://openservice.aliexpress.com/doc/api.htm#/api?cid=3&path=/auth/token/create
API reference, category “AE-Dropshipper” (cid 21038): aliexpress.ds.product.get, aliexpress.ds.freight.query, aliexpress.ds.order.create, aliexpress.trade.ds.order.get, aliexpress.ds.order.tracking.get https://openservice.aliexpress.com/doc/api.htm#/api?cid=21038&path=aliexpress.ds.product.get (and the other paths)
Rule Source Implementation
Business APIs: https://api-sg.aliexpress.com/sync?method={api_path}&{query}; system (authorization) APIs: https://api-sg.aliexpress.com/rest{api_path}?{query} doc 1369 Api::call() POSTs to /sync?method=<api> with every parameter (also method) form-encoded in the body; Api::system() POSTs to /rest/auth/token/.... “All APIs can initiate requests using POST” (doc 1366, notes).
Common parameters app_key, timestamp (GOP: milliseconds, less than 7200 s from UTC), sign_method, access_token (business APIs), sign API reference “Common Parameters”; doc 1366 timestamp = round(microtime(true) * 1000), sign_method = sha256, access_token only on business calls.
Signature: sort every parameter except sign (and byte-array parameters) by name in ASCII order; concatenate name + value; for system APIs put the API path in front; HMAC-SHA256 with the App Secret; upper case hex doc 1367, doc 1366 Api::sign(). Parameters with an empty value are skipped in the signature and not sent (doc 1367 Java sample: areNotEmpty(key, value)). Verified with both published vectors (App Secret “helloworld”): business F7F7926B...EFB5BBB, system 35607762...9F4D81 (tests/unit.php).
Business APIs: method is one of the signed parameters doc 1367 step 1 (“api_path should be used as the request parameter to participate in sorting”), doc 1366 step 3 Included. Note: the doc 1366 sample signs method=aliexpress.logistics.redefining.getonlinelogisticsinfo while its URL names another method; the vector is reproduced exactly as printed.
Object parameters are sent as JSON text API reference (queryDeliveryReq, param_place_order_request4_open_api_d_t_o, ds_extend_request, single_order_query are “Object”) Api::flatten(): arrays to JSON (unescaped Unicode and slashes), booleans to true/false. UNVERIFIED: the text docs do not show the wire form of an Object parameter; JSON text is what the published example values show (queryDeliveryReq = {"quantity":1,...}).
Errors: categories SYSTEM, ISV (business data) and ISP (backend service; “try again after some time”) doc 1372 Api::error(): ISP and isp.* codes are retryable; token / app / signature codes (IllegalAccessToken, IllegalRefreshToken, InvalidAppkey, IncompleteSignature, InvalidCode, AUTH_TYPE_UNSUPPORTED) mark the account “refused” and are never retried.
Answer envelope UNVERIFIED in the text docs (the reference shows only the inner fields) Api::unwrap() accepts both the gateway wrapper {"<method with _>_response": {...}} / {"error_response": {"code","msg","type","request_id"}} and the flat form; Api::listOf() accepts a list either plain or wrapped in an object with one key ({"ae_item_sku_info_d_t_o": [...]}, {"number": [...]}).
Rate limits UNVERIFIED: the Dropshipper reference names no figure; the app category defines an “API traffic control policy” (doc 1361) visible in the App Console Setting “AliExpress requests per second” (default 1), spacing per process; HTTP 429 / ApiCallLimit are retried by the queue with back-off.
User-Agent Not required by the docs KavioLabs-AliExpress-Dropshipping/1.0 (+https://cs-cart.kaviolabs.com).
Step Source Implementation
Authorization URL https://api-sg.aliexpress.com/oauth/authorize?response_type=code&force_auth=true&redirect_uri=${app call back url}&client_id=${appkey} doc 1364 step 1 Client::authorizeUrl(). The add-on also sends state (one-time, 32 hex, bound to the admin session, prefixed A/V for the panel). UNVERIFIED: doc 1364 shows the parameter table as an image only; whether AliExpress echoes state is not stated. An answer without the session’s state is refused (login CSRF protection); the fallback is the manual code field.
The code comes back to the callback URL registered with the app doc 1364 step 3, doc 1361 (“callback URL (the redirect URL used for seller authorization)”) Callback = storefront index.php?dispatch=kv_aliexpress_dropshipping.oauth (one stable URL for admin and vendor panels); it only forwards code/state to admin.php or vendor.php ?dispatch=kv_aliexpress_dropshipping.oauth_return, where the signed-in session is checked.
Code lifetime doc 1364: “will expire within 30 minutes”; /auth/token/create error InvalidCode: “The current validity period of the code is three minutes” (conflict) The add-on exchanges the code immediately; the manual-code help text says “within a few minutes”.
/auth/token/create parameter code (required), uuid (optional, not sent); answer access_token, refresh_token, expires_in, refresh_expires_in, expire_time, refresh_token_valid_time, user_id, user_nick, account, … API reference cid 3 Client::exchange(), Accounts::storeToken(); token JSON sealed (AES-256-GCM).
/auth/token/refresh parameter refresh_token; “the duration of the access token will be reset, but the duration of the refresh token will not”; “only when refresh_expires_in > 0 you can call /auth/token/refresh”; “recommended to refresh it 30 minutes before the token expires”; “save the latest refresh_token” API reference cid 3, doc 1364 “Refresh authorization steps” Client::accessToken(): refresh when less than 30 minutes are left, only with refresh_expires_in > 0, under a per-account GET_LOCK, re-reading the row under the lock, storing the new pair. expire_time / refresh_token_valid_time (milliseconds) win over the relative values.
App Key / App Secret doc 1360 Entered by the store owner, row company_id = 0; vendors authorize through the owner’s app (doc 1365: “Authorized User Limit” per app category).

aliexpress.ds.product.get (Catalog::fetch, Client::check)

Section titled “aliexpress.ds.product.get (Catalog::fetch, Client::check)”
Parameter Required Sent
product_id (Number) yes id parsed from the link or entered
ship_to_country (String) yes rule “Ship-to country” (default US)
target_currency no USD
target_language no rule “Product language” (default en)
remove_personal_benefit, biz_model, province_code, city_code no not sent

Read fields: result.ae_item_base_info_dto.{product_id, subject, product_status_type, category_id, detail, mobile_detail}, result.ae_item_sku_info_dtos[].{sku_id, id, sku_attr, offer_sale_price, sku_price, sku_available_stock, currency_code, barcode, ae_sku_property_dtos[].{sku_property_name, sku_property_value, property_value_definition_name, sku_image}}, result.ae_multimedia_info_dto.image_urls (“;”-separated, per the example), result.package_info_dto.gross_weight (read as kg), result.ae_store_info.{store_name, store_country_code}, rsp_code (200 = success), rsp_msg. Error codes handled: ITEM_ID_NOT_FOUND, Item is not allowed to this country (product marked gone), System Error.

  • On sale = product_status_type = onSelling (example value). UNVERIFIED: the other values are not listed; anything else counts as off sale.
  • Cost = offer_sale_price when > 0, else sku_price (USD per the field description “Value range: 0.01-100000; Unit: USD”). An answer in another currency stops the import.
  • Order line key = sku_attr (falls back to id, “sku attribute unique key”); sku_id identifies the variant for freight and tracking.
  • UNVERIFIED: gross_weight unit (kg assumed from the example “0.050”).

aliexpress.ds.freight.query (Freight, RateCalculator)

Section titled “aliexpress.ds.freight.query (Freight, RateCalculator)”

queryDeliveryReq (Object, required): quantity, shipToCountry, productId, selectedSkuId, language (en_US), locale (en_US), currency (USD) - all required; province optional (sent with the order’s province name), provinceCode/cityCode not sent (doc: send either province or provinceCode, not both). Read: result.success, result.code, result.msg, result.delivery_options[].{code, company, shipping_fee_cent, shipping_fee_format, shipping_fee_currency, free_shipping, min_delivery_days, max_delivery_days, ship_from_country, tracking}. code is “delivery service code, which is used for place order” and goes into logistics_service_name. Errors DELIVERY_NOT_AVAILABLE_TO_YOUR_ADDRESS and DELIVERY_INFO_EMPTY mean “no option”.

  • UNVERIFIED (conflict in the reference): shipping_fee_cent is described as “shipping fee amount in cent” but its example is 1.99 next to shipping_fee_format “US $1.99”. Mapper::fee() reads a value with a decimal point as an amount, and a whole number as cents only when the formatted text confirms it (650 with “US $6.50” = 6.50).
  • Tracked only: options with tracking = false are skipped unless the rule is switched off.
  • The query is one product and SKU at a time; checkout sums the chosen option per SKU (cached 6 hours, 8 SKUs per package at most).

aliexpress.ds.order.create (Fulfilment::push)

Section titled “aliexpress.ds.order.create (Fulfilment::push)”

param_place_order_request4_open_api_d_t_o (required):

Field Required Sent
out_order_id no (“outer order id, used for idempotent checkout”) <prefix>-<order id>, written to the order map before the call
logistics_address.address, city, country, province yes shipping address (billing as fallback); province = state name, the city when the address has no state (UNVERIFIED whether AliExpress accepts that for every country)
logistics_address.address2, zip, full_name, contact_person, mobile_no, phone_country, locale no from the order; phone split into digits and the country calling code (+1) from an ITU E.164 table; locale = en_US
logistics_address.cpf / tax_number no customer tax id from the chosen profile field (cpf for BR)
product_items[].product_id, product_count yes AliExpress product id, quantity
product_items[].sku_attr, logistics_service_name, order_memo no SKU key, chosen delivery code, rule “Note to the seller”

ds_extend_request.payment.{pay_currency, try_to_pay}: USD and "true"/"false" (String, as in the example) from the rule “Payment of AliExpress orders”. Not sent: promotion, trade_extra_param (wholesale, customize map, birthday required for Brazil - not supported in 1.0: Brazilian orders may need it, flagged in the order note by AliExpress), channel_strategy.

Answer: result.{is_success, order_list, error_code, error_msg}; order_list (Number[]) can hold several ids (AliExpress splits per seller); each id becomes a row of kv_aliexpress_dropshipping_ae_orders. Error codes handled: REPEATED_ORDER_ERROR (never answered with a second order: the order needs attention), B_DROPSHIPPER_DELIVERY_ADDRESS_VALIDATE_FAIL, DELIVERY_METHOD_NOT_EXIST, INVENTORY_HOLD_ERROR, PRICE_PAY_CURRENCY_ERROR and the others listed (order failed with the message).

UNVERIFIED: that a second create with the same out_order_id answers REPEATED_ORDER_ERROR. The reference only says out_order_id is “used for idempotent checkout” and lists REPEATED_ORDER_ERROR (“Duplicate order”); the link between the two, its time window and whether a closed order still blocks are not documented. The add-on therefore never relies on it automatically: it sends a second time only after an administrator confirms they checked AliExpress (“Send again”).

Outcome classes after the claim: is_success = true with ids -> placed (S); REPEATED_ORDER_ERROR -> attention (A); is_success = false with an error_code, or an ISV gateway error with a code -> failed (E, nothing created); HTTP 429, ApiCallLimit, token / app errors and SYSTEM gateway errors (doc 1372: request failed basic verification) -> back to P (not processed); everything else (no answer, HTTP 5xx / 408 / 425, ISP errors, System Error, an answer that is not JSON or has neither ids nor an error code) -> outcome unknown (U), never sent again automatically. Automatic runs place an order only while its status is the push status (or Complete), re-checked right before the call.

UNVERIFIED: sku_attr of product.get (example 73:175#Black Green;71:193#Polarized, with the custom value names) is sent as is; the order.create example shows 14:70221 without names. A refusal is shown on the order.

Idempotency (house checklist): order map row with out_order_id written before the call; GET_LOCK per order; atomic claim state P|E -> U (UPDATE ... WHERE state = old, ROW_COUNT() = 1); no answer, HTTP 5xx, 408/425 or an ISP error leave U and the job is not retried, because the Dropshipper API offers no lookup by out_order_id (the only order query, aliexpress.trade.ds.order.get, needs the AliExpress order id). The administrator links the id (verified with aliexpress.trade.ds.order.get under the same lock, never an id of another order) or sends again with the same out_order_id. Rate limits and authorization errors before processing return the row to P (the cron queues it again after 10 minutes).

Section titled “aliexpress.trade.ds.order.get (Fulfilment::poll, Fulfilment::link)”

single_order_query.order_id (required). Read: result.order_status, logistics_status, order_amount.{amount, currency_code}, child_order_list[].{product_id, sku_id, product_count, end_reason}, logistics_info_list[].{logistics_no, logistics_service}. Errors: isv.invalid-authorization (“Accessing data not belonging to you”) refuses a link; isp.service-unavailable is retried.

  • order_id is sent as a number (the reference types it Number).
  • UNVERIFIED: the values of order_status and logistics_status are not listed in the reference (examples: FUND_PROCESSING, SELLER_SEND_GOODS). The add-on does not rank them: it stores them for display, treats an order whose every child order has an end_reason as closed, and treats FINISH (without end reasons) as finished for the optional “delivered” status. Tracking numbers, not statuses, trigger shipments.

aliexpress.ds.order.tracking.get (Fulfilment::poll)

Section titled “aliexpress.ds.order.tracking.get (Fulfilment::poll)”

ae_order_id and language (en_US), both required. Read: result.ret, result.code, result.msg, result.data.tracking_detail_line_list[].{mail_no, carrier_name, cp_name, cp_website_url, eta_time_stamps, detail_node_list[].{tracking_name, tracking_detail_desc, time_stamp}, package_item_list[].{item_id, sku_id, quantity}}. TRACKING DATA NOT FOUND means “no parcel yet”. UNVERIFIED: package_item_list[].item_id is read as the AliExpress product id (its example value is a product id; the field name says item).

Tracking link: the API returns the carrier’s website (cp_website_url), not a page per parcel. The customer link is built from the rule: Cainiao global tracking https://global.cainiao.com/newDetail.htm?mailNoList=<number> (default), 17TRACK https://t.17track.net/en#nums=<number>, the carrier website, or an own https template. UNVERIFIED: Cainiao and 17TRACK pages are public tracking pages of third parties, not part of the AliExpress API. Numbers are accepted only as [A-Z0-9_.-]{4,40} (the order.get example is CP0_318495001) (safe in the URL and in CS-Cart’s sprintf carrier link). For the store owner’s account a fixed page is set as the carrier of the shipment (Tygh\Shippings\Services\KvAliexpressDropshipping::getInfo()), so CS-Cart itself shows the link in the order page and e-mails; otherwise the link is in the shipment comment.

  • Other Dropshipper APIs (aliexpress.ds.text.search, aliexpress.ds.category.get, aliexpress.ds.feed.*, aliexpress.ds.image.searchV2, aliexpress.ds.order.afterpay, …): outside the 1.0 scope (import by link or id, place, track).
  • There is no AliExpress cancel call in the Dropshipper category; cancellations are flagged.
  • No webhooks: tracking and stock are polled.
Table Content Uninstall
kv_aliexpress_dropshipping_accounts App Key, sealed App Secret, sealed token JSON, user nick, auth state kept (retention)
kv_aliexpress_dropshipping_order_map one row per CS-Cart order: out_order_id, state, AliExpress ids, lines kept
kv_aliexpress_dropshipping_ae_orders one row per AliExpress order id: status, amount, tracking numbers -> shipments kept
kv_aliexpress_dropshipping_product_map, _variant_map product and SKU links (sku_id, sku_attr) kept
kv_aliexpress_dropshipping_options, _category_map rules, category mappings kept
kv_aliexpress_dropshipping_jobs, _log, _cache queue, log, freight cache dropped

Kept tables are dropped only when the root setting delete_data_on_uninstall (“Permanently delete AliExpress order and product links on uninstall”) is on (Retention, generated by kv retention).

  • PHP hooks: change_order_status_post (push / cancel jobs), delete_product_post (mapping removal), init_templater_post (Upgrade Synchronizer while pending).
  • Shipping service module kv_aliexpress_dropshipping (codes cheapest, fastest), class Tygh\Shippings\Services\KvAliexpressDropshipping.
  • Test configuration (config.local.php, development only): kv_aliexpress_dropshipping_base_url (mock gateway root), kv_aliexpress_dropshipping_callback_url, kv_aliexpress_dropshipping_min_interval_ms, kv_aliexpress_dropshipping_timeout_s (honoured only together with the base URL override).
  • php81 tests/unit.php / php83 tests/unit.php: signature vectors, envelopes, every mapping with the reference examples (tests/fixtures), token handling, price rules, options, image safety, client requests.
  • node tests/mock-server/server.js 11850: local gateway following the documented contract (signature check, token lifecycle, out_order_id idempotency with REPEATED_ORDER_ERROR, order split per seller, tracking).
  • node tests/e2e/e2e.mjs and tests/e2e/vendor.mjs: refuse to run unless the store’s config points the base URL at the local mock.